前端国际化方案实战:vue-i18n多语言架构与动态切换设计

前端国际化(i18n)是出海项目和跨语言产品的基本工程需求。Vue3生态中,vue-i18n@9作为官方推荐的国际化插件,提供了基于Composition API的响应式翻译、延迟加载、复数处理和日期数字格式化能力。国际化方案的设计质量直接影响用户体验和后期维护成本,本文从架构设计到落地实现,覆盖vue-i18n在大型项目中的完整实践路径。

vue-i18n安装与基础配置

npm install vue-i18n@9

创建i18n实例并注册全局:

// src/i18n/index.ts
import { createI18n } from 'vue-i18n'
import type { I18n } from 'vue-i18n'

// 类型定义,提供TypeScript智能提示
export type MessageSchema = typeof import('./locales/zh-CN.json')
export type LocaleType = 'zh-CN' | 'en-US' | 'ja-JP'

const i18n = createI18n<MessageSchema, LocaleType>([{
    legacy: false,           // 使用Composition API模式
    locale: 'zh-CN',         // 默认语言
    fallbackLocale: 'en-US', // 回退语言
    messages: {
        'zh-CN': await import('./locales/zh-CN.json'),
        'en-US': await import('./locales/en-US.json')
    }
}])

export default i18n

在main.ts中注册:

// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import i18n from './i18n'

const app = createApp(App)
app.use(i18n)
app.mount('#app')

翻译文件组织与命名规范

大型项目中翻译文件需要按模块拆分,避免单个JSON膨胀到难以维护。推荐按功能域嵌套:

// src/i18n/locales/zh-CN.json
{
  "common": {
    "confirm": "确认",
    "cancel": "取消",
    "save": "保存",
    "delete": "删除",
    "search": "搜索"
  },
  "user": {
    "login": {
      "title": "用户登录",
      "username": "用户名",
      "password": "密码",
      "forgotPassword": "忘记密码?",
      "submit": "登录"
    },
    "profile": {
      "editProfile": "编辑资料",
      "avatar": "头像",
      "nickname": "昵称"
    }
  },
  "order": {
    "status": {
      "pending": "待付款",
      "paid": "已付款",
      "shipped": "已发货",
      "completed": "已完成",
      "cancelled": "已取消"
    }
  }
}

对应英文翻译文件:

// src/i18n/locales/en-US.json
{
  "common": {
    "confirm": "Confirm",
    "cancel": "Cancel",
    "save": "Save",
    "delete": "Delete",
    "search": "Search"
  },
  "user": {
    "login": {
      "title": "User Login",
      "username": "Username",
      "password": "Password",
      "forgotPassword": "Forgot Password?",
      "submit": "Login"
    },
    "profile": {
      "editProfile": "Edit Profile",
      "avatar": "Avatar",
      "nickname": "Nickname"
    }
  },
  "order": {
    "status": {
      "pending": "Pending Payment",
      "paid": "Paid",
      "shipped": "Shipped",
      "completed": "Completed",
      "cancelled": "Cancelled"
    }
  }
}

组件内翻译与插值使用

在Composition API模式下使用useI18n:

<template>
  <div class="login-page">
    <h1>{{ t('user.login.title') }}</h1>
    <form @submit.prevent="handleLogin">
      <input 
        :placeholder="t('user.login.username')" 
        v-model="form.username" 
      />
      <input 
        :placeholder="t('user.login.password')" 
        v-model="form.password" 
        type="password" 
      />
      <button type="submit">{{ t('user.login.submit') }}</button>
    </form>
  </div>
</template>

<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { reactive } from 'vue'

const { t } = useI18n()

const form = reactive({
  username: '',
  password: ''
})

const handleLogin = () => {
  // 登录逻辑
}
</script>

带参数的插值

// 翻译文件
{
  "user": {
    "welcome": "欢迎,{name}!您有{count}条未读消息"
  }
}

// 模板中使用
<p>{{ t('user.welcome', { name: userInfo.name, count: unreadCount }) }}</p>

// 输出:欢迎,张三!您有5条未读消息

复数处理与动态语言切换

不同语言对复数的处理规则差异很大。vue-i18n内置了复数规则引擎,支持CLDR标准:

// 翻译文件配置复数(中文不需要区分单复数,但英文需要)
{
  "order": {
    "items": "没有订单 | {count}条订单 | {count}条订单"
  }
}

// 英文翻译
{
  "order": {
    "items": "no items | {count} item | {count} items"
  }
}

// 使用tc(translation choice)方法
<p>{{ t('order.items', unreadCount) }}</p>
// count=0: "没有订单" / "no items"
// count=1: "1条订单" / "1 item"  
// count=5: "5条订单" / "5 items"

动态切换语言不需要刷新页面:

// composables/useLocale.ts
import { useI18n } from 'vue-i18n'
import { setI18nLanguage, loadLocaleMessages } from '@/i18n'
import { ref } from 'vue'
import type { LocaleType } from '@/i18n'

const supportedLocales: LocaleType[] = ['zh-CN', 'en-US', 'ja-JP']
const currentLocale = ref<LocaleType>('zh-CN')

export function useLocale() {
  const { locale, t } = useI18n()

  const changeLocale = async (target: LocaleType) => {
    if (!supportedLocales.includes(target)) return
    
    // 延迟加载语言包
    await loadLocaleMessages(target)
    
    // 切换语言
    locale.value = target
    currentLocale.value = target
    
    // 更新HTML lang属性
    document.querySelector('html')?.setAttribute('lang', target)
    
    // 持久化用户选择
    localStorage.setItem('locale', target)
  }

  return {
    currentLocale,
    supportedLocales,
    changeLocale,
    t
  }
}

语言切换组件:

<template>
  <select v-model="selectedLocale" @change="handleChange">
    <option value="zh-CN">简体中文</option>
    <option value="en-US">English</option>
    <option value="ja-JP">日本語</option>
  </select>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useLocale } from '@/composables/useLocale'

const { currentLocale, changeLocale } = useLocale()
const selectedLocale = ref(currentLocale.value)

const handleChange = () => {
  changeLocale(selectedLocale.value)
}

onMounted(() => {
  // 读取本地存储或浏览器语言偏好
  const saved = localStorage.getItem('locale') as LocaleType | null
  if (saved) {
    selectedLocale.value = saved
    changeLocale(saved)
  } else {
    const browserLang = navigator.language
    const matched = browserLang.startsWith('zh') ? 'zh-CN' :
                    browserLang.startsWith('ja') ? 'ja-JP' : 'en-US'
    selectedLocale.value = matched
    changeLocale(matched)
  }
})
</script>

按需加载与路由级语言包

将所有语言包打包进初始Bundle会显著增加首屏加载时间。按路由模块延迟加载是更合理的方案:

// src/i18n/index.ts
import { createI18n } from 'vue-i18n'
import type { I18n } from 'vue-i18n'

// 只加载通用翻译
const commonMessages = {
  'zh-CN': import('./modules/common/zh-CN.json'),
  'en-US': import('./modules/common/en-US.json')
}

const i18n = createI18n({
  legacy: false,
  locale: 'zh-CN',
  fallbackLocale: 'en-US',
  messages: {
    'zh-CN': (await commonMessages['zh-CN']).default,
    'en-US': (await commonMessages['en-US']).default
  }
})

// 按需加载模块翻译
export async function loadModuleMessages(module: string, locale: LocaleType) {
  const messages = await import(`./modules/${module}/${locale}.json`)
  i18n.global.mergeLocaleMessage(locale, messages.default)
}

// 路由守卫中加载对应模块
// router.beforeEach(async (to) => {
//   if (to.meta.i18nModule) {
//     await loadModuleMessages(to.meta.i18nModule, i18n.locale.value)
//   }
// })

export default i18n

这种方案下,用户路由到订单页面时才加载order模块的语言包,首页只需加载common部分。配合Vite的自动分包,语言包作为独立chunk按需请求,首屏体积可减少40%以上。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/qian-duan-guo-ji-hua-fang-an-shi-zhan-vuei18n-duo-yu-yan/

(0)
小编小编
上一篇 4小时前
下一篇 4小时前

相关推荐