语言适配

This commit is contained in:
李志鹏
2026-05-27 11:04:51 +08:00
parent a4441c6107
commit 1ab121a703
11 changed files with 215 additions and 80 deletions

View File

@@ -1,6 +1,8 @@
export default {
Login: {
signup: 'Sign up',
login: 'Log in',
logoff: 'Log off',
register: 'Register',
loginTip: 'Platform integrated with AiDA.<br />AiDA account login required.',
name: 'Name',
@@ -11,6 +13,7 @@ export default {
enterEmail: 'Enter your email',
enterPassword: 'Enter your password',
enterPasswordAgain: 'Enter your password again',
passwordTip: 'You must satisfy ALL password conditions to register.',
forgotPassword: 'Forget password?',
pleaseInputName: 'Please input the name',
nameLengthError: 'Name length must be between {min} and {max} characters',
@@ -37,7 +40,24 @@ export default {
wechatLogin: 'Sign in with Wechat',
indexTip: 'A multi-agent canvas for rapid, trend driven design iteration.',
sendCodeError: 'Send code error',
retrievePassword: 'Retrieve password'
retrievePassword: 'Retrieve password',
emailVerification: 'Email Verification',
retrievePasswordTitle: 'Please enter your email address below to verify your identity.',
submit: 'Submit',
enterNewPassword: 'Enter a new password for<br/><span>{email}</span>',
passwordsDoNotMatch: 'Passwords do not match',
logOffTip: 'Are you sure to log off?',
},
RegisterSuccess: {
title1: 'Welcome to Stylish Parade!',
title2: 'Please switch to the Login tab to log in.',
title3: 'What awaits you in Stylish Parade',
item1title: 'Behind the design',
item1tip: 'Discover how designers bring ideas to life with AiDA — from first sketch to final look.',
item2title: 'Creative digital works',
item2tip: 'Unlock a growing library of inspiring digital works to refresh your creative mind.',
item3title: 'A fashion community',
item3tip: 'Join a space where fashion speaks — exchange ideas and connect with creators worldwide.',
},
Settings: {
title: 'Settings',
@@ -235,6 +255,8 @@ export default {
digitalItem:{
BestSelling: 'Best Selling',
Price: 'Price: Low to High',
SelectedFirst: 'Selected First',
DateAdded: 'Date Added',
NewestFirst: 'Newest First',
title: 'Digital Item',
info: 'Virtual fashion creations collected in your personal archive',
@@ -250,5 +272,34 @@ export default {
},
checked: {
All: 'All',
}
},
MainHeader: {
Home: 'Home',
CollectionStory: 'Collection Story',
Brand: 'Brand',
DigitalItem: 'Digital Item',
HiName: 'Hi, {name}',
MyWardrobe: 'My Wardrobe',
Notifications: 'Notifications',
Settings: 'Settings',
},
ShoppingCart: {
title: 'Shopping Cart',
listNullTitle: 'Your Cart is empty',
listNullTip: 'Discover new fashion assets and add them to your cart.',
dateTimeFormat: 'SM D, YYYY, h:mm A',
noLongerAvailable: 'No Longer Available',
delistedFromMarketplace: 'Delisted from marketplace',
remove: 'Remove',
removeTip: 'Are you sure to remove this item?',
total: 'Total',
digitalAssets: 'Digital assets. Creator retains copyright.',
checkout: 'Checkout',
exploreDigitalItems: 'Explore Digital Items',
orderSummary: 'Order Summary',
selected: 'Selected',
brands: 'Brands',
item: 'item',
checkoutSelected: 'Checkout Selected',
},
}

View File

@@ -1,6 +1,8 @@
export default {
Login: {
signup: '注册',
login: '登录',
logoff: '退出登录',
register: '注册',
loginTip: '与 AiDA 集成的平台。<br />需要登录 AiDA 账户。',
name: '姓名',
@@ -11,6 +13,7 @@ export default {
enterEmail: '请输入邮箱',
enterPassword: '请输入密码',
enterPasswordAgain: '请输入密码确认',
passwordTip: '你必须满足所有密码条件才能注册。',
forgotPassword: '忘记密码?',
pleaseInputName: '请输入姓名',
nameLengthError: '姓名长度必须在 {min} 到 {max} 个字符之间',
@@ -37,7 +40,24 @@ export default {
wechatLogin: '使用微信登录',
indexTip: '一个多智能体画布,用于快速、趋势驱动的设计迭代。',
sendCodeError: '发送验证码失败',
retrievePassword: '找回密码'
retrievePassword: '找回密码',
emailVerification: '邮箱验证',
retrievePasswordTitle: '请输入您的邮箱地址以验证您的身份。',
submit: '提交',
enterNewPassword: '请输入新密码<br/><span>{email}</span>',
passwordsDoNotMatch: '两次输入密码不一致',
logOffTip: '确定退出登录吗?',
},
RegisterSuccess: {
title1: '欢迎来到 Stylish Parade',
title2: '请切换到登录选项卡以登录。',
title3: '在 Stylish Parade 中等待你的发现',
item1title: '设计灵感',
item1tip: '了解设计师是如何借助 AiDA 将创意变为现实的——从最初的草图到最终的成品。',
item2title: '创意数字作品',
item2tip: '解锁一个增长的数字作品库,刷新你的创意。',
item3title: '时尚社区',
item3tip: '加入一个全球的时尚社区,与设计师分享创意。',
},
Settings: {
title: '设置',
@@ -235,6 +255,8 @@ export default {
digitalItem: {
BestSelling: "畅销优先",
Price: "价格:从低到高",
SelectedFirst: "已选优先",
DateAdded: "添加日期",
NewestFirst: "最新优先",
title: "数字藏品",
info: "收藏于个人档案中的虚拟时装作品",
@@ -250,5 +272,34 @@ export default {
},
checked: {
All: "全部"
}
},
MainHeader: {
Home: '首页',
CollectionStory: '系列故事',
Brand: '品牌',
DigitalItem: '数字藏品',
HiName: '你好,{name}',
MyWardrobe: '我的衣橱',
Notifications: '通知',
Settings: '设置',
},
ShoppingCart: {
title: '购物车',
listNullTitle: '你的购物车为空',
listNullTip: '发现新的时尚资产并将其添加到你的购物车。',
dateTimeFormat: 'YYYY-MM-DD HH:mm',
noLongerAvailable: '已下架',
delistedFromMarketplace: '已从市场移除',
remove: '删除',
removeTip: '确认删除吗?',
total: '总金额',
digitalAssets: '数字资产。创作者保留版权。',
checkout: '结账',
exploreDigitalItems: '探索数字单品',
orderSummary: '订单信息',
selected: '已选',
brands: '品牌',
item: '数字藏品',
checkoutSelected: '结账已选',
},
}

View File

@@ -14,26 +14,28 @@
<div class="close" @click="show = false"><svg-icon name="close" /></div>
<div class="content" v-if="curentTabInfo">
<div class="header">
<div class="title" v-show="curentTabInfo.title">
<div class="title" v-if="curentTabInfo.title">
<div class="icon" @click="onBack"><svg-icon name="back" size="17" /></div>
<div class="label">{{ curentTabInfo.title }}</div>
<div class="label">{{ $t(curentTabInfo.title) }}</div>
</div>
<div class="nav" v-show="!curentTabInfo.title">
<div
class="item"
:class="{
active: [TabNames.register, TabNames.register_success].includes(currentTab)
active: [TabNames.register, TabNames.register_success].includes(
currentTab
)
}"
@click="currentTab = TabNames.register"
>
SIGN UP
{{ $t('Login.signup') }}
</div>
<div
class="item"
:class="{ active: currentTab === TabNames.login }"
@click="currentTab = TabNames.login"
>
LOG IN
{{ $t('Login.login') }}
</div>
</div>
</div>
@@ -70,6 +72,8 @@
import retrievePassword from './retrieve-password.vue'
import myEvent from '@/utils/myEvent'
import { useUserInfoStore } from '@/stores/userInfo'
import { useRouter } from 'vue-router'
const router = useRouter()
const userStore = useUserInfoStore()
const data = ref({
name: '',
@@ -95,12 +99,12 @@
},
{
name: TabNames.email_verify,
title: 'EMAIL VERIFICATION',
title: 'Login.emailVerification',
component: emailVerify
},
{
name: TabNames.retrieve_password,
title: 'RETRIEVE PASSWORD',
title: 'Login.retrievePassword',
component: retrievePassword
},
{
@@ -157,6 +161,9 @@
AccountLogin(value).then((v) => {
userStore.setUserInfo(v)
show.value = false
setTimeout(() => {
router.go(0)
})
})
} else {
value['username'] = data.value.name
@@ -244,6 +251,7 @@
font-size: 2rem;
font-family: KaiseiOpti-Regular;
color: #9f9f9f;
text-transform: uppercase;
&.active {
font-family: KaiseiOpti-Bold;
color: #232323;
@@ -273,6 +281,7 @@
font-family: KaiseiOpti-Bold;
font-size: 2rem;
color: #232323;
text-transform: uppercase;
}
}
}

View File

@@ -1,31 +1,25 @@
<template>
<div class="register-success">
<div class="icon"><svg-icon name="dui" size="20" /></div>
<div class="title">Welcome to Stylish Parade!</div>
<div class="title">Please switch to the Login tab to log in.</div>
<div class="title">{{ t('RegisterSuccess.title1') }}</div>
<div class="title">{{ t('RegisterSuccess.title2') }}</div>
<div class="footer">
<div class="title">
<span class="text">What awaits you in Stylish Parade</span>
<span class="text">{{ t('RegisterSuccess.title3') }}</span>
<span class="icon"><svg-icon name="arrow_right" size="11" /></span>
</div>
<div class="content">
<div>
<div class="title">Behind the design</div>
<div class="tip">
Discover how designers bring ideas to life with AiDA from first sketch to final look.
</div>
<div class="title">{{ t('RegisterSuccess.item1title') }}</div>
<div class="tip">{{ t('RegisterSuccess.item1tip') }}</div>
</div>
<div>
<div class="title">Creative digital works</div>
<div class="tip">
Unlock a growing library of inspiring digital works to refresh your creative mind.
</div>
<div class="title">{{ t('RegisterSuccess.item2title') }}</div>
<div class="tip">{{ t('RegisterSuccess.item2tip') }}</div>
</div>
<div>
<div class="title">A fashion community</div>
<div class="tip">
Join a space where fashion speaks exchange ideas and connect with creators worldwide.
</div>
<div class="title">{{ t('RegisterSuccess.item3title') }}</div>
<div class="tip">{{ t('RegisterSuccess.item3tip') }}</div>
</div>
</div>
</div>

View File

@@ -19,7 +19,7 @@
<div class="password-warning">
<span class="icon"><svg-icon name="warning" size="12" /></span>
<span class="label">You must satisfy ALL password conditions to register.</span>
<span class="label">{{ $t('Login.passwordTip') }}</span>
</div>
<el-form-item :label="$t('Login.email')" prop="email">
<el-input name="email" v-model="formData.email" :placeholder="$t('Login.enterEmail')" />

View File

@@ -7,13 +7,17 @@
ref="form1Ref"
v-show="index === 0"
>
<div class="title">Please enter your email address below to verify your identity.</div>
<div class="title">{{ $t('Login.retrievePasswordTitle') }}</div>
<el-form-item :label="$t('Login.email')" prop="email">
<el-input v-model="formData.email" :placeholder="$t('Login.enterEmail')" name="email" />
<el-input
v-model="formData.email"
:placeholder="$t('Login.enterEmail')"
name="email"
/>
</el-form-item>
<el-form-item class="submit-item">
<button class="submit" type="submit" custom="black" @click.prevent="onSubmit1">
SUBMIT
{{ $t('Login.submit') }}
</button>
</el-form-item>
</el-form>
@@ -32,10 +36,10 @@
ref="form2Ref"
v-show="index === 2"
>
<div class="title">
Enter a new password for <br />
<span>{{ formData.email }}</span>
</div>
<div
class="title"
v-html="$t('Login.enterNewPassword', { email: formData.email })"
></div>
<el-form-item :label="$t('Login.password')" prop="password">
<password-tip :value="formData.password" v-show="showPasswordTip" />
<el-input
@@ -50,7 +54,7 @@
</el-form-item>
<div class="password-warning">
<span class="icon"><svg-icon name="warning" size="12" /></span>
<span class="label">You must satisfy ALL password conditions to register.</span>
<span class="label">{{ $t('Login.passwordTip') }}</span>
</div>
<el-form-item :label="$t('Login.passwordConfirmation')" prop="confirmPassword">
<el-input
@@ -63,7 +67,7 @@
</el-form-item>
<el-form-item class="submit-item">
<button class="submit" type="submit" custom="black" @click.prevent="onSubmit2">
SUBMIT
{{ $t('Login.submit') }}
</button>
</el-form-item>
</el-form>
@@ -77,10 +81,12 @@
import { validateEmail, validatePass } from './tools'
import PasswordTip from './password-tip.vue'
import EmailVerify from './email-verify.vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const emit = defineEmits(['back'])
const validateConfirmPassword = (rule: any, value: string, callback: any) => {
if (value !== formData.password) {
callback(new Error('Passwords do not match'))
callback(new Error(t('Login.passwordsDoNotMatch')))
} else {
callback()
}
@@ -158,6 +164,9 @@
flex-direction: column;
.el-form-item.submit-item {
margin-top: auto;
.submit {
text-transform: uppercase;
}
}
.el-input {
--el-input-height: 4.8rem;

View File

@@ -16,7 +16,7 @@
}"
@click="onNavItemClick(v.path)"
>
<span>{{ v.name }}</span>
<span>{{ $t(v.name) }}</span>
</div>
</div>
<div class="right">
@@ -46,30 +46,32 @@
<img src="@/assets/images/profile-content-bg.jpg" alt="" />
<div class="content">
<div class="profile"></div>
<div class="name">Hi, {{ userInfo.username }}</div>
<div class="name">
{{ $t('MainHeader.HiName', { name: userInfo.username }) }}
</div>
</div>
</div>
<div class="nav-item" @click="onMyWardrobe">
<div class="icon"><svg-icon name="my_wardrobe" size="18" /></div>
<div class="label">My Wardrobe</div>
<div class="label">{{ $t('MainHeader.MyWardrobe') }}</div>
</div>
<div class="nav-item" @click="onNotifications">
<div class="icon"><svg-icon name="notifications" size="14" /></div>
<div class="label">Notifications</div>
<div class="label">{{ $t('MainHeader.Notifications') }}</div>
</div>
<div class="nav-item" @click="onSettings">
<div class="icon"><svg-icon name="settings" size="16" /></div>
<div class="label">Settings</div>
<div class="label">{{ $t('MainHeader.Settings') }}</div>
</div>
<div class="hr"></div>
<div class="nav-item logout" @click="onLogout">
<div class="icon"><svg-icon name="logout" size="20" /></div>
<div class="label">Log off</div>
<div class="label">{{ $t('Login.logoff') }}</div>
</div>
</div>
</template>
</el-popover>
<div class="login" @click="onLogin" v-else>Login</div>
<div class="login" @click="onLogin" v-else>{{ $t('Login.login') }}</div>
<div class="language" @click="onLanguageClick">
<span :class="{ active: locale === 'CHINESE_SIMPLIFIED' }"></span> /
<span :class="{ active: locale === 'ENGLISH' }">ENG</span>
@@ -93,19 +95,19 @@
const activePath = computed(() => route.path)
const navList1 = ref([
{
name: 'Home',
name: 'MainHeader.Home',
path: '/'
},
{
name: 'Collection Story',
name: 'MainHeader.CollectionStory',
path: '/collectionStory'
},
{
name: 'Brand',
name: 'MainHeader.Brand',
path: '/brand'
},
{
name: 'Digital Item',
name: 'MainHeader.DigitalItem',
path: '/digitalItem'
}
])
@@ -142,7 +144,7 @@
}
const onLogout = () => {
hideProfilePopover()
ElMessageBox.confirm('Are you sure to log off?')
ElMessageBox.confirm(t('Login.logOffTip'))
.then(() => {
userInfoStore.logout(true)
})

View File

@@ -1,33 +1,35 @@
<template>
<div class="order-summary">
<div class="title">Order Summary</div>
<div class="title">{{ $t('ShoppingCart.orderSummary') }}</div>
<div class="count">
<span class="label">Selected</span>
<span class="label">{{ $t('ShoppingCart.selected') }}</span>
<span class="value">{{ brandsList.length }}</span>
</div>
<div class="hr"></div>
<div class="brands-header">
<span class="icon"><svg-icon name="order-shop" size="24" /></span>
<span class="text">Brands</span>
<span class="text">{{ $t('ShoppingCart.brands') }}</span>
</div>
<div class="brands-item" v-for="v in brandsList" :key="v.brand">
<span class="label">{{ v.brand }}</span>
<span class="value"
><span>{{ v.children.length }}</span
>item</span
>{{ $t('ShoppingCart.item') }}</span
>
</div>
<br />
<br />
<div class="total">
<span class="label">Total</span>
<span class="label">{{ $t('ShoppingCart.total') }}</span>
<span class="value"
><span>${{ totalAmount }}</span> HKD</span
>
</div>
<div class="hr"></div>
<button class="checkout-btn" custom="black" @click="handleCheckout">CHECKOUT SELECTED</button>
<div class="tip">Digital assets. Creator retains copyright.</div>
<button class="checkout-btn" custom="black" @click="handleCheckout">
{{ $t('ShoppingCart.checkoutSelected') }}
</button>
<div class="tip">{{ $t('ShoppingCart.digitalAssets') }}</div>
</div>
</template>
@@ -162,6 +164,7 @@
> .checkout-btn {
width: 100%;
margin-top: 3rem;
text-transform: uppercase;
}
> .tip {
margin-top: 1rem;

View File

@@ -19,7 +19,7 @@
</div>
<div class="date" v-if="showDate">
<div class="text">
{{ FormatDate(info.date, 'SM D, YYYY, h:mm A') }}
{{ FormatDate(info.date, $t('ShoppingCart.dateTimeFormat')) }}
</div>
</div>
</div>
@@ -27,9 +27,9 @@
<div class="unshelve" v-show="disabled">
<div class="title">
<span><svg-icon name="order-warning" size="20" /></span>
No Longer Available
{{ $t('ShoppingCart.noLongerAvailable') }}
</div>
<div class="tip">Delisted from marketplace</div>
<div class="tip">{{ $t('ShoppingCart.delistedFromMarketplace') }}</div>
</div>
<div class="amount" v-show="!disabled">${{ info.amount }}<span> HKD</span></div>
<SvgIcon
@@ -41,7 +41,7 @@
/>
<div class="remove" v-if="showRemove" @click="onRemove">
<span class="icon"><svg-icon name="order-delete" size="18" /></span>
<span class="text">Remove</span>
<span class="text">{{ $t('ShoppingCart.remove') }}</span>
</div>
</div>
</div>

View File

@@ -4,7 +4,9 @@
<img v-else-if="nullImage === 'brand'" src="@/assets/images/brand-null.png" />
<div class="title">{{ title }}</div>
<div class="tip">{{ tip }}</div>
<button custom v-show="showButton" @click="handleClick">{{ buttonText }}</button>
<button custom v-show="showButton" @click="handleClick">
{{ buttonText || $t('ShoppingCart.exploreDigitalItems') }}
</button>
</div>
</template>
@@ -15,7 +17,7 @@
title: { type: String, default: '' },
tip: { type: String, default: '' },
showButton: { type: Boolean, default: true },
buttonText: { type: String, default: 'EXPLORE DIGITAL ITEMS' }
buttonText: { type: String, default: '' }
})
const emit = defineEmits(['explore'])
const handleClick = () => {
@@ -58,6 +60,7 @@
font-size: 1.6rem;
color: #979797;
margin-top: 3rem;
text-transform: uppercase;
}
}
</style>

View File

@@ -3,7 +3,7 @@
<div class="sc-list" :class="{ mini: isMini, view: isView }">
<div class="header">
<div class="title">
<span class="text">{{ title || 'Shopping Cart' }}</span>
<span class="text">{{ title || $t('ShoppingCart.title') }}</span>
<span class="close" v-if="isMini && !isView" @click="onClose"
><svg-icon name="close" size="13"
/></span>
@@ -15,22 +15,32 @@
:indeterminate="selectedCount === 0 ? false : selectedCount < maxLength"
@click="handleAllAllClick"
/>
<span class="count">{{ selectedCount }}&nbsp;&nbsp;Selected</span>
<span class="count">{{
$t('Wardrobe.assets.selectedCount', { count: selectedCount })
}}</span>
<div class="hr"></div>
<div class="btn" @click="handleAllAllClick(true)">Select All</div>
<div class="btn" @click="handleAllAllClick(false)">Deselect All</div>
<div class="btn" @click="handleAllAllClick(true)">
{{ $t('Wardrobe.assets.selectAll') }}
</div>
<div class="btn" @click="handleAllAllClick(false)">
{{ $t('Wardrobe.assets.deselectAll') }}
</div>
</div>
<div class="right">
<el-select v-model="sortBy" placeholder="Sort By" :teleported="false">
<el-select
v-model="sortBy"
:placeholder="$t('Wardrobe.sort.label')"
:teleported="false"
>
<template #label="{ label }">
<span class="header-label">Sort By</span>
<span class="header-value">{{ label }}</span>
<span class="header-label">{{ $t('Wardrobe.sort.label') }}</span>
<span class="header-value">{{ $t(label) }}</span>
</template>
<el-option
v-for="item in sortByOptions"
:key="item.label"
:value="item.value"
:label="item.label"
:label="$t(item.label)"
/>
</el-select>
</div>
@@ -40,8 +50,8 @@
<sc-list-null
v-show="list.length === 0"
:show-button="!isMini"
title="Your Cart is empty"
tip="Discover new fashion assets and add them to your cart."
:title="$t('ShoppingCart.listNullTitle')"
:tip="$t('ShoppingCart.listNullTip')"
@explore="handleExplore"
/>
<sc-item
@@ -66,12 +76,12 @@
</div>
<div class="footer" v-if="isMini">
<div class="total" v-show="list.length > 0 || isView">
<span class="label">Total</span>
<span class="label">{{ $t('ShoppingCart.total') }}</span>
<span class="value">${{ allAmount }}<span> HKD</span></span>
</div>
<div class="tip" v-if="isView">Digital assets. Creator retains copyright.</div>
<button custom="black" v-if="!isView">CHECKOUT</button>
<div class="tip" v-if="isView">{{ $t('ShoppingCart.digitalAssets') }}</div>
<button custom="black" v-if="!isView">{{ $t('ShoppingCart.checkout') }}</button>
</div>
</div>
</template>
@@ -85,6 +95,8 @@
import scItem from './sc-item.vue'
import scListNull from './sc-list-null.vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const router = useRouter()
const emit = defineEmits(['close', 'selected-change'])
const props = defineProps({
@@ -103,19 +115,19 @@
const sortBy = ref('DateAdded')
const sortByOptions = ref([
{
label: 'Best Selling',
label: 'digitalItem.BestSelling',
value: 'BestSelling'
},
{
label: 'Price: Low to High',
label: 'digitalItem.Price',
value: 'PriceLowToHigh'
},
{
label: 'Selected First',
label: 'digitalItem.SelectedFirst',
value: 'SelectedFirst'
},
{
label: 'Date Added',
label: 'digitalItem.DateAdded',
value: 'DateAdded'
}
])
@@ -177,7 +189,7 @@
handleSelectedChange()
})
const handleRemoveClick = (value: any) => {
ElMessageBox.confirm('Are you sure to remove this item?')
ElMessageBox.confirm(t('ShoppingCart.removeTip'))
.then(() => {
RemoveShoppingCartItem({ listingId: value.listingId }).then(() => {
GetList()
@@ -365,6 +377,7 @@
height: 4.6rem;
--button-font-size: 1.4rem;
margin-top: 2rem;
text-transform: uppercase;
}
}
}