i18n って実際どうやるの
Category: Web
i18n(Internationalization)は、アプリが複数の言語圏に対応できるようにするためのエンジニアリングです。言語そのものだけでなく、国ごとの違いも考慮する必要があります。具体的には、単位(通貨、温度、距離など)、日付・時刻の表記、書字システム(文法、分かち書き、そしてアラビア語の RTL など)。対応する言語が増えれば、その分リーチできるユーザーも増えます(あるいは逆の順序かもしれませんが)。
土台から Unicode に対応する
フロントエンドの表示だけでなく、バックエンドに送るデータ、データベース、CMS、レスポンスやエラーメッセージまで、すべて Unicode を扱えなければなりません。どこか一箇所でも壊れてはいけません。
Locale
Locale(地域)は Language Code(ISO 639 で定義、例: en、zh)に、任意の Country Code(ISO 3166 で定義、例: uk、us、tw)を組み合わせたものです。
プロダクトのプロトタイプはまず英語や慣れた言語で作り、狙う市場やユーザーのフィードバックに応じて、他の言語・地域、さらには通貨・日付・住所・税率・電話番号・法的条項などを追加していけば良いでしょう。重要な原則は、今必要なものから作り、将来の拡張余地は残しつつ、まだ必要かどうか分からないものに過剰設計しないことです。
誰が i18n に関わるべきか?
ほぼ全員です。フロントエンド、バックエンド、QA、PM、デザイナーを含みます。
- フロントエンド/バックエンド:UI 用の翻訳ファイルや locale のメタデータを用意し、API のフォーマット(Accept-Language ヘッダーやリクエストに locale を含めるなど)をすり合わせる
- PM:最初のバージョンでどの言語・国をサポートするか、日付や通貨に対応するか、どの機能をローカライズするか、対応コストの見積もりを決める
- デザイナー(UI/UX):コンポーネントやレイアウトが、翻訳による文字数の違いを吸収できるか
- QA:テキストが途切れていないか、特殊文字が正しく表示されるか、文字化けしていないか、翻訳漏れがないか
もう少し規模が大きくなると、Legal、Data、Marketing、CSM なども関わってきます。最終的に、i18n はチーム横断のプロダクト能力であり、解決すべきはエンジニアリングの問題であって、単一チームだけの孤立した作業ではないと気づくはずです。
フロントエンドでは普段どう i18n をやるのか?
翻訳ファイルを作る
もっとも単純な構成は、アプリ内のテキストをすべて抜き出して key/string のペアにし、Locale に応じて対応する文字列を選ぶというものです。
<button>Submit</button> と書く代わりに <button>{t("button.submit")}</button> のように書き、翻訳ファイルを
{
"button.submit": "Submit",
"button.cancel": "Cancel",
...
}
のように用意します。
key の名前をどうするかは、コードで変数名を決めるのと同じように悩みどころで、たいていは構造化・階層的なパス命名を使います。では key の名前は誰が決めるべきか?答えは同じで、みんなでミーティングして決めましょう。
ある言語を Single Source of Truth にする
複数の翻訳ファイルがすでに揃っている場合、以降のメンテナンスにはこの原則が使えます。
例えば英語を基準にするとして、新しい翻訳を追加するときは英語の翻訳ファイルに key と文字列を追加するだけで済みます。他の言語の翻訳ファイルは触らなくてよく、必要なら key だけ追加します。
こうする理由は、CI/CD のフローに翻訳管理プラットフォームや自動翻訳ボットがある場合、次のようなことが起きるからです。
- ボタンが新しく追加されると、翻訳プラットフォームやボットが「おや、
zh-TW.jsonのこの key の中身がen.jsonとまったく同じ(あるいは空)だぞ!まだ翻訳されていないみたいだから、翻訳に回そう」と気づく - 英語の翻訳が変更されると、翻訳プラットフォームやボットが「おや、
en.jsonのこの key の中身が更新されたのに、中国語はまだ古いままだ、再翻訳が必要そうだ」と気づく
こうしておくことで、翻訳が抜けている場合のフォールバック、人力で翻訳した内容が翻訳プラットフォームやボットに上書きされないこと、エンジニアが維持すべき翻訳ファイルが1つで済むことが保証できます。
i18n ライブラリ
フロントエンドの i18n 作業を肩代わりしてくれる既存のライブラリはたくさんあり、i18next、react-i18next、next-translate などがよく使われます。どれを選ぶかは、要件やアプリのフレームワークなどによって決めます。
ライブラリが処理してくれること:
- テキスト翻訳:
t('key')のような関数を提供し、key を渡すと現在選択されている言語に対応する文字列を返す - 言語切り替え:動的に言語を切り替える API(例:
i18n.changeLanguage('zh-TW'))を提供し、切り替え後は画面が自動または手動で更新され、locale に応じて UI に何を表示すべきかを i18n 側が決める - 変数の埋め込み:
t('welcome', { name: 'Alex' })-> 「おかえりなさい、Alex!」 - 複数形の処理:数値に応じて単数形・複数形を切り替える。言語によってはこれがかなり複雑になる
- フォーマット:国や地域ごとに異なる数値、日付、時刻、通貨のフォーマットを処理する
- フォールバック:key が見つからなくてもアプリがクラッシュしないようにする
テストとパフォーマンス
i18n のアーキテクチャと運用フローが軌道に乗った後、対応する言語や辞書ファイルが増えていくと、次のようなことが必要になります。
- ユニットテスト:複数形処理、日付、時刻、通貨フォーマットなどのロジックをテストする
- 静的解析:CI/CD の段階で自動的に比較する。ファイル間で key が完全に揃っているか?コード内で呼ばれているのに JSON に存在しない key はないか?使われていない余分な key はないか?
- 遅延読み込み(Lazy Loading):初回読み込みや言語切り替え時にだけ、非同期でその言語の JSON ファイルを取得する
- キャッシュと分割:共通または頻繁に使われる言語は先に読み込み、あまり使われない言語や大きなページの翻訳辞書は動的読み込みとキャッシュ戦略を採用し、「通信量の節約」と「快適さ」のバランスを取る