Communitygithub.com

Unity 汉化流水线(含自审计)

把 Unity(Mono)游戏或任意 .NET 程序的界面文本汉化成简体中文,并在写入前用自审计引擎检查完整性、一致性、格式与结构风险。适用于文本硬编码在 Assembly-CSharp.dll 的 #US 字符串堆、没有本地化表、且内嵌动态字体的项目。

Unity 汉化流水线(含自审计) とは?

このスキルは、Unity(Mono)ゲームや任意の .NET アプリケーションの UI テキストを簡体字中国語にローカライズするための、完全自動化されたパイプラインです。特に、テキストが Assembly-CSharp.dll 内の #US 文字列ヒープにハードコードされており、従来のローカライゼーションテーブルが存在しないプロジェクト向けに設計されています。抽出、リスク評価、翻訳、書き込み、そして自己監査という 5 つのフェーズから構成され、書き込み前に 31 項目の自動チェック(P0: 致命的、P1: 高、P2: 低)を実行して、翻訳漏れ、用語の不統一、書式の破壊、致命的な構造リスクを検出します。特に、「翻訳してはいけない文字列」(リソースパス、入力アクション名、フォーマット文字列など)をコードの形状から自動分類するリスクエンジンが特徴で、誤った翻訳によるゲームのクラッシュを防ぎます。dnfile、dncil、UnityPy などの Python ライブラリを使用し、CI/CD パイプラインへの統合も容易です。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/DrunkMan9o/agent-skills/tree/HEAD/skills/unity-zh-l10n

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Unity 漢化パイプライン(自己監査機能付き)

「抽出 → リスク判定 → 翻訳 → 書き込み → 自己監査」を再利用可能な 4 段階パイプラインとして構成します。 核となるのは翻訳そのものではなく、どの文字列を翻訳してはいけないかを事前に判断することです。これこそが、ゲームを壊す唯一の原因です。

使用するタイミング

使用前に以下の 3 つを確認し、1 つでも満たさない場合は使用を控えてください。

  1. テキストの所在: Mono/IL2CPP の Unity ゲームで、テキストがマネージドアセンブリ(*_Data/Managed/Assembly-CSharp.dll)または resources.assets にハードコードされており、Localization テーブル、.po、.csv などのローカライゼーションリソースが存在しないこと。 既存のローカライゼーションテーブルがある場合は、それを使用し、アセンブリを変更しないでください。
  2. オリジナルファイルを入手可能: 実行ファイルを変更するため、変更されていないオリジナルファイルをベースライン(バックアップディレクトリ、Steam 検証後の再ダウンロードなど)として用意できること。ベースラインがない場合は、その場での変更を行わないでください。
  3. 中国語が表示可能: プロジェクトに動的フォント(TMP または Legacy Font)が埋め込まれていること。静的フォント/スプライトフォントの中国語化にはテクスチャの差し替えが必要であり、このパイプラインは動的フォントのみを処理します。

IL2CPP(Assembly-CSharp.dll がなく、GameAssembly.dll のみ)はサポート対象外です。Il2CppDumper + 再コンパイルが必要な場合は、別途対応します。

クイックスタート

# 0) プロジェクトの骨格を作成(l10n.json を生成)
python <skill>/scripts/zhl10n.py init -C あなたのプロジェクトディレクトリ

# 1) すべての ldstr サイトをリストアップ(約 10 秒 / 5000 件)
python <skill>/scripts/zhl10n.py extract

# 2) リスクレベルを確認:翻訳可能、限定翻訳のみ、絶対に翻訳不可
python <skill>/scripts/zhl10n.py risk

# 3) trans_map.json / site_map.json を編集し、書き込みを実行(オリジナルは変更せず、out/build/ に出力)
python <skill>/scripts/zhl10n.py apply

# 4) 自己監査(デフォルトで out/build の成果物を読み込む)
python <skill>/scripts/zhl10n.py audit --fail-on P1

ワンストップ実行:zhl10n.py all(extract → apply → audit)。

依存関係のインストール:pip install dnfile dncil UnityPy fonttools。audit のみの場合は UnityPy がなくても使用できます。

各フェーズの責務

フェーズエントリポイント入力出力責任範囲
initzhl10n.py initターゲットゲームのパスl10n.json、空のテーブル設定を生成するのみで、ゲームファイルには触れない
extractextract.pyオリジナル DLL(+ オプションの assets)out/extract/sites.jsonsites.json の各項目に str/type/method/token/offset を含む読み取り専用。パッチ未適用のオリジナルを読み取る必要がある。そうしないと、既に漢化された文字列が抽出される
riskrisk.pysites.jsonコンソールへのレベル別統計出力判定のみ。テーブルは一切変更しない
applyapply.pyオリジナル DLL + 翻訳テーブル + フォントサブセットout/build/Assembly-CSharp.dllout/build/resources.assets新しいファイルに書き込み、オリジナルは変更しない(--inplace でその場で変更する場合は、事前に .orig を保存)。ブロックルールに一致する翻訳は書き込みを拒否し、エラーを報告する
auditaudit.py成果物 + 翻訳テーブル + 用語集 + ログout/audit/audit.{json,md,html}fixlist.csv読み取り専用。任意のバージョンのファイルに対して実行でき、新旧比較に使用できる

3 層の翻訳テーブル。それぞれの責務は異なります。

  • trans_map.json —— グローバルテーブル。文字列で置換し、すべての出現箇所を置き換えます。'中国語に翻訳してもロジックを壊さない' 文字列のみを配置します。
  • site_map.json —— サイト固有テーブル。キーは 型::メソッド で、その場所の ldstr のみを変更します。'同じ単語でも、メニューでは翻訳すべきだが、入力システムでは翻訳すべきでない' 場合に使用します。
  • enum_map.json —— 列挙型名前テーブル。#Strings ヒープ内のフィールド名を変更します。ToString() から出力される列挙型の表示名に使用します。

判定順序: サイト固有 → グローバル。サイト固有でヒットした場合、グローバルは適用されません。

自己監査

自己監査は、このパイプラインの中核です。書き込み後に成果物を独立して読み取り、翻訳テーブル自身の主張を信用しません。

出力

ファイル内容
audit.json完全な構造化結果。各 finding に check/severity/count/items[]/hint を含む
audit.md人間向けの要約。P0→P2 の順にソート
audit.html折りたたみ可能な単一ファイルレポート
fixlist.csv修正が必要な項目のリスト。1 行 1 項目で、TODO リストとして直接使用可能

チェック項目(31 項目、6 カテゴリ)

CV 完全性——漏れがないか

番号レベルチェック判定基準
CV1P1未翻訳テキストサイトが表示可能 + 両方のテーブルに翻訳がない + リスクルールで許可されていない
CV1bP2UI テキストのように見えるがブロックされているエンジンの制約によるもので、翻訳漏れではないことを示す。サイト固有の翻訳と実機テストが必要
CV2P1空の翻訳 / プレースホルダー翻訳翻訳が空、または TODO/未翻訳 を含む
CV3P1長い文の翻訳が原文と完全に一致翻訳されていない可能性が高い
CV4P2短い単語/記号の翻訳が原文と一致アイコンフォント、キー名などの原文保持は通常正しい
CV5P2サイト固有テーブルのデッドエントリ宣言されているが、オリジナルの IL 内に見つからない。メソッド名が変更されたか、グローバルテーブルで上書きされている

CS 一貫性——用語が一致しているか

番号レベルチェック
CS1P2同一原文がグローバルテーブルとサイト固有テーブルで異なる翻訳
CS2P1用語集の強制翻訳が遵守されていない
CS3P1翻訳が用語集の禁止語にヒット
CS4P2重複キー(大文字/小文字、空白のみが異なる)
CS5P2中国語の句読点が半角に偏っている

FM フォーマット——連結が壊れていないか

番号レベルチェック結果
FM1P1プレースホルダーの不整合String.Format が例外をスローするか、数字が消失する
FM1bP2英語の複数形プレースホルダーを削除正しい方法。実際のパラメータを誤って削除していないことを確認するためのヒントのみ
FM2P2改行数の不一致レイアウトのずれ
FM3P1単位/結合フラグメントの先頭/末尾スペースが欠落1000coins のようにくっつく
FM4P2その他の先頭/末尾スペースの変更中国語のテキスト間では通常問題なし
FM5/FM6P2半角句読点 / 連続する空白見た目の問題
FM7P2翻訳後の長さが過度に増加多くの場合、説明文がラベルに書き込まれている

RK リスク——ゲームが壊れる可能性があるか

番号レベルチェック
RK1P0高リスク文字列が翻訳テーブルに含まれている(パス、エンジンキー、正規表現、入力アクション名など)
RK2P2グローバルテーブルに単一識別子が含まれている。サイト固有テーブルへの変更を推奨
RK3P1翻訳テーブルのキーに中国語が含まれている。2 回目の適用でエラーが発生する
RK4P2翻訳に英語の断片が残っている

IG 構造——ファイル自体がまだ有効か

番号レベルチェック
IG0/IG1/IG2/IG4P0ターゲットファイルの欠落、PE/メタデータの異常、#US の解析不能、ldstr トークンが不正、メタデータの行数がオリジナルと一致しない
IG3P2パッチ後も英語のままのサイト(想定内のホワイトリスト。確認用)
IG5/IG6P1フォントの文字欠け / フォントファイルの解析不能

RT 実行時——ゲームが自ら問題を報告していないか

番号レベルチェック
RT1P1Player.log に例外スタックトレースが出現(source.logs 設定が必要)

トリガー方法

3 つの方法からシナリオに応じて選択します。

# 1. 書き込み直後に自己チェック(開発ループで最も一般的)
zhl10n.py audit

# 2. ゲートとして使用:CI またはリリース前に、基準を満たさない場合は非ゼロで終了
zhl10n.py audit --fail-on P0      # ブロッキングレベルがあれば失敗
zhl10n.py audit --fail-on P1      # デフォルトの厳格さ。リリースにはこれを使用推奨
zhl10n.py audit --fail-on P2      # ヒントもクリアする必要あり

# 3. 回帰比較:今回の変更で前回より増えた問題を確認
zhl10n.py audit --baseline

apply の終了時にも自動的に audit が 1 回実行され、結果が出力されます。書き込みフェーズで、知らないうちに不良パッケージが生成されることはありません。

監査結果の活用方法

  1. まず P0 を確認します。P0 が 1 つでもあるとリリース不可です。'ゲームが起動しない可能性がある' ことを意味します。
  2. 次に P1 を確認します。CV1 は翻訳漏れリスト、FM1 はクラッシュ必須リスト、RT1 はすでにクラッシュしているという証拠です。
  3. P2 はチェックリストとして使用します。CV1b はエンジンの制約によるもので、あなたの漏れではないことを示します。CV5/CS4 は直接削除可能な不要エントリです。
  4. fixlist.csv を 1 行ずつ修正し、修正後再実行して、P0=0 かつ P1=0 になるまで繰り返します。

設定(l10n.json)

{
  "project": "ゲーム名", "version": "1.0",
  "source": {
    "dll":     "…/Managed/Assembly-CSharp.dll",   // 位置特定のみに使用。extract は backup を読み取る
    "backup":  "…/_Backup/Assembly-CSharp.dll",   // 変更されていないオリジナルである必要がある
    "assets":  ["…/resources.assets"],
    "logs":    ["…/Player.log"]                    // オプション。RT1 用
  },
  "output": { "dir": "out" },
  "text":   { "map": "trans_map.json", "site_map": "site_map.json", "enum_map": "enum_map.json" },
  "glossary": "glossary.csv",
  "font":   { "subset": "font/zh-subset.ttf", "source": "font/NotoSansSC-Bold.otf", "targets": {} },
  "audit":  { "severity_fail": "P1", "max_expansion_ratio": 2.5, "max_expansion_abs": 40 },
  "risk":   { "extra_patterns": [], "extra_allow": [] }
}

2 つのエスケープハッチ。エラーが発生した場合は、まずこれらを検討します。

  • risk.extra_patterns —— プロジェクト内で翻訳できないことがわかっている文字列(正規表現)。例:^Levels$
  • risk.extra_allow —— 翻訳する必要がないことが確認されている特定の文字列。これに追加すると、翻訳漏れとしてカウントされなくなります

用語集 glossary.csv:en,zh,forbidden の 3 列。forbidden は | で同義語を区切ります。CS2/CS3 はこれに依存します。存在しない場合は、これらの項目はスキップされます。

判定ルール:絶対に翻訳してはいけない文字列

一言で言えば:コード内で「キー」として使用される文字列は、翻訳すると見つからなくなります。

カテゴリ例翻訳した場合の影響
リソースパス"Levels"、"UI/Fonts/X"レベルテーブルが空 → セーブデータ復元時に NRE → ローディング画面でフリーズ
入力アクション/フィールド名Move/Up/Buttonキーバインドが機能しなくなる
セーブデータフィールドlegacySkillPointCreditセーブデータが読み込めなくなる
列挙型名Deposit(ToString() 以外に Enum.Parse もある)パースに失敗する
正規表現パターン([0-9.]+)% more coinsヒントテキストの書き換えが機能せず、英語に戻る
オブジェクト/リソース名new GameObject("Display glass")通常はクラッシュしないが、リソース名を汚染する
フォーマット文字列{0:N0} / {1:N0}String.Format が例外をスローする

同じ形でも異なる運命:Pause は設定メニューではボタンテキスト(翻訳すべき)だが、GameInput ではアクション名(翻訳すべきでない)。 テキストの見た目だけでは決して区別できません。「どの型、どのメソッドに出現するか」に依存します。これが risk.py の _code_shape() の役割です。

  • オブジェクトを作成するメソッド(.ctor/Build/Initialize/Create/Ensure…)内のリテラル → 名前、翻訳しない
  • UI を描画するメソッド(Draw/OnGUI/Format/get_…)内 → テキスト、翻訳する
  • 比較/検索を行うメソッド(Is/Has/Matches/Find…)内 → マッチングに使用されるキー、翻訳しない

新しいルールを追加した後は、ネガティブテストで検証する必要があります。翻訳テーブルに意図的に "Levels": "等级" を追加し、監査で P0 が報告されることを確認します。

フォントの注意点

埋め込み動的フォントは等長インプレース置換のみ可能です。resources.assets 全体を書き換えると、Texture2D の m_StreamData オフセットが変更され、テクスチャがすべてずれます。

フロー:font フェーズでは、「実際に使用される漢字のセット」に基づいてサブセット化し、TTF(Unity は CFF/OTF のサポートが不安定)に変換し、m_FontData に書き戻します。グリフ数が足りない場合は、フォントサイズを小さくするか、ウェイトを圧縮します。元のサイズを超えることはできません。

Bangers-Regular のような数十 KB しかないフォントには中国語を収容できません。フォントパスを別の中華フォントにリダイレクトする(l10n.json の font.targets で設定)方が、無理に詰め込むより信頼性が高くなります。

納品前チェックリスト

[ ] audit --fail-on P1 に合格
[ ] 実際にゲームを起動し、画面ごとに確認:メインメニュー / 設定 / セーブデータ一覧 / ゲーム内 HUD / ポップアップ / エラー表示
[ ] Player.log に新しい例外がない(RT1 が 0)
[ ] バックアップオリジナルの場所をリリースノートに記載(復元はこれに依存)
[ ] 成果物の MD5 を記録し、バージョンリリースノートに記載

破損箇所の特定方法:まず DLL だけをオリジナルに戻し、他はそのままにする。正常に動作すれば、問題はアセンブリパッチにあることが確定します。次に DLL だけをパッチ済みのものに戻し、他はそのままにして再現するか確認します。二分法はコードを読むより高速です。

参考資料

  • references/flow.md —— 各フェーズの詳細な実装とデータ構造
  • references/audit-spec.md —— 31 項目のチェックの完全な定義、レベル、修正手順
  • references/risk-rules.md —— リスクルールテーブルとその背後にある実際のインシデント
  • assets/l10n.example.json / assets/glossary.example.csv —— 設定テンプレート

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

関連スキル