Vue 3 与 i18n 深度集成

概述

Vue 3 与 vue-i18n@9.x 的深度集成提供了强大的国际化能力,支持复杂应用场景下的多语言需求。本教程将深入探讨 vue-i18n 的高级功能,包括动态翻译加载、自定义格式化器、插件扩展、TypeScript 支持等,帮助你构建更加灵活和可维护的国际化应用。

核心知识

1. 动态翻译加载

在大型应用中,我们可能需要根据用户的操作或权限动态加载翻译内容。vue-i18n 支持动态添加和更新翻译消息:

// src/i18n/index.js
import { createI18n } from 'vue-i18n'

const i18n = createI18n({
  legacy: false,
  locale: 'zh',
  fallbackLocale: 'en',
  messages: {
    zh: {
      welcome: '欢迎使用 Vue 3 i18n'
    },
    en: {
      welcome: 'Welcome to Vue 3 i18n'
    }
  }
})

export default i18n
<script setup>
import { useI18n } from 'vue-i18n'
import i18n from '../i18n'

const { t, locale } = useI18n()

// 动态加载翻译
const loadDynamicTranslations = async () => {
  try {
    // 从 API 或其他来源加载翻译
    const dynamicMessages = await fetch('/api/translations', {
      params: {
        lang: locale.value
      }
    }).then(res => res.json())
    
    // 更新翻译消息
    i18n.global.mergeLocaleMessage(locale.value, dynamicMessages)
  } catch (error) {
    console.error('Failed to load dynamic translations:', error)
  }
}
</script>

<template>
  <div>
    <h1>{{ t('welcome') }}</h1>
    <h2>{{ t('dynamic.content') }}</h2> <!-- 动态加载的翻译 -->
    <button @click="loadDynamicTranslations">加载动态翻译</button>
  </div>
</template>

2. 自定义格式化器

vue-i18n 允许你创建自定义格式化器,用于处理特定类型的翻译内容:

// src/i18n/formatters.js
export const customFormatter = {
  // 自定义日期格式化
  formatDate(value, format, lng) {
    // 自定义日期格式化逻辑
    const date = new Date(value)
    return date.toLocaleDateString(lng, {
      year: 'numeric',
      month: 'long',
      day: 'numeric'
    })
  },
  
  // 自定义数字格式化
  formatNumber(value, format, lng) {
    // 自定义数字格式化逻辑
    return new Intl.NumberFormat(lng, {
      style: 'decimal',
      minimumFractionDigits: 2
    }).format(value)
  }
}
// src/i18n/index.js
import { createI18n } from 'vue-i18n'
import { customFormatter } from './formatters'

const i18n = createI18n({
  legacy: false,
  locale: 'zh',
  fallbackLocale: 'en',
  formatters: {
    custom: customFormatter
  },
  messages: {
    // ...
  }
})
<script setup>
import { useI18n } from 'vue-i18n'

const { f } = useI18n()
const today = new Date()
</script>

<template>
  <p>{{ f(today, 'custom', 'formatDate') }}</p>
  <p>{{ f(1234.56, 'custom', 'formatNumber') }}</p>
</template>

3. 插件扩展

vue-i18n 支持通过插件扩展功能,允许你添加自定义的翻译逻辑:

// src/i18n/plugins/uppercase.js
export const uppercasePlugin = {
  install(i18n) {
    i18n.global.t = new Proxy(i18n.global.t, {
      apply(target, thisArg, args) {
        const result = Reflect.apply(target, thisArg, args)
        // 添加自定义逻辑:如果翻译键包含 "_uppercase",则返回大写
        if (args[0].includes('_uppercase')) {
          return result.toUpperCase()
        }
        return result
      }
    })
  }
}
// src/i18n/index.js
import { createI18n } from 'vue-i18n'
import { uppercasePlugin } from './plugins/uppercase'

const i18n = createI18n({
  legacy: false,
  locale: 'zh',
  fallbackLocale: 'en',
  messages: {
    // ...
  }
})

// 使用插件
i18n.use(uppercasePlugin)

4. TypeScript 支持

vue-i18n 提供了完整的 TypeScript 支持,包括类型定义和类型推断:

// src/i18n/index.ts
import { createI18n, DefineLocaleMessage, DefineDateTimeFormat, DefineNumberFormat } from 'vue-i18n'

// 定义翻译消息类型
type MessageSchema = {
  welcome: string
  greeting: (name: string) => string
  menu: {
    home: string
    about: string
  }
}

// 定义日期时间格式类型
type DateTimeSchema = {
  short: DefineDateTimeFormat
  long: DefineDateTimeFormat
}

// 定义数字格式类型
type NumberSchema = {
  currency: DefineNumberFormat
  decimal: DefineNumberFormat
}

// 创建 i18n 实例
const i18n = createI18n<
  MessageSchema,
  DateTimeSchema,
  NumberSchema
>({
  legacy: false,
  locale: 'zh',
  fallbackLocale: 'en',
  messages: {
    // ...
  },
  datetimeFormats: {
    // ...
  },
  numberFormats: {
    // ...
  }
})

export default i18n

5. 组件范围的 i18n

vue-i18n 支持组件范围的 i18n 配置,允许你为特定组件定义翻译:

<!-- src/components/HelloWorld.vue -->
<script setup>
import { useI18n } from 'vue-i18n'

const { t } = useI18n({
  inheritLocale: true,
  messages: {
    zh: {
      component: '组件范围的翻译'
    },
    en: {
      component: 'Component-scoped translation'
    }
  }
})
</script>

<template>
  <p>{{ t('component') }}</p>
</template>

6. 翻译缓存

为了提高性能,vue-i18n 支持翻译结果缓存。你可以配置缓存策略:

// src/i18n/index.js
import { createI18n } from 'vue-i18n'

const i18n = createI18n({
  legacy: false,
  locale: 'zh',
  fallbackLocale: 'en',
  // 启用缓存
  enableCache: true,
  // 自定义缓存键生成器
  getCacheKey: (key, locale, options) => {
    return `${key}-${locale}-${JSON.stringify(options)}`
  },
  messages: {
    // ...
  }
})

7. 错误处理

vue-i18n 提供了灵活的错误处理机制,允许你处理缺失的翻译键:

// src/i18n/index.js
import { createI18n } from 'vue-i18n'

const i18n = createI18n({
  legacy: false,
  locale: 'zh',
  fallbackLocale: 'en',
  // 处理缺失的翻译键
  missing: (locale, key) => {
    console.warn(`Missing translation for key "${key}" in locale "${locale}"`)
    return `[${key}]`
  },
  // 处理回退
  fallbackRoot: true,
  messages: {
    // ...
  }
})

最佳实践

1. 模块化翻译管理

将翻译按功能模块组织,便于维护和扩展:

src/
├── i18n/
│   ├── index.js          # i18n 配置
│   ├── locales/          # 语言包目录
│   │   ├── en/           # 英文语言包
│   │   │   ├── common.json
│   │   │   ├── home.json
│   │   │   └── about.json
│   │   └── zh/           # 中文语言包
│   │       ├── common.json
│   │       ├── home.json
│   │       └── about.json
│   └── plugins/          # 插件目录
// src/i18n/index.js
import { createI18n } from 'vue-i18n'

// 动态导入所有语言包
const loadLocaleMessages = () => {
  const locales = require.context('./locales', true, /[A-Za-z0-9-_,\s]+\.json$/i)
  const messages = {}
  
  locales.keys().forEach(key => {
    const matched = key.match(/([A-Za-z0-9-_]+)\/([A-Za-z0-9-_]+)\.json$/)
    if (matched && matched.length > 2) {
      const locale = matched[1]
      const module = matched[2]
      
      if (!messages[locale]) {
        messages[locale] = {}
      }
      
      messages[locale][module] = locales(key)
    }
  })
  
  return messages
}

const i18n = createI18n({
  legacy: false,
  locale: 'zh',
  fallbackLocale: 'en',
  messages: loadLocaleMessages()
})

export default i18n

2. 使用 i18n 组件

vue-i18n 提供了 &lt;i18n&gt; 组件,用于处理复杂的翻译场景:

<template>
  <div>
    <i18n path="greeting" tag="h1">
      <template #name>
        <span class="highlight">{{ userName }}</span>
      </template>
    </i18n>
    
    <i18n path="message.count" tag="p">
      <template #count>
        <strong>{{ messageCount }}</strong>
      </template>
    </i18n>
  </div>
</template>

3. 与状态管理结合

将 i18n 状态与 Pinia 或 Vuex 结合,实现更复杂的国际化逻辑:

// src/stores/i18n.js
import { defineStore } from 'pinia'
import i18n from '../i18n'

export const useI18nStore = defineStore('i18n', {
  state: () => ({
    availableLocales: [
      { code: 'en', name: 'English' },
      { code: 'zh', name: '中文' }
    ],
    currentLocale: i18n.global.locale.value
  }),
  
  actions: {
    changeLocale(locale) {
      this.currentLocale = locale
      i18n.global.locale.value = locale
      localStorage.setItem('locale', locale)
    },
    
    async loadModuleTranslations(moduleName) {
      try {
        const translations = await import(`../i18n/locales/${this.currentLocale}/${moduleName}.json`)
        i18n.global.mergeLocaleMessage(this.currentLocale, {
          [moduleName]: translations.default
        })
      } catch (error) {
        console.error(`Failed to load ${moduleName} translations:`, error)
      }
    }
  }
})

4. 性能优化

  • 懒加载语言包:只加载当前语言的翻译内容
  • 按需加载模块:根据路由或组件按需加载翻译模块
  • 使用缓存:启用翻译结果缓存,减少重复计算
  • 避免在模板中使用复杂逻辑:将复杂的翻译逻辑移到 JavaScript 中

5. 测试策略

为国际化应用编写测试,确保翻译功能正常工作:

// src/tests/i18n.spec.js
import { mount } from '@vue/test-utils'
import { createI18n } from 'vue-i18n'
import HelloWorld from '../components/HelloWorld.vue'

const i18n = createI18n({
  legacy: false,
  locale: 'en',
  messages: {
    en: {
      welcome: 'Welcome'
    },
    zh: {
      welcome: '欢迎'
    }
  }
})

describe('i18n tests', () => {
  test('renders correct English translation', () => {
    const wrapper = mount(HelloWorld, {
      global: {
        plugins: [i18n]
      }
    })
    expect(wrapper.text()).toContain('Welcome')
  })
  
  test('renders correct Chinese translation', () => {
    i18n.global.locale.value = 'zh'
    const wrapper = mount(HelloWorld, {
      global: {
        plugins: [i18n]
      }
    })
    expect(wrapper.text()).toContain('欢迎')
  })
})

常见问题与解决方案

1. 问题:TypeScript 类型推断不准确

解决方案

  • 显式定义翻译消息类型
  • 使用 createI18n 的泛型参数
  • 为组件范围的 i18n 提供类型

2. 问题:动态加载翻译不生效

解决方案

  • 确保使用 mergeLocaleMessage 方法更新翻译
  • 检查语言包格式是否正确
  • 验证当前语言是否与加载的翻译匹配

3. 问题:性能问题(大型语言包)

解决方案

  • 实现懒加载机制
  • 按模块拆分语言包
  • 启用翻译缓存
  • 使用动态导入

4. 问题:翻译键冲突

解决方案

  • 使用模块化命名空间
  • 为相同键添加不同上下文
  • 实现自定义的键解析逻辑

5. 问题:SSR 环境下的 i18n 配置

解决方案

  • 使用 createSSRApp 替代 createApp
  • 确保 i18n 配置在服务器和客户端保持一致
  • 使用 useI18nuseScope 选项
// src/i18n/index.js
import { createI18n } from 'vue-i18n'

export function createI18nInstance() {
  return createI18n({
    legacy: false,
    locale: 'zh',
    fallbackLocale: 'en',
    messages: {
      // ...
    }
  })
}

高级学习资源

1. 官方文档

2. 工具和库

3. 最佳实践指南

实践练习

练习 1:动态翻译加载

  1. 创建一个 Vue 3 项目并配置 vue-i18n
  2. 实现动态加载翻译的功能
  3. 从模拟 API 加载翻译内容
  4. 测试动态翻译的效果

练习 2:自定义格式化器

  1. 创建自定义日期和时间格式化器
  2. 实现自定义数字和货币格式化器
  3. 在组件中使用自定义格式化器

练习 3:TypeScript 支持

  1. 为翻译消息添加 TypeScript 类型定义
  2. 配置 i18n 实例的泛型参数
  3. 编写类型安全的翻译代码
  4. 运行 TypeScript 检查确保类型正确

练习 4:模块化翻译管理

  1. 按功能模块组织翻译文件
  2. 实现动态导入所有语言包的功能
  3. 测试不同模块的翻译加载
  4. 优化语言包的加载性能

练习 5:插件扩展

  1. 创建一个自定义 i18n 插件
  2. 实现插件的 install 方法
  3. 在插件中扩展翻译功能
  4. 测试插件的效果

总结

Vue 3 与 &#x76;&#x75;&#x65;&#45;&#x69;&#49;&#x38;&#x6e;&#x40;&#57;&#46;&#120; 的深度集成提供了强大的国际化能力,支持动态翻译加载、自定义格式化、插件扩展等高级功能。通过合理的架构设计和最佳实践,你可以构建出灵活、可维护的国际化应用。

本教程介绍了 vue-i18n 的高级特性和最佳实践,包括动态翻译加载、自定义格式化器、TypeScript 支持、组件范围的 i18n 等。通过学习这些内容,你可以更好地应对复杂应用的国际化需求,为全球用户提供更好的体验。

在实际开发中,你需要根据应用的规模和需求选择合适的国际化策略,结合性能优化和测试,确保国际化功能的正常运行和良好性能。

« 上一篇 Vue 3 国际化和本地化进阶 下一篇 » Vue 3 与 RTL 支持:从右到左布局的完整实现指南