如何使用 vue-i18n 实现 Vue 应用多语言 – wiki基地


精通 Vue 应用多语言:使用 vue-i18n 的全面指南

在构建现代 Web 应用时,考虑到全球用户是至关重要的。这意味着你的应用需要能够支持多种语言,并根据用户的偏好或浏览器的设置来显示相应的文本内容。这个过程称为国际化(Internationalization,简称 i18n),而使其适应特定语言和文化的过程称为本地化(Localization,简称 l10n)。

对于 Vue.js 开发者而言,vue-i18n 是实现应用多语言功能的首选库。它功能强大、易于使用,并且与 Vue.js 的生态系统紧密集成。本文将带你深入了解 vue-i18n,从安装、基本配置,到处理文本、复数、日期、数字,以及高级主题如异步加载和与其他库的集成。

第一部分:理解国际化(i18n)与本地化(l10n)

在开始技术实现之前,先快速回顾一下 i18n 和 l10n 的基本概念:

  • 国际化 (i18n):是使应用具备支持多种语言和文化习惯的能力的过程。它通常涉及从代码中抽象出所有的文本内容,以便它们可以被翻译,并确保应用能够处理不同的日期格式、数字格式、货币符号等。
  • 本地化 (l10n):是根据特定地区或语言(称为“区域设置”或“locale”)调整国际化应用的过程。这包括翻译文本、调整日期/时间/数字格式、使用正确的货币符号,甚至调整布局、颜色或图像,以适应当地文化。

i18n 是基础,l10n 是在 i18n 基础上的具体实现。vue-i18n 库主要帮助我们实现 i18n 的核心部分——文本内容的抽象和管理,并提供本地化所需的格式化工具。

第二部分:为什么选择 vue-i18n?

市场上有许多 i18n 库,为什么 vue-i18n 是 Vue 应用的推荐选择?

  1. 官方推荐和深度集成: vue-i18n 是 Vue.js 核心团队成员 Kazuya Kawaguchi (kazupon) 开发和维护的。它与 Vue 的响应式系统、组件生命周期、单文件组件 (.vue 文件) 等特性完美集成。
  2. 功能丰富: 它提供了处理文本翻译、复数、日期、数字、货币等多种本地化需求的功能。
  3. 使用方便: 提供了简洁的模板语法 ($t, $tc, $d, $n) 和组件 (<i18n>) 来进行翻译。
  4. 性能优化: 支持异步加载翻译消息,可以显著减小初始包体积。
  5. 活跃的社区和良好的文档: 作为官方推荐库,它拥有活跃的社区支持和详细的文档。

综上所述,对于 Vue 应用来说,vue-i18n 是一个强大、可靠且与框架高度契合的解决方案。

第三部分:vue-i18n 的基本使用

让我们从零开始,在一个 Vue 项目中集成并使用 vue-i18n。

步骤 1:安装 vue-i18n

首先,你需要在你的 Vue 项目中安装 vue-i18n。根据你的 Vue 版本,选择相应的 vue-i18n 版本:

  • Vue 3: 使用 vue-i18n@^9.x
  • Vue 2: 使用 vue-i18n@^8.x

本文主要以 Vue 3 和 vue-i18n v9+ 为例进行讲解,但大部分核心概念和用法也适用于 Vue 2 版本。

使用 npm 或 yarn 安装:

“`bash
npm install vue-i18n@next # For Vue 3

或者

yarn add vue-i18n@next # For Vue 3

如果是 Vue 2

npm install vue-i18n@^8

或者

yarn add vue-i18n@^8

“`

步骤 2:创建翻译文件

接下来,你需要存放不同语言的翻译消息。通常,我们会创建一个单独的目录(例如 localeslang),并在其中为每种语言创建一个文件。这些文件通常使用 JSON 格式,但也可以使用 JavaScript 对象、YAML 等。

例如,创建一个 locales 目录,并在其中创建 en.json(英语)和 zh.json(中文)文件:

src/locales/en.json:

json
{
"message": {
"hello": "Hello, world!",
"welcome": "Welcome, {name}!",
"greeting": "Hello, <strong>{name}</strong>!"
},
"button": {
"submit": "Submit",
"cancel": "Cancel"
},
"menu": {
"home": "Home",
"about": "About"
}
}

src/locales/zh.json:

json
{
"message": {
"hello": "你好,世界!",
"welcome": "欢迎,{name}!",
"greeting": "你好,<strong>{name}</strong>!"
},
"button": {
"submit": "提交",
"cancel": "取消"
},
"menu": {
"home": "首页",
"about": "关于我们"
}
}

这些文件包含按层级组织的键值对。键(如 message.hello)是唯一的标识符,值是对应语言的翻译文本。

步骤 3:初始化并配置 vue-i18n 实例

现在,在你的 Vue 应用入口文件(通常是 src/main.jssrc/main.ts)中创建并配置 vue-i18n 实例。

“`javascript
// src/main.js

import { createApp } from ‘vue’;
import { createI18n } from ‘vue-i18n’; // 导入 createI18n

import App from ‘./App.vue’;

// 导入语言文件
import en from ‘./locales/en.json’;
import zh from ‘./locales/zh.json’;

// 1. 创建 i18n 实例
const i18n = createI18n({
// 遗留模式设置为 false 以兼容 Composition API 和新的 API
legacy: false,
// 全局注入 $t 等方法到 Options API,如果只用 Composition API 可以不设置或设置为 false
globalInjection: true,
// 设置默认语言
locale: ‘en’,
// 设置备用语言,当当前语言找不到对应翻译时,会尝试使用备用语言
fallbackLocale: ‘en’,
// 导入所有语言消息
messages: {
en,
zh
}
});

const app = createApp(App);

// 2. 将 i18n 实例挂载到 Vue 应用实例上
app.use(i18n);

app.mount(‘#app’);
“`

配置说明:

  • legacy: false: Vue 3 版本推荐使用 legacy: false 以获得 Composition API 的全面支持和更现代的 API。如果需要兼容 Vue 2 的 Options API $t 等方法,可以将其设置为 true,但 globalInjection: true 是更推荐的方式来在 Options API 中使用这些方法。
  • globalInjection: true: 如果设置为 true,则会在 Vue 模板和 Options API 中自动注入 $t, $tc, $d, $n 等全局方法。这非常方便,但如果你更倾向于 Composition API 的明确导入,可以将其设置为 false
  • locale: 设置当前应用的语言。这里我们初始化为 en。你可以根据用户的浏览器语言、用户设置等来动态设置这个值。
  • fallbackLocale: 设置备用语言。当当前语言(locale)中找不到某个键的翻译时,vue-i18n 会尝试在 fallbackLocale 中查找。这有助于避免出现空白文本。
  • messages: 一个对象,其中键是语言代码(如 en, zh),值是对应语言的翻译消息对象(通常从 JSON 文件导入)。

步骤 4:在组件中使用翻译消息

配置完成后,你就可以在 Vue 组件的模板或 JavaScript 中使用翻译消息了。vue-i18n 提供了几种方式来访问这些消息。

在模板中使用 $t 函数

$t 是一个全局函数,用于在模板中根据当前语言和提供的键获取翻译文本。

src/components/HelloWorld.vue:

“`vue

“`

解释:

  • {{ $t('message.hello') }}: 直接通过键 'message.hello' 获取翻译文本。
  • {{ $t('message.welcome', { name: userName }) }}: 如果翻译文本中包含占位符(如 {name}),可以通过第二个参数传入一个对象来替换这些占位符。键是占位符的名称(不带花括号),值是要插入的数据。

在模板中使用 <i18n> 组件

<i18n> 组件提供了一种更结构化的方式来处理包含 HTML 或需要复杂插值(如将一个 Vue 组件插入到翻译文本中)的翻译文本。

src/components/GreetingMessage.vue:

“`vue

“`

解释:

  • <i18n path="message.greeting" tag="p">: 指定使用 message.greeting 键的翻译,并将其渲染为一个 <p> 标签。
  • <template #name>: 通过具名插槽 (#name) 来提供占位符 {name} 的内容。插槽的名称与翻译文本中的占位符名称一致。这种方式比使用 v-html 更安全,因为它允许你在占位符位置插入 Vue 模板内容,而不是直接注入原始 HTML。

在 JavaScript 中使用 $t 函数

在组件的 JavaScript 代码中,你可以通过 this.$t 来访问翻译函数。

“`vue

“`

解释:

  • this.$t('key'): 在 methods, computed, mounted 等选项中使用 $t
  • this.$t('key', { params }): 同样支持参数替换。

在 Composition API 中使用:

在 Vue 3 的 Composition API 中,你可以使用 useI18n 函数来获取 i18n 实例,然后使用其中的 t 函数。

“`vue

“`

解释:

  • import { useI18n } from 'vue-i18n';: 从 vue-i18n 中导入 useI18n 函数。
  • const { t } = useI18n();: 在 setup 函数中调用 useI18n() 来获取 i18n 相关的函数和属性,包括 t
  • 在模板和 setup 函数中直接使用 t() 函数。

第四部分:处理复数、日期和数字

vue-i18n 不仅处理简单的文本翻译,还提供了处理复数形式、日期和数字格式化的功能,这些都是本地化中常见的需求。

处理复数 ($tc)

不同语言的复数规则不同(例如,英语有单数和复数,而有些语言可能有更多形式)。vue-i18n 使用管道符 (|) 和特定的语法来定义复数规则。$tc 函数用于根据给定的数量选择合适的复数形式。

src/locales/en.json:

json
{
"apple": "no apples | one apple | {count} apples",
"banana": "{count} banana | {count} bananas",
"car": "zero cars | one car | {count} cars"
}

src/locales/zh.json:

json
{
"apple": "{count} 个苹果",
"banana": "{count} 个香蕉",
"car": "{count} 辆车"
}

解释:

  • 英语 (en.json): 管道符分隔不同的复数形式。0no 对应第一部分,1one 对应第二部分,其他数量对应第三部分。{count} 是一个特殊的占位符,会自动被传入的数字替换。
    • "no apples" 对应数量 0。
    • "one apple" 对应数量 1。
    • "{count} apples" 对应数量 > 1。
  • 中文 (zh.json): 中文没有复杂的复数规则,通常只有一种形式,直接包含 {count} 占位符即可。

在组件中使用 $tc:

“`vue

“`

解释:

  • $tc('key', count, { params }): 第一个参数是翻译键,第二个参数是决定复数形式的数量,第三个参数是用于替换占位符的对象(特别是 {count})。即使翻译文本不使用 {count} 占位符,也建议传入 { count: countValue } 作为第三个参数,以确保行为一致。

处理日期和时间 ($d)

不同地区有不同的日期和时间格式(例如,月/日/年 vs 日/月/年)。vue-i18n 可以帮助你格式化日期和时间。你需要先定义格式,然后在 $d 函数中使用。

在创建 i18n 实例时配置 dateTimeFormats:

“`javascript
// src/main.js (部分代码)

const i18n = createI18n({
// …其他配置
dateTimeFormats: {
‘en-US’: {
short: {
year: ‘numeric’, month: ‘short’, day: ‘numeric’
},
long: {
year: ‘numeric’, month: ‘long’, day: ‘numeric’,
weekday: ‘long’, hour: ‘numeric’, minute: ‘numeric’, second: ‘numeric’,
timeZoneName: ‘short’
}
},
‘zh-CN’: {
short: {
year: ‘numeric’, month: ‘short’, day: ‘numeric’
},
long: {
year: ‘numeric’, month: ‘long’, day: ‘numeric’,
weekday: ‘long’, hour: ‘numeric’, minute: ‘numeric’, second: ‘numeric’
}
}
}
});
“`

解释:

  • dateTimeFormats: 一个对象,键是区域设置(如 en-US, zh-CN),值是该区域设置下的格式定义对象。
  • 格式定义对象:键是格式名称(如 short, long),值是符合 Intl.DateTimeFormat 选项的对象。Intl.DateTimeFormat 是 JavaScript 内置的日期格式化 API,vue-i18n 内部使用了它。

在组件中使用 $d:

“`vue

“`

解释:

  • $d(value, formatName, locale?): 第一个参数是要格式化的 Date 对象或时间戳,第二个参数是你在 dateTimeFormats 中定义的格式名称,第三个可选参数是指定使用的区域设置(如果不提供,则使用当前应用的 locale)。

处理数字和货币 ($n)

类似于日期,不同地区对数字(小数点、千位分隔符)和货币有不同的格式。vue-i18n 利用 Intl.NumberFormat API 来实现数字和货币格式化。

在创建 i18n 实例时配置 numberFormats:

“`javascript
// src/main.js (部分代码)

const i18n = createI18n({
// …其他配置
numberFormats: {
‘en-US’: {
currency: {
style: ‘currency’, currency: ‘USD’, minimumFractionDigits: 2
},
percent: {
style: ‘percent’, minimumFractionDigits: 1
}
},
‘zh-CN’: {
currency: {
style: ‘currency’, currency: ‘CNY’, minimumFractionDigits: 2
},
percent: {
style: ‘percent’, minimumFractionDigits: 1
}
}
}
});
“`

解释:

  • numberFormats: 一个对象,键是区域设置(如 en-US, zh-CN),值是该区域设置下的格式定义对象。
  • 格式定义对象:键是格式名称(如 currency, percent),值是符合 Intl.NumberFormat 选项的对象。

在组件中使用 $n:

“`vue

“`

解释:

  • $n(value, formatName, locale?): 第一个参数是要格式化的数字,第二个参数是你在 numberFormats 中定义的格式名称,第三个可选参数是指定使用的区域设置。

第五部分:动态切换语言

应用通常需要提供一个界面供用户切换语言。实现这一点非常简单,只需要更新 i18n 实例的 locale 属性即可。由于 Vue 的响应式特性,所有依赖于当前 locale 的翻译都会自动更新。

创建一个简单的语言切换器组件:

“`vue

“`

解释:

  • useI18n() 返回的对象中包含了 locale 的一个 Ref 引用。直接修改 locale.value 就可以改变当前语言。
  • Options API 中通过 this.$i18n.locale 直接访问和修改。
  • 将用户的语言偏好存储在 localStorage 中是一个好习惯,这样用户下次访问时,你可以读取这个值并设置为初始 locale。可以在 main.js 初始化时读取 localStorage 中的值来设置初始 locale

main.js 中初始化时读取用户偏好:

“`javascript
// src/main.js (部分代码)

// 获取用户存储的语言偏好,如果没有则默认为浏览器语言或指定默认语言 ‘en’
const initialLocale = localStorage.getItem(‘user-locale’) || navigator.language.slice(0, 2) || ‘en’;

const i18n = createI18n({
legacy: false,
globalInjection: true,
locale: initialLocale, // 使用读取到的语言作为初始语言
fallbackLocale: ‘en’,
messages: {
en,
zh
}
});

// …其他代码
“`

第六部分:异步加载(Lazy Loading)翻译消息

当你的应用支持的语言越来越多时,将所有语言的翻译消息一次性打包到主文件中会导致包体积过大,影响加载速度。异步加载允许你只在需要时(例如,用户切换到某种语言时)加载对应语言的翻译文件。

vue-i18n 支持异步加载,你需要调整初始化和语言切换的逻辑。

步骤 1:组织翻译文件(按需导入)

将语言文件改为单独导入,而不是在 main.js 中全部导入。

src/locales/index.js (创建一个文件用于组织语言导入和加载逻辑):

“`javascript
import { nextTick } from ‘vue’;
import { createI18n } from ‘vue-i18n’;

// 定义支持的语言和对应的文件路径
const SUPPORTED_LOCALES = {
en: ‘English’,
zh: ‘中文’
// 添加更多语言…
// fr: ‘Français’,
// es: ‘Español’
};

// 创建 i18n 实例 (不加载所有 messages)
const i18n = createI18n({
legacy: false,
globalInjection: true,
locale: ‘en’, // 初始语言可以先设一个默认值
fallbackLocale: ‘en’,
messages: {} // 初始 messages 为空对象
});

let loadedLocales = []; // 记录已加载的语言

// 异步加载语言文件的函数
async function loadLocaleMessages(locale) {
// 如果该语言已加载,则直接返回
if (loadedLocales.includes(locale)) {
return nextTick(); // 确保所有 DOM 更新完成后再返回
}

try {
// 动态导入对应的语言文件
const messages = await import(./${locale}.json);
// 设置该语言的消息到 i18n 实例中
i18n.global.setLocaleMessage(locale, messages.default);
loadedLocales.push(locale); // 标记该语言已加载

return nextTick(); // 确保所有 DOM 更新完成后再返回

} catch (e) {
console.error(Failed to load locale messages for ${locale}:, e);
// 处理加载失败的情况,例如回退到默认语言或显示错误信息
return Promise.reject(e); // 抛出错误以便调用方处理
}
}

// 设置语言并异步加载
async function setLocale(locale) {
// 如果当前语言已经是目标语言,且已加载,则不进行任何操作
if (i18n.global.locale.value === locale && loadedLocales.includes(locale)) {
return nextTick();
}

await loadLocaleMessages(locale); // 异步加载语言文件
i18n.global.locale.value = locale; // 设置当前语言

// 可选:将语言偏好存储到 localStorage
localStorage.setItem(‘user-locale’, locale);

// 可选:设置文档的 lang 属性,有助于 SEO 和辅助功能
document.querySelector(‘html’).setAttribute(‘lang’, locale);

return nextTick(); // 确保所有 DOM 更新完成后再返回
}

// 初始化加载语言
async function initI18n() {
// 尝试从 localStorage 读取用户偏好
const savedLocale = localStorage.getItem(‘user-locale’);
// 获取浏览器语言(只取前两位)
const browserLocale = navigator.language.slice(0, 2);

// 确定初始语言:用户偏好 > 浏览器语言 > 默认语言
let initialLocale = ‘en’; // 默认语言
if (savedLocale && SUPPORTED_LOCALES[savedLocale]) {
initialLocale = savedLocale;
} else if (browserLocale && SUPPORTED_LOCALES[browserLocale]) {
initialLocale = browserLocale;
}

// 加载并设置初始语言
await setLocale(initialLocale);
}

export { i18n, setLocale, initI18n, SUPPORTED_LOCALES };
“`

步骤 2:在 main.js 中使用异步加载逻辑

修改 main.js,使用上面定义的 initI18n 函数来初始化和加载语言。

“`javascript
// src/main.js

import { createApp } from ‘vue’;
import App from ‘./App.vue’;
import { i18n, initI18n } from ‘./locales’; // 从新的 locales/index.js 导入

async function bootstrap() {
// 初始化并加载初始语言
await initI18n();

const app = createApp(App);

// 挂载 i18n 实例
app.use(i18n);

// 挂载应用
app.mount(‘#app’);
}

// 启动应用
bootstrap();
“`

步骤 3:修改语言切换器使用 setLocale 函数

修改你的语言切换组件,调用 setLocale 函数来切换语言。

“`vue

“`

解释:

  • import(./${locale}.json): 这是 webpack 或 Vite 支持的动态导入语法,它会将每个语言文件拆分成一个独立的 chunk,只在运行时按需加载。
  • i18n.global.setLocaleMessage(locale, messages.default): 使用 setLocaleMessage 方法将异步加载的语言消息添加到 i18n 实例中。
  • await setLocale(lang): 在切换语言时,调用 setLocale 函数,它会先异步加载语言文件(如果尚未加载),然后设置当前语言。由于是异步操作,使用 await 确保加载完成后再继续。
  • nextTick(): 在设置语言消息或切换语言后,使用 nextTick 可以确保 DOM 已经更新,这在某些场景下可能需要。

通过这种方式,你的应用初始加载时只会包含默认语言的翻译,其他语言的翻译会在用户第一次切换到该语言时才加载,从而优化了初始加载性能。

第七部分:与其他库的集成

集成 Vue Router

你可能希望在 URL 中包含语言信息,例如 /en/about/zh/about。你可以结合 Vue Router 和 vue-i18n 来实现。

1. 在路由配置中添加语言参数:

“`javascript
// src/router/index.js

import { createRouter, createWebHistory } from ‘vue-router’;
import HomeView from ‘../views/HomeView.vue’;
import AboutView from ‘../views/AboutView.vue’;

// 导入之前定义的 setLocale 和 SUPPORTED_LOCALES
import { setLocale, SUPPORTED_LOCALES } from ‘@/locales’;

const routes = [
// 重定向根路径到带默认语言的首页
{
path: ‘/’,
redirect: /${Object.keys(SUPPORTED_LOCALES)[0]}/
},
// 动态语言参数路径
{
path: ‘/:locale/’,
// 子路由
children: [
{
path: ”, // /:locale/ 路径,通常是首页
name: ‘home’,
component: HomeView
},
{
path: ‘about’, // /:locale/about 路径
name: ‘about’,
component: AboutView
}
// 添加更多路由…
]
},
// 可选:处理无效的语言路径或 404
{
path: ‘/:pathMatch(.)‘,
name: ‘NotFound’,
component: () => import(‘../views/NotFound.vue’) // 创建一个 404 页面
}
];

const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
});

// 导航守卫:在每次路由切换前根据 URL 参数设置语言
router.beforeEach(async (to, from, next) => {
const locale = to.params.locale;

// 检查 URL 中的语言参数是否是我们支持的语言
if (locale && SUPPORTED_LOCALES[locale]) {
// 如果是支持的语言,则设置应用语言并继续导航
if (locale !== i18n.global.locale.value) { // 避免重复设置
await setLocale(locale); // 异步加载并设置语言
}
// 更新 localStorage 中的语言偏好 (可选,setLocale 中已包含)
// localStorage.setItem(‘user-locale’, locale);
return next();
} else if (locale) {
// 如果 URL 中有语言参数但不被支持,可以重定向到带默认语言的 404 页面
// 例如,重定向到 /en/404 或 /en/notfound
// 更简单的处理是重定向到带默认语言的首页
console.warn(Unsupported locale in URL: ${locale});
// 使用 replace 避免创建历史记录
return next({ path: /${i18n.global.locale.value}/, replace: true });
} else {
// 如果 URL 中没有语言参数 (例如,用户直接访问 /),则重定向到带默认语言的路径
// 这通常在根路径重定向中处理了,但这里作为补充
// 检查根路径重定向是否已处理,避免死循环
if (to.path === ‘/’) {
// 重定向到带默认语言的根路径
// 注意:如果根路径重定向到 /:locale/,这里可能不需要额外处理
// 为了安全起见,可以检查目标路径是否已经是带语言的路径
if (!routes.some(route => route.path === /:locale/ && to.path.startsWith(/${i18n.global.locale.value}/))) {
return next({ path: /${i18n.global.locale.value}${to.fullPath === '/' ? '/' : to.fullPath}, replace: true });
}
}
// 如果不是根路径且没有语言参数,可能需要根据用户偏好或浏览器语言重定向
// 或者直接加载应用默认语言并继续
// 为了简化,这里假设前面已处理了根路径重定向,其他无语言路径可能不常见或由服务器处理
return next();
}
});

export default router;
“`

解释:

  • 我们在路由路径中添加了 :locale 参数。
  • router.beforeEach 导航守卫中,我们检查当前路由的 params.locale
  • 如果 locale 参数存在且是支持的语言,我们就调用 setLocale 来设置应用语言。
  • 如果 locale 参数存在但不支持,或者不存在,我们可以根据需要进行重定向(例如,重定向到默认语言的对应页面或首页)。
  • 需要在 main.js 中将 router 挂载到 Vue 应用实例上。

2. 在组件中使用带语言的路由链接:

你可以通过编程导航或 <router-link> 来生成带语言的 URL。

“`vue

“`

解释:

  • 通过 params: { locale: locale.value } 将当前语言作为参数传递给路由。

集成状态管理 (Vuex / Pinia)

你可以将当前语言存储在你的状态管理库中,以便在应用中的任何地方访问和修改。

使用 Pinia (Vue 3 推荐):

src/stores/app.js:

“`javascript
import { defineStore } from ‘pinia’;
import { setLocale, SUPPORTED_LOCALES } from ‘@/locales’; // 导入语言设置函数和支持的语言列表

export const useAppStore = defineStore(‘app’, {
state: () => ({
currentLocale: localStorage.getItem(‘user-locale’) || navigator.language.slice(0, 2) || ‘en’,
}),
actions: {
async changeLocale(locale) {
if (SUPPORTED_LOCALES[locale]) {
await setLocale(locale); // 调用异步加载和设置语言的函数
this.currentLocale = locale; // 更新 Pinia state
} else {
console.warn(Unsupported locale attempted: ${locale});
}
}
}
});
“`

main.js 中初始化时使用 store 的值:

“`javascript
// src/main.js

import { createApp } from ‘vue’;
import { createPinia } from ‘pinia’;
import App from ‘./App.vue’;
import { i18n } from ‘./locales’; // 导入 i18n 实例,但不再由 locales 初始化语言
import router from ‘./router’; // 导入 router

async function bootstrap() {
const app = createApp(App);
const pinia = createPinia();

app.use(pinia);
app.use(i18n); // 挂载 i18n 实例
app.use(router); // 挂载 router

// 在 router 的 beforeEach 守卫中根据路由参数设置语言
// Pinia store 的初始化值应与路由守卫中的逻辑保持一致
// 或者,在 Pinia action 中触发路由导航,确保两者同步

// 注意:这里不再直接调用 initI18n,因为路由守卫会根据 URL 设置语言
// Pinia state 中的 initialLocale 只是一个初始值,实际使用的语言由路由守卫决定和更新

app.mount(‘#app’);
}

bootstrap();
“`

修改路由守卫使用 store 的 action:

“`javascript
// src/router/index.js (部分修改)

import { createRouter, createWebHistory } from ‘vue-router’;
// …其他导入
import { useAppStore } from ‘@/stores/app’; // 导入你的 Pinia store

const router = createRouter({ / …配置 / });

router.beforeEach(async (to, from, next) => {
const appStore = useAppStore(); // 获取 store 实例

const locale = to.params.locale;
const supportedLocales = Object.keys(SUPPORTED_LOCALES); // 从 locales 导入或直接使用 store 中的列表

if (locale && supportedLocales.includes(locale)) {
// 如果 URL 语言有效,使用 store action 设置语言 (会同步更新 i18n 和 localStorage)
if (appStore.currentLocale !== locale) { // 避免不必要的设置
await appStore.changeLocale(locale);
}
return next();
} else if (locale) {
// URL 中有语言参数但不支持
console.warn(Unsupported locale in URL: ${locale});
// 重定向到带当前语言的首页 (或者默认语言的首页)
return next({ path: /${appStore.currentLocale}/, replace: true });
} else {
// URL 中没有语言参数
// 重定向到带当前语言 (store 中) 的路径
// 确保重定向目标路径不已经是带语言的,避免死循环
const currentLocale = appStore.currentLocale;
const targetPath = /${currentLocale}${to.fullPath === '/' ? '/' : to.fullPath};

// 避免重定向到当前已经在的带语言路径
if (to.path !== targetPath && !to.path.startsWith(`/${currentLocale}/`)) {
     console.log(`Redirecting to ${targetPath}`);
     return next({ path: targetPath, replace: true });
}

// 如果已经在带语言的路径,或者不需要重定向,则继续
return next();

}
});

export default router;
“`

修改语言切换器使用 store 的 action:

“`vue

“`

解释:

  • Pinia (或 Vuex) store 持有当前语言状态,并且 changeLocale action 负责调用 setLocale 函数来实际加载和设置语言到 vue-i18n 实例。
  • Vue Router 的导航守卫根据 URL 参数来获取目标语言,并调用 store 的 action 来进行语言切换和状态更新。
  • 所有需要知道当前语言的组件都可以从 store 中获取 currentLocale

这种集成方式使得语言状态在整个应用中集中管理,并且与路由、vue-i18n 实例保持同步。

第八部分:高级用法与最佳实践

<i18n> 自定义块

在单文件组件 (.vue 文件) 中,你可以使用 <i18n> 自定义块来直接在组件内部定义该组件特有的翻译消息。这有助于将组件的逻辑、模板和翻译消息组织在一起。

“`vue


{
“en”: {
“welcome_component”: “Welcome from the component!”,
“greeting_user”: “Hello, {name}!”
},
“zh”: {
“welcome_component”: “来自组件的欢迎!”,
“greeting_user”: “你好,{name}!”
}
}

“`

解释:

  • <i18n> 块中的 JSON 对象会作为该组件的本地消息。
  • 在组件内部使用 $t (或 Composition API 中的 t) 时,会优先查找组件的本地消息,如果找不到,则会查找全局消息。
  • 这需要配置 Vue Loader 或 Vite 插件来处理 <i18n> 块。对于 Vue CLI 项目,安装 vue-loader 通常已经支持。对于 Vite,需要安装 @intlify/vite-plugin-vue-i18n 并进行配置。

Vite 配置示例:

vite.config.js:

“`javascript
import { fileURLToPath, URL } from ‘node:url’;
import { defineConfig } from ‘vite’;
import vue from ‘@vitejs/plugin-vue’;
import vueI18n from ‘@intlify/vite-plugin-vue-i18n’; // 导入插件
import path from ‘node:path’; // 导入 path 模块

export default defineConfig({
plugins: [
vue(),
vueI18n({
// 需要指定国际化资源的目录
include: path.resolve(__dirname, ‘./src/locales/**’),
// 如果你的 .vue 文件使用了 块,也需要指定资源类型为 vue-i18n
// 这通常不需要额外配置,插件会自动处理 .vue 文件
})
],
resolve: {
alias: {
‘@’: fileURLToPath(new URL(‘./src’, import.meta.url))
}
}
});
“`

翻译键的组织

良好的翻译键组织方式能够提高可维护性:

  • 按功能或模块分组: 例如 user.profile.name, product.details.price
  • 按组件分组: 可以在全局消息中为每个组件创建一个命名空间,或者使用上面提到的 <i18n> 块。
  • 使用有意义的键: 避免使用 key1, stringA 这样的键。使用能够描述内容或位置的键。
  • 扁平 vs 嵌套: 嵌套结构(如 JSON)更清晰,但在某些工具中可能处理更复杂。扁平结构(如 user_profile_name)简单,但不够直观。选择适合你项目和团队的方式。

处理丢失的翻译

当某个键在当前语言和备用语言中都找不到时,vue-i18n 会发出警告(在开发模式下)并显示原始键。你可以通过 missing 选项来自定义行为,例如返回一个特定的字符串,或者记录到日志服务。

性能考虑

  • 异步加载: 这是处理大型应用翻译消息最重要的性能优化手段。
  • 避免不必要的 $t 调用: 在计算属性或 Watcher 中频繁调用 $t 可能导致性能问题,特别是在复杂的组件树中。如果翻译内容不经常变化,考虑将其结果缓存起来。
  • 使用 v-once: 如果某个部分的翻译文本在整个组件生命周期内都不会改变,可以使用 v-once 指令来避免其响应性更新,优化性能。但要注意,一旦使用 v-once,这部分 DOM 将不再响应任何数据变化,包括语言切换。通常不与需要响应语言切换的内容一起使用。

给翻译人员提供上下文

仅仅提供一个 JSON 文件给翻译人员是不足够的。好的 i18n 实践应该为翻译人员提供每个键的使用位置和上下文信息,以便他们能够做出准确的翻译。这通常需要借助专门的 i18n 管理平台或工具来辅助。

第九部分:总结

恭喜你!通过阅读本文,你应该已经全面了解了如何在 Vue 应用中使用 vue-i18n 实现多语言功能。我们从基础安装和配置开始,学习了如何在模板和 JavaScript 中使用 $t<i18n> 组件,掌握了处理复数、日期和数字的技巧。更进一步,我们探讨了如何实现异步加载以优化性能,以及如何将 vue-i18n 与 Vue Router 和状态管理库(如 Pinia)集成。

国际化是一个持续的过程,不仅仅是翻译文本。它涉及到对用户体验、文化习惯和可访问性的深入思考。vue-i18n 为我们提供了强大的工具来应对这些挑战,让你能够更轻松地构建面向全球用户的 Vue.js 应用。

记住最佳实践:抽象文本内容、使用有意义的键、考虑异步加载、为翻译人员提供清晰的上下文。持续学习和实践,你的多语言 Vue 应用将会越来越完善!

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部