feat: 参赛表单页面i18n

This commit is contained in:
2026-02-04 13:33:05 +08:00
parent 0c8b3ee8f1
commit d4fb435db9
5 changed files with 326 additions and 101 deletions

View File

@@ -2,15 +2,15 @@
<div class="apply-container">
<div class="banner">
<div class="slogan">
BLOOM YOUR CREATIVITY AiDA GLOBAL FASHION AWARD 2026
{{ t('AwardApply.slogan') }}
</div>
<div class="title poppins-medium">Application Form</div>
<div class="title poppins-medium">{{ t('AwardApply.applicationForm') }}</div>
<div
class="form-header"
v-if="!isCompleted && !isExpired"
>
<div class="form-title poppins-bold">Email Verification</div>
<div class="desc">AiDA Users Only</div>
<div class="form-title poppins-bold">{{ t('AwardApply.emailVerification') }}</div>
<div class="desc">{{ t('AwardApply.aidaUsersOnly') }}</div>
</div>
</div>
<Success
@@ -34,7 +34,7 @@
<a-form-item
name="email"
required
label="Email Address"
:label="t('AwardApply.emailAddress')"
>
<div class="email-wrapper flex align-center">
<a-input v-model:value="form.email" />
@@ -47,7 +47,7 @@
{{
isCountingDown
? formatCountdown(countdown)
: 'Send Code'
: t('AwardApply.sendCode')
}}
</div>
<img
@@ -58,13 +58,13 @@
/>
</div>
<div class="tips">
Please use the email address you registered with AiDA.
{{ t('AwardApply.pleaseUseRegisteredEmail') }}
</div>
</a-form-item>
</div>
<div class="form-row full-row">
<div class="form-title poppins-bold">Personal Information</div>
<div class="desc">Tell us about yourself</div>
<div class="form-title poppins-bold">{{ t('AwardApply.personalInformation') }}</div>
<div class="desc">{{ t('AwardApply.tellUsAboutYourself') }}</div>
</div>
<div class="user-info flex">
<template
@@ -74,7 +74,7 @@
<a-form-item
v-if="item.key !== 'email'"
:required="item.required"
:label="item.label"
:label="getFieldLabel(item.key)"
:name="item.key"
>
<a-input
@@ -89,7 +89,7 @@
<a-select
:disabled="readOnly"
v-model:value="form[item.key]"
:options="genderOptions"
:options="getGenderOptions()"
>
<template #suffixIcon>
<!-- <img
@@ -109,13 +109,13 @@
</template>
</div>
<div class="form-row full-row">
<div class="form-title poppins-bold">Design Information</div>
<div class="desc">Share your creative vision</div>
<div class="form-title poppins-bold">{{ t('AwardApply.designInformation') }}</div>
<div class="desc">{{ t('AwardApply.shareYourCreativeVision') }}</div>
</div>
<a-form-item
class="full-row design-title"
name="designTitle"
label="Design Title"
:label="t('AwardApply.designTitle')"
required
>
<a-input
@@ -126,24 +126,24 @@
<a-form-item
class="full-row design-desc"
name="designDescription"
label="Design description"
:label="t('AwardApply.designDescription')"
required
>
<a-textarea
class="textarea"
:disabled="readOnly"
v-model:value="form.designDescription"
placeholder="Briefly describe your design concept, inspiration, and creative direction..."
:placeholder="t('AwardApply.designDescriptionPlaceholder')"
/>
</a-form-item>
<div class="form-row full-row">
<div class="form-title poppins-bold">Submission Files</div>
<div class="desc">Upload your design materials</div>
<div class="form-title poppins-bold">{{ t('AwardApply.submissionFiles') }}</div>
<div class="desc">{{ t('AwardApply.uploadYourDesignMaterials') }}</div>
</div>
<div class="information full-row">
<div class="information-title flex align-center">
<div class="point"></div>
<div class="text poppins-bold">Submission Requirements</div>
<div class="text poppins-bold">{{ t('AwardApply.submissionRequirements') }}</div>
</div>
<ul class="information-list flex space-between">
<li class="information-item">
@@ -169,7 +169,7 @@
name="pdfPath"
required
:validate-trigger="[]"
label="How will you use AiDA in your design process?"
:label="t('AwardApply.uploadPdfTitle')"
>
<div>
<a-upload-dragger
@@ -187,8 +187,8 @@
alt=""
class="upload-icon"
/>
<p class="desc">Click to upload or drag and drop</p>
<p class="limit">PDF file, max 20MB</p>
<p class="desc">{{ t('AwardApply.clickToUploadPdf') }}</p>
<p class="limit">{{ t('AwardApply.pdfFileLimit') }}</p>
<template #itemRender="{ file, actions }">
<div
class="custom-upload-list flex align-center space-between"
@@ -240,7 +240,7 @@
name="videoPath"
required
:validate-trigger="[]"
label="How will you use AiDA in your design process?"
:label="t('AwardApply.uploadVideoTitle')"
>
<div>
<a-upload-dragger
@@ -258,9 +258,9 @@
alt=""
class="upload-icon"
/>
<p class="desc">Click to upload or drag and drop</p>
<p class="desc">{{ t('AwardApply.clickToUploadVideo') }}</p>
<p class="limit">
Video file (MP4, MOV), 1080p, max 100MB
{{ t('AwardApply.videoFileLimit') }}
</p>
<template #itemRender="{ file, actions }">
<div
@@ -308,40 +308,39 @@
</a-form-item>
</div>
</a-form>
<div class="conditions">
<div class="confitions-title poppins-bold">Terms & Conditions</div>
<div class="condition-list flex flex-col">
<div
class="condition-item flex align-center"
v-for="item in conditionsList"
:key="item.id"
>
<a-checkbox v-model:checked="item.check" />
<span>
{{ item.text }}
<span
class="required"
v-if="item.required"
>
*
</span>
</span>
</div>
</div>
</div>
<div class="submit-container">
<div class="conditions">
<div class="confitions-title poppins-bold">{{ t('AwardApply.termsAndConditions') }}</div>
<div class="condition-list flex flex-col">
<div
class="submit-btn poppins-bold"
@click="handleSubmitForm"
class="condition-item flex align-center"
v-for="item in conditionsList"
:key="item.id"
>
Submit your Design
</div>
<div class="desc">
The link in the AiDA in-platform message will save your unfinished
form.
<a-checkbox v-model:checked="item.check" />
<span>
{{ t('AwardApply.' + item.translationKey) }}
<span
class="required"
v-if="item.required"
>
*
</span>
</span>
</div>
</div>
</div>
<div class="submit-container">
<div
class="submit-btn poppins-bold"
@click="handleSubmitForm"
>
{{ t('AwardApply.submitYourDesign') }}
</div>
<div class="desc">
{{ t('AwardApply.unfinishedFormTip') }}
</div>
</div>
</div>
</div>
<a-modal
v-model:visible="showModal"
@@ -359,9 +358,8 @@
class="close-icon"
@click="handleCloseModal"
/>
<div class="title poppins-bold">Check your email</div>
<div class="desc">Enter the 6-digital code sent to</div>
<div class="email">{{ form.email }}</div>
<div class="title poppins-bold">{{ t('AwardApply.checkYourEmail') }}</div>
<div class="desc">{{ t('AwardApply.enterSixDigitCode') }} {{ form.email }}</div>
<div class="code-box">
<VerifycationCodeInput
:ct="emailCode"
@@ -372,7 +370,7 @@
class="verify-btn"
@click="handleVerifyCode"
>
Verify
{{ t('AwardApply.verify') }}
</div>
<div
class="cutdown"
@@ -381,8 +379,8 @@
>
{{
isCountingDown
? `Resend Code in ${formatCountdown(countdown)}`
: 'Resend'
? `${t('AwardApply.resendCodeIn')} ${formatCountdown(countdown)}`
: t('AwardApply.resendCode')
}}
</div>
</div>
@@ -392,6 +390,7 @@
<script setup lang="ts">
import { ref, onUnmounted, onMounted, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { debounce } from 'lodash-es'
import type { Rule } from 'ant-design-vue/es/form'
import { message, Upload } from 'ant-design-vue'
@@ -402,13 +401,15 @@
import UploadStatus from './components/UploadStatus.vue'
import Success from './components/Success.vue'
// 是否晚于2026年7月22
// 是否晚于2026年7月15
const isExpired = computed(() => {
const now = new Date()
const targetDate = new Date('2026-03-15')
const targetDate = new Date('2026-07-15')
return now > targetDate
})
const { t } = useI18n()
const route = useRoute()
const isCompleted = ref(false)
@@ -451,41 +452,41 @@
}
const validEmail = (rule: Rule, value: string) => {
if (!value) {
return Promise.reject('Please input the email address')
return Promise.reject(t('AwardApply.pleaseInputEmail'))
}
if (!isValidEmail(value)) {
return Promise.reject('Please input a valid email address')
return Promise.reject(t('AwardApply.pleaseInputValidEmail'))
}
return Promise.resolve()
}
const validateNumber = async (_rule: Rule, value: string) => {
if (value === '') {
return Promise.reject('Please fill in this section.')
return Promise.reject(t('AwardApply.pleaseInputAge'))
}
if (!Number.isInteger(value)) {
return Promise.reject('Please input digits')
if (!Number.isInteger(Number(value))) {
return Promise.reject(t('AwardApply.pleaseInputDigits'))
}
return Promise.resolve()
}
const validatePhone = async (_rule: Rule, value: string) => {
if (value === '') {
return Promise.reject('Please enter your phone number.')
return Promise.reject(t('AwardApply.pleaseInputPhoneNumber'))
}
const phoneRegex = /^[\+]?[1-9][\d]{0,15}$/
if (!phoneRegex.test(value)) {
return Promise.reject('Please enter a valid phone number.')
return Promise.reject(t('AwardApply.pleaseInputValidPhone'))
}
return Promise.resolve()
}
const validateVerificationCode = async (_rule: Rule, value: string) => {
if (value === '') {
return Promise.reject('Please enter the verification code.')
return Promise.reject(t('AwardApply.pleaseEnterCompleteCode'))
}
const codeRegex = /^\d{6}$/
if (!codeRegex.test(value)) {
return Promise.reject('Please enter a 6-digit verification code.')
return Promise.reject(t('AwardApply.pleaseEnterCompleteCode'))
}
return Promise.resolve()
}
@@ -596,6 +597,39 @@
])
const showModal = ref(false)
// 获取表单标签的翻译
const getFieldLabel = (key: string) => {
const labelMap: Record<string, string> = {
firstName: t('AwardApply.firstName'),
lastName: t('AwardApply.lastName'),
gender: t('AwardApply.gender'),
occupation: t('AwardApply.occupation'),
age: t('AwardApply.age'),
countryRegionCity: t('AwardApply.countryRegionCity'),
phoneNumber: t('AwardApply.phoneNumber'),
portfolioUrl: t('AwardApply.portfolioUrl')
}
return labelMap[key] || key
}
// 获取性别选项的翻译
const getGenderOptions = () => {
return [
{
label: t('AwardApply.male'),
value: 'Male'
},
{
label: t('AwardApply.female'),
value: 'Female'
},
{
label: t('AwardApply.other'),
value: 'Other'
}
]
}
// 倒计时相关状态
const countdown = ref(0)
const countdownTimer = ref<NodeJS.Timeout | null>(null)
@@ -662,7 +696,7 @@
console.log(verifyCode.value)
if (verifyCode.value.length !== 6) {
message.error('Please enter the complete 6-digit verification code')
message.error(t('AwardApply.pleaseEnterCompleteCode'))
return
}
Https.axiosGet(Https.httpUrls.checkOTP, {
@@ -676,21 +710,21 @@
form.value.secureToken = res.data.secureToken
message.success('Verification successful!')
message.success(t('AwardApply.verificationSuccess'))
showModal.value = false
})
}
const handleSubmitForm = () => {
if (!hasValidEmail.value) {
message.error('Please verify your email first')
message.error(t('AwardApply.pleaseVerifyEmailFirst'))
return
}
const validCondition = conditionsList.value.filter(
item => item.required && !item.check
)
if (validCondition.length > 0) {
message.error('Please check the terms and conditions')
message.error(t('AwardApply.pleaseCheckTerms'))
return
}
formRef.value
@@ -704,7 +738,7 @@
})
.catch(err => {
console.log(err)
message.error('Please fill in all the required fields')
message.error(t('AwardApply.pleaseFillRequiredFields'))
})
}
@@ -728,7 +762,7 @@
// 统一的文件上传前验证
const beforeUploadFile = (type: FileType, file: File) => {
if (!hasValidEmail.value) {
message.error('Please verify your email first')
message.error(t('AwardApply.pleaseVerifyEmailFirst'))
return Upload.LIST_IGNORE
}
let maxSize: number
@@ -740,12 +774,12 @@
maxSize = 20 * 1024 * 1024 // 20MB
allowedExtensions = ['pdf']
allowedMimeTypes = ['application/pdf']
errorMessage = 'Please upload a PDF file only.'
errorMessage = t('AwardApply.uploadPdfOnly')
} else if (type === 'video') {
maxSize = 100 * 1024 * 1024 // 100MB
allowedExtensions = ['mp4', 'mov']
allowedMimeTypes = ['video/mp4', 'video/quicktime']
errorMessage = 'Please upload a MP4 or MOV file only.'
errorMessage = t('AwardApply.uploadVideoOnly')
} else {
return Upload.LIST_IGNORE
}
@@ -777,7 +811,7 @@
if (file.size > maxSize) {
const sizeLimit = type === 'pdf' ? '20MB' : '100MB'
message.error(
`File size exceeds ${sizeLimit} limit. Please upload a smaller file.`
t('AwardApply.fileSizeExceeds', { sizeLimit })
)
// 从文件列表中移除
// if (type === 'pdf') {
@@ -899,7 +933,7 @@
const status = info.file.status
if (status === 'done') {
message.success(`${info.file.name} file uploaded successfully.`)
message.success(t('AwardApply.fileUploadedSuccess', { fileName: info.file.name }))
if (type === 'pdf') {
isUploadingPdf.value = false
uploadProgressPdf.value = 0
@@ -910,7 +944,7 @@
videoUploadStatus.value = 'success'
}
} else if (status === 'error') {
message.error(`${info.file.name} file upload failed.`)
message.error(t('AwardApply.fileUploadFailed', { fileName: info.file.name }))
if (type === 'pdf') {
isUploadingPdf.value = false
uploadProgressPdf.value = 0
@@ -928,7 +962,7 @@
const file = option.file as File
if (!form.value.email) {
message.error('Please input the email address first')
message.error(t('AwardApply.pleaseInputEmail'))
option.onError?.(new Error('Email required'))
return
}
@@ -973,7 +1007,7 @@
option.onSuccess?.({ uploadId }, option.file)
} catch (error: any) {
console.error('Upload error:', error)
message.error(error?.message || 'Upload failed')
message.error(error?.message || t('AwardApply.uploadFailed'))
option.onError?.(error)
if (type === 'pdf') {
@@ -1017,23 +1051,27 @@
check: false,
required: true,
text: 'I confirm that all submitted work is original and created by me.',
translationKey: 'conditionFirst',
id: 'first'
},
{
check: false,
required: true,
text: 'I understand that Code-Create has marketing and promotional rights to all submitted designs and videos.',
translationKey: 'conditionSecond',
id: 'second'
},
{
check: false,
required: true,
text: 'I agree to participate in finalist activities if selected, including AiDA training and award ceremony.',
translationKey: 'conditionThird',
id: 'third'
},
{
check: false,
text: 'I would like to receive updates about AiDA products and future competitions. (Optional)',
translationKey: 'conditionFourth',
id: 'forth'
}
])

View File

@@ -5,9 +5,9 @@
alt=""
class="icon-img"
/>
<div class="title">{{ info.title }}</div>
<div class="title">{{ $t(info.title) }}</div>
<div class="desc">
{{ info.desc }}
{{ $t(info.desc) }}
<!-- <div>
Please review your submitted information in the AiDA in-platform message.
</div>
@@ -37,14 +37,14 @@
if (props.isExpired) {
return {
icon: expiredIcon,
title: t('AwardsPage.deadlinePassed'),
desc: t('AwardsPage.deadlinePassedDesc')
title: 'AwardsPage.deadlinePassed',
desc: 'AwardsPage.deadlinePassedDesc'
}
} else {
return {
icon: successIcon,
title: t('AwardsPage.submissionSuccessful'),
desc: t('AwardsPage.submissionSuccessfulDesc')
title: 'AwardsPage.submissionSuccessful',
desc: 'AwardsPage.submissionSuccessfulDesc'
}
}
})

View File

@@ -15,8 +15,8 @@
class="progress-icon successful-icon"
/>
</div>
<div class="text">{{ text }}</div>
<div class="tips">{{ tips }}</div>
<div class="text">{{ $t(text) }}</div>
<div class="tips">{{ $t(tips) }}</div>
</div>
</div>
</template>
@@ -32,16 +32,16 @@
const textMap: Record<string, string> = {
idle: '',
uploading: computed(() => t('AwardsPage.uploadInProgress')).value,
success: computed(() => t('AwardsPage.uploadSuccess')).value,
error: computed(() => t('AwardsPage.uploadFailed')).value
uploading: 'AwardsPage.uploadInProgress',
success:'AwardsPage.uploadSuccess',
error: 'AwardsPage.fileUploadFailed'
}
const tips = computed(() => {
if (props.type === 'pdf') {
return t('AwardsPage.pdfFileTip')
return 'AwardsPage.pdfFileTip'
} else if (props.type === 'video') {
return t('AwardsPage.videoFileTip')
return 'AwardsPage.videoFileTip'
}
return ''
})