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:
Generated
+65
@@ -14,6 +14,7 @@
|
|||||||
"element-plus": "^2.9.1",
|
"element-plus": "^2.9.1",
|
||||||
"pinia": "^2.3.0",
|
"pinia": "^2.3.0",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
|
"vue-i18n": "^9.14.4",
|
||||||
"vue-router": "^4.5.0"
|
"vue-router": "^4.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -553,6 +554,50 @@
|
|||||||
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
|
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@intlify/core-base": {
|
||||||
|
"version": "9.14.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.14.4.tgz",
|
||||||
|
"integrity": "sha512-vtZCt7NqWhKEtHa3SD/322DlgP5uR9MqWxnE0y8Q0tjDs9H5Lxhss+b5wv8rmuXRoHKLESNgw9d+EN9ybBbj9g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@intlify/message-compiler": "9.14.4",
|
||||||
|
"@intlify/shared": "9.14.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/kazupon"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@intlify/message-compiler": {
|
||||||
|
"version": "9.14.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.14.4.tgz",
|
||||||
|
"integrity": "sha512-vcyCLiVRN628U38c3PbahrhbbXrckrM9zpy0KZVlDk2Z0OnGwv8uQNNXP3twwGtfLsCf4gu3ci6FMIZnPaqZsw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@intlify/shared": "9.14.4",
|
||||||
|
"source-map-js": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/kazupon"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@intlify/shared": {
|
||||||
|
"version": "9.14.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.14.4.tgz",
|
||||||
|
"integrity": "sha512-P9zv6i1WvMc9qDBWvIgKkymjY2ptIiQ065PjDv7z7fDqH3J/HBRBN5IoiR46r/ujRcU7hCuSIZWvCAFCyuOYZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/kazupon"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/sourcemap-codec": {
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
"version": "1.5.5",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||||
@@ -1990,6 +2035,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-i18n": {
|
||||||
|
"version": "9.14.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.14.4.tgz",
|
||||||
|
"integrity": "sha512-B934C8yUyWLT0EMud3DySrwSUJI7ZNiWYsEEz2gknTthqKiG4dzWE/WSa8AzCuSQzwBEv4HtG1jZDhgzPfWSKQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@intlify/core-base": "9.14.4",
|
||||||
|
"@intlify/shared": "9.14.4",
|
||||||
|
"@vue/devtools-api": "^6.5.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/kazupon"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vue-router": {
|
"node_modules/vue-router": {
|
||||||
"version": "4.6.4",
|
"version": "4.6.4",
|
||||||
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz",
|
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"element-plus": "^2.9.1",
|
"element-plus": "^2.9.1",
|
||||||
"pinia": "^2.3.0",
|
"pinia": "^2.3.0",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
|
"vue-i18n": "^9.14.4",
|
||||||
"vue-router": "^4.5.0"
|
"vue-router": "^4.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { createI18n } from 'vue-i18n'
|
||||||
|
import zhCN from './locales/zh-CN.json'
|
||||||
|
import en from './locales/en.json'
|
||||||
|
|
||||||
|
const savedLang = localStorage.getItem('lang') || 'zh-CN'
|
||||||
|
|
||||||
|
const i18n = createI18n({
|
||||||
|
legacy: false,
|
||||||
|
locale: savedLang,
|
||||||
|
fallbackLocale: 'zh-CN',
|
||||||
|
messages: {
|
||||||
|
'zh-CN': zhCN,
|
||||||
|
'en': en,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export function switchLang(lang) {
|
||||||
|
i18n.global.locale.value = lang
|
||||||
|
localStorage.setItem('lang', lang)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default i18n
|
||||||
@@ -16,16 +16,16 @@
|
|||||||
:collapse-transition="false"
|
:collapse-transition="false"
|
||||||
@select="showMobileMenu = false"
|
@select="showMobileMenu = false"
|
||||||
>
|
>
|
||||||
<el-menu-item index="/discovery"><el-icon><Search /></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>工作台</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>客户管理</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>产品库</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>报价单</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>智能翻译</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>营销素材</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>智能跟进</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>数据分析</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>团队协作</span></el-menu-item>
|
<el-menu-item index="/team"><el-icon><UserFilled /></el-icon><span>{{ $t('nav.team') }}</span></el-menu-item>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
@@ -35,13 +35,14 @@
|
|||||||
<el-icon :size="20"><Expand /></el-icon>
|
<el-icon :size="20"><Expand /></el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-breadcrumb separator="/" class="breadcrumb">
|
<el-breadcrumb separator="/" class="breadcrumb">
|
||||||
<el-breadcrumb-item :to="'/workspace'">工作台</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">{{ route.meta.title }}</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>
|
</el-breadcrumb>
|
||||||
<div class="topbar-right">
|
<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-button v-if="creditBalance !== null" text class="credit-btn" @click="$router.push('/credits')">
|
||||||
<el-icon><Coin /></el-icon>
|
<el-icon><Coin /></el-icon>
|
||||||
<span class="credit-text">{{ creditBalance }} 次</span>
|
<span class="credit-text">{{ creditBalance }} {{ $t('topbar.credits') }}</span>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-badge :value="unread" :hidden="!unread" class="notif-badge">
|
<el-badge :value="unread" :hidden="!unread" class="notif-badge">
|
||||||
<el-button text style="font-size:18px" @click="$router.push('/notifications')">
|
<el-button text style="font-size:18px" @click="$router.push('/notifications')">
|
||||||
@@ -56,9 +57,9 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
<el-dropdown-item @click="$router.push('/profile')">个人中心</el-dropdown-item>
|
<el-dropdown-item @click="$router.push('/profile')">{{ $t('nav.profile') }}</el-dropdown-item>
|
||||||
<el-dropdown-item @click="$router.push('/notifications')">通知中心</el-dropdown-item>
|
<el-dropdown-item @click="$router.push('/notifications')">{{ $t('nav.notifications') }}</el-dropdown-item>
|
||||||
<el-dropdown-item divided @click="handleLogout">退出登录</el-dropdown-item>
|
<el-dropdown-item divided @click="handleLogout">{{ $t('common.confirm') }}退出</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
@@ -89,18 +90,27 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { getUnreadCount, getCreditBalance } from '@/api'
|
import { getUnreadCount, getCreditBalance } from '@/api'
|
||||||
import AiAssistant from '@/components/AiAssistant.vue'
|
import AiAssistant from '@/components/AiAssistant.vue'
|
||||||
|
import { switchLang } from '@/i18n'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { locale } = useI18n()
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
const collapsed = ref(false)
|
const collapsed = ref(false)
|
||||||
const showMobileMenu = ref(false)
|
const showMobileMenu = ref(false)
|
||||||
const unread = ref(0)
|
const unread = ref(0)
|
||||||
const creditBalance = ref(null)
|
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() {
|
async function loadCreditBalance() {
|
||||||
try {
|
try {
|
||||||
const res = await getCreditBalance()
|
const res = await getCreditBalance()
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"nav": {
|
||||||
|
"discovery": "Find Buyers",
|
||||||
|
"workspace": "Dashboard",
|
||||||
|
"customers": "Customers",
|
||||||
|
"products": "Products",
|
||||||
|
"quotations": "Quotations",
|
||||||
|
"translate": "Translate",
|
||||||
|
"marketing": "Marketing",
|
||||||
|
"followup": "Follow-ups",
|
||||||
|
"analytics": "Analytics",
|
||||||
|
"team": "Team",
|
||||||
|
"notifications": "Notifications",
|
||||||
|
"profile": "Profile",
|
||||||
|
"credits": "Buy Credits",
|
||||||
|
"settings": "Settings"
|
||||||
|
},
|
||||||
|
"topbar": {
|
||||||
|
"credits": "credits",
|
||||||
|
"purchase": "Buy Credits",
|
||||||
|
"notifications": "Notifications"
|
||||||
|
},
|
||||||
|
"discovery": {
|
||||||
|
"title": "Discover New Customers",
|
||||||
|
"subtitle": "Enter a product description to find potential buyers worldwide",
|
||||||
|
"product_placeholder": "e.g. LED lighting, solar panels, auto parts",
|
||||||
|
"market_placeholder": "Target market (e.g. US, Germany, leave empty for global)",
|
||||||
|
"search": "Search Buyers",
|
||||||
|
"searching": "Searching...",
|
||||||
|
"results": "Search Results",
|
||||||
|
"no_results": "No results found. Try different keywords",
|
||||||
|
"credits_cost": "Each search costs 10 credits",
|
||||||
|
"analyze": "Deep Analysis",
|
||||||
|
"outreach": "Generate Outreach",
|
||||||
|
"add_customer": "Add as Customer",
|
||||||
|
"visit_website": "Visit Website",
|
||||||
|
"match_score": "Match Score",
|
||||||
|
"contact": "Contact Info"
|
||||||
|
},
|
||||||
|
"credits": {
|
||||||
|
"title": "Credits",
|
||||||
|
"balance": "Current Balance",
|
||||||
|
"packages": "Buy Credits",
|
||||||
|
"subscription": "Subscription",
|
||||||
|
"history": "Transaction History",
|
||||||
|
"purchase": "Buy Now",
|
||||||
|
"subscribe": "Subscribe",
|
||||||
|
"per_month": "/month",
|
||||||
|
"per_credit": "per credit",
|
||||||
|
"pay_alipay": "Alipay",
|
||||||
|
"pay_wechat": "WeChat Pay",
|
||||||
|
"confirm_pay": "Confirm Payment",
|
||||||
|
"scan_pay": "Scan to Pay",
|
||||||
|
"insufficient": "Insufficient credits",
|
||||||
|
"buy_more": "Buy More",
|
||||||
|
"total_purchased": "Total Purchased",
|
||||||
|
"total_used": "Total Used"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"loading": "Loading...",
|
||||||
|
"error": "Something went wrong",
|
||||||
|
"retry": "Retry",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"save": "Save",
|
||||||
|
"delete": "Delete",
|
||||||
|
"edit": "Edit",
|
||||||
|
"back": "Back",
|
||||||
|
"no_data": "No data",
|
||||||
|
"search": "Search",
|
||||||
|
"all": "All",
|
||||||
|
"status": "Status",
|
||||||
|
"time": "Time"
|
||||||
|
},
|
||||||
|
"lang": {
|
||||||
|
"switch_to": "中文",
|
||||||
|
"current": "English"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"nav": {
|
||||||
|
"discovery": "发现客户",
|
||||||
|
"workspace": "工作台",
|
||||||
|
"customers": "客户管理",
|
||||||
|
"products": "产品库",
|
||||||
|
"quotations": "报价单",
|
||||||
|
"translate": "智能翻译",
|
||||||
|
"marketing": "营销素材",
|
||||||
|
"followup": "跟进提醒",
|
||||||
|
"analytics": "数据分析",
|
||||||
|
"team": "团队协作",
|
||||||
|
"notifications": "通知中心",
|
||||||
|
"profile": "个人中心",
|
||||||
|
"credits": "购买次数",
|
||||||
|
"settings": "设置"
|
||||||
|
},
|
||||||
|
"topbar": {
|
||||||
|
"credits": "次",
|
||||||
|
"purchase": "购买次数",
|
||||||
|
"notifications": "通知"
|
||||||
|
},
|
||||||
|
"discovery": {
|
||||||
|
"title": "发现新客户",
|
||||||
|
"subtitle": "输入产品关键词,找到全球潜在买家",
|
||||||
|
"product_placeholder": "例如:LED lighting, solar panels, auto parts",
|
||||||
|
"market_placeholder": "目标市场(如 US, Germany, 留空为全球)",
|
||||||
|
"search": "搜索买家",
|
||||||
|
"searching": "正在搜索...",
|
||||||
|
"results": "搜索结果",
|
||||||
|
"no_results": "暂无结果,请尝试其他关键词",
|
||||||
|
"credits_cost": "每次搜索消耗 10 次",
|
||||||
|
"analyze": "深度分析",
|
||||||
|
"outreach": "生成开发信",
|
||||||
|
"add_customer": "添加为客户",
|
||||||
|
"visit_website": "访问网站",
|
||||||
|
"match_score": "匹配度",
|
||||||
|
"contact": "联系方式"
|
||||||
|
},
|
||||||
|
"credits": {
|
||||||
|
"title": "信用次数",
|
||||||
|
"balance": "当前余额",
|
||||||
|
"packages": "购买次数",
|
||||||
|
"subscription": "订阅套餐",
|
||||||
|
"history": "消费记录",
|
||||||
|
"purchase": "立即购买",
|
||||||
|
"subscribe": "开通订阅",
|
||||||
|
"per_month": "次/月",
|
||||||
|
"per_credit": "次均",
|
||||||
|
"pay_alipay": "支付宝",
|
||||||
|
"pay_wechat": "微信支付",
|
||||||
|
"confirm_pay": "确认支付",
|
||||||
|
"scan_pay": "扫码支付",
|
||||||
|
"insufficient": "次数不足",
|
||||||
|
"buy_more": "去购买",
|
||||||
|
"total_purchased": "已购买",
|
||||||
|
"total_used": "已使用"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"loading": "加载中...",
|
||||||
|
"error": "出错了",
|
||||||
|
"retry": "重试",
|
||||||
|
"cancel": "取消",
|
||||||
|
"confirm": "确认",
|
||||||
|
"save": "保存",
|
||||||
|
"delete": "删除",
|
||||||
|
"edit": "编辑",
|
||||||
|
"back": "返回",
|
||||||
|
"no_data": "暂无数据",
|
||||||
|
"search": "搜索",
|
||||||
|
"all": "全部",
|
||||||
|
"status": "状态",
|
||||||
|
"time": "时间"
|
||||||
|
},
|
||||||
|
"lang": {
|
||||||
|
"switch_to": "English",
|
||||||
|
"current": "中文"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,10 +6,12 @@ import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
|||||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
|
import i18n from './i18n'
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
app.use(createPinia())
|
app.use(createPinia())
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
app.use(i18n)
|
||||||
app.use(ElementPlus, { locale: zhCn })
|
app.use(ElementPlus, { locale: zhCn })
|
||||||
for (const [k, v] of Object.entries(ElementPlusIconsVue)) app.component(k, v)
|
for (const [k, v] of Object.entries(ElementPlusIconsVue)) app.component(k, v)
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|||||||
Reference in New Issue
Block a user