章节一:国际化的核心概念与Vue的契合点
国际化的核心是什么?简单来说,就是让你的应用能无缝适配不同语言、文化和地区的需求,比如中文用户看到"欢迎",英文用户看到"Welcome",而日文用户看到"ようこそ"。在Vue中,国际化(通常简称为 i18n,因为"internationalization"首尾字母间有18个字符)不仅关乎翻译文本,还涉及日期格式、货币、数字格式等本地化细节。
为什么Vue适合做国际化?
Vue的响应式特性和组件化设计让它天生适合处理动态语言切换。你可以用 Vue插件(如vue-i18n)集中管理翻译资源,通过响应式数据绑定实现语言切换的丝滑体验。相比其他框架,Vue的轻量和灵活性让国际化配置更直观,尤其适合中小型项目快速上手。
国际化的核心挑战
-
翻译管理:如何组织多语言文本,避免杂乱无章?
-
动态切换:如何让用户切换语言时界面实时更新?
-
性能优化:大量翻译资源如何加载而不拖慢应用?
-
文化适配:如何处理不同地区的日期、货币等格式?
-
开发效率:如何减少重复工作,自动化国际化流程?
章节二:搭建Vue项目并集成vue-i18n
要开始国际化冒险,第一步是准备一个Vue项目并集成 vue-i18n。我们假设你已经熟悉Vue CLI或Vite的用法(如果不熟,别慌,后面会提到如何快速上手)。
1. 创建Vue项目
用Vite创建一个新项目,因为它更快、更现代:
npm create vite@latest my-i18n-app --template vue
cd my-i18n-app
npm install
npm run dev
这会生成一个基础Vue项目,运行在 http://localhost:5173。如果你更喜欢Vue CLI,也没问题,命令是:
vue create my-i18n-app
2. 安装vue-i18n
目前(2025年8月),推荐使用 vue-i18n@9,它完美支持Vue 3的组合式API。安装命令:
npm install vue-i18n@9
3. 配置vue-i18n
在项目根目录下创建 src/i18n.js,这是我们管理国际化的核心文件:
import { createI18n } from 'vue-i18n'
const messages = {
en: {
welcome: 'Welcome to My App',
greeting: 'Hello, {name}!'
},
zh: {
welcome: '欢迎使用我的应用',
greeting: '你好,{name}!'
},
ja: {
welcome: '私のアプリへようこそ',
greeting: 'こんにちは、{name}!'
}
}
const i18n = createI18n({
locale: 'en', // 默认语言
fallbackLocale: 'en', // 回退语言
messages
})
export default i18n
代码解析:
-
messages 是一个对象,键是语言代码(如 en、zh),值是翻译内容。
-
locale 设置默认语言,fallbackLocale 指定当翻译缺失时的备用语言。
-
createI18n 是vue-i18n的工厂函数,生成i18n实例。
4. 在Vue应用中引入i18n
修改 src/main.js:
import { createApp } from 'vue'
import App from './App.vue'
import i18n from './i18n'
const app = createApp(App)
app.use(i18n)
app.mount('#app')
现在,vue-i18n已经集成到你的项目中!
5. 在组件中使用翻译
编辑 src/App.vue,添加简单的多语言切换功能:
<template>
<div>
<h1>{{ $t('welcome') }}</h1>
<p>{{ $t('greeting', { name: 'Alice' }) }}</p>
<button @click="changeLanguage('en')">English</button>
<button @click="changeLanguage('zh')">中文</button>
<button @click="changeLanguage('ja')">日本語</button>
</div>
</template>
<script>
export default {
methods: {
changeLanguage(lang) {
this.$i18n.locale = lang
}
}
}
</script>
<style>
button {
margin: 0 10px;
padding: 8px 16px;
cursor: pointer;
}
</style>
运行效果:
-
页面显示"Welcome to My App"和"Hello, Alice!"(默认英语)。
-
点击"中文"按钮,切换为"欢迎使用我的应用"和"你好,Alice!"。
-
{name} 是动态插值,vue-i18n支持这种占位符用法。
注意事项:
-
确保翻译键(如 welcome)在所有语言的 messages 中都有定义,否则会回退到 fallbackLocale。
-
$t 是vue-i18n提供的全局方法,用于获取翻译内容。
-
语言切换通过修改 $i18n.locale 实现,响应式会自动更新界面。
6. 调试小技巧
-
如果翻译没生效,检查 i18n.js 是否正确导入了 messages。
-
用 console.log(this.$i18n.locale) 确认当前语言。
-
翻译缺失时,vue-i18n会在控制台输出警告,帮你快速定位问题。
章节三:优雅地组织翻译资源
当你的应用支持多种语言,且翻译内容越来越多,i18n.js 里的 messages 对象会变得臃肿不堪。想象一下,几十种语言、数百条翻译塞在一个文件里,维护简直是噩梦!我们来学习如何模块化管理翻译资源。
1. 按语言拆分文件
在 src/locales 目录下,为每种语言创建单独的 JSON 文件:
-
src/locales/en.json:
{
"welcome": "Welcome to My App",
"greeting": "Hello, {name}!",
"nav": {
"home": "Home",
"about": "About"
}
} -
src/locales/zh.json:
{
"welcome": "欢迎使用我的应用",
"greeting": "你好,{name}!",
"nav": {
"home": "首页",
"about": "关于"
}
} -
src/locales/ja.json:
{
"welcome": "私のアプリへようこそ",
"greeting": "こんにちは、{name}!",
"nav": {
"home": "ホーム",
"about": "アバウト"
}
}
2. 动态加载翻译
修改 src/i18n.js 来加载这些文件:
import { createI18n } from 'vue-i18n'
const messages = {
en: require('./locales/en.json'),
zh: require('./locales/zh.json'),
ja: require('./locales/ja.json')
}
const i18n = createI18n({
locale: 'en',
fallbackLocale: 'en',
messages
})
export default i18n
Vite用户注意:Vite不支持 require,需要用动态导入:
import { createI18n } from 'vue-i18n'
async function loadMessages() {
const messages = {
en: (await import('./locales/en.json')).default,
zh: (await import('./locales/zh.json')).default,
ja: (await import('./locales/ja.json')).default
}
return messages
}
let i18n
loadMessages().then(messages => {
i18n = createI18n({
locale: 'en',
fallbackLocale: 'en',
messages
})
})
export default i18n
为什么要拆分?
-
可维护性:每个语言一个文件,修改翻译无需翻找大对象。
-
协作友好:翻译团队可以直接编辑JSON文件,无需懂代码。
-
按需加载:后面会讲如何异步加载,优化首屏性能。
3. 使用嵌套键
JSON文件中的 nav.home 这样的嵌套结构,在模板中这样访问:
<template>
<nav>
<a href="/">{{ $t('nav.home') }}</a>
<a href="/about">{{ $t('nav.about') }}</a>
</nav>
</template>
小技巧:用点号(.)访问嵌套翻译,保持代码简洁。如果键名包含点号(比如 nav.home.page),vue-i18n会自动解析。
4. 避免踩坑
-
编码问题:确保JSON文件用UTF-8编码,避免中文或日文乱码。
-
缺失翻译:可以用 fallbackLocale 设置默认语言,也可以在 i18n.js 中添加警告:
const i18n = createI18n({
locale: 'en',
fallbackLocale: 'en',
messages,
missingWarn: true // 缺失翻译时打印警告
}) -
热更新:Vite开发时,修改JSON文件会触发热更新,检查是否实时生效。
章节四:打造动态语言切换的交互体验
语言切换是国际化的核心功能之一,用户希望点击按钮或下拉框后,界面能瞬间变身,从中文到英文再到日文,毫无违和感。我们来实现一个优雅的语言切换组件,并解决一些常见的交互问题。
1. 创建语言切换组件
在 src/components/LanguageSwitcher.vue 中:
<template>
<div class="language-switcher">
<label for="lang-select">选择语言:</label>
<select id="lang-select" v-model="$i18n.locale">
<option value="en">English</option>
<option value="zh">中文</option>
<option value="ja">日本語</option>
</select>
</div>
</template>
<style scoped>
.language-switcher {
display: flex;
align-items: center;
gap: 10px;
margin: 20px;
}
select {
padding: 5px;
border-radius: 4px;
border: 1px solid #ccc;
cursor: pointer;
}
</style>
在 App.vue 中引入:
<template>
<div>
<LanguageSwitcher />
<h1>{{ $t('welcome') }}</h1>
</div>
</template>
<script>
import LanguageSwitcher from './components/LanguageSwitcher.vue'
export default {
components: { LanguageSwitcher }
}
</script>
运行效果:用户选择语言后,页面会立即更新,所有 $t 调用的文本都会切换到对应语言。
2. 持久化语言选择
用户切换语言后,刷新页面不能回到默认语言!我们可以用 localStorage 保存用户偏好。修改 i18n.js:
import { createI18n } from 'vue-i18n'
const messages = {
en: require('./locales/en.json'),
zh: require('./locales/zh.json'),
ja: require('./locales/ja.json')
}
const savedLocale = localStorage.getItem('locale') || 'en'
const i18n = createI18n({
locale: savedLocale,
fallbackLocale: 'en',
messages
})
// 监听语言变化并保存
i18n.global.locale.watch((newLocale) => {
localStorage.setItem('locale', newLocale)
})
export default i18n
Vite用户:如果用动态导入,确保 i18n 初始化后再监听:
async function loadMessages() {
const messages = {
en: (await import('./locales/en.json')).default,
zh: (await import('./locales/zh.json')).default,
ja: (await import('./locales/ja.json')).default
}
return messages
}
let i18n
loadMessages().then(messages => {
const savedLocale = localStorage.getItem('locale') || 'en'
i18n = createI18n({
locale: savedLocale,
fallbackLocale: 'en',
messages
})
i18n.global.locale.watch((newLocale) => {
localStorage.setItem('locale', newLocale)
})
})
export default i18n
3. 处理动态内容
有时,翻译内容需要动态生成,比如根据用户输入显示问候语。修改 App.vue:
<template>
<div>
<LanguageSwitcher />
<input v-model="userName" placeholder="输入你的名字" />
<p>{{ $t('greeting', { name: userName || 'Guest' }) }}</p>
</div>
</template>
<script>
import LanguageSwitcher from './components/LanguageSwitcher.vue'
export default {
components: { LanguageSwitcher },
data() {
return {
userName: ''
}
}
}
</script>
效果:用户输入名字后,greeting 会根据当前语言动态更新,比如中文显示"你好,张三!"。
4. 优化交互体验
-
过渡动画:语言切换时,添加淡入淡出效果。修改 App.vue:
<template><LanguageSwitcher /> <transition name="fade"></template> <style> .fade-enter-active, .fade-leave-active { transition: opacity 0.3s; } .fade-enter, .fade-leave-to { opacity: 0; } </style>{{ $t('welcome') }}
</transition> -
无障碍支持:为 <select> 添加 aria-label:
<select id="lang-select" v-model="$i18n.locale" aria-label="选择语言"> -
防抖处理:如果你的应用有复杂逻辑,避免频繁切换语言导致性能问题,可以用 lodash 的防抖:
npm install lodash
<script> import { debounce } from 'lodash' import LanguageSwitcher from './components/LanguageSwitcher.vue'export default {
components: { LanguageSwitcher },
methods: {
changeLanguage: debounce(function (lang) {
this.$i18n.locale = lang
}, 300)
}
}
</script>
注意:防抖时间不宜过长,300ms是个不错的平衡点。
5. 常见问题与解决
-
切换不生效?检查 v-model 是否正确绑定到 $i18n.locale。
-
翻译延迟?可能是翻译资源加载问题,下一节会讲异步加载优化。
-
样式错乱?某些语言(如阿拉伯语)是右到左(RTL)排版,后面会专门讲RTL支持。
章节五:处理日期、货币和数字的本地化
国际化不只是翻译文字,日期、货币和数字格式在不同地区差异巨大。比如,美国用 MM/DD/YYYY,中国用 YYYY-MM-DD,而货币符号和数字分隔符也各不相同。我们用 Intl API 和 vue-i18n 的扩展功能来搞定这些。
1. 日期格式化
JavaScript 的 Intl.DateTimeFormat 是处理日期本地化的神器。修改 App.vue:
<template>
<div>
<LanguageSwitcher />
<p>当前日期:{{ formatDate(new Date()) }}</p>
</div>
</template>
<script>
import LanguageSwitcher from './components/LanguageSwitcher.vue'
export default {
components: { LanguageSwitcher },
methods: {
formatDate(date) {
const formatter = new Intl.DateTimeFormat(this.$i18n.locale, {
year: 'numeric',
month: 'long',
day: 'numeric'
})
return formatter.format(date)
}
}
}
</script>
效果:
-
英文:August 24, 2025
-
中文:2025年8月24日
-
日文:2025年8月24日
小技巧:可以用 dateStyle 简化:
formatDate(date) {
return new Intl.DateTimeFormat(this.$i18n.locale, { dateStyle: 'full' }).format(date)
}
2. 货币格式化
假设你的应用显示价格,用 Intl.NumberFormat:
<template>
<p>价格:{{ formatCurrency(1234.56) }}</p>
</template>
<script>
export default {
methods: {
formatCurrency(value) {
const currencyMap = {
en: 'USD',
zh: 'CNY',
ja: 'JPY'
}
const formatter = new Intl.NumberFormat(this.$i18n.locale, {
style: 'currency',
currency: currencyMap[this.$i18n.locale]
})
return formatter.format(value)
}
}
}
</script>
效果:
-
英文:$1,234.56
-
中文:¥1,234.56
-
日文:¥1,234
3. 数字格式化
处理千位分隔符或小数点差异:
formatNumber(value) {
return new Intl.NumberFormat(this.$i18n.locale).format(value)
}
效果:
-
英文:1,234,567.89
-
法语(假设支持):1 234 567,89
4. 用vue-i18n的格式化
vue-i18n 也支持日期和数字格式化,需配置 datetimeFormats 和 numberFormats。修改 i18n.js:
const i18n = createI18n({
locale: 'en',
fallbackLocale: 'en',
messages,
datetimeFormats: {
en: {
short: {
year: 'numeric',
month: 'short',
day: 'numeric'
}
},
zh: {
short: {
year: 'numeric',
month: 'long',
day: 'numeric'
}
}
},
numberFormats: {
en: {
currency: {
style: 'currency',
currency: 'USD'
}
},
zh: {
currency: {
style: 'currency',
currency: 'CNY'
}
}
}
})
在模板中使用:
<template>
<p>日期:{{ $d(new Date(), 'short') }}</p>
<p>价格:{{ $n(1234.56, 'currency') }}</p>
</template>
优势:用 d 和 n 比直接用 Intl 更简洁,且翻译配置集中管理。
5. 踩坑经验
-
时区问题:Intl.DateTimeFormat 默认使用系统时区,必要时指定 timeZone。
-
货币代码:确保 currency 参数有效(ISO 4217标准,如 USD、CNY)。
-
浏览器兼容性:Intl API 在现代浏览器支持良好,但老旧浏览器(如IE)需 polyfill:
npm install @formatjs/intl
import '@formatjs/intl-datetimeformat/polyfill'
import '@formatjs/intl-numberformat/polyfill'
章节六:支持右到左(RTL)语言的布局与样式调整
国际化不仅要翻译文字,还要适配不同语言的阅读习惯。像阿拉伯语、希伯来语这样的 右到左(RTL)语言,会让你的界面布局完全"反转"!如果不处理好,文字、按钮甚至整个页面都会显得别扭。我们来一步步搞定RTL支持,让你的Vue应用在全球用户面前都优雅自如!
1. 理解RTL的挑战
RTL语言的文本从右向左排列,布局也需要相应调整。比如:
-
文本方向:英文从左到右,阿拉伯语从右到左。
-
UI元素:按钮、导航栏、图标等可能需要镜像翻转。
-
CSS样式:margin-left 在RTL中可能需要变成 margin-right。
Vue结合 vue-i18n 和 CSS 可以轻松实现RTL支持。我们以阿拉伯语(ar)为例,扩展之前的项目。
2. 添加阿拉伯语翻译
在 src/locales 下创建 ar.json:
{
"welcome": "مرحبًا بك في تطبيقي",
"greeting": "مرحبًا، {name}!",
"nav": {
"home": "الرئيسية",
"about": "حول"
}
}
更新 src/i18n.js:
import { createI18n } from 'vue-i18n'
async function loadMessages() {
const messages = {
en: (await import('./locales/en.json')).default,
zh: (await import('./locales/zh.json')).default,
ja: (await import('./locales/ja.json')).default,
ar: (await import('./locales/ar.json')).default
}
return messages
}
let i18n
loadMessages().then(messages => {
const savedLocale = localStorage.getItem('locale') || 'en'
i18n = createI18n({
locale: savedLocale,
fallbackLocale: 'en',
messages
})
i18n.global.locale.watch((newLocale) => {
localStorage.setItem('locale', newLocale)
})
})
export default i18n
更新 LanguageSwitcher.vue,添加阿拉伯语选项:
<template>
<div class="language-switcher">
<label for="lang-select">选择语言:</label>
<select id="lang-select" v-model="$i18n.locale" aria-label="选择语言">
<option value="en">English</option>
<option value="zh">中文</option>
<option value="ja">日本語</option>
<option value="ar">العربية</option>
</select>
</div>
</template>
3. 设置文本方向
HTML 的 dir 属性控制文本方向(ltr 为左到右,rtl 为右到左)。在 App.vue 中动态设置:
<template>
<div :dir="isRtl ? 'rtl' : 'ltr'">
<LanguageSwitcher />
<h1>{{ $t('welcome') }}</h1>
<p>{{ $t('greeting', { name: 'Ali' }) }}</p>
</div>
</template>
<script>
import LanguageSwitcher from './components/LanguageSwitcher.vue'
export default {
components: { LanguageSwitcher },
computed: {
isRtl() {
return this.$i18n.locale === 'ar'
}
}
}
</script>
效果:选择阿拉伯语后,页面文本从右向左排列,标题和段落自动对齐到右侧。
4. 调整CSS样式
RTL布局需要镜像翻转部分样式。创建 src/styles/rtl.css:
[dir="rtl"] {
text-align: right;
}
[dir="rtl"] .language-switcher {
flex-direction: row-reverse;
}
[dir="rtl"] button,
[dir="rtl"] select {
margin-left: 0;
margin-right: 10px;
}
在 main.js 中引入:
import { createApp } from 'vue'
import App from './App.vue'
import i18n from './i18n'
import './styles/rtl.css'
const app = createApp(App)
app.use(i18n)
app.mount('#app')
进阶技巧:用 CSS 逻辑属性(如 margin-inline-start)替代 margin-left/margin-right,自动适配RTL:
button, select {
margin-inline-start: 10px;
}
好处:逻辑属性根据 dir 自动调整,无需为RTL单独写样式。
5. 处理字体和间距
阿拉伯语等非拉丁语言需要合适的字体支持。推荐使用 Noto Sans Arabic(Google字体,免费且开源)。在 index.html 中引入:
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+Arabic:wght@400;700&display=swap">
在 rtl.css 中应用:
[dir="rtl"] {
font-family: 'Noto Sans Arabic', sans-serif;
}
注意:阿拉伯语文字可能比英文更宽,检查布局是否溢出,必要时调整 padding 或 width。
6. 调试RTL问题
-
文字未翻转?检查 dir 属性是否正确应用到根元素。
-
样式错乱?用浏览器的开发者工具检查 dir="rtl" 下的CSS。
-
字体未加载?确认网络是否加载了Google字体,或考虑本地字体回退。
章节七:异步加载翻译资源,优化首屏性能
当你的应用支持多种语言,每种语言的翻译文件可能包含数百条内容,如果在首屏加载所有语言,性能会大打折扣。异步加载 是解决之道:只加载当前语言的翻译,切换语言时再动态加载其他语言资源。我们用 Vite 的动态导入和 vue-i18n 的 loadLocaleMessages 来实现。
1. 修改i18n配置
重构 src/i18n.js,支持异步加载:
import { createI18n } from 'vue-i18n'
const i18n = createI18n({
locale: localStorage.getItem('locale') || 'en',
fallbackLocale: 'en',
messages: {} // 初始为空
})
async function loadLocaleMessages(locale) {
if (i18n.global.availableLocales.includes(locale)) {
return
}
const messages = await import(`./locales/${locale}.json`)
i18n.global.setLocaleMessages(locale, messages.default)
}
loadLocaleMessages(i18n.global.locale)
export default i18n
export { loadLocaleMessages }
代码解析:
-
messages 初始为空,减少首屏加载量。
-
loadLocaleMessages 动态加载指定语言的JSON文件。
-
setLocaleMessages 是 vue-i18n 提供的 API,用于动态添加翻译。
2. 更新语言切换逻辑
修改 LanguageSwitcher.vue,在切换语言时加载资源:
<template>
<div class="language-switcher" :dir="isRtl ? 'rtl' : 'ltr'">
<label for="lang-select">选择语言:</label>
<select id="lang-select" @change="switchLanguage" v-model="selectedLocale" aria-label="选择语言">
<option value="en">English</option>
<option value="zh">中文</option>
<option value="ja">日本語</option>
<option value="ar">العربية</option>
</select>
</div>
</template>
<script>
import { loadLocaleMessages } from '../i18n'
export default {
data() {
return {
selectedLocale: this.$i18n.locale
}
},
computed: {
isRtl() {
return this.$i18n.locale === 'ar'
}
},
methods: {
async switchLanguage() {
await loadLocaleMessages(this.selectedLocale)
this.$i18n.locale = this.selectedLocale
localStorage.setItem('locale', this.selectedLocale)
}
}
}
</script>
<style scoped>
.language-switcher {
display: flex;
align-items: center;
gap: 10px;
margin: 20px;
}
select {
padding: 5px;
border-radius: 4px;
border: 1px solid #ccc;
cursor: pointer;
}
</style>
效果:选择语言后,动态加载对应JSON文件,页面实时更新。
3. 添加加载状态
切换语言时,异步加载可能有延迟,添加加载提示提升体验。在 LanguageSwitcher.vue 中:
<template>
<div class="language-switcher" :dir="isRtl ? 'rtl' : 'ltr'">
<label for="lang-select">选择语言:</label>
<select id="lang-select" @change="switchLanguage" v-model="selectedLocale" :disabled="isLoading">
<option value="en">English</option>
<option value="zh">中文</option>
<option value="ja">日本語</option>
<option value="ar">العربية</option>
</select>
<span v-if="isLoading">加载中...</span>
</div>
</template>
<script>
import { loadLocaleMessages } from '../i18n'
export default {
data() {
return {
selectedLocale: this.$i18n.locale,
isLoading: false
}
},
computed: {
isRtl() {
return this.$i18n.locale === 'ar'
}
},
methods: {
async switchLanguage() {
this.isLoading = true
try {
await loadLocaleMessages(this.selectedLocale)
this.$i18n.locale = this.selectedLocale
localStorage.setItem('locale', this.selectedLocale)
} catch (error) {
console.error('加载语言失败:', error)
} finally {
this.isLoading = false
}
}
}
}
</script>
优化点:
-
isLoading 控制加载状态,禁用select避免重复点击。
-
错误处理确保加载失败时有提示。
4. 性能测试
用浏览器的 Network 面板检查:
-
首屏只加载默认语言的JSON文件(比如 en.json)。
-
切换语言时,动态请求其他语言文件(如 ar.json)。
-
确认文件是否被缓存(Vite默认启用HTTP缓存)。
5. 踩坑经验
-
文件路径错误:确保 import('./locales/${locale}.json') 的路径正确,Vite对大小写敏感。
-
异步延迟:加载大文件时,考虑添加骨架屏(skeleton screen)。
-
回退语言:如果目标语言加载失败,fallbackLocale 会生效,检查是否符合预期。
章节八:SEO优化与服务端渲染(SSR)的国际化
如果你的Vue应用需要被搜索引擎抓取(比如Google),国际化会带来新挑战:如何确保不同语言的页面都能被正确索引?服务端渲染(SSR) 和 SEO优化 是关键。我们以 Nuxt 3(Vue的SSR框架)为例,结合 vue-i18n 实现多语言SEO。
1. 创建Nuxt 3项目
安装Nuxt 3:
npx nuxi@latest init my-i18n-nuxt
cd my-i18n-nuxt
npm install
npm run dev
安装 vue-i18n:
npm install vue-i18n@9
2. 配置Nuxt的i18n
Nuxt有官方的 @nuxtjs/i18n 模块,简化SSR国际化。安装:
npm install @nuxtjs/i18n
在 nuxt.config.ts 中配置:
export default defineNuxtConfig({
modules: ['@nuxtjs/i18n'],
i18n: {
locales: [
{ code: 'en', iso: 'en-US', file: 'en.json' },
{ code: 'zh', iso: 'zh-CN', file: 'zh.json' },
{ code: 'ja', iso: 'ja-JP', file: 'ja.json' },
{ code: 'ar', iso: 'ar-SA', file: 'ar.json' }
],
defaultLocale: 'en',
langDir: 'locales',
vueI18n: {
fallbackLocale: 'en'
}
}
})
创建 locales 目录,放入之前的 en.json、zh.json、ja.json、ar.json。
3. 使用Nuxt的i18n
在页面组件(如 pages/index.vue)中使用:
<template>
<div :dir="$i18n.locale === 'ar' ? 'rtl' : 'ltr'">
<h1>{{ $t('welcome') }}</h1>
<p>{{ $t('greeting', { name: 'World' }) }}</p>
<nav>
<NuxtLink v-for="locale in $i18n.locales" :key="locale.code" :to="switchLocalePath(locale.code)">
{{ locale.code }}
</NuxtLink>
</nav>
</div>
</template>
关键点:
-
switchLocalePath 是 @nuxtjs/i18n 提供的函数,生成语言切换的URL(如 /en、/zh)。
-
iso 字段(如 en-US)用于生成 <html lang> 属性,利于SEO。
4. SEO优化
为每种语言生成正确的元标签,在 nuxt.config.ts 中配置:
export default defineNuxtConfig({
app: {
head: {
htmlAttrs: {
lang: 'en-US' // 默认值,动态覆盖
},
link: [
{ rel: 'alternate', hreflang: 'en-US', href: '/en' },
{ rel: 'alternate', hreflang: 'zh-CN', href: '/zh' },
{ rel: 'alternate', hreflang: 'ja-JP', href: '/ja' },
{ rel: 'alternate', hreflang: 'ar-SA', href: '/ar' }
]
}
},
modules: ['@nuxtjs/i18n'],
i18n: {
// ... 其他配置
}
})
动态设置 lang 属性,在页面组件中:
<script>
export default {
head() {
return {
htmlAttrs: {
lang: this.$i18n.localeProperties.iso
}
}
}
}
</script>
效果:搜索引擎会识别 /en、/zh 等为不同语言的页面,提升抓取效率。
5. 动态路由与SSR
Nuxt的 @nuxtjs/i18n 自动为每种语言生成路由,比如:
-
/en/about → 英文关于页面
-
/zh/about → 中文关于页面
创建 pages/about.vue:
<template>
<div :dir="$i18n.locale === 'ar' ? 'rtl' : 'ltr'">
<h1>{{ $t('nav.about') }}</h1>
</div>
</template>
注意:确保翻译文件中有 nav.about,否则回退到 fallbackLocale。
6. 调试SEO效果
-
用浏览器的 Elements 面板检查 <html lang> 和 <link rel="alternate"> 是否正确。
-
用 Google 的 URL Inspection Tool 测试页面是否被正确索引。
-
检查 robots.txt 和 sitemap,确保所有语言的URL都包含。
7. 性能优化
SSR模式下,翻译文件会在服务端加载,避免客户端异步请求。可以用 nuxt generate 生成静态站点,进一步提升性能:
npm run generate
踩坑经验:
-
路由冲突:确保语言代码(如 en)不与现有路由冲突。
-
翻译缺失:SSR时,缺失翻译会导致空内容,检查 fallbackLocale 是否生效。
-
CDN部署:静态站点部署到CDN后,确认 alternate 链接使用绝对URL。
章节九:自动化测试国际化的最佳实践
国际化的代码一旦复杂起来,手动测试多语言切换、RTL布局、日期格式等会让你抓狂!自动化测试 是救星,能确保你的Vue应用在各种语言环境下都稳如磐石。
1. 设置测试环境
假设你用的是Vite项目,先安装测试依赖:
npm install --save-dev vitest @vue/test-utils jsdom @testing-library/vue
在 package.json 中添加测试脚本:
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest"
}
}
配置 vite.config.js:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: './tests/setup.js'
}
})
创建 tests/setup.js 初始化测试环境:
import { config } from '@vue/test-utils'
import { createI18n } from 'vue-i18n'
const i18n = createI18n({
locale: 'en',
fallbackLocale: 'en',
messages: {
en: require('../src/locales/en.json'),
zh: require('../src/locales/zh.json'),
ja: require('../src/locales/ja.json'),
ar: require('../src/locales/ar.json')
}
})
config.global.plugins = [i18n]
说明:这里直接加载所有翻译文件便于测试,生产环境中仍用异步加载。
2. 测试翻译渲染
为 App.vue 写测试用例,验证翻译是否正确渲染。创建 tests/App.spec.js:
import { render, screen } from '@testing-library/vue'
import App from '../src/App.vue'
describe('App.vue', () => {
it('renders English translations', async () => {
render(App, { global: { plugins: [i18n] } })
expect(screen.getByText('Welcome to My App')).toBeInTheDocument()
expect(screen.getByText('Hello, Ali!')).toBeInTheDocument()
})
it('renders Chinese translations', async () => {
i18n.global.locale = 'zh'
render(App, { global: { plugins: [i18n] } })
expect(screen.getByText('欢迎使用我的应用')).toBeInTheDocument()
expect(screen.getByText('你好,Ali!')).toBeInTheDocument()
})
})
运行测试:
npm run test
效果:测试验证英文和中文翻译是否正确显示。如果翻译缺失或错误,测试会失败。
3. 测试语言切换
测试 LanguageSwitcher.vue 的切换功能,创建 tests/LanguageSwitcher.spec.js:
import { render, screen, fireEvent } from '@testing-library/vue'
import LanguageSwitcher from '../src/components/LanguageSwitcher.vue'
describe('LanguageSwitcher.vue', () => {
it('switches to Arabic and updates RTL', async () => {
render(LanguageSwitcher, { global: { plugins: [i18n] } })
const select = screen.getByLabelText('选择语言')
await fireEvent.update(select, 'ar')
expect(i18n.global.locale).toBe('ar')
expect(document.body.querySelector('div').getAttribute('dir')).toBe('rtl')
})
})
关键点:
-
fireEvent.update 模拟用户选择语言。
-
检查 dir 属性确保RTL布局生效。
4. 测试日期和货币格式
为日期和货币格式化写测试,扩展 tests/App.spec.js:
import { render, screen } from '@testing-library/vue'
import App from '../src/App.vue'
describe('App.vue', () => {
it('formats date in English', async () => {
render(App, { global: { plugins: [i18n] } })
const dateRegex = /August \d{1,2}, 2025/
expect(screen.getByText(dateRegex)).toBeInTheDocument()
})
it('formats currency in Chinese', async () => {
i18n.global.locale = 'zh'
render(App, { global: { plugins: [i18n] } })
expect(screen.getByText('¥1,234.56')).toBeInTheDocument()
})
})
注意:日期测试用正则匹配,因为具体日期可能因时区而异。
5. 模拟异步加载
测试异步加载翻译资源,修改 tests/setup.js 支持动态导入:
import { createI18n } from 'vue-i18n'
const i18n = createI18n({
locale: 'en',
fallbackLocale: 'en',
messages: {}
})
async function loadLocaleMessages(locale) {
const messages = await import(`../src/locales/${locale}.json`)
i18n.global.setLocaleMessages(locale, messages.default)
}
loadLocaleMessages('en')
export { i18n, loadLocaleMessages }
测试用例:
import { render, screen } from '@testing-library/vue'
import { i18n, loadLocaleMessages } from './setup'
import App from '../src/App.vue'
describe('App.vue with async locale loading', () => {
it('loads Japanese translations asynchronously', async () => {
await loadLocaleMessages('ja')
i18n.global.locale = 'ja'
render(App, { global: { plugins: [i18n] } })
expect(screen.getByText('私のアプリへようこそ')).toBeInTheDocument()
})
})
6. 常见问题与解决
-
测试失败?检查翻译文件路径是否正确,JSON是否有效。
-
RTL不生效?确保 dir 属性被正确设置,检查CSS选择器。
-
异步加载慢?用 vi.mock 模拟导入:
vi.mock('../src/locales/ja.json', () => ({
default: {
welcome: '私のアプリへようこそ',
greeting: 'こんにちは、{name}!'
}
})) -
覆盖率不足?用 vitest --coverage 检查未覆盖的代码,补充测试用例。
通过自动化测试,你的国际化功能质量更有保障!接下来,我们聊聊如何与翻译平台集成,解放你的双手。
章节十:与翻译平台(如Crowdin)集成,简化翻译流程
手动维护多语言JSON文件很痛苦,尤其是当翻译量巨大或需要专业译者参与时。Crowdin 是一个流行的翻译管理平台,能让开发者和翻译团队高效协作。我们将展示如何将Vue项目的翻译流程与Crowdin集成,实现自动化更新。
1. 在Crowdin上创建项目
-
注册Crowdin账号(https://crowdin.com)。
-
创建新项目,选择"文件上传"方式。
-
上传 src/locales/en.json 作为源文件,Crowdin会基于它生成其他语言的翻译。
文件结构:
-
源文件:en.json(英文,基准语言)
-
目标语言:zh.json、ja.json、ar.json 等
2. 配置Crowdin CLI
安装Crowdin CLI:
npm install -g @crowdin/cli
创建 crowdin.yml 配置文件:
project_id: 'your-project-id'
api_token: 'your-api-token'
base_path: './'
files:
- source: '/src/locales/en.json'
translation: '/src/locales/%two_letters_code%.json'
languages_mapping:
two_letters_code:
zh-CN: zh
ja-JP: ja
ar-SA: ar
获取凭证:
-
project_id 和 api_token 从Crowdin的项目设置中获取。
-
languages_mapping 将Crowdin的语言代码映射到你的文件命名。
3. 同步翻译
上传源文件到Crowdin:
crowdin upload sources
下载翻译后的文件:
crowdin download
效果:Crowdin会将翻译好的内容下载到 src/locales/zh.json 等文件,自动更新你的项目。
4. 集成到CI/CD
将Crowdin同步加入GitHub Actions,创建 .github/workflows/i18n.yml:
name: Sync Translations
on:
push:
branches: [main]
jobs:
sync-translations:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install Crowdin CLI
run: npm install -g @crowdin/cli
- name: Upload sources to Crowdin
run: crowdin upload sources
env:
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
- name: Download translations
run: crowdin download
- name: Commit changes
run: |
git config user.name 'GitHub Action'
git config user.email 'action@github.com'
git add src/locales/*
git commit -m 'Update translations from Crowdin' || true
git push
设置密钥:
- 在GitHub仓库的"Settings > Secrets"中添加 CROWDIN_PROJECT_ID 和 CROWDIN_PERSONAL_TOKEN。
效果:每次推送到 main 分支,Crowdin会自动同步翻译,更新本地文件。
5. 优化翻译流程
-
翻译记忆:Crowdin支持翻译记忆库(TM),复用已有翻译,节省时间。
-
术语表:为关键术语创建术语表,确保翻译一致性。
-
预览功能:Crowdin提供在线预览,翻译者可看到实际效果。
6. 踩坑经验
-
文件格式:确保 en.json 格式规范,Crowdin对JSON严格要求。
-
语言代码:Crowdin用 zh-CN 而不是 zh,注意映射。
-
权限管理:为翻译者设置只读权限,避免误改源文件。
通过Crowdin,你的翻译流程已经自动化,开发和翻译团队可以愉快协作!接下来,我们聊聊国际化调试的实用工具。
章节十一:国际化调试工具与技巧
国际化的代码复杂后,调试会变得棘手:翻译没生效?RTL布局错乱?日期格式不对?别急,我们介绍几种工具和技巧,帮你快速定位问题。
1. Vue Devtools
Vue Devtools(浏览器扩展)支持检查i18n状态:
-
安装 Vue Devtools(Chrome/Firefox)。
-
在"Vue"面板中查看 $i18n 对象的 locale 和 messages。
-
切换语言时,观察响应式更新。
技巧:在 i18n.js 中添加调试日志:
i18n.global.locale.watch((newLocale) => {
console.log(`语言切换到: ${newLocale}`, i18n.global.messages[newLocale])
localStorage.setItem('locale', newLocale)
})
2. i18n Ally插件
VS Code 的 i18n Ally 插件是国际化神器:
-
安装:搜索"i18n Ally"并安装。
-
配置:指向 src/locales 目录。
-
功能:
-
自动检测缺失翻译。
-
提供翻译建议。
-
支持快速跳转到翻译文件。
-
3. 浏览器开发者工具
-
Network:检查异步加载的JSON文件是否正确请求。
-
Elements:验证 dir 和 lang 属性。
-
Console:查看 vue-i18n 的警告(如缺失翻译)。
4. 自定义调试组件
创建一个调试组件,实时显示i18n状态,创建 src/components/I18nDebugger.vue:
<template>
<div class="debugger" :dir="isRtl ? 'rtl' : 'ltr'">
<h3>国际化调试</h3>
<p>当前语言:{{ $i18n.locale }}</p>
<p>可用语言:{{ $i18n.availableLocales.join(', ') }}</p>
<p>翻译内容:{{ JSON.stringify($i18n.messages[$i18n.locale], null, 2) }}</p>
</div>
</template>
<script>
export default {
computed: {
isRtl() {
return this.$i18n.locale === 'ar'
}
}
}
</script>
<style scoped>
.debugger {
position: fixed;
bottom: 20px;
right: 20px;
background: #fff;
border: 1px solid #ccc;
padding: 10px;
max-width: 300px;
max-height: 400px;
overflow: auto;
}
</style>
在 App.vue 中引入:
<template>
<div :dir="isRtl ? 'rtl' : 'ltr'">
<LanguageSwitcher />
<I18nDebugger />
<h1>{{ $t('welcome') }}</h1>
</div>
</template>
<script>
import LanguageSwitcher from './components/LanguageSwitcher.vue'
import I18nDebugger from './components/I18nDebugger.vue'
export default {
components: { LanguageSwitcher, I18nDebugger },
computed: {
isRtl() {
return this.$i18n.locale === 'ar'
}
}
}
</script>
效果:页面右下角显示调试面板,实时展示当前语言和翻译内容。
5. 常见调试问题
-
翻译不更新?检查 i18n.locale 是否正确响应,可能需要强制刷新 forceUpdate。
-
RTL布局错乱?用开发者工具检查CSS优先级。
-
异步加载失败?确认网络请求状态,检查JSON文件路径。
章节十二:多语言表单验证的实现
表单验证是Web应用的常见需求,但国际化后,错误提示需要适配不同语言。我们以 VeeValidate(Vue的表单验证库)为例,结合 vue-i18n 实现多语言验证。
1. 安装VeeValidate
安装依赖:
npm install vee-validate @vee-validate/i18n
2. 配置VeeValidate与i18n
在 src/main.js 中初始化:
import { createApp } from 'vue'
import App from './App.vue'
import i18n from './i18n'
import { Form, Field, ErrorMessage, defineRule, configure } from 'vee-validate'
import { localize } from '@vee-validate/i18n'
import { required, email } from '@vee-validate/rules'
const app = createApp(App)
// 定义验证规则
defineRule('required', required)
defineRule('email', email)
// 配置多语言错误提示
configure({
generateMessage: localize({
en: {
messages: {
required: 'This field is required',
email: 'Please enter a valid email'
}
},
zh: {
messages: {
required: '此字段为必填项',
email: '请输入有效的邮箱地址'
}
},
ja: {
messages: {
required: 'このフィールドは必須です',
email: '有効なメールアドレスを入力してください'
}
},
ar: {
messages: {
required: 'هذا الحقل مطلوب',
email: 'يرجى إدخال بريد إلكتروني صحيح'
}
}
})
})
app.use(i18n)
app.component('Form', Form)
app.component('Field', Field)
app.component('ErrorMessage', ErrorMessage)
app.mount('#app')
说明:localize 定义每种语言的错误提示,与 vue-i18n 的 locale 同步。
3. 创建表单组件
创建 src/components/LoginForm.vue:
<template>
<Form v-slot="{ errors }" :dir="isRtl ? 'rtl' : 'ltr'">
<div>
<label for="email">邮箱</label>
<Field name="email" type="email" rules="required|email" />
<ErrorMessage name="email" class="error" />
</div>
<div>
<label for="name">姓名</label>
<Field name="name" type="text" rules="required" />
<ErrorMessage name="name" class="error" />
</div>
<button type="submit">提交</button>
</Form>
</template>
<script>
export default {
computed: {
isRtl() {
return this.$i18n.locale === 'ar'
}
}
}
</script>
<style scoped>
div {
margin: 10px 0;
}
.error {
color: red;
font-size: 0.9em;
}
</style>
4. 同步语言切换
VeeValidate的错误提示需要与 vue-i18n 的语言同步,修改 i18n.js:
import { createI18n } from 'vue-i18n'
import { localize } from '@vee-validate/i18n'
async function loadMessages() {
const messages = {
en: (await import('./locales/en.json')).default,
zh: (await import('./locales/zh.json')).default,
ja: (await import('./locales/ja.json')).default,
ar: (await import('./locales/ar.json')).default
}
return messages
}
let i18n
loadMessages().then(messages => {
i18n = createI18n({
locale: localStorage.getItem('locale') || 'en',
fallbackLocale: 'en',
messages
})
i18n.global.locale.watch((newLocale) => {
localStorage.setItem('locale', newLocale)
localize(newLocale) // 同步VeeValidate语言
})
})
export default i18n
export { loadLocaleMessages }
5. 测试表单验证
为 LoginForm.vue 写测试用例,创建 tests/LoginForm.spec.js:
import { render, screen, fireEvent } from '@testing-library/vue'
import LoginForm from '../src/components/LoginForm.vue'
import { i18n } from './setup'
describe('LoginForm.vue', () => {
it('shows English validation errors', async () => {
render(LoginForm, { global: { plugins: [i18n] } })
await fireEvent.click(screen.getByText('提交'))
expect(screen.getByText('This field is required')).toBeInTheDocument()
})
it('shows Arabic validation errors', async () => {
i18n.global.locale = 'ar'
render(LoginForm, { global: { plugins: [i18n] } })
await fireEvent.click(screen.getByText('提交'))
expect(screen.getByText('هذا الحقل مطلوب')).toBeInTheDocument()
})
})
6. 优化用户体验
-
实时验证:用 v-validate 的 immediate 模式:
<Field name="email" type="email" rules="required|email" :validateOnInput="true" /> -
RTL支持:确保错误提示文本在RTL语言下右对齐:
[dir="rtl"] .error {
text-align: right;
}
7. 踩坑经验
-
语言不同步?确认 localize(newLocale) 在语言切换时调用。
-
错误提示不显示?检查 ErrorMessage 组件是否正确注册。
-
自定义规则?用 defineRule 添加自定义验证逻辑。
现在,你的表单验证已经完美支持多语言!这标志着我们国际化教程的圆满收尾!
章节十三:国际化性能监控与优化
国际化功能虽好,但翻译文件多、语言切换频繁时,性能可能成为瓶颈。加载过多的翻译资源会导致首屏时间延长,而动态语言切换可能引发卡顿。我们来探讨如何用性能监控工具 和优化策略让你的Vue应用在多语言场景下依然丝滑流畅!
1. 监控国际化性能
性能问题得先测出来!以下是几种实用工具和方法:
(1)浏览器开发者工具
-
Network面板:检查翻译文件(如 en.json)的加载时间和大小。
-
Performance面板:记录页面加载和语言切换的性能曲线,找出瓶颈。
-
Lighthouse:运行 npx lighthouse http://localhost:5173 生成性能报告,关注"Time to Interactive"指标。
(2)Vue Devtools
-
检查 $i18n 对象的渲染性能,尤其是在切换语言时,观察响应式更新的耗时。
-
用"Performance"选项记录组件渲染时间,确认翻译切换是否触发不必要的重渲染。
(3)自定义性能监控
添加一个简单的性能监控函数,在 src/i18n.js 中:
import { createI18n } from 'vue-i18n'
async function loadLocaleMessages(locale) {
const start = performance.now()
if (!i18n.global.availableLocales.includes(locale)) {
const messages = await import(`./locales/${locale}.json`)
i18n.global.setLocaleMessages(locale, messages.default)
}
const end = performance.now()
console.log(`加载 ${locale} 语言耗时: ${end - start}ms`)
}
let i18n
loadMessages().then(messages => {
i18n = createI18n({
locale: localStorage.getItem('locale') || 'en',
fallbackLocale: 'en',
messages
})
i18n.global.locale.watch((newLocale) => {
localStorage.setItem('locale', newLocale)
loadLocaleMessages(newLocale)
})
})
export default i18n
export { loadLocaleMessages }
效果:每次加载翻译文件时,控制台会打印耗时,帮你定位慢加载的语言。
2. 优化翻译文件大小
翻译文件(JSON)可能包含数百条内容,体积过大会拖慢加载。我们可以用以下方法压缩:
(1)精简翻译内容
-
删除冗余:检查 en.json 等文件,移除不必要的翻译键。
-
缩短键名:将长键名(如 navigation.homepage.title)简化为 nav.home.title,但保持语义清晰。
-
合并相似翻译:比如"保存"和"确认保存"可以复用相同的翻译,通过参数插值实现:
{
"save": "保存",
"confirm_save": "确认{name}保存"
}
(2)压缩JSON文件
用工具如 jsonminify 去除JSON中的换行和空格:
npm install -g jsonminify
jsonminify src/locales/en.json > src/locales/en.min.json
修改 i18n.js 使用压缩后的文件:
async function loadMessages() {
const messages = {
en: (await import('./locales/en.min.json')).default,
zh: (await import('./locales/zh.min.json')).default,
ja: (await import('./locales/ja.min.json')).default,
ar: (await import('./locales/ar.min.json')).default
}
return messages
}
注意:压缩后文件可读性降低,建议保留原始文件供翻译团队编辑。
(3)按需加载模块
将翻译文件按模块拆分,比如 nav.json 和 form.json,只加载当前页面需要的翻译:
async function loadModuleMessages(locale, module) {
const messages = await import(`./locales/${locale}/${module}.json`)
i18n.global.setLocaleMessages(`${locale}.${module}`, messages.default)
}
在组件中调用:
export default {
async created() {
await loadModuleMessages(this.$i18n.locale, 'nav')
}
}
好处:减少单次加载的翻译量,适合大型应用。
3. 缓存优化
-
浏览器缓存:Vite默认启用HTTP缓存,确保翻译文件有稳定的 ETag 或 Cache-Control 头。
-
Service Worker:用 Workbox 缓存翻译文件,加速二次访问:
npm install workbox-cli
创建 sw.js:
import { precacheAndRoute } from 'workbox-precaching'
precacheAndRoute([
{ url: '/locales/en.min.json', revision: '1' },
{ url: '/locales/zh.min.json', revision: '1' },
// ...
])
在 main.js 注册:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
}
4. 优化语言切换
频繁切换语言可能导致多次请求相同资源,添加防抖和预加载:
import { debounce } from 'lodash'
export default {
methods: {
switchLanguage: debounce(async function (locale) {
await loadLocaleMessages(locale)
this.$i18n.locale = locale
localStorage.setItem('locale', locale)
}, 300)
}
}
预加载热门语言:
async function preloadLocales(locales) {
await Promise.all(locales.map(locale => loadLocaleMessages(locale)))
}
preloadLocales(['en', 'zh']) // 预加载英文和中文
5. 踩坑经验
-
文件过大?检查是否有重复翻译,考虑按模块拆分。
-
切换卡顿?用 requestIdleCallback 延迟非关键翻译加载。
-
缓存失效?更新翻译文件时,修改 revision 或文件名避免缓存问题。
通过这些优化,你的国际化应用性能将大幅提升!
章节十四:处理多语言动态内容
很多时候,翻译内容不是静态的JSON文件,而是从后端API动态返回,比如用户生成的内容、产品描述或实时更新的公告。如何在Vue中优雅处理这些动态多语言内容?我们将结合 vue-i18n 和后端API,打造一个灵活的解决方案。
1. 后端API设计
假设后端提供一个 /translations 接口,返回指定语言的动态翻译:
{
"locale": "en",
"messages": {
"announcement": "New feature released on August 24, 2025!",
"product": {
"title": "Super Widget",
"description": "A powerful tool for your needs"
}
}
}
中文版本:
{
"locale": "zh",
"messages": {
"announcement": "2025年8月24日发布新功能!",
"product": {
"title": "超级小部件",
"description": "满足您需求的强大工具"
}
}
}
2. 动态加载API翻译
修改 i18n.js 支持API加载:
import { createI18n } from 'vue-i18n'
import axios from 'axios'
async function loadLocaleMessages(locale) {
if (!i18n.global.availableLocales.includes(locale)) {
// 加载静态翻译
const staticMessages = await import(`./locales/${locale}.json`)
i18n.global.setLocaleMessages(locale, staticMessages.default)
// 加载动态翻译
try {
const { data } = await axios.get(`/api/translations?locale=${locale}`)
i18n.global.setLocaleMessages(locale, {
...staticMessages.default,
...data.messages
})
} catch (error) {
console.error(`加载动态翻译失败 (${locale}):`, error)
}
}
}
let i18n
loadMessages().then(messages => {
i18n = createI18n({
locale: localStorage.getItem('locale') || 'en',
fallbackLocale: 'en',
messages
})
i18n.global.locale.watch((newLocale) => {
localStorage.setItem('locale', newLocale)
loadLocaleMessages(newLocale)
})
})
export default i18n
export { loadLocaleMessages }
依赖:
npm install axios
说明:
-
静态翻译(en.json)作为基础,动态翻译(API)覆盖或补充。
-
错误处理确保API失败时仍使用静态翻译。
3. 在组件中使用动态翻译
在 App.vue 中展示动态内容:
<template>
<div :dir="isRtl ? 'rtl' : 'ltr'">
<LanguageSwitcher />
<h1>{{ $t('announcement') }}</h1>
<p>{{ $t('product.title') }}: {{ $t('product.description') }}</p>
</div>
</template>
<script>
import LanguageSwitcher from './components/LanguageSwitcher.vue'
export default {
components: { LanguageSwitcher },
computed: {
isRtl() {
return this.$i18n.locale === 'ar'
}
}
}
</script>
效果:切换语言时,自动从API加载动态翻译,更新页面内容。
4. 缓存动态翻译
为避免重复请求API,使用 localStorage 缓存:
async function loadLocaleMessages(locale) {
if (!i18n.global.availableLocales.includes(locale)) {
const cacheKey = `translations_${locale}`
const cached = localStorage.getItem(cacheKey)
let messages = await import(`./locales/${locale}.json`).default
if (cached) {
messages = { ...messages, ...JSON.parse(cached) }
} else {
try {
const { data } = await axios.get(`/api/translations?locale=${locale}`)
messages = { ...messages, ...data.messages }
localStorage.setItem(cacheKey, JSON.stringify(data.messages))
} catch (error) {
console.error(`加载动态翻译失败 (${locale}):`, error)
}
}
i18n.global.setLocaleMessages(locale, messages)
}
}
优化:设置缓存过期时间(如24小时):
const cacheDuration = 24 * 60 * 60 * 1000 // 24小时
const cacheKey = `translations_${locale}`
const cacheTimeKey = `translations_${locale}_time`
const cached = localStorage.getItem(cacheKey)
const cacheTime = localStorage.getItem(cacheTimeKey)
if (cached && cacheTime && Date.now() - parseInt(cacheTime) < cacheDuration) {
messages = { ...messages, ...JSON.parse(cached) }
} else {
// 加载API并更新缓存
}
5. 处理动态内容格式
动态内容可能包含HTML或复杂格式,用 vue-i18n 的 v-t 指令渲染:
<template>
<div v-t="'announcement'" />
</template>
注意:确保API返回的内容已消毒,防止XSS攻击:
npm install dompurify
import DOMPurify from 'dompurify'
export default {
computed: {
sanitizedAnnouncement() {
return DOMPurify.sanitize(this.$t('announcement'))
}
}
}
6. 踩坑经验
-
API延迟?显示加载状态(参考章节七的 isLoading)。
-
翻译冲突?优先级明确:API翻译覆盖静态翻译。
-
格式不一致?与后端约定统一的翻译键结构。
动态翻译让你的应用更灵活,适应实时变化的内容!这也是我们教程的最后一章。