Skip to content

國際化 (i18n)

本文件詳細介紹如何在項目中使用和配置國際化功能,包括語言包管理、在 Vue 和 TypeScript 文件中的使用方法、語言切換、以及如何添加新語言等。

配置說明

1. 環境變數配置

.env 文件中設置預設語言:

bash
# .env.dev / .env.prod / .env.test
VITE_DEFAULT_LANG=zhTW

目前支持的語言代碼:

  • zhTW: 正體中文
  • zhCN: 簡體中文
  • enUS: 美式英文

2. 語言包檔案位置

語言包檔案位於 locales/ 目錄下:

locales/
├── zhTW.json    # 正體中文
├── zhCN.json    # 簡體中文
└── enUS.json    # 美式英文

3. 核心設定檔

  • src/modules/i18n.ts: i18n 實例配置
  • src/utils/i18n.ts: 工具函數和 Naive UI 語言配置
  • src/store/model/language/index.ts: 語言狀態管理
  • src/utils/tools/defaultLang.ts: 預設語言獲取邏輯

語言包結構

語言包採用嵌套的 JSON 結構,按功能模組組織:

json
{
  "common": {
    "confirm": "確認",
    "cancel": "取消",
    "save": "保存"
  },
  "userCenter": {
    "updateEmail": "更新信箱",
    "updateMobile": "更新手機號碼"
  },
  "language": {
    "zhCN": "簡體中文",
    "zhTW": "正體中文",
    "enUS": "美式英文",
    "confirmToSwitchLanguage": "確定要切換語言嗎?"
  }
}

在 Vue 文件中使用

1. 在template中使用

vue
<script setup lang="ts">
// 無需額外導入,$t 已全局註冊
</script>

<template>
  <div>
    <!-- 直接使用 $t 函數 -->
    <h1>{{ $t('common.title') }}</h1>

    <!-- 在屬性中使用 -->
    <n-button :placeholder="$t('common.inputPlaceholder')">
      {{ $t('common.submit') }}
    </n-button>
  </div>
</template>

2. 在script中中使用

vue
<script setup lang="ts">
function handleSubmit() {
  // 顯示成功消息
  window.$message.success($t('common.saveSuccess'))

  // 顯示確認對話框
  window.$dialog.warning({
    title: $t('common.warn'),
    content: $t('common.deleteConfirm')
  })
}
</script>

3. 動態語言切換範例

vue
<script setup lang="ts">
import { useLanguageStore } from '@/store/model/language'

const languageStore = useLanguageStore()
</script>

<template>
  <n-popselect
    :value="languageStore.current"
    :options="languageStore.list"
    @update:value="languageStore.setAppLang"
  >
    <CommonWrapper>
      <icon-park-outline-translate />
    </CommonWrapper>
  </n-popselect>
</template>

在 TypeScript 文件中使用

typescript
import { $t } from '@/utils'

// 在函數中使用
export function validateEmail(email: string) {
  if (!email) {
    return $t('userCenter.inputEmail')
  }
  return true
}

語言切換

1. 使用語言切換組件

項目提供了 LangsSwitch 組件,可以直接使用:

vue
<template>
  <LangsSwitch />
</template>

2. 編程式切換

typescript
import { useLanguageStore } from '@/store/model/language'
// 直接設置語言(不顯示確認對話框)
import { setLocale } from '@/utils'

const languageStore = useLanguageStore()

// 切換語言(會顯示確認對話框)
languageStore.setAppLang('enUS')
setLocale('enUS')
local.set('languageCurrent', 'enUS')

3. 語言切換流程

  1. 用戶選擇新語言
  2. 顯示確認對話框
  3. 確認後更新語言設定
  4. 更新本地儲存
  5. 重新載入頁面以應用新語言

設置預設語言

1. 環境變數設置

.env 文件中設置:

bash
VITE_DEFAULT_LANG=zhCN

2. 瀏覽器語言檢測

系統會自動檢測瀏覽器語言,如果瀏覽器語言在支持的語言列表中,則使用瀏覽器語言:

typescript
// src/utils/tools/defaultLang.ts
export function defaultLang(): Language {
  const { VITE_DEFAULT_LANG } = import.meta.env
  const browserLang = navigator.language.replace(/-/g, '')

  if (Object.keys(language).includes(browserLang)) {
    return browserLang as Language
  }

  return VITE_DEFAULT_LANG
}

3. 語言優先度

  1. 本地儲存中的 languageCurrent
  2. 瀏覽器語言(如果在支持列表中)
  3. 環境變數 VITE_DEFAULT_LANG

創建新語言

1. 添加語言類型定義

src/modules/i18n.ts 中添加新語言類型:

typescript
export type Language = 'enUS' | 'zhTW' | 'zhCN' | 'jaJP' // 添加日語

export const language = {
  enUS,
  zhTW,
  zhCN,
  jaJP, // 添加日語導入
}

2. 創建語言包文件

創建 locales/jaJP.json 文件:

json
{
  "common": {
    "confirm": "確認",
    "cancel": "キャンセル",
    "save": "保存"
  },
  "language": {
    "jaJP": "日本語",
    "confirmToSwitchLanguage": "言語を切り替えますか?"
  }
}

3. 更新 i18n 配置

src/modules/i18n.ts 中導入新語言包:

typescript
import jaJP from '../../locales/jaJP.json'

export const language = {
  enUS,
  zhTW,
  zhCN,
  jaJP, // 添加新語言
}

4. 更新 Naive UI 語言配置

src/utils/i18n.ts 中添加 Naive UI 語言配置:

typescript
import { dateJaJP, jaJP as naiveJaJP } from 'naive-ui'

export const naiveI18nOptions: Record<string, { locale: NLocale | null, dateLocale: NDateLocale | null }> = {
  // ... 現有配置
  jaJP: {
    locale: naiveJaJP,
    dateLocale: dateJaJP,
  },
}

5. 更新環境變數類型

src/typings/env.d.ts 中更新類型定義:

typescript
interface ImportMetaEnv {
  readonly VITE_DEFAULT_LANG: 'zhCN' | 'zhTW' | 'enUS' | 'jaJP'
}

6. 添加語言顯示名稱

在所有語言包的 language 部分添加新語言:

json
// zhCN.json, zhTW.json, enUS.json
{
  "language": {
    "jaJP": "日語"
    // 或 "jaJP": "日語", "jaJP": "Japanese"
  }
}

7. 測試新語言

  1. 更新環境變數 VITE_DEFAULT_LANG=jaJP
  2. 重啟開發伺服器
  3. 檢查語言切換功能
  4. 驗證所有文本是否正確顯示

常見問題與避免踩坑

1. 語言包鍵值缺失

問題: 控制台出現 "Missing key" 警告

解決方案:

  • 確保所有語言包都有相同的鍵結構
  • 使用 missingWarn: true 配置來發現缺失的鍵
  • 定期檢查語言包的一致性

2. 語言切換後頁面不更新

問題: 切換語言後部分文本沒有更新

解決方案:

  • 確保使用 $t() 函數而不是寫死文本
  • 檢查計算屬性是否正確響應語言變化
  • 語言切換後會重新載入頁面,確保狀態正確重設

3. 類型安全問題

問題: TypeScript 類型檢查失敗

解決方案:

  • 更新 Language 類型定義
  • 確保環境變數類型正確
  • 使用類型斷言時要謹慎
typescript
// 正確的類型使用
const currentLang = local.get('languageCurrent') as Language || defaultLang()

4. 語言包文件過大

問題: 語言包文件過大影響載入性能

解決方案:

  • 按功能模組拆分語言包
  • 使用動態導入載入語言包
  • 考慮使用 CDN 載入語言包

5. 嵌套對象訪問

問題: 訪問深層嵌套的翻譯鍵

解決方案:

  • 使用點號分隔的鍵路徑
  • 避免過深的嵌套結構
  • 考慮扁平化語言包結構
typescript
// 正確的方式
$t('userCenter.personalInfo.nickname')

// 避免過深嵌套
$t('userCenter.personalInfo.details.contact.mobile.placeholder')

6. 動態鍵值

問題: 需要根據變數動態生成翻譯鍵

解決方案:

  • 使用模板字串
  • 確保鍵值存在
  • 提供預設值
typescript
// 動態鍵值範例
function getFieldLabel(fieldName: string) {
  const key = `userCenter.${fieldName}Label`
  return $t(key) !== key ? $t(key) : fieldName
}

7. 數字和日期格式化

問題: 不同語言的數字和日期格式

解決方案:

  • 使用 Naive UI 的在地化配置
  • 配置正確的 dateLocale
  • 考慮使用 Intl API 進行格式化

8. 語言包維護

最佳實踐:

  • 定期檢查語言包一致性
  • 使用工具驗證鍵值完整性
  • 建立語言包更新流程
  • 保持語言包結構簡單清晰

9. 性能最佳化

建議:

  • 避免在模板中頻繁調用 $t()
  • 使用計算屬性快取翻譯結果
  • 考慮預載入常用語言包
vue
<script setup lang="ts">
// 好的做法:使用計算屬性
const pageTitle = computed(() => $t('common.title'))

// 避免:在模板中直接調用
// {{ $t('common.title') }}
</script>

10. 除錯技巧

除錯工具:

  • 使用瀏覽器開發者工具檢查語言包載入
  • 開啟 missingWarnfallbackWarn
  • 檢查網路請求中的語言頭設置
typescript
// 在開發環境中啟用詳細警告
export const i18n = createI18n({
  missingWarn: true,
  fallbackWarn: true,
  // ... 其他配置
})

通過遵循這些指南和最佳實踐,您可以有效地在項目中使用和管理國際化功能,避免常見問題,並提供良好的多語言用戶體驗。