ヘルプセンター

Angular i18next

国際化(i18n)は、グローバルなAngularアプリケーションを構築するために不可欠です。このガイドでは、適切なリソース読み込み、初期化、使用パターンを用いてAngularでi18nextを実装する方法を紹介します。

なぜAngularでi18nextなのか?

i18nextは最も人気のあるi18nフレームワークの1つであり、強力な機能と柔軟性を提供します:

  • フレームワークにとらわれない - Angular、React、Vueなどでシームレスに動作します。
  • 強力な補間、複数形の形式、コンテキスト認識をサポート。
  • 遅延読み込みとコード分割をサポート。
  • 型安全性を備えたTypeScriptサポート。
  • プラグインや拡張機能が豊富な巨大なエコシステム。

インストール

npmまたはyarnを使用してi18nextをインストールします:

npm install i18next

以上です!i18nextはフレームワーク固有の依存関係を持たないスタンドアロンライブラリです。angular-i18nextやその他のラッパーは不要で、Angularアプリケーションで直接i18nextを使用するだけです。

翻訳リソースの読み込み

Angularでi18nextを使用して翻訳リソースを読み込むには、主に2つのアプローチがあります:

動的インポート(推奨)

動的インポートを使用すると、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のベストプラクティス

意味のあるキーを使用する

'loginTitle'や生のテキストではなく、'auth:login.title'のように、アプリの構造を反映したドット表記を使用してください。

名前空間で整理する

メンテナンス性を高めるために、翻訳を論理的な名前空間(例: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メソッドがstringの代わりにTranslationKeyを受け入れるように変更します。これにより、テンプレート内で型安全性が提供され、有効な翻訳キーのみが使用されるようになります。

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ワークフローを効率化する準備はできましたか?