实现思路
1. 项目初始化与依赖安装
javascript
npm install vue-i18n@9
# 如果使用 axios
npm install axios
2. 创建 i18n 实例并配置基础结构
在项目中创建一个 i18n.js(或 i18n.ts)文件,初始化 i18n 实例。通常我们会:
-
设置默认语言(如
zh-CN) -
设置后备语言(fallback locale)
-
可以预先加载默认语言包(也可以全部动态加载)
javascript
// i18n.js
import { createI18n } from 'vue-i18n'
// 默认语言
const defaultLocale = 'zh-CN'
// 可以预先导入本地默认语言包(可选)
import zhCN from './locales/zh-CN.json'
const i18n = createI18n({
legacy: false, // 使用 Composition API 模式
locale: defaultLocale,
fallbackLocale: 'en-US', // 后备语言
messages: {
'zh-CN': zhCN
}
})
export default i18n
然后在 main.js 中挂载:
javascript
import { createApp } from 'vue'
import App from './App.vue'
import i18n from './i18n'
const app = createApp(App)
app.use(i18n)
app.mount('#app')
3. 定义语言包结构
语言包为标准的 JSON 格式,例如:
javascript
// zh-CN.json
{
"home": {
"title": "首页",
"welcome": "欢迎使用"
},
"common": {
"save": "保存",
"cancel": "取消"
}
}
javascript
// en-US.json
{
"home": {
"title": "Home",
"welcome": "Welcome"
},
"common": {
"save": "Save",
"cancel": "Cancel"
}
}
4. 从后端 API 动态加载语言包
我们需要一个函数,根据语言代码从后端获取对应的 JSON 文件,然后通过 i18n.global.setLocaleMessage() 将语言包添加到 i18n 实例中,最后切换 locale。
同时,为了避免重复请求,可以缓存已加载的语言包(i18n 内部本身会存储,但我们可以额外用一个变量记录已加载状态)。
javascript
// utils/localeLoader.js
import i18n from '@/i18n'
import axios from 'axios'
// 记录正在加载中的语言,防止并发重复请求
const loadingLocales = new Set()
/**
* 加载指定语言包
* @param {string} locale - 语言代码,如 'en-US'
* @returns {Promise}
*/
export async function loadLocaleMessages(locale) {
// 如果已经加载过,直接返回(i18n.global.getLocaleMessage(locale) 可检查是否存在)
if (i18n.global.availableLocales.includes(locale)) {
return Promise.resolve()
}
// 避免重复请求同一个语言包
if (loadingLocales.has(locale)) {
// 可以返回一个 pending 的 promise,或者简单等待
return new Promise((resolve) => {
const check = setInterval(() => {
if (i18n.global.availableLocales.includes(locale)) {
clearInterval(check)
resolve()
}
}, 50)
})
}
loadingLocales.add(locale)
try {
// 假设后端 API 路由为 /api/locales/{locale}.json
const response = await axios.get(`/api/locales/${locale}.json`)
const messages = response.data
// 将语言包设置到 i18n 实例中
i18n.global.setLocaleMessage(locale, messages)
// 可选:同时将语言包存入 localStorage 或 IndexedDB 做离线缓存
// localStorage.setItem(`locale_${locale}`, JSON.stringify(messages))
} catch (error) {
console.error(`Failed to load locale messages for ${locale}:`, error)
throw error
} finally {
loadingLocales.delete(locale)
}
}
5. 切换语言功能
切换语言时,先调用 loadLocaleMessages 确保语言包已加载,然后修改 i18n 的 locale 值。由于 locale 是响应式 ref,所有使用了 $t 或 t 函数的地方会自动更新。
为了持久化用户偏好,可以将当前语言保存到 localStorage。
在组件中实现语言切换器:
javascript
<!-- components/LanguageSwitcher.vue -->
<template>
<select v-model="currentLocale" @change="changeLanguage">
<option value="zh-CN">中文</option>
<option value="en-US">English</option>
<option value="ja-JP">日本語</option>
<!-- 动态语言选项可以从后端获取支持的语言列表 -->
</select>
</template>
<script setup>
import { ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { loadLocaleMessages } from '@/utils/localeLoader'
const { locale } = useI18n()
const currentLocale = ref(locale.value)
// 切换语言
const changeLanguage = async () => {
const newLocale = currentLocale.value
// 加载语言包
await loadLocaleMessages(newLocale)
// 更新 locale
locale.value = newLocale
// 保存到 localStorage
localStorage.setItem('user-locale', newLocale)
}
// 初始化时从 localStorage 读取上次语言并加载
const initLocale = async () => {
const savedLocale = localStorage.getItem('user-locale')
if (savedLocale && savedLocale !== locale.value) {
currentLocale.value = savedLocale
await loadLocaleMessages(savedLocale)
locale.value = savedLocale
}
}
initLocale()
</script>
6. 页面实时响应
vue-i18n 的 locale 是一个 ref,当它的值改变时,所有依赖它的 t 函数或组件内的翻译都会自动重新计算。只要切换语言后执行了 locale.value = newLocale,页面所有文本会立即更新,无需额外操作。
7. 动态新增自定义语言
支持动态新增语言意味着我们可以通过管理界面让用户定义新语言(例如添加一个语言代码和上传对应的 JSON 文件),然后将其保存到后端,前端再获取并应用。
步骤:
-
提供一个管理界面,允许用户输入语言代码(如
fr-FR)和上传 JSON 文件或在线编辑键值对。 -
将新的语言包发送到后端 API 进行保存(例如存到数据库或文件系统)。
-
后端提供一个接口返回所有支持的语言列表(如
GET /api/locales),前端在初始化时可以请求该列表,用于构建语言切换器的选项。 -
当用户选择新语言时,通过上述
loadLocaleMessages从后端获取并加载。
动态语言列表示例:
javascript
// 在 LanguageSwitcher 中增加动态选项
import { ref, onMounted } from 'vue'
import axios from 'axios'
const supportedLocales = ref([])
onMounted(async () => {
const res = await axios.get('/api/locales')
supportedLocales.value = res.data // 例如 [{ code: 'fr-FR', name: 'Français' }]
})
模板中:
html
<select v-model="currentLocale" @change="changeLanguage">
<option v-for="loc in supportedLocales" :key="loc.code" :value="loc.code">
{{ loc.name }}
</option>
</select>
8. 进阶优化
-
预加载常用语言:在应用初始化时,可以预先加载几种常用语言(如当前语言和后备语言),减少切换时的等待。
-
加载状态处理:切换语言时如果网络慢,可以显示 loading 状态,避免界面卡顿。
-
错误处理:加载语言包失败时,可降级使用后备语言或显示错误提示。
-
离线缓存:将已加载的语言包存入 localStorage 或 IndexedDB,下次打开应用时可直接使用,减少请求。
9. 完整的 i18n 配置文件示例
javascript
// i18n.js
import { createI18n } from 'vue-i18n'
const defaultLocale = localStorage.getItem('user-locale') || 'zh-CN'
const i18n = createI18n({
legacy: false,
locale: defaultLocale,
fallbackLocale: 'en-US',
messages: {
// 可以只放一个空对象,完全动态加载
'zh-CN': {}, // 启动后立刻加载
'en-US': {}
}
})
export default i18n
然后在 App.vue 或根组件中,异步加载默认语言包:
javascript
import { onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { loadLocaleMessages } from '@/utils/localeLoader'
export default {
setup() {
const { locale } = useI18n()
onMounted(async () => {
await loadLocaleMessages(locale.value)
})
}
}