実装Read this article in English

ゲームテキストのプレースホルダー方式まとめ — なぜ1つに統一すべきか

ゲームテキストのほとんどの行には「穴」が空いています。プレイヤー名、アイテム数、ダメージ量。この穴は実行時に、原文に埋め込まれたプレースホルダーのトークンによって埋められます。このトークンの構文はエンジンやフレームワーク、自作の文字列システムによって大きく異なります。プロジェクトで1つを選ぶ(あるいは既存のものを引き継ぐ)前に、代表的な方式を知っておく価値があります。

printf形式: %s, %d, %1$s

C言語のprintf系から受け継がれた方式で、型コードがトークンに直接埋め込まれます。%sは文字列、%dは整数を受け取ります。単純な%s/%dは、文字列中に現れる順番で位置が決まります。

一部の実装では明示的な位置インデックスをサポートしており、翻訳者は実行環境側で並べ替えなくても、訳文の文法に合わせて引数の順序を変えられます。

You picked up %1$s x%2$d
アイテム「%1$s」を%2$d個手に入れた

インデックス形式: {0}, {1}

.NET系の書式指定や多くの自作エンジンで見られる、よりシンプルな位置指定方式です。トークン自体には型コードが含まれず、型は呼び出し側で決まります。訳文で{1}を{0}より先に出す並べ替えは、インデックスそのものが位置情報を持つため、そのままサポートされることが多いです。

{0} dealt {1} damage to {2}

名前付き: {playerName}, {itemCount}

名前付きプレースホルダーは簡潔さと引き換えに明快さを得る方式です。{playerName} defeated {enemyName}という文を訳す翻訳者は、引数0と引数1が何を指すかを推測する必要がありません。名前自体がドキュメントになっています。文字列数が増え、レビュアーが1行ごとのソースコードの文脈を把握できなくなるほど、これが効いてきます。

{playerName} defeated {enemyName} in {turnCount} turns

ICU MessageFormat: 複数形・性別分岐を内蔵

ICU MessageFormatは、単純な置換を超えた標準化された構文です。ロケールごとの複数形ルールや性別による文の分岐を、パターンとして評価できます。基本的な複数形パターンは次のようになります。

{itemCount, plural,
  one {# item}
  other {# items}
}

エンジン独自タグ

多くのゲームエンジンやダイアログシステムは、独自の角括弧や山括弧トークンを使い、時には置換とインラインの書式指定やリッチテキストを同じトークン空間に混在させます。例えば<color=red>{damage}</color>や[player]のような形です。これらはプロジェクト固有であり、エンジン自身のリファレンス以外にほとんど文書化されていないため、統一が最も難しい部類です。

1方式に統一し、文書化することが混在に勝る理由

問題が起きるのは、ファイルをまたいで方式が混在しているときです。{0}形式しか見たことのない翻訳者は、%1$sで引数のインデックスを保持しなければならないことも、ICUの複数形ブロックが自由に書き換えていい平文ではないことも、直感的にはわかりません。上に挙げたどの方式も正当な選択肢です。リスクはどれを選ぶかではなく、誰にも伝えずに複数の方式を混ぜてしまうことにあります。

プロジェクト全体でプレースホルダー方式を1つに統一し、翻訳者が実際に見る場所に書いておきましょう。オンボーディング資料に埋もれた一段落ではなく、トークンの型ごとに実例を1つ添えた短いリファレンスシートです。{itemName}のルールを知っている翻訳者は、1行目でも10,000行目でも正しく訳せます。見たことのない方式を推測しながら訳す翻訳者は、確認しづらいまさにそのタイミング — スプレッドシートの奥深く、ゲーム本体から遠く離れた場所 — で間違えます。

  • プロジェクト全体で1つのプレースホルダー方式を選ぶ。ファイル単位や機能単位で変えない
  • 各トークンの型を、名前だけでなく実例付きで文書化する
  • 実行環境が位置の並べ替えに対応しているかを明記する。対応していない場合は特に明示する(他プロジェクト出身の翻訳者は対応していると思い込みがち)
  • リファレンスは、翻訳者がアクセスできない社内Wikiではなく、実際に開くエクスポートファイルのそばに置く

関連記事