翻訳したテキストが□(豆腐)になる理由
ゲームを翻訳して、エクスポートもきれいに通ったのに、セリフ枠が□□□で埋まっている。それはほとんどの場合、翻訳の問題ではなくフォントの問題で、よく混同される 2 つの失敗とは直し方が違います。グリフ欠けと、文字が捨てられた場合と、文字化けの見分け方、そして Unity、TextMeshPro、Ren'Py、実行時ツールのそれぞれでの直し方をまとめます。
エクスポートが終わり、ゲームが起動し、セリフの行はどれも同じ形の空の四角が並んでいる。翻訳には何ひとつ問題がありません。ファイルの中のテキストはプロバイダーが返したそのままで、テキストエディターで開けばちゃんと読めます。壊れているのは連鎖の最後の段階です:ゲームがレンダラーに渡しているフォントに、その文字を描く形が入っていないのです。
□(豆腐)とは実際には何なのか
フォントとは、Unicode のコードポイントから字形への対応表です。フォントに含まれないコードポイントを描くよう求められると、レンダラーはそのフォントの欠字グリフで代用します — たいていは空か斜線の入った四角で、正式には .notdef、ほかの場所では豆腐(tofu box)と呼ばれるものです。四角 1 つが、フォントの描けなかった文字 1 つ。仕組みはこれだけです。
そこから 2 つの結果が出てきて、どちらも診断に役立ちます。第一に、四角の数は文字数と一致します:タイ文字 5 文字なら四角も 5 つで、短く化けた列にはなりません。第二に、メモリー上の文字列は無事です — ゲームはそれを測ることも、比べることも、保存して読み直すこともできます。欠けているのはピクセルだけです。翻訳されたメニューがまったく読めないのに操作は正しくできるのは、このためです。
3 つの症状、3 つの別々の問題
「翻訳が壊れた」まわりの混乱のほとんどは、このうちどれかの直し方を別のものに当てはめていることが原因です。何かを変える前に、壊れ方の形を読んでください。
- □□□ の四角 — 文字は正しく、フォントがそれを描けません。フォントを直します。
- ???? — 文字そのものが失われています。何かがその文字列をより狭い文字集合へ再エンコードし、表せないものをすべて置き換えました。どんなフォントでも取り戻せません。エンコーディングを直すか、その形式が持てるターゲット言語を選びます。
- `テã‚スト` のような崩れたラテン文字、あるいは
テキストがテキストと表示される — 文字化けです。バイトは正しく、デコーダーが違います:ある文字コードで書かれたテキストが、別の文字コードとして読まれています。どの文字コードで読むかを直します。
手早い切り分け方:□も文字化けも、バイト数は元とだいたい比例したままですが、文字化けは目に見えて、まちまちのゴミ文字を出すのに対し、豆腐は同一で均一な四角を出します。均一なら「フォント」、まちまちなら「エンコーディング」です。
□:グリフが無い
そのゲームは、自分に必要な範囲だけをカバーするフォントを積んで出荷されました。パイプラインのどこも壊れていません — 作った人が入れなかったコードポイントを、あなたが要求したのです。直し方は常に、そのグリフを持つフォントをレンダラーに渡すこと。フォントを差し替えるか、主フォントで見つからなかったときにレンダラーが参照するフォールバックを足します。
疑問符:文字が捨てられた
テキストを従来の 1 バイトまたは 2 バイト系のエンコーディングで保存する形式へ文字列を書き戻さなければならないとき、そのエンコーディングの外にある文字はすべて何かに変換されるしかありません。慣例上、それが ? です。Shift-JIS や cp932 は日本語と ASCII は持てますが、それ以外はほとんど持てません。厳密な ASCII のフィールドは、ギリシャ文字もキリル文字もタイ文字も CJK も持てません。これはフォントではなくファイル形式の性質で、エクスポートの時点で起きます — ゲームがそのファイルを読むころには、情報はもう失われています。
RuneTranslate でこれが実際の制限になっているエンジンが 1 つあり、隠さずに明記しています:YU-RIS は文字列を cp932 で再エンコードするため、英語やそのほかのラテン文字の出力とは相性がよい一方、タイ語は ? として出てしまいます。このエンジンで cp932 外のターゲットを扱うにはフォントハックが必要で、それはまだ作られていません。
崩れたラテン文字:文字化け
Shift-JIS で書かれた日本語を UTF-8 として読む(またはその逆)と、アクセント付きのラテン文字や罫線素片が長く連なります。ツールが違う文字コードでファイルを書いたとき、あるいはゲーム自身のローダーが、あなたのエクスポートとは違う文字コードを前提にしているときに出ます。直し方はエンジンが期待する文字コードでファイルを書くことで、フォントを替えても何も変わりません。
CJK とタイ語がいちばん割を食う理由
ラテン文字ならグリフは百ほどで足ります。日本語は数千、中国語はもっと必要です。フォントファイルは大きいので、ゲームは自分の文字体系をカバーする最小のものを積みます。つまり、受け継ぐカバー範囲は、そのゲームがどの言語向けに作られたかで決まります。
- 日本語向けに作られたゲームは、たいていかな、常用漢字、ASCII を持っています。それをロシア語、ギリシャ語、タイ語、韓国語に翻訳すると、ASCII 以外の文字はすべて抜け落ちます。
- 英語向けに作られたゲームは、たいていラテン文字しか持っていません — つまり日本語、中国語、韓国語、タイ語、ギリシャ語、キリル文字が一度に失敗します。
- タイ語が実際にはいちばん厳しいケースです:一般的なゲームフォントのどれにも入っていないうえ、母音記号や声調記号を基底文字の上下に配置する必要があるため、コードポイントは持っているフォントでもきれいに描けないことがあります。
- ラテン文字なら自動的に安全、というわけでもありません。 ポーランド語、チェコ語、ハンガリー語、トルコ語、ベトナム語、ルーマニア語は Latin Extended の文字を使い、英語や日本語向けに作られたフォントはそれらをよく欠いています。RuneTranslate が Ren'Py でまさにそうしたターゲット向けに広いフォントを同梱しているのは、「ラテン文字なら大丈夫」が誤りだと分かったからです。
Unity と TextMeshPro に固有の話
Unity にはテキストシステムが 2 つあり、壊れ方が違います。レガシーの uGUI Text は Font オブジェクトを使います。最近のゲームのほとんどが使う TextMeshPro は、実行時にフォントファイルをまったく使いません。使うのは TMP フォントアセット — あらかじめ描画したグリフ画像のテクスチャアトラスと、コードポイントをアトラス上の位置へ対応させる表です。
だから TMP フォントアセットには固定された文字集合があります。開発者がそれを生成したとき、文字の範囲 — ふつうは出荷する言語の分だけ — を選び、TMP はちょうどその分のグリフをアトラスへ焼き込みました。静的なフォントアセットは、何を渡してもその集合の外を描くことはできません。動的なフォントアセットは元のフォントファイルへの参照を保持し、必要に応じて新しいグリフをラスタライズできます。開発者が想定しなかった言語まで広げられるのは、このモードだけです。
TMP の逃げ道はフォールバックリストです。各フォントアセットには m_fallbackFontAssets のリストがあり、TMP Settings にはグローバルなフォールバックリストがあります。主となるアセットにそのコードポイントのグリフが無いとき、TMP はそれらのリストをたどって持っているものを探します。ここが手を入れるべき正しい場所です。ゲーム自身のフォントが、すでに描けるものすべてを引き続き担当するからです — UI は意図されたままの見た目を保ち、扱えない文字だけがよそから来ます。
実行時ツールで直す
XUnity.AutoTranslator はゲームの実行中にフックし、設定ファイルからフォントを差し替えられます。関係する設定は [Behaviour] の下にあります:
OverrideFont— レガシーの Unity UITextコンポーネントが使うフォントを置き換えます。ゲームまたは OS が解決できるフォントの名前を指定します。OverrideFontTextMeshPro— TMP フォントアセットをまるごと置き換えます。TMP フォントの AssetBundle ファイル、またはゲームがすでに持っているアセットの名前を指定します。FallbackFontTextMeshPro— 2 つのうち安全なほうです:ゲーム自身のアセットに無いグリフのときだけ TMP が参照するフォントを足し、それ以外の場所では元の書体をそのまま残します。- tmp_font_assetbundles —
OverrideFontTextMeshProとFallbackFontTextMeshProの指定先としてよく使われる、コミュニティが管理するビルド済み TMP フォント AssetBundle 集です。これらは Unity のバージョンごとにビルドされていて、ゲームと違うバージョン向けのバンドルは読み込みに失敗します — バージョンを合わせるのが、見落とされがちな手順です。
実行時ツールはマシンごと・設定ごとです:インストールした PC の上でそのゲームを直すだけで、同じビルドを動かすほかの人にも同じ設定が要ります。RuneTranslate はプラグイン自身の _AutoGeneratedTranslations.txt を読み書きできるので、すでにそれを使っているプロジェクトが行き止まりになることはありません — どちらの手法がどこに向くかは比較ページをご覧ください。
ファイルの側で直す
RuneTranslate は同じフォールバックの道を、ゲームのデータの中で直接たどります。Unity のエクスポート時に、ゲームの TMP Settings、テンプレートとして使えるフォントアセット、そしてフォントを見つけ、ターゲットの文字体系をカバーする同梱フォントを軸に Dynamic モードの新しい TMP フォントアセットを作って、フォールバックリストへ追加します。既存のフォールバックの項目には手を触れず、ゲーム自身のフォントが主のままなので、日本語のゲームをタイ語へ翻訳しても、ラテン文字まわりは元のスタイルを保ち、タイ文字のグリフだけ注入したアセットから取られます。
これについて率直な注意が 2 つ。ゲームのアセットの同じコンテナーの中に TMP Settings、使えるフォントアセットのテンプレート、そして Font が揃っている必要があり、それらの置き方が違うゲームはこの仕組みから外れることがあります。そして注入が省略されたときは、緑のチェックで終わるのではなく、エクスポートが警告でそう伝えます — □で出荷される翻訳が、ちゃんと動く翻訳とまったく同じに見えてはいけないからです。その警告が出たときは、上の実行時ツールの道が次善策になります。
Ren'Py には TMP が無いので、別の仕組みで同じ結果を出します:RuneTranslate は CJK や非ラテンのフォントを書き出したビルドへ同梱し、Ren'Py 自身の config.font_replacement_map を通して登録できるので、ゲームのフォントでは足りない場所ではエンジンがそれを代わりに使います。
実務のチェックリスト
- 壊れ方を見る。均一な四角か、
?の文字か、崩れた文字か — 何かに触る前に、3 つのどれなのかを決めます。 ?なら、書き出したファイルを開きます。ファイルの中に疑問符があるなら、その文字は書き込みの時点で失われていて、どんなフォントも助けになりません。エンジンの文字列エンコーディングがターゲット言語を持てるかを確認してください。- 崩れた文字なら、フォントではなく、そのファイルについてエンジンが期待する文字コードを確認します。
- □なら、ファイルの中身が正しいことを確認し — ほぼ必ず正しいはずです — フォントの仕事として扱います。
- Unity では、ゲームのフォントを置き換えるよりフォールバックを足すほうを選びます。置き換えは、正しく描けていた文字列も含めてすべての見た目を変えてしまいます。
- TMP フォントの AssetBundle を使うなら、ゲームの Unity バージョンに合わせます。
- 書き出し直したら、最初のセリフだけでなく、失敗していたその文字を確認します。Latin Extended やタイ語の記号は、抜き取り検査を通り抜けて別の場所で失敗しがちです。
次に読むもの
Unity の手順全体 — Unity が実際に何を露出していて、何が手の届かないままか — は Unity ゲームを翻訳する方法 と Unity エンジンのページ をご覧ください。データではなく絵に焼き込まれたテキストは別の問題で、別のツールが要ります:画像翻訳をご覧ください。そのほか初回によくある驚きはよくある質問でカバーしています。
RuneTranslate はすべてのエンジンとすべてのプロバイダーが開いた状態で無料で使えます。うち 3 つは API キーがまったく要りません。ゲーム自身のファイルを解析し、手元に残るプレイできる翻訳済みビルドを書き出します。ダウンロードするか、対応エンジンをご覧ください。
RuneTranslate を試してみませんか?
無料プランですべてのエンジンとすべての翻訳プロバイダーが使えます。Supporter(月額 $3)ならフルスピードで翻訳できます。
Windows 版をダウンロード
