実装Read this article in English

Unity Localization の CSV 運用 — 更新に耐える往復の設計

Unity の Localization パッケージは、String Table のコレクションを CSV に書き出し、翻訳済みの CSV を取り込み直せます。操作は数クリックで、たいていの解説はそこで終わります。だからこそ、最初の翻訳バッチは順調に終わったのに、更新を3回挟んだあたりで、何が変わったのか分からない、キーをリネームしたら200件の訳文が迷子になった、Smart String の半分が壊れて返ってきた、という状態になるチームが多いのです。

設計すべきなのは書き出しボタンではありません。その周りの運用の形 — プロジェクトの寿命を通じて行を同定するものは何か、翻訳者が実際に必要とする列は何か、変わった行だけをどう送るか、取り込む前に欠落がないことをどう確かめるか — です。ここを決めておけば CSV の往復は何年も退屈なままですが、決めていないと毎回のバッチが発掘作業になります。

この記事では、その判断を、実際に決める必要がある順番で扱います。列の設定項目やインポータのオプションの詳細は、使っているバージョンのパッケージのドキュメントにあり、バージョン間で変わります。ここで扱うのは、それらのオプションが何のために存在するのか、という側の話です。

書き出す前に、行を同定するものを決める

String Table のエントリは2つの同一性を持ちます。パッケージが振って安定して保持する数値の ID と、コードから参照する人間可読な名前であり誰でもいつでも変更できるキーです。この区別が、CSV 往復について理解すべきもっとも重要な一点です。

書き出す CSV にキーしか載せていないと、Unity 側でキーをリネームしたとき — プロジェクトを整理していれば自然に起きます — 次の取り込みでそれは新しい未翻訳エントリになり、古い訳文は誰も参照しないキーの下に取り残されます。ID の列を必ず含め、キーは人間向けの情報として一緒に運ぶ形にしてください。そうすればインポータは安定した同一性で照合しつつ、翻訳者には意味の分かる名前が見えます。

ファイルの粒度も同時に決めます。テーブルコレクション1つにつき CSV 1本が、ほぼ常に正解です。差分が読める大きさに収まり、1つのシステムだけを翻訳に出せて、取り込みのミスが1ファイルの中に閉じます。全コレクションを1本にまとめた書き出しは、見た目は整理されていますが、運用は明確に苦しくなります。

列の設計 — 翻訳者がシートに本当に必要とするもの

動くシートの最小構成は4つです。安定した ID、キー、原文の言語の値、そして訳文を入れる空の列。これで翻訳して取り込むことはできますが、良い翻訳をしてもらうには足りません。

コメント列を足して、実際に使ってください。共有コメントは言語を問わずそのエントリに付く情報で、文脈を書く場所です。どの画面に出るのか、誰の台詞か、コロンの後ろは変数なのか、文字数の上限はあるのか。ロケールごとのコメント列は、翻訳者が質問やメモをファイルの中で返せる経路になります。チャットに流れて消える質問が減ります。どちらも書き出し時のコストはゼロで、メールでの往復の大半をなくせます。

インポータが読まない列 — 進捗の状態、バッチ番号、スクリーンショットの参照先 — を一緒に運ぶこともできます。ただし、どの列が意味を持つのかを列のマッピングで明示していることが前提です。これはマッピング次第で変わる設定なので、思い込みで進めず、捨ててよいファイルで先に挙動を確認してください。

Id,Key,Shared Comments,Japanese(ja),English(en),English(en) Comments,Status
1042,menu.settings.title,設定画面のタイトル。12文字まで。,設定,Settings,,done
1043,shop.buy_confirm,{0} はアイテム名。,{0}を購入しますか?,Buy {0}?,,done
1044,quest.reward_gold,{0} は数値。Smart String。,{0}ゴールドを手に入れた,,1のときの複数形は?,question

Smart String とプレースホルダーの規則

Smart String は、便利さと壊れやすさが同居している場所です。波括弧の中は変数名を指し、書式・複数形の選択・条件分岐まで書けます。つまり波括弧の中身は、文の中に置かれたコードです。翻訳者はプレースホルダーを自分の言語の文法が要求する位置へ動かすことが期待されており、それこそが目的なのですが、名前を変える・消す・複製する・波括弧の中に空白を入れる、これらは決してしてはいけません。

壊れ方は驚くほど一定です。日本語入力が全角の波括弧を入れてしまい、見た目はほぼ同じなのに何にも一致しない。翻訳者が親切心で波括弧の中の変数名まで訳す。複数形や条件の分岐が、重複したテキストに見えたために1つにまとめられる。どれも実行時エラーになるか、出荷されたゲームの画面に波括弧がそのまま表示されるかのどちらかで、通常の速度でシートを読んでいて気づけるものではありません。

対策は2つあり、両方必要です。1つは、毎回のバッチに1ページのルールシートを添えること。説明文ではなく、正しい例と間違った例を並べます。もう1つは、取り込み前にプレースホルダーの集合を機械的に照合すること。各行について、訳文に含まれるプレースホルダーの集合が原文と一致していること(順序は問わない)を確認します。小さなスクリプトで済み、最初のバッチで元が取れます。

もう一点、早めに確認しておくことがあります。あるエントリが Smart String かどうかはテキストではなくエントリ側の属性なので、その属性が書き出しと取り込みを通じて保たれるかどうかを、テスト用のファイルで確かめてください。CSV を経由しないのであれば、その切り替えは Unity 側でのみ行う作業になります。それ自体は問題ありませんが、翻訳者から返ってきたファイルで十数件が黙って通常の文字列に戻る前に知っておく必要があります。

全件ではなく差分を送る

最初のバッチのあと、毎回全件を書き出して送るのが既定の流れになりがちですが、これは誤りです。翻訳者は、意味のある40行を見つけるために変わっていない数千行を読み直すことになり、レビューは不可能になり、すでに承認して出荷済みの行がうっかり編集される確率が上がります。

機能する型は2つあります。1つは、バッチごとに書き出した CSV をバージョン管理にコミットしておき、次回の書き出しをそれと比較して、自分が管理しているスクリプトで変更行を抜き出す方法です。差分が正であり、作るコストはゼロで、いつ何を送ったかの記録も兼ねます。もう1つは、自分側の管理シートに状態列を持ち、絞り込んだ部分集合を書き出す方法です。手作業は増えますが、1人でパイプライン全体を持っている小規模なプロジェクトなら十分に回ります。

部分的な CSV に頼る前に、捨ててよいブランチで1つだけ確かめてください。テーブルには存在するのに CSV には無いエントリを、取り込みはどう扱うか。差分運用で欲しいのは「触らない」挙動です。「削除する」も全件置き換え運用としては妥当な挙動ですが、前者だと思い込んでいた場合には事故になります。本番のテーブルで知るのではなく、意図的に試して確かめてください。

ファイルそのもの — 文字コード、表計算ソフト、クオート

CSV はゲームテキストの入れ物としては脆く、そして被害の大半は Unity の中ではなく、書き出してから翻訳者のファイルが返ってくるまでの間に起きます。文字コードは明示的に合意し、返ってきたファイルは信用せずに確認してください。表計算ソフトが UTF-8 のファイルを別の前提で開くと、日本語をすべて別の文字に書き換えたうえで、警告もなく保存し直してくれます。

もう一つの弱点がクオート処理です。ゲームテキストには、1つの文字列の中にカンマ、引用符、改行が入ります。CSV はこれをクオートの規則で扱いますが、手で編集されたファイルでは簡単に崩れます。翻訳者のエディタが直線の引用符を曲がった引用符に変換したり、改行コードを揃えたり、意味のあった行末の空白を落としたりすると、構造としては正しいのに内容が変わったファイルが返ってきます。共有の表計算を渡して、書き出しは自分の手で行う運用にすると、この種の問題はまとめて消えます。手順が1つ増えるだけです。

書き出したファイルも返ってきたファイルも、バッチ名と日付を付けてバージョン管理に残してください。半年後に「この行は翻訳に出したことがあるのか」と訊かれたとき、答えになるのはその記録だけで、維持のコストはほぼゼロです。

取り込む前の検査と、取り込んだ後の確認

取り込む前に、返ってきたファイルへ機械的な検査をかけます。行数が送ったものと一致すること。ID が存在し、重複せず、変わっていないこと。以前は訳文があった行が空になっていないこと。プレースホルダーの集合が行ごとに一致すること。訳文の欄に原文がそのままコピーされている行がないこと。最後のものは、訳されずに飛ばされた行の典型的な痕跡です。

取り込みは必ずブランチ上で行い、主線に直接入れないでください。テーブルのアセットも他と同じくファイルなので、取り込みは差分として読める形になります。変更されたエントリ数が送った行数と一致するはずで、それを超える変更が出ているなら、マージ前に止まって中身を見る合図です。

そのうえでゲームを起動します。自動検査はプレースホルダーの欠落、空のエントリ、文字化けといった機械的な破損を捕まえますが、そのメニュー項目が文法的には正しいのにその画面にはまったく合っていない、ということはどんなスクリプトにも言えません。ロケールを切り替え、そのバッチが触れた画面を歩き、プレイヤーが見る場所でテキストを読んでください。CSV の往復は配管であり、文脈の中で結果を読むことが、翻訳の良し悪しを決める工程です。

  • 行数と ID が、送ったファイルと一致している
  • 以前は訳文があったエントリが空で返ってきていない
  • プレースホルダーの集合が行ごとに原文と一致している(順序は不問)
  • 訳文の欄に原文がそのまま入っている行がない
  • 取り込みはブランチ上で行い、アセットの差分をマージ前にレビューした
  • 対象の画面を、対象ロケールで実際に起動して確認した

関連記事