12k
All articles

ReactアプリへのInternationalizationの追加

Reactでreact-i18nextを使い国際化を設定。補間、複数形、RTLレイアウト、ロケール書式、Next.jsのSSRまで解説。

OpenReplay Team
OpenReplay Team
ReactアプリへのInternationalizationの追加

ReactアプリにInternationalizationを追加するとは、ユーザーに表示されるすべての文字列を言語別ファイルに外部化し、JSXにテキストをハードコードする代わりに翻訳レイヤーを通じてレンダリングすることを意味します。

ビルドをリリースした際に生のt('main.header')がユーザーの画面に表示されたり、英語では問題なく見えたボタンがドイツ語の文字列によってレイアウトが崩れたりした経験があれば、セットアップ自体が難しいわけではないことはご存知でしょう。配線作業は半日もあれば終わりますが、ロケール固有のエッジケースへの対処にはスプリントの残り時間をすべて費やすことになります。本番環境での標準的な実装方法は、i18nextフレームワークのReactバインディングであるreact-i18nextです。react-i18nextを標準として採用することをお勧めします。フックベースで設計されており、名前空間とレイジーローディングをサポートし、サーバーサイドレンダリングにも対応し、最大規模のi18nextプラグインエコシステムを活用できます。ICUメッセージ構文への対応が必須の場合に限り、react-intlを検討してください。本ガイドでは、現在の正しいセットアップ方法を説明した後、本番環境でよく問題となる5つの事項を取り上げます:補間、複数形、ロケール対応の数値・日付フォーマット、右から左へのレイアウト(RTL)、そしてSSRです。

重要なポイント

  • i18nextの設定でinterpolation.escapeValue: falseを設定してください。ReactはレンダリングJSX前に値をエスケープ処理するため、i18nextのエスケープ処理を有効にしたままにすると二重エスケープが発生します。
  • 現在のi18nextでは、複数形のキーはCLDR/Intlのサフィックス(_zero_one_two_few_many_other)を使用します。レガシーの_pluralサフィックスは旧JSONv3フォーマットのものであり、選択に使用する変数はcountという名前でなければなりません。
  • 数値と日付のフォーマットは言語だけでなく地域にも依存するため、ロケールを明確に指定し(en-USar-EG)、i18nextのIntlフォーマッターを{{value, number}}および{{date, datetime}}の形式で使用してください。
  • 翻訳ファイルはi18next-http-backendloadPathを使ってJSONファイルから読み込んでください。require()でインライン化すると、すべての言語がメインバンドルに含まれてしまい、レイジーローディングが機能しなくなります。
  • Next.jsでは、SSRのi18nを独自実装しないでください。next-i18next v16は、App RouterとPages Routerの両方を1つのパッケージで対応しています。

react-i18nextのセットアップ方法

コアフレームワーク、Reactバインディング、そして言語検出とファイル読み込みを担う2つのプラグインをインストールします。セットアップに必要な4つのパッケージはそれぞれ異なる役割を持っています。

パッケージバージョン役割
i18next26.xコアエンジン:キー検索、補間、複数形、フォーマット
react-i18next17.xReactバインディング:useTranslationTrans
i18next-browser-languagedetector8.xユーザーの言語を検出
i18next-http-backend4.xHTTP経由で翻訳JSONを読み込み

注意点として、i18next-http-backend v4はネイティブfetchが必要です。Node.js 18以降、すべてのモダンブラウザ、Deno、Bunではデフォルトでfetchがサポートされています。古いランタイムを使用する場合は、ポリフィルを提供するかv3を使用し続けてください。

npm install i18next react-i18next i18next-browser-languagedetector i18next-http-backend

src/i18n.tsを作成し、一度だけ初期化します。

import i18n from 'i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: 'en',
    supportedLngs: ['en', 'es', 'ar'],
    load: 'languageOnly',
    backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' },
    interpolation: { escapeValue: false },
  });

export default i18n;

interpolation.escapeValue: falseを設定する理由は、ReactがレンダリングJSX前に値をエスケープ処理するためです。i18nextのエスケープ処理を有効にしたままにすると、文字列が二重エスケープされてしまいます。loadPathの設定も重要です。require()でリソースをインライン化する方法(Create React App / Webpack時代のパターン)は、すべての言語をメインバンドルに含めてしまい、レイジーローディングが機能しなくなります。設定ファイルはレンダリングの前に、エントリーポイントで一度だけインポートしてください。main.tsximport './i18n';を追加します。

useTranslationフックを使った文字列の外部化

翻訳ファイルはpublic/locales/<lng>/translation.json配下の言語別JSONファイルに格納し、コンポーネントはuseTranslationフックのt関数を通じて読み込みます。ハードコードされたすべての文字列をキー検索に置き換えてください。

{ "main": { "header": "Welcome to the app!" } }
import { useTranslation } from 'react-i18next';

export default function Header() {
  const { t } = useTranslation();
  return <h1>{t('main.header')}</h1>;
}

ネストされたキー(main.header)と名前空間を使用することで、大量の文字列を整理できます。インラインのマークアップやリンクを含むテキストの場合、通常のt()呼び出しではJSXが正しく処理されません。そのような場合はTransコンポーネントを使用してください。これにより、マークアップをJSONではなくコンポーネント内に保ちながら、翻訳された文章にReact要素を補間できます。

<Trans i18nKey="main.docs" components={{ docsLink: <a href="https://react.i18next.com/" /> }} />

言語の切り替えと検出方法

i18n.changeLanguage(lng)でアクティブな言語を変更すると、useTranslationを使用しているすべてのコンポーネントが自動的に再レンダリングされます。言語切り替えUIはボタンや<select>要素でこのメソッドを呼び出すだけです。

const { i18n } = useTranslation();
<select
  value={i18n.resolvedLanguage}
  onChange={(e) => i18n.changeLanguage(e.target.value)}
>
  <option value="en">English</option>
  <option value="ar">العربية</option>
</select>

言語検出は言語検出プラグインが担当し、以下の順序でソースを確認します:クエリ文字列(?lng=en)、Cookie、localStorage、ブラウザのnavigator、そして<html lang>属性。サポートされている言語が最初に見つかった時点で検索を停止します。解決された言語はlocalStorageにキャッシュされるため、再訪問したユーザーは以前の言語設定が維持されます。また、手動でchangeLanguageを呼び出した場合もキャッシュが更新されます。

よくある5つの落とし穴

i18nのバグのほとんどは、通常の動作フロー以外の部分に潜んでいます。以下は、ローカルのQAでは問題なく通過し、実際のユーザーのロケールでのみ表面化する障害パターンです。

補間(Interpolation)。 動的な値は{{var}}構文で注入し、第2引数として渡します。例:"Hello, {{name}}"に対してt('greeting', { name })を使用します。ReactのエスケープとともにXSS対策としてescapeValue: falseを設定することで安全性が確保されます。

複数形(Pluralization)。 英語では2つの複数形が必要で、アラビア語では6つ必要です。これがif (count === 1)を手書きしてはいけない理由です。countt()に渡し、Intl.PluralRulesに適切なキーを選択させてください。CLDRサフィックスを使って複数形を定義します:_zero_one_two_few_many_other。変数名は必ずcountにしてください。

{
  "messages_one": "You have one message",
  "messages_other": "You have {{count}} new messages"
}

旧来の_pluralサフィックスはレガシーのJSON v3形式のものです。i18nextはJSON v4フォーマットの導入時に、複数形のサフィックスをIntl APIで使用されるものと一致するよう整理しました。v24以降、Intl APIは必須となっています。ランタイムにIntl.PluralRulesがない場合はポリフィルが必要です。v3の複数形処理へのフォールバックは廃止され、compatibilityJSON'v3'を受け付けなくなりました。

数値と日付のフォーマット。 i18nextの組み込みIntlフォーマッターを使用してください:{{value, number}}{{date, datetime}}、そして{{value, number(style: percent)}}のようなオプションも使用できます。フォーマットは地域によって異なるため、ロケールを明確に指定し(en-USar-EG)、数字の表記や日付の順序がブラウザ間で一貫するようにしてください。

右から左へのレイアウト(RTL)。 RTL言語に対応するには、言語変更のたびにi18n.dir()からドキュメントの方向を設定することで、コンポーネントごとにCSSを変更することなくレイアウト全体を適切に反映させることができます。

useEffect(() => {
  const apply = (lng: string) => {
    document.documentElement.lang = lng;
    document.documentElement.dir = i18n.dir(lng);
  };
  i18n.on('languageChanged', apply);
  return () => i18n.off('languageChanged', apply);
}, [i18n]);

i18n.dir()languageChangedハンドラー内で呼び出し、言語切り替えの途中で同期的に呼び出さないようにしてください。changeLanguage()の後、i18next.languageがリソースの読み込み完了後にのみ新しい言語を反映するためです。

SSR。 Next.jsでサーバーサイドi18nを独自実装しないでください。next-i18next v16はi18nextとreact-i18nextの薄いラッパーで、Next.js固有の配線(ミドルウェア、サーバー/クライアントの分割、リソースのハイドレーション)を自動的に処理します。App Router(Server Components、Client Components、ミドルウェア)とPages Routerの両方をサポートし、Server ComponentsにはgetT()、Client ComponentsにはuseT()を提供します。windowの存在を前提とせず、クライアントツリーは<Suspense>でラップしてください。これらのロケール固有の不具合(main.headerのような生のキーがユーザーに表示される、RTLのパディングがテキストを切り取る、翻訳済み画面にフォールバック言語のテキストが混入するなど)は、デフォルトロケールのQAでは検出されず、実際のセッションを対象ロケールで確認した際にのみ表面化します。これこそがセッションリプレイが真価を発揮する場面です。

名前空間とキー抽出によるスケーリング

文字列の数が増えてきたら、翻訳を名前空間に分割し、useTranslation('dashboard')を使ってルートごとに読み込むようにしてください。これにより、各ページは自身のJSONのみを取得し、バンドルサイズを小さく保てます。文字列がコードベース全体に広がってきたら、自動化ツールの導入を検討してください。i18next-cliはキー抽出、コードのリント、ロケールの同期、型生成を一括で処理できる公式のコマンドラインツールです。また、実際のローカライズ作業が始まったら、Lokalise、Phrase、Crowdinなどの翻訳管理システムを使って翻訳者との作業を調整することをお勧めします。

これで、正しいreact-i18nextのセットアップ方法と、高度な考慮事項のマップが揃いました。設定を配線し、文字列を外部化した後、バンドルが大きくなったら名前空間を、サーバーサイドレンダリングが必要になったらnext-i18nextを導入してください。i18nextのコアとバインディングは頻繁にリリースされるため、インストール時にnpmで正確なパッケージバージョンを確認してください。

よくある質問(FAQ)

i18nextとreact-i18nextの違いは何ですか?

i18nextは、キー検索、補間、複数形処理、フォーマットなど、実際の翻訳ロジックを処理するコアフレームワークです。react-i18nextはその上に構築されたReactバインディングで、useTranslationなどのフック、Transコンポーネント、言語変更時の自動再レンダリングを提供します。両方をインストールする必要があります。i18nextが実際の処理を行い、react-i18nextがコンポーネントとの接続を担当します。react-i18nextはモダンなi18nextのピア依存関係を必要とするため、互換性のあるメジャーバージョンを維持してください。

翻訳キーが翻訳された文字列ではなくそのままテキストとして表示されるのはなぜですか?

main.headerのような生のキーがユーザーに表示される場合、ルックアップが解決できなかったことを意味し、ほとんどの場合、その言語または名前空間のJSONファイルが読み込まれていないことが原因です。よくある原因としては、loadPathがファイルの実際の場所と一致していない、名前空間が登録されていない、i18n設定がレンダリング前にインポートされていない、またはファイルにキーが存在しないことが挙げられます。ネットワークタブでlocalesパスへのリクエストが失敗していないか確認し、正しい言語ファイルにキーが存在することを確認してください。

i18nextの複数形キーに_pluralサフィックスを使用しますか?

いいえ。_pluralサフィックスはレガシーのJSON v3フォーマットのものです。現在のi18nextはIntl.PluralRulesと一致するCLDR/Intlの単語サフィックスを使用します:_zero、_one、_two、_few、_many、_otherです。英語は2つの形式(_oneと_other)を使用し、アラビア語は6つすべてを使用します。形式を選択する変数はcountという名前でなければならず、countがない場合のフォールバックはありません。Intl.PluralRulesが利用できない場合はポリフィルが必要です。v24以降、旧v3の複数形処理へのフォールバックはなく、compatibilityJSONはv3を受け付けなくなりました。

翻訳はJSONファイルに保存する必要がありますか、それとも設定にインライン化できますか?

resourcesオプションを使って翻訳をインライン化することは可能ですが、ごく小規模なアプリを除き、i18next-http-backendと/locales/{{lng}}/{{ns}}.jsonのようなloadPathを使ってJSONファイルから読み込むべきです。require()ですべての言語をインライン化すると、すべての翻訳がメインバンドルに含まれてしまい、レイジーローディングが機能しなくなります。その結果、ユーザーは使用しない言語の文字列もダウンロードすることになります。ファイルベースの読み込みでは、アクティブな言語と名前空間のみをオンデマンドで取得します。なお、i18next-http-backend v4はネイティブfetchが必要なため、Node.js 18以降が必要です。

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.