Unity多言語化の進め方 — 方式の選び方とつまずく5か所

結論から言うと、多くのUnityプロジェクトは公式のLocalizationパッケージを使うのが妥当です。例外は狭く、文字列が200件程度で画像や音声の差し替え予定もない場合か、パッケージが連れてくるAddressablesへの依存をどうしても入れられない場合だけです。いま方式を決めかけているなら、答えはこれです。

以下はマニュアルの範囲外、つまり方式の選択と、実際につまずく箇所です。

Unityのローカライズ方式は3つ — 公式パッケージ・自作・アセット

公式のローカライズ用パッケージ(Localization)が用意するのは、対応言語を表すLocaleアセットの一覧、キーとロケールごとの文字列を対応させるString Table、画像や音声にも同じことをするAsset Table、複数形や条件分岐を扱うSmart String、そしてCSVとXLIFFの入出力です。テーブルの読み込みはAddressables(アセットを外部から読み込むUnityの仕組み)経由です。導入手順はマニュアルのインストールとクイックスタートの章にあります。

自作の読み込み処理という選択肢もあります。CSVかJSONを起動時に辞書へ読み込み、参照用の関数を1つ用意するだけです。テキストが1画面に収まるゲームならこれが正解です。ただしパッケージが肩代わりしていたものは全部自分の責任になります。複数形の規則、キーの欠落を見つける道具、そして壊れたファイルを出荷前に弾く取り込み処理です。

アセットストアの資産(Naninovel等)はその中間で、保守リスクを負います。判断材料は直近の更新がいつかと、1週間で自作に置き換えられる規模かどうかです。

選ぶ基準は文字列の件数ではなく、ロケールごとに何が変わるかです。次の問いに答えてから、その下の対応表と突き合わせてください。

  • プレイヤーは再起動せずに言語を変えるか。辞書を差し替えるだけでは足りず、変更を通知する仕組みが要ります。
  • チーム外の人がテキストを編集するか。するなら実行時の仕組みより受け渡し形式が重要で、CSVやXLIFFの入出力が決定要因になります。
状況                                   方式
--------------------------------------------------------------------
200件程度・個人・テキストのみ           自作のCSV/JSON読み込み
UI+セリフ+アイテム名、今後も増える      公式Localizationパッケージ
画像・音声・動画もロケール別            公式パッケージ(Asset Table)
複数形や性の一致が必要                  公式パッケージ(Smart String)
                                       または自前のICUライブラリ
Addressablesを入れられない              自作の読み込み処理
ビルド工程を増やせない                  自作の読み込み処理
コンソール審査・複数SKU                 公式パッケージ(対応ロケール
                                       一覧を提出物として示せる)
セッション途中の言語切り替えが必要      公式パッケージ、または自前の
                                       変更通知の仕組み

翻訳者に渡す表と、項目数の検査だけでは足りない理由

どの方式でも翻訳者が受け取るのは表です。公式パッケージのCSV書き出しは、キーの列、数値の識別子の列、ロケールごとの列という構成で、翻訳者向けのコメント列を持たせる設定もできます。列名と列の選択肢はマニュアルのCSVの章で確認してください。形の例は節末にあります。

このファイルへの分かりやすい検査は構造的なものです。各行の項目数がヘッダーと一致しているか、引用符が閉じているか。どちらも必要ですが、どちらも十分ではありません。わざと壊した行を解析すると分かります。セル内の引用符を、CSVの規則どおり2つ重ねるのではなく1つだけ書いた場合です。

一般的なCSV解析器はこれを黙って受け付けます。項目数は正しいままで引用符の閉じ忘れも残らないので、構造的な検査は通ります。出てくるのは引用符が消えた文です。1言語の1行だけ、引用符のないアイテム名が表示されます。

一方でプレースホルダーの比較は本当の破損を即座に捕まえます。英語の原文には残したまま日本語のセルからプレースホルダーを1つ消すと、並べ替えた集合が一致しなくなり、その行だけが不一致として検出されました。数行の検査が、実行時の書式エラーになるバグを拾います。

だから自動化する検査には、項目数に加えてプレースホルダー集合の一致と、引用符や括弧の個数が往復で変わっていないことを入れます。順序は違ってよく、下の例の簡体字中国語の行は金額を先に置いています。

列の設計と差分だけを送る運用はUnityのCSV入出力の手順に、英語の文言を直しても壊れないキーの付け方は文字列キーの設計に、シーンやスクリプトから文字列を外へ出す作業はハードコードされたテキストの外部化にまとめてあります。

Key,Id,en,ja-JP,zh-Hans,pt-BR
menu.settings.title,10001,Settings,設定,设置,Configurações
shop.buy_confirm,10002,"Buy {0} for {1} gold?","{0}を{1}ゴールドで購入しますか?","用 {1} 金币购买 {0}?","Comprar {0} por {1} de ouro?"

ロケール名はBCP 47で付け、SystemLanguageだけに頼らない

ロケール識別子は厳密に確かめられます。最近のJavaScript実行環境には、別の問いに答える2つの処理があります。正規化はどの形が正しく、どれが書き換えられ、どれが拒否されるかを示し、補完(likely subtagsのデータを使うIntl.Locale.maximize)は空欄が埋まるとそのタグが何を意味するかを示します。下の表は同じタグを両方に通したものです。

一番多い間違いはアンダースコアです。表記の好みの問題ではなく、拒否されます。旧表記も罠で、ヘブライ語とインドネシア語の古い2文字コードは黙って現代の形に書き換えられます。大文字小文字の違いは違いではないので、2通りの綴りを2つのロケールとして扱わないでください。

より気づきにくいのは表の下半分、つまり正規化ではなく補完の側です。言語だけのタグは中立ではありません。likely subtagsのデータは、ポルトガル語をブラジル、繁体字中国語を台湾として解決します。繁体字中国語が上半分では変わらず下半分で地域を得るのはこのためです。「ポルトガル語」という名前で出荷すると、どのポルトガル語かは下流のどこかが決めてしまいます。差が意味を持つ言語では地域まで自分で書いてください。

正規化 (Intl.getCanonicalLocales)
ja-JP       -> ja-JP
zh-Hans     -> zh-Hans
zh-Hant     -> zh-Hant        この処理では変わらない
pt-BR       -> pt-BR
es-419      -> es-419         中南米スペイン語。419は地域コード
zh-hans-cn  -> zh-Hans-CN     大文字小文字は正規化される(差ではない)
pt_BR       -> 拒否           アンダースコアは区切りとして無効
zh-CHS      -> 拒否           .NET由来の旧表記。BCP 47ではない
iw          -> he             旧コードは黙って書き換えられる
in          -> id             同様

空欄の補完 (likely subtags、Intl.Locale.maximize)
pt          -> pt-Latn-BR     ポルトガル語はブラジルに解決される
zh-Hant     -> zh-Hant-TW     繁体字中国語は台湾に
es          -> es-Latn-ES
ja          -> ja-Jpan-JP

パッケージを入れた後に壊れる5か所

1つ目は、エディタでは表示されるのに実機ビルドで空欄になる文字列です。パッケージはテーブルをAddressableなアセットとして読み込むため実機ビルドにはコンテンツビルドが必要で、エディタ側では既定の再生モードがアセットデータベースから直接読むので問題が隠れます。手順はAddressablesのドキュメントで確認してください。症状は例外ではなく空のラベルなので、まだ翻訳していない画面では動作確認を通過します。

2つ目は豆腐です。フォントアセットは自分の文字テーブルにあるグリフしか描画できないので、ラテン文字の文字セットから作ったフォントに日本語や中国語を追加すると、足りない文字はエラーではなく白い四角になります。実行時にグリフを追加する動的フォントアセットと、出荷するテキストから作る静的なアトラスのどちらにするかを決め、すり抜けた文字用にフォールバックの連鎖を設定してください。仕組みはグリフ欠落で豆腐が出る理由、和欧をまたぐ難しいケースは日本語と英語のピクセルフォントにあります。

3つ目は、翻訳者が複数形や条件分岐の記法を壊すことです。複数形は単数と複数の2択ではありません。カテゴリの数は英語が2つ、日本語と簡体字中国語が1つ、ロシア語とポーランド語が4つ、アラビア語が6つです。ロシア語では1と21が同じカテゴリで11は別なので、「1が単数でそれ以外は複数」は21でも31でも101でも間違いです。対象言語が持つカテゴリを翻訳者に渡し、実行時ではなく取り込み時に記法を検証してください。記法はICU MessageFormatの解説にまとめています。

4つ目は、文字列連結が語順を固定することです。コード側で部品から組み立てた文は翻訳者が並べ替えられません。日本語では語順がほぼ必ず変わります。文は1件のエントリに丸ごと入れ、番号付きのプレースホルダーで埋めてください。

5つ目は実行時のロケール選択です。選択ロケールを変更すると、変更イベントを購読しているコンポーネントは更新されますが、自分のスクリプトから一度代入したテキストもキャッシュした文字列も更新されません。タイトル画面からだけでなくセッション途中で切り替え、到達できる全画面を確認し、選択は再起動後も残るよう保存してください。システム言語は最初の推測にだけ使います。Unityはこれを地域のない言語の列挙として返すため、ポルトガル語はブラジルとポルトガルを区別できず、中国語は簡体字と繁体字という表記体系の区別しか持ちません。一覧はスクリプトリファレンスのSystemLanguageの項にあり、対応づける前にBCP 47言語タグの構造を読んでおいてください。

翻訳が1件もない段階で擬似ロケールを通す

擬似ロケールは原文から生成する偽の言語です。各文字をアクセント付きの似た字に置き換え、訳文が長くなる分を見込んで文字を足し、全体を括弧で囲みます。公式パッケージにも擬似ロケールの機能があり、マニュアルに説明があります。

1周プレイすると欠陥が3種類まとめて見えます。素の文字のまま表示されているテキストは参照処理を通っていません。はみ出した水増し分は、訳文が長くなると耐えられないレイアウトです。閉じ括弧が見えない箇所は文字が切れている枠で、最後の単語がたまたま収まっている状態はこれ以外では気づけません。

守る約束は2つです。水増しの比率は決めたら固定します。固定すればビルド間で比較できますが、文字列ごとに変えるとはみ出しが劣化のように見えます。もう1つ、プレースホルダーの内側には足しません。記法で文字列を分割し、文字だけの部分を変換して結合します。見える文字数の40パーセントを足すと次のようになります。

Settings                                     ->  【Šéttîñgš····】                       8 -> 14 文字
Continue                                     ->  【Çøñtîñüé····】                       8 -> 14
Buy {0} for {1} gold?                        ->  【Büý {0} før {1} gøld?······】       21 -> 29
Are you sure you want to abandon this quest? ->  【Åré ýøü šüré ýøü wåñt tø åbåñdøñ thîš qüéšt?··················】  44 -> 64

最小構成と、後から足すもの

最初の一歩に仕組み全部は要りません。2言語目を足すときに、書き直しではなく取り込みで済む状態になっていれば十分です。

  • 最小構成: 原文と対象のロケールを1つずつ、プレイヤーに見える全文字列を安定したキー付きで1つのテーブルに、各表記体系についてゲーム内で確認したフォント、プレイヤーが変更できる言語設定、擬似ロケールを1周。
  • 数百件を超えたら: 全件ではなく差分での書き出しと取り込みの往復。
  • テキスト以外がロケールで変わるなら: Asset Table と、どのアセットがロケール別かの一覧。
  • 複数形や性の一致が必要な言語を入れるなら: Smart String か自前のICUライブラリ。
  • コンソール審査の前に: 提出できる対応ロケール一覧と、テーブルにエントリが欠けていたら出荷ではなく失敗するビルド。

関連記事