エンジンにローカライズ機能がないゲームを多言語対応する
すべてのゲームが、既製のローカライズ機能を備えたエンジンの上に作られているわけではありません。自作エンジン、社内の小規模なフレームワーク、そして自前のローカライズツールが用意される前のエンジンで作られた実績あるゲームも数多く、結局は同じ立場に行き着きます。自分で作るしかない、という立場です。良いニュースは、その中核となる仕組みはそれほど複雑ではないということです。フォールバック言語付きのキー・バリュールックアップであり、ゲームジャム向けに午後の数時間で書くにせよ、何年も続くライブサービスのアップデートを見据えて作るにせよ、同じ設計判断が当てはまります。
この記事は、そのシステムのための設計チェックリストです。どう構造化するか、後から作り直すのが辛いので早めに正しくしておくべきこと、そして成熟したエンジンのツール群であれば自動的に防いでくれていたはずの、自作ローカライズシステムが陥りがちな具体的な罠を扱います。
中核部分 — キー・バリューストアとルックアップ関数
最低限必要なのは、キーとロケールごとの翻訳文字列を対応させるデータ構造と、プレイヤーに見えるすべてのテキストが生の文字列リテラルの代わりに通過する関数です。この関数のシグネチャは早い段階で正しく決めておく価値があります。コードベース中のすべての呼び出し箇所がそれに依存することになるからです。
function t(key: string, params?: Record<string, string | number>): string {
const table = translations[currentLocale] ?? translations[fallbackLocale];
const template = table[key] ?? translations[fallbackLocale][key] ?? key;
return interpolate(template, params);
}ファイル形式を選ぶ
JSON、CSV、YAMLが一般的な選択肢で、どれが適しているかは誰がどのようにファイルを編集するかによります。JSONは自然にネストでき追加のパース処理なしで読み込みやすい一方、手で編集するには厳しく、翻訳者に他の言語との相対的な位置関係を視覚的に伝えてくれません。CSVはスプレッドシートで問題なく開け、それは翻訳者がまさに求めている形であることが多く、未翻訳の抜けを列の中の空セルとして視覚的に見せてくれますが、本来ネストしているキー構造をフラット化してしまい、セル内のカンマ・引用符・改行の扱いに注意が必要です。YAMLは構造を保ちながらJSONより人間が編集しやすい一方、空白文字のミスに敏感です。
どれを選ぶにせよ、原本となるファイルは厳密に1つ(あるいはロケールごとに1つ、混在は避ける)に保ち、コード中に存在するキーがそのファイルに存在しない状態を許さないでください。欠落しているキーは、翻訳者やプレイヤーが遭遇する前にツールが検知すべきものです。
フォールバック言語は省略できない
アクティブなロケールにキーが存在しないときに、空文字列を表示したり、最悪クラッシュしたりと静かに失敗するルックアップは、自作ローカライズシステムで最もよくある失敗モードです。翻訳が欠けている場合は必ず、既知の完全な言語(通常は原語)にフォールバックし、カバレッジの抜けが壊れた・空のUI要素ではなく「一時的に未翻訳のテキスト」に留まるようにしてください。フォールバック自体も静かにしてはいけません。チームが実際に目にする場所に欠落キーをログ出力するかフラグを立て、抜けが放置されずに埋められるようにしてください。
プレースホルダーと文字列の埋め込み
パーツから組み立てるテキストにはすべて、プレースホルダーの記法と埋め込み処理が必要で、値を差し込む必要が最初に生じた瞬間に生の文字列連結に手を伸ばすのではなく、意図的に設計しておく価値があります。位置指定のプレースホルダー(`%s`、`%d`)ではなく名前付きプレースホルダー(`{playerName}`、`{damage}`)を使えば、翻訳者は数値の位置を追跡しなくても自分の言語の語順に合わせて並べ替えられ、これは正しく扱うのも自動検証するのも容易です。
複数形は明示的に扱う必要がある
英語の複数形に決め打ちで `(s)` を付けるような素朴なやり方は、英語の単純な単数/複数の区別より多くの複数カテゴリを持つ言語には一般化できません。これを正しく作るには、ある翻訳者から「この言語の複数形のルールは今のフォーマットでは表現できない」と指摘されてから後付けするのではなく、最初から1つの概念につき複数の複数形パターンをサポートできるようキー構造を設計しておく(対象言語が必要とする数だけ)ことが必要です。
開発中のホットリロード
ロケールファイルをゲームを再起動せずに再読み込みできる仕組み — ディスク上のファイル変更を監視して再パースする、あるいは現在のロケールを再読み込みするデバッグコマンドを用意する — があれば、文字列を微調整して実際の文脈で確認するループが劇的に速くなり、これはプロジェクトの寿命全体で見ると想像以上に重要です。これは「あれば嬉しい」機能としてではなく、早い段階で作っておく価値があります。後から追加するコスト自体は変わりませんが、それを作らずにいる間ずっと遅いループの代償を払い続けることになるからです。
自作システムだからこそ陥りがちな罠
以下は、成熟したエンジンのローカライズツールであれば設計上ほぼ自動的に防いでくれる失敗モードであり、自作システムでは意図的に防がなければならないものです。
- 欠落キーの沈黙 — ルックアップの失敗が、フォールバックと欠落フラグの代わりに何も表示しないかクラッシュに繋がる
- プレースホルダーの代わりの文字列連結 — `"You found " + count + " items"` のように文を組み立てると原語の語順が焼き付き、語順が異なる言語で破綻する
- 翻訳者への文脈の欠如 — メモやスクリーンショット、キャラクター/画面のメタデータのないフラットなキー・バリューファイルは翻訳者に推測を強い、その推測は規模が大きくなるほど無視できない頻度で外れる
- 検証ステップの欠如 — すべてのロケールがすべてのキーを持っているか、原文と訳文でプレースホルダーのトークンが一致しているか、コード中で参照されているキーが実際にデータに存在するかを何もチェックしていない
- 描画層でテキストを後回しに扱う — その場では手っ取り早いからとUIコンポーネントに文字列を直書きしてしまい、後から手作業で見つけて移行することになる
根底にある原則
ここまでの内容に特別なものは何もありません。成熟したエンジンのローカライズシステムが代わりに強制してくれている規律を、ここでは何も強制してくれないからこそ明示的に行っているだけです。テキストは、ゲームが依存する他のあらゆるデータと同じように扱ってください。単一の原本、検証対象となるスキーマ、そして何かが見つからなかった場合にたまたま表示される挙動ではなく、明確に定義された振る舞いを持つルックアップ経路です。