前端国际化(i18n)是出海项目和跨语言产品的基本工程需求。Vue3生态中,vue-i18n@9作为官方推荐的国际化插件,提供了基于Composition API的响应式翻译、延迟加载、复数处理和日期数字格式化能力。国际化方案的设计质量直接影响用户体验和后期维护成本,本文从架构设计到落地实现,覆盖vue-i18n在大型项目中的完整实践路径。
vue-i18n安装与基础配置
npm install vue-i18n@9
创建i18n实例并注册全局:
// src/i18n/index.ts
import { createI18n } from 'vue-i18n'
import type { I18n } from 'vue-i18n'
// 类型定义,提供TypeScript智能提示
export type MessageSchema = typeof import('./locales/zh-CN.json')
export type LocaleType = 'zh-CN' | 'en-US' | 'ja-JP'
const i18n = createI18n<MessageSchema, LocaleType>([{
legacy: false, // 使用Composition API模式
locale: 'zh-CN', // 默认语言
fallbackLocale: 'en-US', // 回退语言
messages: {
'zh-CN': await import('./locales/zh-CN.json'),
'en-US': await import('./locales/en-US.json')
}
}])
export default i18n
在main.ts中注册:
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import i18n from './i18n'
const app = createApp(App)
app.use(i18n)
app.mount('#app')
翻译文件组织与命名规范
大型项目中翻译文件需要按模块拆分,避免单个JSON膨胀到难以维护。推荐按功能域嵌套:
// src/i18n/locales/zh-CN.json
{
"common": {
"confirm": "确认",
"cancel": "取消",
"save": "保存",
"delete": "删除",
"search": "搜索"
},
"user": {
"login": {
"title": "用户登录",
"username": "用户名",
"password": "密码",
"forgotPassword": "忘记密码?",
"submit": "登录"
},
"profile": {
"editProfile": "编辑资料",
"avatar": "头像",
"nickname": "昵称"
}
},
"order": {
"status": {
"pending": "待付款",
"paid": "已付款",
"shipped": "已发货",
"completed": "已完成",
"cancelled": "已取消"
}
}
}
对应英文翻译文件:
// src/i18n/locales/en-US.json
{
"common": {
"confirm": "Confirm",
"cancel": "Cancel",
"save": "Save",
"delete": "Delete",
"search": "Search"
},
"user": {
"login": {
"title": "User Login",
"username": "Username",
"password": "Password",
"forgotPassword": "Forgot Password?",
"submit": "Login"
},
"profile": {
"editProfile": "Edit Profile",
"avatar": "Avatar",
"nickname": "Nickname"
}
},
"order": {
"status": {
"pending": "Pending Payment",
"paid": "Paid",
"shipped": "Shipped",
"completed": "Completed",
"cancelled": "Cancelled"
}
}
}
组件内翻译与插值使用
在Composition API模式下使用useI18n:
<template>
<div class="login-page">
<h1>{{ t('user.login.title') }}</h1>
<form @submit.prevent="handleLogin">
<input
:placeholder="t('user.login.username')"
v-model="form.username"
/>
<input
:placeholder="t('user.login.password')"
v-model="form.password"
type="password"
/>
<button type="submit">{{ t('user.login.submit') }}</button>
</form>
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { reactive } from 'vue'
const { t } = useI18n()
const form = reactive({
username: '',
password: ''
})
const handleLogin = () => {
// 登录逻辑
}
</script>
带参数的插值:
// 翻译文件
{
"user": {
"welcome": "欢迎,{name}!您有{count}条未读消息"
}
}
// 模板中使用
<p>{{ t('user.welcome', { name: userInfo.name, count: unreadCount }) }}</p>
// 输出:欢迎,张三!您有5条未读消息
复数处理与动态语言切换
不同语言对复数的处理规则差异很大。vue-i18n内置了复数规则引擎,支持CLDR标准:
// 翻译文件配置复数(中文不需要区分单复数,但英文需要)
{
"order": {
"items": "没有订单 | {count}条订单 | {count}条订单"
}
}
// 英文翻译
{
"order": {
"items": "no items | {count} item | {count} items"
}
}
// 使用tc(translation choice)方法
<p>{{ t('order.items', unreadCount) }}</p>
// count=0: "没有订单" / "no items"
// count=1: "1条订单" / "1 item"
// count=5: "5条订单" / "5 items"
动态切换语言不需要刷新页面:
// composables/useLocale.ts
import { useI18n } from 'vue-i18n'
import { setI18nLanguage, loadLocaleMessages } from '@/i18n'
import { ref } from 'vue'
import type { LocaleType } from '@/i18n'
const supportedLocales: LocaleType[] = ['zh-CN', 'en-US', 'ja-JP']
const currentLocale = ref<LocaleType>('zh-CN')
export function useLocale() {
const { locale, t } = useI18n()
const changeLocale = async (target: LocaleType) => {
if (!supportedLocales.includes(target)) return
// 延迟加载语言包
await loadLocaleMessages(target)
// 切换语言
locale.value = target
currentLocale.value = target
// 更新HTML lang属性
document.querySelector('html')?.setAttribute('lang', target)
// 持久化用户选择
localStorage.setItem('locale', target)
}
return {
currentLocale,
supportedLocales,
changeLocale,
t
}
}
语言切换组件:
<template>
<select v-model="selectedLocale" @change="handleChange">
<option value="zh-CN">简体中文</option>
<option value="en-US">English</option>
<option value="ja-JP">日本語</option>
</select>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useLocale } from '@/composables/useLocale'
const { currentLocale, changeLocale } = useLocale()
const selectedLocale = ref(currentLocale.value)
const handleChange = () => {
changeLocale(selectedLocale.value)
}
onMounted(() => {
// 读取本地存储或浏览器语言偏好
const saved = localStorage.getItem('locale') as LocaleType | null
if (saved) {
selectedLocale.value = saved
changeLocale(saved)
} else {
const browserLang = navigator.language
const matched = browserLang.startsWith('zh') ? 'zh-CN' :
browserLang.startsWith('ja') ? 'ja-JP' : 'en-US'
selectedLocale.value = matched
changeLocale(matched)
}
})
</script>
按需加载与路由级语言包
将所有语言包打包进初始Bundle会显著增加首屏加载时间。按路由模块延迟加载是更合理的方案:
// src/i18n/index.ts
import { createI18n } from 'vue-i18n'
import type { I18n } from 'vue-i18n'
// 只加载通用翻译
const commonMessages = {
'zh-CN': import('./modules/common/zh-CN.json'),
'en-US': import('./modules/common/en-US.json')
}
const i18n = createI18n({
legacy: false,
locale: 'zh-CN',
fallbackLocale: 'en-US',
messages: {
'zh-CN': (await commonMessages['zh-CN']).default,
'en-US': (await commonMessages['en-US']).default
}
})
// 按需加载模块翻译
export async function loadModuleMessages(module: string, locale: LocaleType) {
const messages = await import(`./modules/${module}/${locale}.json`)
i18n.global.mergeLocaleMessage(locale, messages.default)
}
// 路由守卫中加载对应模块
// router.beforeEach(async (to) => {
// if (to.meta.i18nModule) {
// await loadModuleMessages(to.meta.i18nModule, i18n.locale.value)
// }
// })
export default i18n
这种方案下,用户路由到订单页面时才加载order模块的语言包,首页只需加载common部分。配合Vite的自动分包,语言包作为独立chunk按需请求,首屏体积可减少40%以上。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/qian-duan-guo-ji-hua-fang-an-shi-zhan-vuei18n-duo-yu-yan/