說明中心

Angular i18next

國際化 (i18n) 對於建構全球化的 Angular 應用程式至關重要。本指南將向您展示如何透過正確的資源載入、初始化和使用模式在 Angular 中實作 i18next。

為什麼 Angular 要選擇 i18next?

i18next 是最受歡迎的 i18n 框架之一,提供強大的功能與靈活性:

  • 框架無關 - 可與 Angular、React、Vue 等無縫協作。
  • 強大的插值、複數形式和語境感知支援。
  • 支援延遲載入和程式碼分割。
  • 支援 TypeScript 並具備型別安全。
  • 擁有豐富的插件和擴充功能生態系統。

安裝

使用 npm 或 yarn 安裝 i18next:

npm install i18next

就這樣!i18next 是一個獨立的函式庫,沒有框架特定的依賴關係。您不需要 angular-i18next 或任何其他封裝程式 - 只需在您的 Angular 應用程式中直接使用 i18next 即可。

載入翻譯資源

在 Angular 中使用 i18next 載入翻譯資源主要有兩種方法:

動態匯入(推薦)

使用動態匯入可利用 Webpack 的程式碼分割功能來按需載入翻譯。此方法非常適合擁有許多語言或大型翻譯檔案的應用程式。

優點:

  • 套件大小最佳化 - 僅載入所需的翻譯。
  • 更好的效能 - 更快的初始載入時間。
  • 完美支援 SSR 和預渲染。
  • 無需額外的 HTTP 請求。

範例:使用動態匯入載入

private async loadLanguageResources(
  lang: string,
): Promise<Record<string, Record<string, unknown>>> {
  const languageResources: Record<string, Record<string, unknown>> = {};

  // Load all namespaces for the given language in parallel
  await Promise.all(
    NAMESPACES.map(async (namespace) => {
      const translationModule = await this.loadJson(lang, namespace);
      languageResources[namespace] = translationModule.default;
    }),
  );
  return languageResources;
}

private async loadJson(
  language: string,
  namespace: string,
): Promise<{ default: Record<string, unknown> }> {
  // Webpack automatically code-splits this into separate chunks
  return import(`../../i18n/${language}/${namespace}.json`);
}

此方法將翻譯檔案匯入為 ES 模組,允許 Webpack 自動將其分割成獨立的區塊,並在需要時載入。

i18next-http-backend

HTTP 後端插件透過伺服器或 CDN 的 HTTP 請求載入翻譯。當您希望在不重新建置應用程式的情況下更新翻譯時,這非常有用。

優點:

  • 無需重新部署即可更新翻譯。
  • 從外部 CDN 載入翻譯。
  • 適用於執行時期的翻譯管理。

缺點:

  • 額外的 HTTP 請求會拖慢初始載入速度。
  • 需要網路連線。
  • SSR 設定較為複雜。

範例:使用 HTTP 後端載入

import HttpBackend from 'i18next-http-backend';

await init({
  lng: initialLanguage,
  fallbackLng: DEFAULT_LANGUAGE,
  backend: {
    loadPath: '/assets/i18n/{{lng}}/{{ns}}.json',
  },
  use: [HttpBackend],
  ns: NAMESPACES,
  defaultNS: DEFAULT_NAMESPACE,
});

此方法在執行時期從伺服器獲取翻譯檔案。請注意,這需要針對 SSR 進行額外設定。

該選擇哪種方法?

在以下情況使用動態匯入:

  • 您希望獲得最佳的套件大小和效能。
  • 翻譯是建置流程的一部分。
  • 您需要 SSR/預渲染支援。

在以下情況使用 HTTP 後端:

  • 您需要在不重新部署的情況下更新翻譯。
  • 翻譯由外部管理。
  • 您有 CDN 可用於提供翻譯檔案。

正確的 i18next 初始化

正確初始化 i18next 對於避免執行時期錯誤並確保應用程式渲染時翻譯可用至關重要。

使用 provideAppInitializer

Angular 的 provideAppInitializer 函式(於 Angular 18 引入)確保 i18next 在應用程式開始渲染前已完全初始化。提供的函式會在應用程式啟動期間執行,且直到 Promise 解析完成前,初始化不會結束。這可防止翻譯鍵直接顯示在畫面上,而非翻譯後的文字。

範例:App Initializer 設定

export const appConfig: ApplicationConfig = {
  providers: [
    // ... other providers
    provideAppInitializer(initializeI18n),
    // ... more providers
  ],
};

為什麼這很重要:

  • 該函式在應用程式啟動期間的注入上下文中執行。
  • 防止初始載入時出現閃爍或顯示翻譯鍵。
  • 確保翻譯從一開始就在所有元件中可用。
  • 正確支援 SSR、預渲染和用戶端渲染。

i18next 初始化函式

初始化函式負責載入資源並設定 i18next 配置。

範例:I18next 初始化

public async initializeI18n(): Promise<void> {
  const initialLanguage = this.getInitialLanguage();
  const resources: Record<string, Record<string, Record<string, unknown>>> = {};

  if (isPlatformBrowser(this.platformId)) {
    // Browser: Only load the specific language needed
    const languageResources = await this.loadLanguageResources(initialLanguage);
    resources[initialLanguage] = languageResources;
  } else {
    // SSR: Load all languages for prerendering
    for (const lang of SUPPORTED_LANGUAGES) {
      const languageResources = await this.loadLanguageResources(lang);
      resources[lang] = languageResources;
    }
  }

  await init({
    lng: initialLanguage,
    fallbackLng: DEFAULT_LANGUAGE,
    resources,
    ns: NAMESPACES,
    defaultNS: DEFAULT_NAMESPACE,
    interpolation: {
      escapeValue: false,
    },
  });
}

關鍵點:

  • 在瀏覽器中僅載入當前語言以獲得最佳效能。
  • 在 SSR 期間載入所有語言以支援預渲染。
  • 使用 fallbackLng 優雅地處理缺失的翻譯。
  • 配置命名空間以實現更好的組織。

翻譯管線

Angular 翻譯管線提供了一種在模板中使用翻譯的簡潔方式。

管線實作

範例:翻譯管線

import { Pipe, PipeTransform } from '@angular/core';
import { t } from 'i18next';

@Pipe({
  name: 't',
  standalone: true,
  pure: false, // Need to update when language changes
})
export class TranslatePipe implements PipeTransform {
  transform(key: string, options?: Record<string, unknown>): string {
    return t(key, options);
  }
}

該管線被標記為 pure: false,以確保在語言變更時進行更新。這很重要,因為語言變更本身並不會修改翻譯鍵。

使用翻譯管線

在模板中:

<!-- With namespace -->
<p>{{ "common:welcomeMessage" | t }}</p>

<!-- With nested keys -->
<p>{{ "dashboard.title" | t }}</p>

<!-- Simple interpolation -->
<p>{{ "greeting" | t: { name: userName } }}</p>
<!-- Result: "Hello, John!" from greeting: "Hello, {{name}}!" -->

<!-- With Angular pipes -->
<p>{{ "price" | t: { amount: 29.99 | currency } }}</p>
<!-- Result: "Price: $29.99" from: "Price: {{amount}}" -->

<!-- Multiple variables -->
<p>{{ "updated" | t: { date: lastModified | date, user: currentUser } }}</p>
<!-- Result: "Updated on Jan 15, 2024 by Alice" from: "Updated on {{date}} by {{user}}." -->

插值允許您將動態值插入到翻譯中。您可以傳遞變數、使用 Angular 管線進行格式化,並在單一翻譯字串中組合多個值。

在元件程式碼中:

import { Component } from '@angular/core';
import { t } from 'i18next';

@Component({
  selector: 'app-dashboard',
  template: `
    <h1>{{ title }}</h1>
    <p>{{ welcomeMsg }}</p>
  `,
})
export class DashboardComponent {
  title = t('dashboard.title');
  welcomeMsg = t('greeting', { name: 'John' });
}

對於模板使用,管線語法簡潔且易讀。對於程式存取,請直接呼叫 t()。這對於顯示通知、設定動態標題或在元件邏輯中處理翻譯非常有用。

Angular i18next 最佳實踐

使用有意義的翻譯鍵

使用反映應用程式結構的點記法:使用 'auth:login.title' 而非 'loginTitle' 或原始文字。

使用命名空間進行組織

將翻譯拆分為邏輯命名空間(例如 common.json, auth.json, dashboard.json)以提高可維護性。

處理複數形式

使用 i18next 內建的複數形式支援,以正確處理所有語言的單數/複數形式。

等待初始化

務必確保在使用 provideAppInitializer() 於應用程式啟動期間執行初始化之前,i18next 已完成初始化。

翻譯鍵的型別安全

使用 TypeScript 直接從 JSON 檔案擷取翻譯鍵,以實現完整的型別安全。與其手動維護型別定義,不如利用 TypeScript 的 typeof 和動態匯入,從實際的翻譯檔案中自動產生型別。

定義一個型別,使用遞迴的 NestedKeys 輔助型別從 JSON 翻譯檔案中擷取所有可能的鍵。

import common from './en/common.json'; // defaultNS: DEFAULT_NAMESPACE,
import auth from './en/auth.json'; // namespaces

type NestedKeys<T, Prefix extends string = ''> = T extends object
  ? {
      [K in keyof T & (string | number)]: T[K] extends object
        ?
            | NestedKeys<T[K], `${Prefix}${K & (string | number)}.`>
            | `${Prefix}${K & (string | number)}`
        : `${Prefix}${K & (string | number)}`;
    }[keyof T & (string | number)]
  : never;

export type TranslationKey =
  | `common:${NestedKeys<typeof common>}`
  | `auth:${NestedKeys<typeof auth>}`;

將管線的 transform 方法改為接受 TranslationKey 而非 string。這在模板中提供了型別安全,並確保僅能使用有效的翻譯鍵。

import { Pipe, PipeTransform } from '@angular/core';
import { t } from 'i18next';

import { TranslationKey } from '../../i18n/translation-key';

@Pipe({
  name: 't',
  standalone: true,
  pure: false, // Need to update when language changes
})
export class TranslatePipe implements PipeTransform {
  transform(key: TranslationKey, options?: Record<string, unknown>): string {
    return t(key, options);
  }
}

現在,您可以在 TypeScript 程式碼和模板中獲得完整的 IntelliSense 自動完成功能,以及無效鍵的編譯時錯誤檢查。

t('common:greeting');       // ✅ Autocomplete
t('auth:login.title');      // ✅ Valid
t('invalid.key');           // ❌ IDE shows error

{{ "common:greeting" | t }} // ✅ Valid in templates
{{ "invalid.key" | t }}     // ❌ IDE shows error

型別安全翻譯鍵的好處:

  • IDE 中所有命名空間內可用翻譯鍵的自動完成。
  • 若使用不存在的鍵,會出現編譯時錯誤,在執行時期前捕獲拼字錯誤。
  • 重構支援 - 安全地重新命名整個程式碼庫中的翻譯鍵。
  • 零執行時期開銷 - 型別在編譯過程中會被完全移除。
  • 從實際 JSON 檔案自動產生 - 無需手動維護型別。

語言偵測與切換

實作智慧語言偵測,並允許使用者在執行時期切換語言,同時自動載入資源。

透過優先順序檢查多個來源,決定應用程式啟動時使用的語言。

private getInitialLanguage(): string {
  if (isPlatformBrowser(this.platformId)) {
    // Try to get language from localStorage (user preference)
    const savedLang = localStorage.getItem('language');
    if (savedLang) {
      return savedLang;
    }

    // Fall back to browser language
    return navigator.language ?? DEFAULT_LANGUAGE;
  }

  // Default language for SSR
  return DEFAULT_LANGUAGE;
}

此方法首先檢查 localStorage(使用者偏好),然後回退到瀏覽器語言 (navigator.language),最後預設為應用程式的預設語言。

在執行時期切換語言,同時按需自動載入翻譯資源。

public async switchLanguage(
  language: string
): Promise<void> {
  // In browser, check if we need to load the translation first
  if (isPlatformBrowser(this.platformId)) {
    const i18next = await import('i18next');

    await Promise.all(
      NAMESPACES.map(async (namespace) => {
        const currentResources = i18next.default.getResourceBundle(
          lang,
          namespace,
        );
        // Check if any namespace is missing for this language
        if (!currentResources) {
          // Translation not loaded yet, load it dynamically
          const translationModule = await this.loadJson(lang, namespace);
          i18next.default.addResourceBundle(
            lang,
            namespace,
            translationModule.default,
          );
        }
      }),
    );
  }

  // Switch to the new language
  await changeLanguage(lang);

  // Save preference to localStorage
  if (isPlatformBrowser(this.platformId)) {
    localStorage.setItem('language', lang);
  }
}

使用動態匯入時,翻譯會按需載入。switchLanguage() 方法會使用 getResourceBundle() 檢查資源是否已載入。若缺失,則使用 addResourceBundle() 動態匯入並新增它們。這可防止冗餘的網路請求,同時確保所有需要的翻譯皆可用。

關鍵實作細節:

  • 優先檢查 localStorage 以恢復使用者的偏好語言。
  • 使用 navigator.language 作為瀏覽器語言偏好的回退。
  • 使用 addResourceBundle() 動態載入缺失的資源。

使用 AI 自動化翻譯

手動管理多語言翻譯既耗時又容易出錯。這就是 AI 驅動的本地化發揮作用的地方。

為什麼選擇 l10n.dev?

l10n.dev 是一款專為 i18next JSON 檔案設計的 AI 驅動翻譯服務:

  • 保留 JSON 結構、翻譯鍵和巢狀物件。
  • 正確維護如 {{name}}、{{count}} 等佔位符。
  • 自動為所有語言產生複數形式。
  • 智慧處理語境和插值。
  • 支援所有 i18next 功能,包括命名空間。

簡單的工作流程

  1. 匯出您的基礎語言 JSON 檔案(例如 en/common.json)
  2. 上傳至 l10n.dev 並選擇目標語言。
  3. AI 以語境感知和正確格式進行翻譯。
  4. 下載翻譯好的檔案,即可在您的 Angular 應用程式中使用。

節省數小時的手動翻譯工作,並避免常見錯誤,如損壞的佔位符或錯誤的複數形式。

準備好簡化您的 Angular i18n 工作流程了嗎?