构建无国界的 Vue 应用:Vue I18n 深度指南
随着全球化趋势的不断加速,开发一个能够服务于不同语言和文化背景用户的应用程序变得越来越重要。国际化(Internationalization,简称 i18n)和本地化(Localization,简称 l10n)是实现这一目标的关键。国际化是指设计和开发应用程序,使其无需修改代码就能适应各种区域和语言;本地化则是指为特定地区或语言的用户界面、内容和功能进行适配的过程,包括翻译、日期/时间格式、货币符号等的调整。
对于 Vue.js 开发者来说,Vue I18n 是一个强大、灵活且与 Vue 生态系统紧密集成的国际化解决方案。它提供了丰富的功能,帮助我们轻松地管理翻译文本、处理复数、插入变量、格式化日期/时间/数字等,从而构建真正的多语言应用。
本文将深入探讨如何使用 Vue I18n,从基础安装配置到高级用法,带你全面掌握这一重要的前端技能。
1. 为什么选择 Vue I18n?
在 Vue.js 应用中实现国际化,我们可以手动管理翻译对象,或者使用第三方库。选择 Vue I18n 的理由显而易见:
- 与 Vue 深度集成: Vue I18n 作为 Vue 的官方推荐国际化库,完美契合 Vue 的组件化开发模式,提供了
$t
、$tc
(在v9+中使用新语法)等实例方法和<i18n>
组件,使用起来非常自然和高效。 - 丰富的功能: 除了基本的文本翻译,它还支持变量插值、复数处理、列表格式化、HTML 内容处理、日期/时间/数字本地化等多种高级功能。
- 组件级别和全局级别: 支持在组件内部定义独立的翻译消息,与全局消息灵活结合,方便管理。
- 异步加载: 支持按需异步加载语言文件,优化应用性能,尤其适用于大型应用。
- 活跃的社区和完善的文档: 作为成熟的库,拥有活跃的社区支持和详细的文档,遇到问题容易找到解决方案。
2. 前期准备
在开始之前,确保你已经搭建好了 Vue.js 开发环境,并且对 Vue 的基本概念(组件、模板、实例方法、生命周期等)有一定了解。你需要 Node.js 和 npm 或 yarn 来安装依赖。
本文将以 Vue 3 为例进行讲解,Vue I18n 的最新版本(v9+)与 Vue 3 配合更佳,并引入了一些新的语法和特性。如果你的项目是 Vue 2,部分语法(如复数处理 $tc
)可能有所不同,但核心概念是相通的。
3. 安装 Vue I18n
使用 npm 或 yarn 在你的 Vue 项目中安装 Vue I18n:
“`bash
使用 npm
npm install vue-i18n@next
使用 yarn
yarn add vue-i18n@next
“`
@next
标签表示安装最新版本,通常是与 Vue 3 兼容的版本(v9+)。
4. 基本配置和使用
安装完成后,我们需要在 Vue 应用中配置和挂载 Vue I18n 插件。
4.1 创建语言文件
首先,创建用于存放翻译文本的语言文件。通常,我们为每种语言创建一个 JSON 或 JavaScript 文件,并按照一定的结构组织翻译键值对。例如,在 src
目录下创建一个 locales
文件夹:
src/
├── main.js
└── locales/
├── en.json
└── zh.json
en.json
(英文):
json
{
"message": {
"hello": "Hello, World!"
},
"greeting": "Welcome, {name}!",
"items": "Item | Items",
"description": "This is a <strong>simple</strong> example."
}
zh.json
(中文):
json
{
"message": {
"hello": "你好,世界!"
},
"greeting": "欢迎,{name}!",
"items": "个项目",
"description": "这是一个<strong>简单</strong>的示例。"
}
这里定义了一些简单的翻译键值对,包括嵌套结构 (message.hello
)、带有变量 (greeting
)、复数(items
)和带有 HTML 内容 (description
) 的示例。
4.2 创建 Vue I18n 实例并挂载
在你的 Vue 应用入口文件(通常是 src/main.js
)中,导入语言文件,创建 VueI18n
实例,并将其提供给 Vue 应用实例。
“`javascript
// src/main.js
import { createApp } from ‘vue’;
import { createI18n } from ‘vue-i18n’;
import App from ‘./App.vue’;
// 导入语言文件
import en from ‘./locales/en.json’;
import zh from ‘./locales/zh.json’;
// 1. 创建 i18n 实例
const i18n = createI18n({
legacy: false, // composition API support
locale: ‘en’, // 默认语言
fallbackLocale: ‘en’, // 回退语言,当当前语言没有某个翻译时,尝试使用回退语言
messages: {
en,
zh
}
});
// 2. 创建 Vue 应用实例
const app = createApp(App);
// 3. 将 i18n 实例挂载到 Vue 应用
app.use(i18n);
// 4. 挂载根组件
app.mount(‘#app’);
“`
解释:
createI18n
: 这是 Vue I18n v9+ 提供的用于创建 i18n 实例的函数。legacy: false
: 启用组合式 API (Composition API) 支持,并关闭兼容旧版 Options API 的$t
等全局方法。在 Vue 3 项目中,通常推荐使用legacy: false
。如果你需要在 Options API 中直接使用$t
等,可以设置为true
,但组合式 API 的方式更灵活。本文主要以legacy: false
为例,但也会提及 Options API 的使用。locale
: 设置应用的当前语言,这里初始化为英文 (en
)。fallbackLocale
: 当当前语言(例如zh
)中找不到某个翻译键时,i18n 会尝试在回退语言(例如en
)中查找,这非常有用,可以避免某些文本没有翻译时出现空白或错误。messages
: 一个对象,键是语言代码(如en
,zh
),值是对应的翻译消息对象。
4.3 在模板中使用翻译
在 Vue 组件的模板中,我们可以使用 $t
方法来访问翻译文本。如果使用的是 legacy: false
模式,你需要先获取 i18n 实例的全局翻译方法。
在 Options API 组件中 (使用 legacy: true
或通过 this.$i18n.global.t
):
“`vue
{{ $t(‘message.hello’) }}
{{ $t(‘greeting’, { name: ‘Vue User’ }) }}
“`
在 Composition API 组件中 (推荐使用 legacy: false
):
在 Composition API 中,我们可以使用 useI18n
钩子来获取翻译函数 t
。
“`vue
{{ t(‘message.hello’) }}
{{ t(‘greeting’, { name: ‘Vue User’ }) }}
“`
解释:
{{ t('message.hello') }}
: 这会查找当前语言包中键为message.hello
的值并显示。{{ t('greeting', { name: 'Vue User' }) }}
: 这是带变量插值的例子。翻译键greeting
中使用了{name}
作为占位符,通过第二个参数传递一个对象来替换这个占位符。<i18n-t keypath="description"></i18n-t>
: 这是 Vue I18n v9+ 推荐用于处理包含 HTML 标签的翻译文本的方式。keypath
指定翻译键。<i18n-t>
组件能够安全地渲染 HTML 内容,并且可以处理更复杂的插槽逻辑(见后续章节)。
4.4 在 JavaScript 中使用翻译
在 <script>
标签中,无论是 Options API 还是 Composition API,都可以访问到翻译方法。
Options API (使用 legacy: true
):
javascript
export default {
mounted() {
alert(this.$t('message.hello'));
}
}
Options API (使用 legacy: false
):
通过 this.$i18n.global
访问全局实例:
javascript
export default {
mounted() {
alert(this.$i18n.global.t('message.hello'));
}
}
Composition API (推荐使用 legacy: false
):
使用 useI18n
钩子获取 t
函数:
“`vue
“`
5. 管理和切换语言
5.1 获取和设置当前语言
当前语言存储在 i18n 实例的 locale
属性中。你可以读取它来获取当前语言,也可以修改它来切换语言。
Options API:
javascript
export default {
computed: {
currentLocale() {
return this.$i18n.locale;
}
},
methods: {
changeLocale(newLocale) {
this.$i18n.locale = newLocale;
}
}
}
Composition API:
“`vue
Current Locale: {{ currentLocale }}
“`
通过修改 i18n.locale.value
,所有使用 $t
或 t
的地方都会响应式地更新,显示新的语言内容。
5.2 语言切换的最佳实践
在实际应用中,用户可能通过界面上的语言切换器、浏览器的语言设置、URL 参数等方式来选择语言。
- 语言切换器: 提供下拉菜单或按钮组,用户点击后调用
changeLocale
方法。 - 浏览器语言: 可以在应用加载时检测
navigator.language
或navigator.languages
,并尝试加载匹配的语言包。 - URL 参数/路由: 将语言代码作为 URL 的一部分(例如
/en/about
,/zh/contact
),通过路由参数来确定当前语言。这有利于 SEO,但需要路由和 i18n 配合处理。 - 本地存储: 将用户选择的语言保存在
localStorage
或cookies
中,以便用户下次访问时记住选择。
示例:根据浏览器语言和本地存储设置默认语言
“`javascript
// src/main.js
import { createApp } from ‘vue’;
import { createI18n } from ‘vue-i18n’;
import App from ‘./App.vue’;
import en from ‘./locales/en.json’;
import zh from ‘./locales/zh.json’;
// 获取用户偏好的语言,优先从 localStorage 读取,然后是浏览器设置
function getStartingLocale() {
const savedLocale = localStorage.getItem(‘user-locale’);
if (savedLocale) {
return savedLocale;
}
const browserLocale = navigator.language.split(‘-‘)[0]; // 例如 ‘en-US’ -> ‘en’
const supportedLocales = [‘en’, ‘zh’]; // 应用支持的语言列表
if (supportedLocales.includes(browserLocale)) {
return browserLocale;
}
return ‘en’; // 默认回退到英文
}
const i18n = createI18n({
legacy: false,
locale: getStartingLocale(), // 使用获取到的语言作为初始语言
fallbackLocale: ‘en’,
messages: {
en,
zh
}
});
const app = createApp(App);
app.use(i18n);
app.mount(‘#app’);
“`
在语言切换方法中,除了修改 i18n.locale.value
,别忘了更新本地存储:
javascript
// 在你的语言切换方法中...
const changeLocale = (newLocale) => {
i18n.locale.value = newLocale;
localStorage.setItem('user-locale', newLocale); // 保存到本地存储
};
6. 高级用法
Vue I18n 提供了丰富的高级功能来处理更复杂的本地化场景。
6.1 复数处理 (Pluralization)
处理不同数量名词的复数形式是国际化的常见需求。例如,”1 item” vs. “2 items”。不同的语言有不同的复数规则。Vue I18n v9+ 使用了 Message Format Specifier 语法来处理复数。
语言文件 (en.json
):
json
{
"item_count": "no items | {count} item | {count} items"
}
这里使用了 |
符号分隔不同的复数形式。Vue I18n 会根据 count
的值自动选择合适的复数形式。
模板中使用:
“`vue
{{ t(‘item_count’, { count: 0 }) }}
{{ t(‘item_count’, { count: 1 }) }}
{{ t(‘item_count’, { count: 5 }) }}
{{ t(‘item_count’, 100) }}
“`
对于中文这样的语言,通常没有复数概念,可以直接使用一个形式:
语言文件 (zh.json
):
json
{
"item_count": "{count} 个项目"
}
在这种情况下,无论 count
是多少,都会显示相同形式的文本。Vue I18n 根据语言的复数规则(Plural Rules)来决定使用哪种形式。你可以通过配置 pluralRules
来自定义复数规则,但这通常只在处理少数特殊语言时需要。
注意: 在 Vue I18n v8 及更早版本中,复数处理使用 $tc
方法和不同的语法。在 v9+ 中,推荐使用 t
方法配合 Message Format Specifier。
6.2 命名格式化 (Named Formatting)
前面已经看到了使用 {name}
进行简单的变量插值。这称为命名格式化。
语言文件:
json
{
"welcome_message": "Hello, {user}! You have {count} new messages."
}
模板中使用:
“`vue
{{ t(‘welcome_message’, { user: ‘Alice’, count: 3 }) }}
“`
输出:Hello, Alice! You have 3 new messages.
6.3 列表格式化 (List Formatting)
有时需要插入列表中的多个值。可以使用 [index]
语法。
语言文件:
json
{
"items_list": "Items: [0], [1] and [2]."
}
模板中使用:
“`vue
{{ t(‘items_list’, [‘Apple’, ‘Banana’, ‘Cherry’]) }}
“`
输出:Items: Apple, Banana and Cherry.
6.4 HTML 内容处理
如果翻译文本中包含 HTML 标签,直接使用 {{ t(...) }}
插入到模板中会被转义,显示为纯文本。
语言文件:
json
{
"bold_text": "This text is <strong>bold</strong>."
}
直接使用 {{ t('bold_text') }}
会渲染为 This text is <strong>bold</strong>.
方法 1: 使用 v-html
(不推荐,有 XSS 风险)
你可以将翻译结果赋值给一个变量,然后使用 v-html
指令。
“`vue
“`
注意: 使用 v-html
时,务必确保你的翻译源是安全可靠的,避免注入恶意脚本 (XSS 攻击)。通常不推荐使用这种方式,除非你能完全控制并信任翻译内容。
方法 2: 使用 <i18n-t>
组件 (推荐,安全且灵活)
Vue I18n v9+ 提供了 <i18n-t>
组件,专门用于处理包含 HTML 或需要插入 Vue 组件的翻译文本。
语言文件:
json
{
"html_example": "Please read the {link}.",
"complex_html_example": "This is a <strong>bold</strong> text and a {link}."
}
模板中使用 <i18n-t>
:
“`vue
“`
<i18n-t>
组件通过 <template>
标签和命名插槽 (#slotName
) 提供了一种安全且强大的方式来构建复杂的本地化文本,包括插入 Vue 组件。插槽名对应于翻译键中的占位符(如 {link}
) 或 HTML 标签名(如 <strong>
的插槽名是 strong
)。
6.5 组件级别本地化 (Component-level Localization)
有时,某些翻译文本只在特定组件中使用,将其放在全局语言文件中可能会导致文件过大或命名冲突。Vue I18n 允许在组件内部定义局部翻译消息。
在 Options API 组件中:
使用 i18n
选项:
“`vue
{{ $t(‘component.title’) }}
{{ $t(‘message.hello’) }}
“`
在 Composition API 组件中:
使用 useI18n
钩子并提供 messages
选项:
“`vue
{{ t(‘component.title’) }}
{{ t(‘message.hello’) }}
“`
查找顺序: 当你调用 t('key')
时,Vue I18n 会按照一定的顺序查找翻译:
1. 如果在使用 <i18n-t>
组件且指定了 scope="parent"
,则在父组件的 i18n
选项中查找。
2. 在当前组件的 i18n
选项(或 useI18n
的 messages
)中查找。
3. 如果当前组件没有找到,会沿着父组件链向上查找,直到根组件。
4. 如果在任何组件的局部消息中都没有找到,最后会在全局消息中查找。
5. 如果全局也没有找到,并且设置了 fallbackLocale
,则在回退语言的全局消息中查找。
6. 如果依然没有找到,并且设置了 missing
函数,则调用该函数处理;否则,会显示原始键名或空字符串(取决于配置)。
了解这个查找顺序对于管理翻译的层级非常重要。
6.6 异步加载语言文件 (Lazy Loading Messages)
对于包含大量语言或语言文件很大的应用,一次性加载所有语言文件会增加应用的初始加载时间。Vue I18n 支持异步加载语言文件。
示例:按需加载语言文件
-
修改
main.js
,初始化 i18n 时只加载默认语言或常用语言,并移除其他语言的直接导入。“`javascript
// src/main.js
import { createApp } from ‘vue’;
import { createI18n } from ‘vue-i18n’;
import App from ‘./App.vue’;// 只加载默认语言
import en from ‘./locales/en.json’;const i18n = createI18n({
legacy: false,
locale: ‘en’,
fallbackLocale: ‘en’,
messages: {
en // 初始化只包含英文
}
});const app = createApp(App);
app.use(i18n);
app.mount(‘#app’);
“` -
创建一个函数来加载语言文件并在加载完成后设置到 i18n 实例中。
“`javascript
// src/i18n.js (或者你希望存放这个函数的地方)
import { nextTick } from ‘vue’;
import i18n from ‘./main’; // 导入 main.js 中创建的 i18n 实例// 用于存储已加载的语言
const loadedLocales = [‘en’]; // 初始已加载英文async function loadLocaleMessages(locale) {
// 如果语言已经加载过,直接返回
if (loadedLocales.includes(locale)) {
return nextTick(); // 返回一个 Promise,确保在 DOM 更新后 resolve
}try {
// 使用动态 import 异步加载语言文件
const messages = await import(./locales/${locale}.json
);
// 将加载到的消息设置到 i18n 实例中
i18n.global.setLocaleMessage(locale, messages.default);
// 标记为已加载
loadedLocales.push(locale);
return nextTick();
} catch (e) {
console.error(Failed to load locale ${locale}:
, e);
// 可以根据需要处理错误,例如回退到默认语言
return nextTick(); // 同样返回 Promise
}
}// 修改语言并加载对应的语言文件
export async function switchLanguage(locale) {
// 如果语言不支持,可以添加检查或回退逻辑
if (i18n.global.locale.value === locale) {
return nextTick();
}await loadLocaleMessages(locale); // 等待语言文件加载完成
i18n.global.locale.value = locale; // 设置当前语言
// 可以选择将新语言保存到 localStorage 等
localStorage.setItem(‘user-locale’, locale);
}export default i18n; // 导出 i18n 实例供 main.js 使用
// 修改 main.js,导入 i18n.js 并挂载
// import i18n from ‘./i18n’;
// app.use(i18n);
// …
“` -
在需要切换语言的地方调用
switchLanguage
函数。“`vue
…
“`
这样,只有当用户切换到某种语言时,对应的语言文件才会被加载,大大减少了初始加载的资源。
6.7 处理缺失的翻译 (Handling Missing Messages)
如果在当前语言包中找不到某个翻译键,Vue I18n 默认会回退到 fallbackLocale
。如果在回退语言中也找不到,它会打印一个警告(在开发模式下),并在模板中显示该键名本身。
你可以通过配置 missing
函数来自定义处理缺失翻译的行为,例如:
javascript
const i18n = createI18n({
// ... 其他配置
missingTrack: true, // 跟踪缺失的key
missing: (locale, key, vm, values) => {
// locale: 当前语言
// key: 缺失的键
// vm: 使用翻译的 Vue 实例/组件 (如果是全局 $t 则为根实例)
// values: 传递的插值参数
console.warn(`[i18n] Missing key '${key}' for locale '${locale}'`);
// 返回一个自定义字符串,例如在开发环境显示键名,在生产环境显示空字符串
if (process.env.NODE_ENV === 'development') {
return `[MISSING] ${key}`;
} else {
return ''; // Or return key to show the key name
}
}
});
这对于在开发过程中发现未翻译的文本非常有用。
6.8 日期、时间和数字本地化 (Date, Time, Number Localization)
除了文本,日期、时间、数字、货币等格式也需要根据不同地区进行本地化。Vue I18n 集成了浏览器内置的 Intl
API,通过 $d
和 $n
方法提供这些功能。
配置日期/时间格式:
在 createI18n
配置中添加 dateTimeFormats
选项。
javascript
const i18n = createI18n({
// ... 其他配置
dateTimeFormats: {
en: {
short: {
year: 'numeric', month: 'short', day: 'numeric'
},
long: {
year: 'numeric', month: 'long', day: 'numeric',
weekday: 'long', hour: 'numeric', minute: 'numeric', hour12: true
}
},
zh: {
short: {
year: 'numeric', month: 'short', day: 'numeric'
},
long: {
year: 'numeric', month: 'long', day: 'numeric',
weekday: 'long', hour: 'numeric', minute: 'numeric', hour12: false
}
}
}
});
格式选项遵循 Intl.DateTimeFormat
的标准。
在模板中使用 $d
:
Options API:
“`vue
{{ $d(new Date(), ‘short’) }}
{{ $d(new Date(), ‘long’) }}
“`
Composition API:
需要获取 $d
方法,它通常在全局 i18n 实例上。
“`vue
{{ d(new Date(), ‘short’) }}
{{ d(new Date(), ‘long’) }}
“`
配置数字/货币格式:
在 createI18n
配置中添加 numberFormats
选项。
javascript
const i18n = createI18n({
// ... 其他配置
numberFormats: {
en: {
currency: {
style: 'currency', currency: 'USD', notation: 'standard'
},
decimal: {
style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2
}
},
zh: {
currency: {
style: 'currency', currency: 'CNY', notation: 'standard'
},
decimal: {
style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2
}
}
}
});
格式选项遵循 Intl.NumberFormat
的标准。
在模板中使用 $n
:
Options API:
“`vue
{{ $n(12345.67, ‘currency’) }}
{{ $n(123.456, ‘decimal’) }}
“`
Composition API:
“`vue
{{ n(12345.67, ‘currency’) }}
{{ n(123.456, ‘decimal’) }}
“`
通过配置 dateTimeFormats
和 numberFormats
,并使用 $d
和 $n
(或 d
和 n
),你可以方便地实现日期、时间、数字和货币的本地化显示。
7. 组织和管理翻译
随着应用规模的增长,翻译文本会越来越多。良好的组织和管理策略至关重要。
- 文件结构: 按照功能模块、页面或组件来组织语言文件,例如
locales/en/components/button.json
,locales/zh/pages/about.json
。然后在main.js
或异步加载时,将这些文件合并。 - 键名规范: 使用清晰、有意义的键名,例如
page.auth.login.title
,button.submit
,message.success.item_added
。避免使用过于简略或含糊不清的键。 - 单一职责: 一个翻译键只包含一个完整的文本片段。避免将多个独立的句子合并到一个键中,因为不同语言的语序和语法可能不同。
- 上下文: 向翻译人员提供足够的上下文信息,例如文本出现的页面、组件、UI 位置等,以确保翻译的准确性。
- 翻译管理工具: 对于大型项目,考虑使用专业的翻译管理平台(TMS),如 Crowdin, Lokalise, Transifex 等。这些工具可以帮助团队协作翻译、管理版本、提供翻译记忆库等,并通常支持导入/导出 Vue I18n 兼容的 JSON/YAML 文件。
8. 测试多语言应用
开发过程中,务必测试你的多语言功能:
- 手动切换: 在浏览器中通过界面上的语言切换器手动切换不同的语言,检查所有文本、日期、数字是否正确显示。
- 浏览器设置: 修改浏览器的语言设置,重新加载应用,检查是否根据浏览器语言正确加载了默认语言。
- 测试不同的数据: 特别是对于复数和带变量的文本,使用不同的数值和变量值进行测试,确保格式化正确。
- RTL (Right-to-Left) 语言: 如果你的应用需要支持阿拉伯语、希伯来语等从右到左书写的语言,除了翻译文本,还需要调整应用的布局和样式(CSS)。Vue I18n 本身不处理 RTL 布局,你需要结合 CSS 和可能的其他库来实现。
9. 常见问题和注意事项
- 键名错误: 如果模板或脚本中的键名与语言文件中的不匹配,会导致翻译失败。仔细检查键名,利用
missing
函数帮助发现错误。 - 异步加载时序: 在异步加载语言文件时,确保在设置新的
locale
之前,对应的语言消息已经加载并设置到 i18n 实例中,否则可能会短暂显示缺失的文本。 - HTML 内容安全: 重申
<i18n-t>
是处理 HTML 内容的首选方式,避免滥用v-html
。 - SEO: 对于需要搜索引擎优化的应用,考虑将语言代码放在 URL 中,并使用
hreflang
标签来告知搜索引擎不同语言版本的页面。 - 首屏加载: 对于首屏内容,确保所需的语言文件是同步加载的,或者在渲染前异步加载并等待完成,避免用户看到未翻译的内容闪烁。
10. 总结
Vue I18n 是一个功能全面且与 Vue 生态系统高度集成的国际化解决方案。通过本文的介绍,你应该已经掌握了:
- 安装和配置 Vue I18n 的基本步骤。
- 如何在模板和脚本中使用
$t
或t
方法进行文本翻译。 - 如何组织语言文件和切换应用语言。
- 如何利用高级功能处理复数、变量插值、HTML 内容、日期/时间/数字本地化。
- 如何在组件级别定义翻译以及异步加载语言文件。
- 处理缺失翻译和组织翻译文件的最佳实践。
实现多语言应用是一个持续的过程,需要开发、设计和翻译团队的紧密协作。借助 Vue I18n 提供的强大工具,你可以更高效地构建面向全球用户的 Vue.js 应用,提升用户体验,拓展应用的市场范围。
现在就开始在你的 Vue 项目中实践国际化吧!