ヘルプセンター

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を使用できます。

翻訳リソースの読み込み

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

動的インポート(推奨)

動的インポートを使用すると、Webpackのコード分割を活用して、必要に応じて翻訳を読み込みます。このアプローチは、多くの言語や大きな翻訳ファイルを持つアプリケーションに最適です。

利点:

  • バンドルサイズの最適化 - 必要な翻訳のみを読み込む。
  • パフォーマンスの向上 - 初期読み込み時間が速くなる。
  • SSRおよびプリレンダリングと完全に連携。
  • 追加のHTTPリクエストは不要。

例:動的インポートによる読み込み

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

  for (const namespace of NAMESPACES) {
    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が解決されるまで初期化は完了しません。これにより、翻訳キーが翻訳されたテキストの代わりに表示されるのを防ぎます。

例:アプリ初期化子の設定

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中にすべての言語を読み込んでプリレンダリングのサポートを提供する。
  • missing translationsを優雅に処理するために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}}." -->

補間(Interpolation)を使用すると、翻訳に動的な値を挿入できます。変数を渡したり、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の組み込みの複数形サポートを使用して、すべての言語で単数/複数形を正しく処理します。

初期化を待つ

アプリをレンダリングする前に、i18nextが初期化されていることを常に確認し、アプリのブートストラップ中にprovideAppInitializer()を使用して初期化を実行します。

翻訳キーの型安全性

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');

    // Check if any namespace is missing for this language
    for (const namespace of NAMESPACES) {
      const currentResources = i18next.default.getResourceBundle(lang, namespace);

      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ワークフローを合理化する準備はできていますか?