「{playerName}」がそのまま画面に表示される — 原因を切り分ける
プレイヤーがスクリーンショットを撮る。「ようこそ、Alexさん」と出るはずが「ようこそ、{playerName}さん」とそのまま表示されている。あるいはダメージ表示が「%1$sが%2$dのダメージを与えた」のように、名前も数値も置き換わらずに出る。文字化けではなく、文章としては読める。これは置換の失敗であり、原因は少なくとも4通りあります。同じ症状に見えても対処法はまったく違います。
このバグは該当箇所以外では文字列自体が正常に表示されるため、レビューで見落とされやすく、特定の画面や特定の言語でしか再現しないことも多い問題です。この記事では、症状だけからどの原因かを切り分ける手順を整理します。
まず確認すること — 全言語で起きているか、一部だけか
この一問で原因を大きく二分できます。原文の言語も含めて全言語でそのまま表示されるなら、原因は翻訳より手前、つまりコード側か文字列キー自体にあります。特定の言語だけで起きるなら、翻訳作業の過程で問題が入り込んでいます。
原因1: 翻訳作業でトークンの文字が変わってしまった
一部言語だけで発生する場合に最も多い原因です。翻訳者がプレースホルダーをそのままコピーせず打ち直し、全角文字が混入したケースです。半角の { と } は、日本語・中国語で使われる全角の ( ) と見た目がよく似ており、プレースホルダーを保護しない翻訳ツールでは %s が %s に、{playerName} が {playerName} に置き換わってしまうことがあります。
実行時の文字列フォーマット処理は文字を厳密に一致させてトークンを判定するため、全角の中括弧はトークンの開始として認識されず、そのまま文字として表示されてしまいます。
- 対処: プレースホルダー部分は原文と1文字も違わない状態に戻す。打ち直さずコピー&ペーストする
- 予防: 原文と訳文それぞれから {token} や %s や %1$s を機械的に抽出して集合を比較するチェックを入れれば、リリース前に検出できる
en: "Welcome, {playerName}"
ja (壊れている): "ようこそ、{playerName}さん" // 全角の中括弧で認識されない
ja (修正後): "ようこそ、{playerName}さん"原因2: そもそも値が渡されていない
原文の言語のビルドでもプレースホルダーがそのまま表示される場合、テキストの問題ではありません。コードのどこかでフォーマット関数が引数なしで呼ばれているか、渡しているキー名が違うため、プレースホルダーが期待する値そのものが供給されていません。この場合にエラーで落ちるフォーマット処理系もありますが、多くは黙ってトークンをそのまま出力します。データ欠落で落ちるより安全側に倒した挙動です。
- 対処: そのキーの呼び出し箇所を追い、文字列が宣言している全プレースホルダーが引数オブジェクトに存在するか確認する
- 予防: 各文字列のプレースホルダー集合と、呼び出し箇所で渡している引数を突き合わせるチェックをlintやテストに組み込む
// バグ: playerNameが渡されていない
formatMessage("welcome_line", {})
// → "Welcome, {playerName}"
// 修正後
formatMessage("welcome_line", { playerName: user.displayName })
// → "Welcome, Alex"原因3: フォーマット記法がランタイムの想定と一致していない
フォーマット処理系ごとにプレースホルダーの記法は異なります。{name}、%s、%1$s、{0}、ICUの{name, plural, ...}はそれぞれ別物で互換性がありません。ある記法で書かれた文字列を、別の記法しか解釈しないフォーマッタに渡すと、トークンとして認識されずそのまま文字として出力されます。人間の目には「プレースホルダーらしきもの」に見えても、コードにとってはただの文字列です。
これはローカライズツールやテンプレート、ファイル形式を移行したタイミングで起きやすい症状です。異なる記法を前提とするシステム間で文字列がコピーされ、見た目のレビューでは違和感なく通過してしまいます。
- 対処: 見た目が正しいかではなく、実際に使っているフォーマッタが解釈できる記法に書き直す
- 予防: プロジェクト全体でプレースホルダーの記法を一種類に統一し、実際のフォーマッタで検証する
原因4: キー自体がローカライズされておらず、生の値がそのまま出ている
まれに、表示されている文字列は誤訳ですらなく、そのキーに対応するローカライズ文字列の読み込みが失敗し、キー名やフォールバック値がそのまま出ているケースがあります。この場合は他の3つと少し様子が異なり、トークン部分だけでなく行全体がおかしい、あるいは表示文字列が内部のキー名と完全に一致している、といった見え方になります。
- 対処: そのキーが読み込み対象のロケールファイルに存在するか、そのビルドの読み込みパスに実際に含まれているかを確認する
- 予防: キー欠落チェックをビルドの一部として組み込み、プレイヤーに見つかる前に検出する
報告文だけから原因を切り分ける
順番に3つ問いかけます。原文の言語でも起きるか — 起きるなら原因2か4で、コードか読み込みの問題です。特定の言語だけなら、プレースホルダーの文字そのものが微妙に違って見えないか確認する — 全角化やスペースの混入があれば原因1です。文字は同一に見えるのに置換されない場合は、記法をフォーマッタの仕様と突き合わせる — それが原因3です。この順で確認すれば、見当違いの箇所を触らずに正しい原因にたどり着けます。