Skip to content

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@9
ts
// 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/i18nvue-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 / 自建
校验前端 + QAeslint-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: truelocale 是 stringlegacy: false 或用 i18n.global.locale = ...
复数不工作没写 `` 分隔的多个选项
日期格式跟系统不一致d() 没传 format 字符串d(date, 'short', 'zh-CN')
SSR 闪烁服务端没加载对应语言包在 setup 顶部 await loadLocale()

实战 demo 锚点

路径演示
/learning-path/ecosystem/#04.vue-i18nComposition API 风格、复数、占位符
/learning-path/ecosystem/#05.unhead-usehead配合 useSeoMeta 切换 <html lang>

延伸阅读

资源内容
vue-i18n 官方API 文档
Composition 模式legacy: false
i18n-allyVS Code 插件
Vue 官方 i18n 指南框架级推荐