Vue I18n 实战
本节不重复 vue-i18n 文档的 API 索引,而是实战视角:站在「项目里怎么落地多语言」的入口,串起 Composition API 风格、懒加载、SSR 同步三个核心问题。配套的可交互 demo 见
/learning-path/ecosystem/#04.vue-i18n。
选型边界
| 场景 | 选 vue-i18n? |
|---|---|
| 单语言项目 | 不用 |
| 2-5 种语言 + 静态文案 | 强烈推荐 |
| 10+ 种语言 + 实时切换 | 推荐,懒加载语言包 |
| 业务文案由后端下发(CMS) | 不用 vue-i18n 静态资源;用 fetch + 自建 i18n 状态 |
起步
安装与实例
bash
pnpm add vue-i18n@9ts
// src/i18n.ts
import { createI18n } from 'vue-i18n'
export const i18n = createI18n({
legacy: false, // Composition API 风格
locale: navigator.language, // 初始按浏览器
fallbackLocale: 'en',
messages: {
en: { hello: 'Hello, {name}' },
'zh-CN': { hello: '你好,{name}' }
}
})接入应用
ts
import { createApp } from 'vue'
import { i18n } from './i18n'
import App from './App.vue'
createApp(App).use(i18n).mount('#app')在组件中使用
Composition API 风格
vue
<script setup>
import { useI18n } from 'vue-i18n'
const { t, locale, n, d } = useI18n()
function switchLocale(loc: string) {
locale.value = loc
}
</script>
<template>
<p>{{ t('hello', { name: 'Vue' }) }}</p>
<p>{{ n(1234.5, 'currency', { currency: 'USD' }) }}</p>
<p>{{ d(new Date(), 'short') }}</p>
</template>组件式翻译(带富文本)
vue
<template>
<i18n-t keypath="tos">
<template #link>
<a href="/terms">{{ t('tos.link') }}</a>
</template>
</i18n-t>
</template>
<!-- messages: { en: { tos: 'Please accept our {link}' } } -->复数
json
{
"car": "no cars | 1 car | {count} cars"
}ts
t('car', 0) // "no cars"
t('car', 1) // "1 car"
t('car', 5, { named: { count: 5 } }) // "5 cars"懒加载语言包
把语言包拆到独立 JSON,按需 fetch,首屏只加载默认语言。
ts
// src/i18n.ts
import { createI18n } from 'vue-i18n'
export const i18n = createI18n({
legacy: false,
locale: 'zh-CN',
fallbackLocale: 'en',
})
export async function loadLocale(locale: string) {
// 动态 import 触发代码分割
const messages = await import(`./locales/${locale}.json`)
i18n.global.setLocaleMessage(locale, messages.default)
i18n.global.locale.value = locale
document.documentElement.setAttribute('lang', locale)
}ts
// 在路由切换前预加载
router.beforeEach(async (to) => {
const locale = to.meta.locale || 'zh-CN'
if (!i18n.global.availableLocales.includes(locale)) {
await loadLocale(locale)
}
})构建产物体积影响:zh-CN.json 30KB、en.json 40KB,首屏按需加载 < 100ms 完成。
SSR 与 VitePress 集成
VitePress 多语言站点的关键是服务端就能拿到语言包,否则首屏会出现"先 en 再 zh"的闪烁:
ts
// .vitepress/config.ts
locales: {
'zh-CN': {
label: '简体中文',
lang: 'zh-CN',
themeConfig: { /* ... */ }
},
en: { label: 'English', lang: 'en' }
}VitePress 自带简单 i18n;如果你要更复杂的(如语言包懒加载、自定义 fallback),装 @vuepress/i18n 或 vue-i18n@9 自己集成。
类型安全
把 messages 用 TS 类型描述,IDE 能补全 key:
ts
const messages = {
en: {
hello: 'Hello, {name}',
cart: '{count} items'
}
} as const
type MessageSchema = typeof messages.en
declare module 'vue-i18n' {
export interface DefineLocaleMessage extends MessageSchema {}
}之后 t('helo')(拼错)会编译期报错。
团队工作流
| 步骤 | 谁来做 | 工具 |
|---|---|---|
| 提取文案 | 前端 | i18n-ally(VS Code 插件)高亮未翻译 key |
| 翻译 | 翻译者 / 后端翻译平台 | Crowdin / Lokalise / 自建 |
| 校验 | 前端 + QA | eslint-plugin-vue-i18n 检查硬编码中文 |
| 灰度 | 前端 + 运营 | 后端开关决定 locale 初始值 |
防止硬编码
js
// eslint-plugin-vue-i18n
rules: {
'@intlify/vue-i18n/no-raw-text': ['error', {
attributes: { '/.+/': ['placeholder', 'title'] },
vueComponents: ['MyButton']
}]
}常见坑
| 现象 | 原因 | 修复 |
|---|---|---|
t('xxx') 返回 'xxx' 而非翻译 | 没在 messages 注册 | 用 fallbackWarn: true 提醒 |
| 切换语言没反应 | 用了 legacy: true 但 locale 是 string | 改 legacy: false 或用 i18n.global.locale = ... |
| 复数不工作 | 没写 ` | ` 分隔的多个选项 |
| 日期格式跟系统不一致 | d() 没传 format 字符串 | d(date, 'short', 'zh-CN') |
| SSR 闪烁 | 服务端没加载对应语言包 | 在 setup 顶部 await loadLocale() |
实战 demo 锚点
| 路径 | 演示 |
|---|---|
/learning-path/ecosystem/#04.vue-i18n | Composition API 风格、复数、占位符 |
/learning-path/ecosystem/#05.unhead-usehead | 配合 useSeoMeta 切换 <html lang> |
延伸阅读
| 资源 | 内容 |
|---|---|
| vue-i18n 官方 | API 文档 |
| Composition 模式 | legacy: false |
| i18n-ally | VS Code 插件 |
| Vue 官方 i18n 指南 | 框架级推荐 |