React・本番で気をつけること

多言語にする(i18next)

文言をコードから追い出して、日本語と英語を切りかえます。変数の埋めこみ、複数形、日付と数字の書き分け、URLで言語を分ける設計まで。あとから入れると大変な部分を先に見ておきます。

日本語だけで作ったアプリを、あとから英語対応する——これがいちばん大変な作業です。文言がJSXの中に散らばっているので、抜き出すだけで丸1日かかります。

先に見ておけば、そうならずに済みます。

文言をコードの外に出す

npm install --save-exact i18next react-i18next

設定を1つ置きます。

import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'

i18n.use(initReactI18next).init({
  resources: {
    ja: {
      translation: {
        title: 'ことりずかん',
        search_placeholder: '名前やすんでいる場所でさがす',
        favorite_one: 'お気に入り{{count}}羽',
        favorite_other: 'お気に入り{{count}}羽',
      },
    },
    en: {
      translation: {
        title: "Shima's Bird Book",
        search_placeholder: 'Search by name or place',
        favorite_one: '{{count}} favorite bird',
        favorite_other: '{{count}} favorite birds',
      },
    },
  },
  lng: 'ja',
  fallbackLng: 'en',
  interpolation: { escapeValue: false },   // Reactが既にエスケープしている
})

export default i18n

main.tsxで読みこめば準備完了です。使う側はこうなります。

import { useTranslation } from 'react-i18next'

function BirdListPage() {
  const { t } = useTranslation()

  return (
    <>
      <h1>{t('title')}</h1>
      <input placeholder={t('search_placeholder')} />
      <p>{t('favorite', { count: favoriteIds.length })}</p>
    </>
  )
}

t('title')が、いまの言語の文言に置きかわります。切りかえは1行です。

const { i18n } = useTranslation()
i18n.changeLanguage('en')

実際に切りかえると、こうなりました。

項目jaen
t('title')ことりずかんShima’s Bird Book
t('favorite', { count: 1 })お気に入り1羽1 favorite bird
t('favorite', { count: 3 })お気に入り3羽3 favorite birds

birdbirdsが自動で切りかわっているのが、_one/_otherという書き方の効果です。

やってはいけない書き方

いちばん多い失敗は、文字列をつなげて文を作ることです。

// ✗ 翻訳できない
<p>{birds.length}{t('birds_unit')}{t('found')}</p>

// ○ 文をまるごと1項目にする
<p>{t('found_birds', { count: birds.length })}</p>
{ "found_birds_other": "{{count}}羽みつかりました" }
{ "found_birds_other": "Found {{count}} birds" }

語順は言語ごとに違います。日本語は「3羽・みつかりました」、英語は「Found・3・birds」。つなげ方を決め打ちすると、どうやっても訳せない文ができあがります。

日付・数字・通貨

これは翻訳ではなく書式の話で、ブラウザに入っているIntlが答えを持っています。

new Intl.NumberFormat('ja-JP').format(1234567)          // 1,234,567
new Intl.NumberFormat('de-DE').format(1234567)          // 1.234.567

new Intl.DateTimeFormat('ja-JP').format(new Date())     // 2026/8/7
new Intl.DateTimeFormat('en-US').format(new Date())     // 8/7/2026

桁区切りが.の国、日と月が逆の国があります。自分でpadStartして組み立てると、そこで詰みます。ライブラリを入れる前に、まずIntlです。

URLで言語を分ける

画面のボタンだけで切りかえると、URLが同じまま中身が変わります。共有できず、検索エンジンにも1言語ぶんしか認識されません。

/ja/birds/8
/en/birds/8

パスで分けるのが定番です。react-routerなら言語をパスの先頭に入れます。

<Route path="/:lang/birds/:id" element={<BirdDetailPage />} />

入れるかどうか

状況判断
多言語の予定がない入れない。文言はそのまま書いてよい
予定がある・将来ありそう早めに入れる。あとからの抜き出しが本当に大変
日本語だけだが表記ゆれを直したい文言をまとめるだけでも効く(t()なしでも定数ファイルで足りる)
フレームワークを使っているNext.jsなど側の仕組みを先に見る(ルーティングと絡むため)
ReactContextで配らずに共有する言語の切りかえは、Contextの典型的な使いどころでもあります(i18nextは内部で同じことをしています)

公式ドキュメントも見てみる

react.i18next.com外部サイトQuick start | react-i18next documentationreact-i18next公式のクイックスタート。useTranslationTransの使い分けはここが最短です(英語)

このレッスンのまとめ

  1. 文言はコードの外(辞書ファイル)に出す。あとから抜き出すのは本当に大変
  2. useTranslation()t('キー')で引く。切りかえはi18n.changeLanguage()
  3. 複数形は_one/_otherでライブラリに任せる。言語ごとに種類の数が違う
  4. 文字列をつなげて文を作らない。語順が言語ごとに違う
  5. 文の中にリンクや強調が入るときは<Trans>
  6. 日付・数字・通貨は翻訳ではなく書式。Intlを使う
  7. 言語はURLで分ける/ja/ /en/)。<html lang>も切りかえる
  8. 予定がなければ入れない。予定があるなら最初から

次は、データが増えても重くならないようにする回です。

やってみよう:ことりずかんを英語にも出す

文言を辞書に移して、切りかえられるようにします。

  1. npm install --save-exact i18next react-i18next
  2. src/i18n.tsを作り、jaenの辞書を書く(見出し・検索欄・件数の3つでよい)
  3. main.tsximport './i18n.ts'
  4. BirdListPageの文言をt('…')に置きかえる
  5. 「日本語 / English」のボタンを置き、i18n.changeLanguage()で切りかえる
  6. 件数を_one/_otherにして、1羽と3羽で英語の表記が変わることを確認する
<button onClick={() => i18n.changeLanguage(i18n.language === 'ja' ? 'en' : 'ja')}>
  {i18n.language === 'ja' ? 'English' : '日本語'}
</button>

6番のbirdbirdsの切りかわりを見ておくと、なぜ自分で三項演算子を書いてはいけないかが腑に落ちます。

よくある質問

多言語対応は最初からやるべきですか?
予定があるなら早いほうが楽です。文言がコードに散らばったあとで抜き出す作業は、機械的な割に量が多く、抜け漏れも出ます。予定がないなら入れなくて構いません。
文字列をつなげて文を作ってはいけないのですか?
いけません。語順は言語ごとに違うので、つなげ方を決め打ちすると翻訳できない文ができます。変数を埋めこめる形の文をまるごと1つの項目として持ちます。
日付や数字も翻訳が必要ですか?
翻訳ではなく書式の切り替えが必要です。ブラウザに入っているIntlを使うと、言語に合わせた日付や通貨の表記が得られます。自前で組み立てないのが正解です。
言語ごとにURLを分けるべきですか?
検索エンジンに両方を認識させたいなら分けます。/ja/と/en/のようにパスで分けるのが一般的で、切り替えのたびにURLが変わるので共有もしやすくなります。