実装Read this article in English

ゲームテキストの文字エンコーディング — 何が壊れ、なぜファイルごとに確認すべきか

文字エンコーディングは、テキストファイルのディスク上のバイト列を、画面に表示される文字にどう対応させるかを決める層です。多くのゲームローカライズパイプラインが正当な理由でUTF-8に落ち着きます。特定の文字体系にしか対応していない古いエンコーディングと違い、プロジェクトが出荷しうるあらゆる言語のあらゆる文字を表現できるからです。しかし「パイプラインがUTF-8を選んでいる」ことと「そのパイプライン上のすべてのファイルが実際にUTF-8である」ことは別の話です。この2つのギャップに、特定の、繰り返し発生するバグの温床があります。

UTF-8をデフォルトにする理由

UTF-8はすべてのUnicodeコードポイントを1〜4バイトで表現し、最初の128コードポイントについては素のASCIIと後方互換性があり、Web上のテキストや多くの現代的なファイル形式で支配的なエンコーディングです。プロジェクト全体のデフォルトとして選ぶことで、ファイルごと言語ごとにエンコーディングを選び直すという判断そのものがまるごと不要になります。日本語用、ドイツ語用、韓国語用と別々のエンコーディングを選ぶ必要がありません。

BOMとは何か、なぜパーサーを驚かせるのか

BOM(バイト順マーク)は、一部のツールがファイルの先頭に書き込む、エンコーディングを示すための任意のバイト列です(UTF-8の場合はEF BB BF)。多くのテキストエディタでは見た目上は見えませんが、ファイル内には実在するバイト列です。それを想定していないパーサーは、このバイト列をコンテンツの1行目の一部として読み込んでしまいます。よくある症状は、CSVのヘッダーの最初の列名に、見えない余分な文字が先頭に付いた状態で読み込まれ、コードが期待するキーと一致しなくなるというものです。

-- ファイルの先頭にバイト列 EF BB BF があり、その後に続く:
key,en,ja
-- 素朴なパーサーは最初のヘッダーセルを "key" ではなく "\uFEFFkey" として読む

文字化けの正体

文字化けとは、あるエンコーディングで正しく書き込まれたファイルのバイト列を、読み込み側が別のエンコーディングとして解釈した結果表示されるものです。ディスク上のバイト列自体は一切変わっていません。変わったのは解釈だけです。だからこそ文字化けは、明らかにテキストが欠けているというより、無関係な記号の羅列に見えることが多いのです。デコーダーは何も出力できていないのではなく、間違った入力に対して有効な出力を(それらしく)生成してしまっているのです。

レガシーエンコーディングが現代のパイプラインに紛れ込む経路

最も多い経路はスプレッドシートからの書き出しです。スプレッドシートアプリケーションがCSVに書き出す際、常にUTF-8がデフォルトとは限りません。デフォルトはOSの地域設定に依存することがあり、日本語や中国語ロケールに設定されたマシンで書き出したファイルは、レガシーなマルチバイトエンコーディングで出力されることがあります。書き出しの過程で、それを行っている人にこれを知らせる仕組みは何もありません。自分のマシンでは問題なく開けるため、UTF-8を前提とするパイプラインに届いて初めて壊れて見えます。

見分けるべき症状

見た目でそれとわかる症状はいくつかあり、それぞれ違う問題を指しています。

  • 置換文字(黒いひし形の中に疑問符、U+FFFD) — デコーダーが、想定したエンコーディングでは無効なバイト列に遭遇し、推測せずにプレースホルダーに置き換えたことを示す
  • かなや漢字が無関係な記号に置き換わっている — 典型的な文字化けで、あるマルチバイトエンコーディングで正しく書かれたバイト列が別のエンコーディングとしてデコードされた状態
  • ファイルの1行目の先頭に見えない余分な文字がある — パーサーが取り除かなかったBOM
  • あるエディタでは正しく表示されるが別のエディタでは表示が崩れる — 実データの問題というより、少なくとも一方のツールが想定しているエンコーディングとファイルの実際のエンコーディングが異なる強いサイン

なぜエンコーディングは前提ではなくファイルごとに確認すべきか

すべてのファイルをUTF-8だと前提するパイプラインは、実際にUTF-8であるファイルに対しては正しく動作し、そうでないファイルは黙って壊します。ファイルの境界でエラーが出るわけではなく、下流の、多くの場合は問題のファイルが入ってきた地点から遠く離れた場所で、文字が化けて現れるだけです。多くの場合、入口はパイプラインの制御が及ばない人間による書き出し作業であるため、エンコーディングはプロジェクト全体で一度前提を置くのではなく、インポート時にファイルごとに検証する必要があります。

関連記事