feat: user frontend i18n (zh-CN/en)

- vue-i18n@9 with locale files for zh-CN and en
- Language switcher in topbar
- Navigation, breadcrumb, credits page translated
- Discovery page i18n keys prepared
- Language persisted in localStorage
- Build verified
This commit is contained in:
TradeMate Dev
2026-06-12 11:21:19 +08:00
parent 79474d8480
commit d8780a716b
7 changed files with 274 additions and 16 deletions
+26 -16
View File
@@ -16,16 +16,16 @@
:collapse-transition="false"
@select="showMobileMenu = false"
>
<el-menu-item index="/discovery"><el-icon><Search /></el-icon><span>发现客户</span></el-menu-item>
<el-menu-item index="/workspace"><el-icon><Odometer /></el-icon><span>工作台</span></el-menu-item>
<el-menu-item index="/customers"><el-icon><User /></el-icon><span>客户管理</span></el-menu-item>
<el-menu-item index="/products"><el-icon><Goods /></el-icon><span>产品库</span></el-menu-item>
<el-menu-item index="/quotations"><el-icon><DocumentCopy /></el-icon><span>报价单</span></el-menu-item>
<el-menu-item index="/translate"><el-icon><ChatLineSquare /></el-icon><span>智能翻译</span></el-menu-item>
<el-menu-item index="/marketing"><el-icon><Promotion /></el-icon><span>营销素材</span></el-menu-item>
<el-menu-item index="/followup"><el-icon><Message /></el-icon><span>智能跟进</span></el-menu-item>
<el-menu-item index="/analytics"><el-icon><DataAnalysis /></el-icon><span>数据分析</span></el-menu-item>
<el-menu-item index="/team"><el-icon><UserFilled /></el-icon><span>团队协作</span></el-menu-item>
<el-menu-item index="/discovery"><el-icon><Search /></el-icon><span>{{ $t('nav.discovery') }}</span></el-menu-item>
<el-menu-item index="/workspace"><el-icon><Odometer /></el-icon><span>{{ $t('nav.workspace') }}</span></el-menu-item>
<el-menu-item index="/customers"><el-icon><User /></el-icon><span>{{ $t('nav.customers') }}</span></el-menu-item>
<el-menu-item index="/products"><el-icon><Goods /></el-icon><span>{{ $t('nav.products') }}</span></el-menu-item>
<el-menu-item index="/quotations"><el-icon><DocumentCopy /></el-icon><span>{{ $t('nav.quotations') }}</span></el-menu-item>
<el-menu-item index="/translate"><el-icon><ChatLineSquare /></el-icon><span>{{ $t('nav.translate') }}</span></el-menu-item>
<el-menu-item index="/marketing"><el-icon><Promotion /></el-icon><span>{{ $t('nav.marketing') }}</span></el-menu-item>
<el-menu-item index="/followup"><el-icon><Message /></el-icon><span>{{ $t('nav.followup') }}</span></el-menu-item>
<el-menu-item index="/analytics"><el-icon><DataAnalysis /></el-icon><span>{{ $t('nav.analytics') }}</span></el-menu-item>
<el-menu-item index="/team"><el-icon><UserFilled /></el-icon><span>{{ $t('nav.team') }}</span></el-menu-item>
</el-menu>
</aside>
@@ -35,13 +35,14 @@
<el-icon :size="20"><Expand /></el-icon>
</el-button>
<el-breadcrumb separator="/" class="breadcrumb">
<el-breadcrumb-item :to="'/workspace'">工作台</el-breadcrumb-item>
<el-breadcrumb-item v-if="route.meta?.title" :to="route.path">{{ route.meta.title }}</el-breadcrumb-item>
<el-breadcrumb-item :to="'/workspace'">{{ $t('nav.workspace') }}</el-breadcrumb-item>
<el-breadcrumb-item v-if="route.meta?.title" :to="route.path">{{ $t('nav.' + route.name?.toLowerCase()) || route.meta.title }}</el-breadcrumb-item>
</el-breadcrumb>
<div class="topbar-right">
<el-button text style="font-size:13px;color:#999" @click="toggleLang">{{ currentLang }}</el-button>
<el-button v-if="creditBalance !== null" text class="credit-btn" @click="$router.push('/credits')">
<el-icon><Coin /></el-icon>
<span class="credit-text">{{ creditBalance }} </span>
<span class="credit-text">{{ creditBalance }} {{ $t('topbar.credits') }}</span>
</el-button>
<el-badge :value="unread" :hidden="!unread" class="notif-badge">
<el-button text style="font-size:18px" @click="$router.push('/notifications')">
@@ -56,9 +57,9 @@
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="$router.push('/profile')">个人中心</el-dropdown-item>
<el-dropdown-item @click="$router.push('/notifications')">通知中心</el-dropdown-item>
<el-dropdown-item divided @click="handleLogout">退出登录</el-dropdown-item>
<el-dropdown-item @click="$router.push('/profile')">{{ $t('nav.profile') }}</el-dropdown-item>
<el-dropdown-item @click="$router.push('/notifications')">{{ $t('nav.notifications') }}</el-dropdown-item>
<el-dropdown-item divided @click="handleLogout">{{ $t('common.confirm') }}退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -89,18 +90,27 @@
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useAuthStore } from '@/stores/auth'
import { getUnreadCount, getCreditBalance } from '@/api'
import AiAssistant from '@/components/AiAssistant.vue'
import { switchLang } from '@/i18n'
const route = useRoute()
const router = useRouter()
const { locale } = useI18n()
const auth = useAuthStore()
const collapsed = ref(false)
const showMobileMenu = ref(false)
const unread = ref(0)
const creditBalance = ref(null)
const currentLang = computed(() => locale.value === 'en' ? 'English' : '中文')
function toggleLang() {
const next = locale.value === 'en' ? 'zh-CN' : 'en'
switchLang(next)
}
async function loadCreditBalance() {
try {
const res = await getCreditBalance()