Communitygithub.com

DrunkMan9o/agent-skills

把 Unity(Mono)游戏或任意 .NET 程序的界面文本汉化成简体中文,并在写入前用自审计引擎检查完整性、一致性、格式与结构风险。适用于文本硬编码在 Assembly-CSharp.dll 的 #US 字符串堆、没有本地化表、且内嵌动态字体的项目。Use when localizing a Unity/.NET game to Chinese, extracting hardcoded UI strings from #US heap, patching ldstr tokens, or auditing an existing Chinese localization for missing/untranslated/inconsistent text.

agent-skills 是什么?

agent-skills is a Claude Code agent skill that 把 Unity(Mono)游戏或任意 .NET 程序的界面文本汉化成简体中文,并在写入前用自审计引擎检查完整性、一致性、格式与结构风险。适用于文本硬编码在 Assembly-CSharp.dll 的 #US 字符串堆、没有本地化表、且内嵌动态字体的项目。Use when localizing a Unity/.NET game to Chinese, extracting hardcoded UI strings from #US heap, patching ldstr tokens, or auditing an existing Chinese localization for missing/untranslated/inconsistent text.

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/DrunkMan9o/agent-skills/tree/HEAD/skills/unity-zh-l10n

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Unity Chinese Localization Pipeline (with Self-Audit)

Turns the "extract → risk assessment → translation → write → self-audit" process into a reusable four-stage pipeline. The core is not translation itself, but first determining which strings cannot be translated — this is the only thing that will break the game.

When to Use It

Before using, confirm all three conditions. If any one is not met, do not proceed:

  1. Where is the text: A Mono/IL2CPP Unity game where text is hardcoded in the managed assembly (*_Data/Managed/Assembly-CSharp.dll) or resources.assets, with no Localization table, .po, .csv, or similar localization resources. If an existing localization table is available, use that directly; do not modify the assembly.
  2. Can get the original file: You are modifying an executable. You must have an unmodified original as a baseline (from a backup directory, or re-downloaded after Steam verification). Do not make in-place modifications without a baseline.
  3. Chinese can be displayed: The project uses embedded dynamic fonts (TMP or Legacy Font). Static fonts/atlas fonts require texture swapping; this pipeline only handles dynamic fonts.

IL2CPP (no Assembly-CSharp.dll, only GameAssembly.dll) is not supported; that requires Il2CppDumper + recompilation, discussed elsewhere.

Quick Start

# 0) Create project skeleton (generates l10n.json)
python <skill>/scripts/zhl10n.py init -C <your_project_dir>

# 1) Enumerate all ldstr sites (about 10s / 5000 entries)
python <skill>/scripts/zhl10n.py extract

# 2) View risk levels: which can be translated, which can only be directionally translated, and which must absolutely not be translated
python <skill>/scripts/zhl10n.py risk

# 3) Fill in trans_map.json / site_map.json, then write (does not modify originals, outputs to out/build/)
python <skill>/scripts/zhl10n.py apply

# 4) Self-audit (reads out/build artifacts by default)
python <skill>/scripts/zhl10n.py audit --fail-on P1

One-shot: zhl10n.py all (extract → apply → audit).

Install dependencies: pip install dnfile dncil UnityPy fonttools. Only audit works without UnityPy.

Responsibilities of Each Stage

StageEntry PointInputOutputBoundary of Responsibility
initzhl10n.py initTarget game pathl10n.json, empty tablesOnly generates configuration, does not touch game files
extractextract.pyOriginal DLL (+ optional assets)out/extract/sites.jsonEach item in sites.json includes str/type/method/token/offsetRead-only. Must read the unpatched original, otherwise already localized strings will be extracted
riskrisk.pysites.jsonConsole hierarchy statisticsOnly assesses, no table modifications
applyapply.pyOriginal DLL + translation tables + font subsetout/build/Assembly-CSharp.dllout/build/resources.assetsWrites new files; originals untouched (use --inplace to modify in place, which creates a .orig backup first). Any translation hitting a block rule is rejected and causes an error
auditaudit.pyArtifacts + translation tables + glossary + logsout/audit/audit.{json,md,html}fixlist.csvRead-only. Can run on any version of files, used for before/after comparison

Three-layer translation tables, each with distinct responsibilities:

  • trans_map.json — Global table, replaces strings at all occurrences. Only include strings where "translating to Chinese will not break logic."
  • site_map.json — Site-specific table, keyed by Type::Method, only modifies the ldstr at that location. Used when "the same word should be translated in menus but not in the input system."
  • enum_map.json — Enum name table, modifies field names in the #Strings heap. Used for display names from ToString() on enums.

Decision order: site-specific → global. If site-specific matches, global is not used.

Self-Audit

The self-audit is the focus of this pipeline. It independently reads artifacts after writing, not trusting the translation tables' own assertions.

Output

FileContent
audit.jsonFull structured results: each finding includes check/severity/count/items[]/hint
audit.mdHuman-readable summary, sorted by P0→P2
audit.htmlCollapsible single-file report
fixlist.csvList of items needing fixes, one per line, ready to use as a to-do list

Checks (31 items, 6 families)

CV Completeness — Anything missing

IDLevelCheckJudgment Basis
CV1P1Untranslated textSite is displayable + no translation in either table + not exempted by risk rules
CV1bP2Text that looks like UI text but was blockedIndicating engine constraint, not missing translation; requires site-specific translation and manual testing
CV2P1Empty/placeholder translationTranslation is empty or contains TODO/待译
CV3P1Long sentence translation identical to originalSuspects it wasn't translated at all
CV4P2Short word/symbol translation identical to originalIcon words, key names kept as original are usually correct
CV5P2Dead entries in site-specific tableDeclared but not found in original IL; method renamed or already covered by global table

CS Consistency — Are terms aligned

IDLevelCheck
CS1P2Same original translated differently in global vs. site-specific tables
CS2P1Mandatory glossary translation not followed
CS3P1Translation hits glossary forbidden words
CS4P2Duplicate keys (differing only by case/whitespace)
CS5P2Chinese punctuation style tending toward half-width

FM Format — Will concatenation break

IDLevelCheckConsequence
FM1P1Placeholder count mismatchString.Format throws exception or numbers disappear
FM1bP2Removed English plural placeholderCorrect action, just confirm no actual parameter was accidentally deleted
FM2P2Line break count mismatchTypography misalignment
FM3P1Unit/splicing fragment missing leading/trailing space1000coins concatenation
FM4P2Other leading/trailing space changesUsually okay between Chinese characters
FM5/FM6P2Half-width punctuation / consecutive whitespaceAesthetics
FM7P2Translation length expansion too largeOften due to writing explanations into labels

RK Risk — Will it break the game

IDLevelCheck
RK1P0High-risk string included in translation tables (paths, engine keys, regex, input action names…)
RK2P2Global table contains single identifiers, recommend moving to site-specific
RK3P1Translation table key contains Chinese, will cause errors on second application
RK4P2Translation contains residual English fragments

IG Structure — Is the file itself still valid

IDLevelCheck
IG0/IG1/IG2/IG4P0Target file missing, PE/metadata anomaly, #US unparseable, ldstr token invalid, metadata line count inconsistent with original
IG3P2Sites still in English after patching (expected whitelist, used for cross-checking)
IG5/IG6P1Font missing glyphs / font file unparseable

RT Runtime — Did the game itself complain

IDLevelCheck
RT1P1Exception stack trace found in Player.log (requires source.logs configuration)

Trigger Methods

Three scenarios, choose accordingly:

# 1. Self-check immediately after writing (most common in dev loop)
zhl10n.py audit

# 2. As a gate: CI or pre-release, fails with non-zero exit if threshold not met
zhl10n.py audit --fail-on P0      # Fail if any blocking level exists
zhl10n.py audit --fail-on P1      # Default strictness, recommended for release
zhl10n.py audit --fail-on P2      # Clear even hints

# 3. Regression comparison: more issues than last time after this change
zhl10n.py audit --baseline

apply also automatically runs audit at the end, printing the results — the write stage will not produce a bad package unknowingly.

How to Use Audit Results

  1. Look at P0 first. No P0 findings allowed for release — they mean "the game might not start."
  2. Then look at P1. CV1 is a list of missing translations, FM1 is a list of guaranteed crashes, RT1 is evidence that it already crashed.
  3. P2 serves as a checklist: CV1b tells you which are engine constraints (not your omissions), CV5/CS4 are junk entries you can delete directly.
  4. fixlist.csv — fix them one by one, re-run, until P0=0 and P1=0.

Configuration (l10n.json)

{
  "project": "GameName", "version": "1.0",
  "source": {
    "dll":     "…/Managed/Assembly-CSharp.dll",   // Only for locating; extract reads backup
    "backup":  "…/_Backup/Assembly-CSharp.dll",   // Must be unmodified original
    "assets":  ["…/resources.assets"],
    "logs":    ["…/Player.log"]                    // Optional, for 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": [] }
}

Two escape hatches, think of them first when getting errors:

  • risk.extra_patterns — Project-specific strings known not to be translatable (regex). E.g., ^Levels$
  • risk.extra_allow — Specific strings confirmed as not needing translation. Adding them here exempts them from missing translation checks

Glossary glossary.csv: en,zh,forbidden three columns; forbidden uses | to separate synonyms. CS2/CS3 depend on it; skip these items if no glossary.

Decision Rules: Which Strings Must Absolutely Not Be Translated

One-liner: Strings used as "keys" in code will break lookups if translated.

CategoryExampleWhat happens if translated
Resource paths"Levels", "UI/Fonts/X"Level table empty → save restore NRE → stuck on loading screen
Input actions/field namesMove/Up/ButtonKey bindings break
Save fieldslegacySkillPointCreditSave cannot be read
Enum namesDeposit (beyond ToString(), also Enum.Parse)Parsing fails
Regex patterns([0-9.]+)% more coinsHint text rewriting fails, falls back to English
Object/resource namesnew GameObject("Display glass")Usually doesn't crash, but pollutes resource names
Format strings{0:N0} / {1:N0}String.Format exception

Same form, different fate: Pause is a button text in the settings menu (should be translated), but an action name in GameInput (cannot be translated). The visual form alone can never distinguish this; only "which type and which method it appears in" can. This is what risk.py's _code_shape() does:

  • Literals in object construction methods (.ctor/Build/Initialize/Create/Ensure…) → names, do not translate
  • Literals in UI-drawing methods (Draw/OnGUI/Format/get_…) → copy, translate
  • Literals in comparison methods (Is/Has/Matches/Find…) → keys for matching, do not translate

After adding a new rule, must verify with a negative test: deliberately insert "Levels": "等级" into the translation table and confirm the audit reports P0.

Pitfalls with Fonts

Embedded dynamic fonts only support equal-length in-place replacement. Overwriting the entire resources.assets changes the Texture2D m_StreamData offsets, causing all textures to be misaligned.

Process: The font stage subsets according to the "actual set of Chinese characters used," converts to TTF (Unity's support for CFF/OTF is unstable), then fills into m_FontData. If the glyph count is insufficient, reduce font size or weight, never exceed original size.

Fonts like Bangers-Regular, which are only a few dozen KB, cannot accommodate Chinese — redirect the font path to another Chinese font (configured in l10n.json's font.targets), which is more reliable than forcibly embedding.

Pre-Delivery Checklist

[ ] audit --fail-on P1 passed
[ ] Actually launch the game, walk through every screen: main menu / settings / save list / in-game HUD / popups / error screens
[ ] Player.log has no new exceptions (RT1 = 0)
[ ] Backup original file location recorded in release notes (restoration depends on it)
[ ] Record artifact MD5 in version notes

Method for locating a broken modification: First, only restore the original DLL, keep everything else — if it works, the issue is definitely in the assembly patch; then only restore the DLL, keep everything else — if the issue recurs, confirm. Binary search is faster than reading code.

Reference Documents

  • references/flow.md — Detailed implementation and data structures for each stage
  • references/audit-spec.md — Full definitions, levels, and fix instructions for all 31 checks
  • references/risk-rules.md — Risk rules table and the real incidents behind them
  • assets/l10n.example.json / assets/glossary.example.csv — Configuration templates

Individual skills in this repo

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

相关技能