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

Implementing i18n in Vue 3 + Vite Project

Summary

This article uses a real Vue 3 + Vite + TypeScript project as an example to show step-by-step how to access 'vue-i18n@9', implement lazy loading of language packs, use translation in components and routes, synchronize language state with Pinia, and give verification and release precautions. The sample code is located in the project files: 'src/i18n.ts', 'src/components/common/ub_language_switcher.vue', etc.

Target Audience

Front-end engineers, Vue3 users, teams looking to take internationalization from PoC to production.

Project and premise

  • Tech stack: Vue 3 + Vite + TypeScript
  • Status Management: Pinia ('src/stores/index.ts')
  • UI: Arco Design (globally registered at 'src/main.ts')
  • Key Documents (In-Project):
    • 'src/i18n.ts' — i18n initialization and lazy loading implementation
    • 'src/locales/zh.ts' / 'src/locales/en.ts' — language pack
    • 'src/components/common/ub_language_switcher.vue' — Language switching component example
    • 'src/main.ts' — Loads the language before mounting
    • 'src/stores/index.ts' — Optional language state sync point
    • 'src/router/index.ts' — example of internationalization of route titles

1. Installation

Run at the project root:

bash Copy
npm install vue-i18n@9

2. Initialization (Core Idea)

Use the Composition API pattern to implement 'loadLocale(lang)' to lazily load language packages. Key takeaways:

  • The initial message is empty
  • Dynamically inject language content using 'i18n.global.setLocaleMessage'
  • Save the user's selected language in 'localStorage' to avoid losing it after refreshing

Key code (from 'src/i18n.ts'):

ts Copy
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. Register at the entrance and preload the language

Register the i18n in 'src/main.ts' and load the default language before mounting to avoid flickering above the fold:

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

4. Component layer switching (UI example)

Example component in the project: 'src/components/common/ub_language_switcher.vue', use the Arco icon (or button) to click to switch language, call 'loadLocale' and write 'localStorage'. Example highlights:

  • Call 'loadLocale(lang)' on switchover
  • Optional synchronization Pinia: 'useStore().setLanguage(lang)' (if implemented)
  • 'useI18n()' can be used to listen for 'locale'

Example snippet (excerpt):

vue Copy
<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()
Call loadLocale(...) on switchover
</script>

5. Lazy loading vs. build impact

  • Lazy loading language packs will chunk each language as a separate chunk (Vite's dynamic import behavior).
  • Advantages: small size of the first pack; Disadvantage: Build more output language packages.
  • Verification: You can see the language chunk request by switching the language in the Network panel of the browser's DevTools.

6. Pinia Sync (optional)

Add the 'language' state and the 'setLanguage' action to 'src/stores/index.ts' to make the language a global state:

ts Copy
// 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. Route title internationalization

Change the route 'meta.title' to 'meta.titleKey' and set 'document.title' after the switch:

ts Copy
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. Date and number localization

  • 'dayjs' or Intl API is recommended.
  • If you are using dayjs, you can dynamically load dayjs locale and call dayjs.locale(lang)' after 'loadLocale' is successful.

9. Verification steps

bash Copy
npm install
npm run dev

Verification Points:

  • 'lang' in localStorage is correct ('zh' or 'en')
  • Page text changes after switching languages
  • Lazy loading language chunks appear in the Network panel
  • If enabled, the browser tab title changes with the route key

10. FAQs and debugging

  • Not Effective: Confirm that 'i18n' is registered with 'main.ts' and load the language before mounting
  • Lazy loading failed: Check the language package path 'src/locales/{lang}.ts'
  • Asynchronous request requires locale header: read 'i18n.global.locale.value' on request

11. SEO, accessibility, and publishing recommendations

  • Populate the title/description with 'meta.titleKey' and the corresponding translation (if you need SSR, you need to handle it on the server side)
  • Add 'aria-label' or 'a-tooltip' to toggle controls to improve accessibility
  • Published with 2~3 screenshots: language switching, network lazy loading, route title change

12. Conclusion and expansion

By lazily loading language packs and synchronizing language states with Pinia, internationalization can be both lightweight and maintainable. The next step is to consider machine translation first draft + manual verification, or connect to a translation management platform (such as Crowdin, POEditor).