在 Vue 3 + Vite 项目中实现 i18n(实战) 摘要 本文以一个真实的 Vue 3 + Vite + TypeScript 项目为例,逐步展示如何接入 vuei18n@9、实现懒加载语言包、在组件与路由中使用翻译、与 Pinia 同步语言状态,并给出验证与发布注意点。示例代码位于项目文件...
Vue 3 + Vite 项目中实现 i18n
发布时间: 2025-08-18 (a year ago)
Vue

在 Vue 3 + Vite 项目中实现 i18n(实战)

摘要

本文以一个真实的 Vue 3 + Vite + TypeScript 项目为例,逐步展示如何接入 vue-i18n@9、实现懒加载语言包、在组件与路由中使用翻译、与 Pinia 同步语言状态,并给出验证与发布注意点。示例代码位于项目文件:src/i18n.tssrc/components/common/ub_language_switcher.vue 等。

目标读者

前端工程师、Vue3 使用者、希望把国际化从 PoC 推到生产环境的团队。

项目与前提

  • 技术栈:Vue 3 + Vite + TypeScript
  • 状态管理:Pinia(src/stores/index.ts
  • UI:Arco Design(全局注册在 src/main.ts
  • 关键文件(项目内):
    • src/i18n.ts — i18n 初始化与懒加载实现
    • src/locales/zh.ts / src/locales/en.ts — 语言包
    • src/components/common/ub_language_switcher.vue — 语言切换组件示例
    • src/main.ts — 在挂载前加载语言
    • src/stores/index.ts — 可选的语言状态同步点
    • src/router/index.ts — 路由 title 国际化示例

1. 安装

在项目根目录运行:

bash 复制代码
npm install vue-i18n@9

2. 初始化(核心思路)

使用 Composition API 模式实现 loadLocale(lang) 做懒加载语言包。核心要点:

  • 初始 messages 为空
  • 使用 i18n.global.setLocaleMessage 动态注入语言内容
  • 将用户选择的语言保存在 localStorage 中,避免刷新后丢失

关键代码(摘自 src/i18n.ts):

ts 复制代码
import { createI18n } from 'vue-i18n'
const DEFAULT_LANG = localStorage.getItem('lang') || 'zh'
const i18n = createI18n({
  legacy: false,
  globalInjection: true,
  locale: DEFAULT_LANG,
  fallbackLocale: 'en',
  messages: {}
})

export async function loadLocale(lang: string) {
  if (i18n.global.availableLocales.includes(lang)) {
    i18n.global.locale.value = lang
    localStorage.setItem('lang', lang)
    return
  }
  const msgs = await import(/* @vite-ignore */ `./locales/${lang}.ts`)
  i18n.global.setLocaleMessage(lang, msgs.default || msgs)
  i18n.global.locale.value = lang
  localStorage.setItem('lang', lang)
}
export default i18n

3. 在入口注册并预加载语言

src/main.ts 中注册 i18n,并在挂载前加载默认语言,避免首屏闪烁:

ts 复制代码
import i18n, { loadLocale } from './i18n'
app.use(i18n)
const lang = localStorage.getItem('lang') || 'zh'
loadLocale(lang).finally(() => app.mount('#app'))

4. 组件层切换(UI 示例)

项目中示例组件:src/components/common/ub_language_switcher.vue,使用 Arco 图标(或按钮)点击切换语言,调用 loadLocale 并写入 localStorage。示例要点:

  • 切换时调用 loadLocale(lang)
  • 可选同步 Pinia:useStore().setLanguage(lang)(如果实现)
  • 可使用 useI18n() 监听 locale

示例片段(摘录):

vue 复制代码
<template>
  <div class="ub-language-switcher" @click="toggleLanguage">
    <IconChineseFill v-if="current === 'zh'"/>
    <IconEnglishFill v-else />
  </div>
</template>

<script setup lang="ts">
import { useI18n } from 'vue-i18n'
const { locale } = useI18n()
// 切换时调用 loadLocale(...)
</script>

5. 懒加载与构建影响

  • 懒加载语言包会把每个语言打成独立 chunk(Vite 的动态 import 行为)。
  • 好处:首包体积小;坏处:构建输出语言包数量增多。
  • 验证:在浏览器 DevTools 的 Network 面板切换语言能看到 language chunk 请求。

6. Pinia 同步(可选)

src/stores/index.ts 中添加 language 状态与 setLanguage action,使语言成为全局状态:

ts 复制代码
// state:
language: localStorage.getItem('lang') || 'zh',

// action:
async setLanguage(lang: string) {
  this.language = lang
  localStorage.setItem('lang', lang)
  await import('@/i18n').then(m => m.loadLocale(lang))
}

7. 路由 title 国际化

把路由 meta.title 改为 meta.titleKey,并在切换后设置 document.title

ts 复制代码
import { tGlobal } from '@/i18n'
router.afterEach((to) => {
  const key = (to.meta as any).titleKey
  if (key) document.title = tGlobal(key as string) as string
})

8. 日期与数字本地化

  • 推荐使用 dayjs 或 Intl API。
  • 若使用 dayjs,可在 loadLocale 成功后动态加载 dayjs locale 并调用 dayjs.locale(lang)

9. 验证步骤

bash 复制代码
npm install
npm run dev

验证点:

  • localStorage 中 lang 正确(zhen
  • 切换语言后页面文本变化
  • Network 面板出现懒加载的 language chunk
  • 如启用,浏览器标签页 title 随路由 key 变化

10. 常见问题与调试

  • 未生效:确认 i18n 已在 main.ts 注册并在挂载前加载语言
  • 懒加载失败:检查语言包路径 src/locales/{lang}.ts
  • 非同步请求需要 locale header:在请求时读取 i18n.global.locale.value

11. SEO、可访问性与发布建议

  • 使用 meta.titleKey 和对应翻译填充 title/description(如需 SSR,需在服务端处理)
  • 为切换控件添加 aria-labela-tooltip 提升可访问性
  • 发布时附带 2~3 张截图:语言切换、Network 懒加载、路由 title 变化

12. 结论与扩展

通过懒加载语言包和将语言状态与 Pinia 同步,可以把国际化做到既轻量又可维护。下一步可以考虑:机器翻译初稿 + 人工校验,或接入翻译管理平台(如 Crowdin、POEditor)。