Unity-Lokalisierungspipeline (mit Selbstprüfung)
Eine wiederverwendbare Vier-Phasen-Pipeline für: Extraktion → Risikobewertung → Übersetzung → Anwendung → Selbstprüfung. Der Kern liegt nicht in der Übersetzung selbst, sondern darin, zuerst zu entscheiden, welche Strings nicht übersetzt werden dürfen – das ist der einzige Punkt, an dem das Spiel kaputtgehen kann.
Wann man sie verwendet
Überprüfe vor der Nutzung drei Bedingungen:
- Wo ist der Text?: Unity-Spiel mit Mono/IL2CPP, Text ist hartcodiert in der verwalteten Assembly (
*_Data/Managed/Assembly-CSharp.dll) oderresources.assets, ohneLocalization-Tabelle,.po- oder.csv-Ressourcen. Bei vorhandener Lokalisierungstabelle direkt damit arbeiten, die Assembly nicht ändern. - Zugriff auf Originaldatei: Änderungen betreffen ausführbare Dateien, daher muss eine unveränderte Originaldatei als Basis (Backup, neu heruntergeladen nach Steam-Validierung) vorhanden sein. Ohne Basis keine direkten Änderungen.
- Chinesisch wird angezeigt: Das Projekt verwendet eine eingebettete dynamische Schriftart (TMP oder Legacy
Font). Bei statischen Schriftarten/Bitmap-Schriften müsste die Textur ersetzt werden – diese Pipeline verarbeitet nur dynamische Schriftarten.
IL2CPP (keine Assembly-CSharp.dll, nur GameAssembly.dll) wird nicht unterstützt – hierfür ist Il2CppDumper + Neukompilierung nötig.
Schnellstart
# 0) Projektgerüst erstellen (l10n.json generieren)
python <skill>/scripts/zhl10n.py init -C dein_projektverzeichnis
# 1) Alle ldstr-Stellen erfassen (ca. 10 s / 5000 Einträge)
python <skill>/scripts/zhl10n.py extract
# 2) Risikostufen anzeigen: welche übersetzbar, welche nur gezielt, welche absolut nicht
python <skill>/scripts/zhl10n.py risk
# 3) trans_map.json / site_map.json füllen, dann anwenden (Original unverändert, Ausgabe in out/build/)
python <skill>/scripts/zhl10n.py apply
# 4) Selbstprüfung (liest out/build-Ergebnisse)
python <skill>/scripts/zhl10n.py audit --fail-on P1
Kompletter Durchlauf: zhl10n.py all (extract → apply → audit).
Abhängigkeiten installieren: pip install dnfile dncil UnityPy fonttools. Nur audit erfordert kein UnityPy.
Verantwortlichkeiten der Phasen
| Phase | Einstieg | Eingabe | Ausgabe | Verantwortungsbereich |
|---|---|---|---|---|
| init | zhl10n.py init | Ziel-Spiel-Pfad | l10n.json, leere Tabellen | Nur Konfiguration erzeugen, Spiel-Dateien nicht anfassen |
| extract | extract.py | Originale DLL (+ optionale assets) | out/extract/sites.jsonJeder Eintrag mit str/type/method/token/offset | Nur lesen. Muss ungepatchtes Original lesen, sonst werden bereits lokalisierte Strings extrahiert |
| risk | risk.py | sites.json | Konsolenstatistik der Stufen | Nur bewerten, keine Tabellen ändern |
| apply | apply.py | Originale DLL + Übersetzungstabellen + Schriftarten-Subset | out/build/Assembly-CSharp.dllout/build/resources.assets | Neue Dateien schreiben, Original unverändert (--inplace erstellt .orig-Backup). Übersetzungen, die auf Blocklist treffen, werden abgelehnt und gemeldet |
| audit | audit.py | Produkte + Übersetzungstabellen + Glossar + Logs | out/audit/audit.{json,md,html}fixlist.csv | Nur lesen. Kann auf beliebigen Versionen laufen, für Vorher-Nachher-Vergleich |
Drei Übersetzungstabellen, unterschiedliche Aufgaben:
trans_map.json– Globale Tabelle: Ersetzt Strings an allen Vorkommen. Nur Strings, die „als Chinesisch übersetzt die Logik nicht beschädigen“.site_map.json– Gezielte Tabelle: Schlüssel istTyp::Methode, ändert nur dasldstran dieser Stelle. Für Fälle wie „gleiches Wort im Menü übersetzbar, im Eingabesystem nicht“.enum_map.json– Enumerationsnamen-Tabelle: Ändert Feldnamen im#Strings-Heap. Für vonToString()zurückgegebene Anzeigenamen von Enums.
Prüfreihenfolge: Gezielt → Global. Wenn gezielt zutrifft, wird global nicht mehr angewendet.
Selbstprüfung (Audit)
Die Selbstprüfung ist der Schwerpunkt dieser Pipeline. Sie liest nach dem Schreiben die Produkte unabhängig und vertraut nicht den Aussagen der Übersetzungstabellen.
Ausgabe
| Datei | Inhalt |
|---|---|
audit.json | Vollständige strukturierte Ergebnisse: jeder Fund mit check/severity/count/items[]/hint |
audit.md | Menschlich lesbare Zusammenfassung, sortiert nach P0→P2 |
audit.html | Einklappbarer Einzeldatei-Report |
fixlist.csv | Liste der zu behebenden Probleme, eine Zeile pro Problem, direkt als Aufgabenliste nutzbar |
Prüfpunkte (31 Punkte, 6 Familien)
CV Vollständigkeit – Fehlende Übersetzungen
| Nummer | Stufe | Prüfung | Entscheidungsgrundlage |
|---|---|---|---|
| CV1 | P1 | Unübersetzter Text | Anzeigbarer String + keine Übersetzung in beiden Tabellen + nicht von Risikoregeln ausgenommen |
| CV1b | P2 | Text, der wie UI-Text aussieht, blockiert wurde | Erklärung: Engine-Einschränkung, kein fehlender Übersetzungsfehler; gezielte Übersetzung + Test nötig |
| CV2 | P1 | Leere / Platzhalter-Übersetzung | Übersetzung leer oder enthält TODO/待译 |
| CV3 | P1 | Übersetzung von langen Strings identisch mit Original | Verdacht: Gar nicht übersetzt |
| CV4 | P2 | Übersetzung von kurzen Wörtern/Symbolen identisch mit Original | Icons, Tastennamen belassen meist richtig |
| CV5 | P2 | Tote Einträge in gezielfter Tabelle | Deklariert, aber im Original-IL nicht gefunden; Methoden umbenannt oder von globaler Tabelle überdeckt |
CS Konsistenz – Terminologie stimmt nicht überein
| Nummer | Stufe | Prüfung | |---|---|---|---| | CS1 | P2 | Gleicher Originaltext in globaler und gezielter Tabelle unterschiedlich übersetzt | | CS2 | P1 | Pflichtübersetzung aus Glossar nicht eingehalten | | CS3 | P1 | Übersetzung enthält verbotene Glossarbegriffe | | CS4 | P2 | Doppelte Schlüssel (nur Groß-/Kleinschreibung / Leerzeichen unterschiedlich) | | CS5 | P2 | Chinesische Satzzeichen teilweise halbbreit |
FM Format – Verkettungen könnten brechen
| Nummer | Stufe | Prüfung | Konsequenz |
|---|---|---|---|
| FM1 | P1 | Platzhalter nicht konserviert | String.Format löst Ausnahme aus oder Zahlen verschwinden |
| FM1b | P2 | Entfernung englischer Plural-Platzhalter | Korrektes Vorgehen, nur Hinweis, dass nicht versehentlich echte Parameter gelöscht wurden |
| FM2 | P2 | Unterschiedliche Zeilenumbrüche | Layout verschoben |
| FM3 | P1 | Einheit/Teilstück fehlende führende/nachfolgende Leerzeichen | 1000coins verklebt |
| FM4 | P2 | Andere Leerzeichen am Anfang/Ende geändert | Zwischen Chinesisch meist egal |
| FM5/FM6 | P2 | Halbbreite Satzzeichen / aufeinanderfolgende Leerzeichen | Ästhetik |
| FM7 | P2 | Übersetzung zu stark verlängert | Meist wird Erklärung ins Label geschrieben |
RK Risiko – Könnte das Spiel zum Absturz bringen
| Nummer | Stufe | Prüfung | |---|---|---|---| | RK1 | P0 | Hochrisiko-Strings in Übersetzungstabelle aufgenommen (Pfade, Engine-Keys, Regex, Eingabeaktionen…) | | RK2 | P2 | Globale Tabelle enthält Einzelidentifikator, besser gezielt | | RK3 | P1 | Übersetzungstabellen-Key enthält Chinesisch, zweite Anwendung fehlerhaft | | RK4 | P2 | Übersetzung enthält englische Fragmente |
IG Struktur – Ist die Datei selbst noch gültig
| Nummer | Stufe | Prüfung |
|---|---|---|---|
| IG0/IG1/IG2/IG4 | P0 | Ziel-Datei fehlt, PE/Metadaten anomal, #US nicht lesbar, ldstr-Token ungültig, Metadaten-Zeilenanzahl abweichend |
| IG3 | P2 | Nach Patch noch englische Stellen (erwartete Whitelist, zum Abgleich) |
| IG5/IG6 | P1 | Schriftart fehlende Zeichen / Schriftart-Datei nicht lesbar |
RT Laufzeit – Spiel hat selbst etwas gestanden
| Nummer | Stufe | Prüfung |
|---|---|---|---|
| RT1 | P1 | Ausnahme-Stack in Player.log (benötigt source.logs Konfiguration) |
Auslösemethoden
# 1. Sofort nach dem Schreiben (im Entwicklungszyklus am häufigsten)
zhl10n.py audit
# 2. Als Gate: CI oder vor Veröffentlichung, nicht bestanden führt zu Exit-Code != 0
zhl10n.py audit --fail-on P0 # Bestehen nur ohne blockierende Fehler
zhl10n.py audit --fail-on P1 # Standard-Strenge, für Veröffentlichung empfohlen
zhl10n.py audit --fail-on P2 # Auch Hinweise müssen beseitigt werden
# 3. Regression: Diese Änderung vs. letzte – welche neuen Probleme
zhl10n.py audit --baseline
apply führt nach dem Schreiben automatisch audit aus und gibt die Ergebnisse aus – so wird kein defektes Paket unwissentlich produziert.
Wie man Audit-Ergebnisse nutzt
- Zuerst
P0ansehen. Kein P0 darf veröffentlicht werden, es bedeutet „Spiel könnte nicht starten“. - Dann
P1ansehen.CV1sind fehlende Übersetzungen,FM1sind Absturzgarantien,RT1ist Beweis für bereits aufgetretene Abstürze. P2als Checkliste nutzen:CV1bzeigt Engine-Einschränkungen, keine fehlenden Übersetzungen;CV5/CS4sind direkt löschbare Müll-Einträge.fixlist.csvdirekt Zeile für Zeile abarbeiten, dann erneut laufen lassen, bisP0=0 und P1=0.
Konfiguration (l10n.json)
{
"project": "Spielname", "version": "1.0",
"source": {
"dll": "…/Managed/Assembly-CSharp.dll", // Nur zur Orientierung, extract liest backup
"backup": "…/_Backup/Assembly-CSharp.dll", // Muss unverändertes Original sein
"assets": ["…/resources.assets"],
"logs": ["…/Player.log"] // Optional, für 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": [] }
}
Zwei Ausweichmöglichkeiten bei Fehlern:
risk.extra_patterns– Projektbekannte, nicht übersetzbare Strings (Regex). Beispiel:^Levels$risk.extra_allow– Bestätigte, nicht zu übersetzende Strings. Aufnahme gilt nicht als fehlende Übersetzung
Glossar glossary.csv: en,zh,forbidden drei Spalten, forbidden mit | getrennte Synonyme. CS2/CS3 benötigen es, sonst werden diese Prüfungen übersprungen.
Entscheidungsregeln: Welche Strings dürfen auf keinen Fall übersetzt werden
Kurzversion: Strings, die im Code als „Schlüssel“ verwendet werden, sind nach Übersetzung unauffindbar.
| Kategorie | Beispiel | Folge bei Übersetzung |
|---|---|---|
| Ressourcenpfade | "Levels", "UI/Fonts/X" | Leveltabelle leer → NRE bei Speicherwiederherstellung → Hängenbleiben im Ladebildschirm |
| Eingabeaktionen/Feldnamen | Move/Up/Button | Tastenbelegungen ungültig |
| Speicherfelder | legacySkillPointCredit | Speicherstand nicht lesbar |
| Enumerationsnamen | Deposit (neben ToString() gibt es Enum.Parse) | Parse-Fehler |
| Regex-Muster | ([0-9.]+)% more coins | Textüberarbeitung fehlgeschlagen, zurück zu Englisch |
| Objekt-/Ressourcennamen | new GameObject("Display glass") | Normalerweise kein Absturz, aber verunreinigt Ressourcennamen |
| Format-Strings | {0:N0} / {1:N0} | String.Format-Ausnahme |
Gleiche Form, unterschiedliche Bedeutung: Pause im Einstellungsmenü ist Schaltflächentext (übersetzbar), in GameInput ist es Aktionsname (nicht übersetzbar).
Nur durch den Kontext „in welchem Typ, welcher Methode“ unterscheidbar. Das macht risk.py's _code_shape():
- Methoden, die Objekte erstellen (
.ctor/Build/Initialize/Create/Ensure…): Literale → Namen, nicht übersetzen - Methoden, die UI zeichnen (
Draw/OnGUI/Format/get_…): Texte, übersetzen - Methoden, die vergleichen (
Is/Has/Matches/Find…): Werte zum Abgleich, nicht übersetzen
Nach Hinzufügen neuer Regeln muss mit Negativtest validiert werden: Absichtlich "Levels": "等级" in die Übersetzungstabelle einfügen, Audit muss P0 melden.
Schriftart-Problematik
Eingebettete dynamische Schriftarten können nur gleichlang in situ ersetzt werden. Ein vollständiges Neuschreiben von resources.assets verändert die Texture2D m_StreamData-Offsets, Texturen werden komplett verschoben.
Vorgehen: In der font-Phase ein Subset nach „tatsächlich benötigtem Zeichensatz“ erstellen, in TTF konvertieren (Unity unterstützt CFF/OTF instabil), dann in m_FontData einsetzen. Wenn die Anzahl der Glyphen nicht ausreicht, Schriftgröße reduzieren oder Gewicht verringern, darf die Originalgröße nicht überschreiten.
Bangers-Regular (nur wenige KB) kann kein Chinesisch aufnehmen – besser den Schriftpfad auf eine andere chinesische Schriftart umleiten (in l10n.json unter font.targets konfigurieren), das ist zuverlässiger als gewaltsames Einfügen.
Checkliste vor Auslieferung
[ ] audit --fail-on P1 bestanden
[ ] Spiel tatsächlich starten, jeden Bildschirm durchgehen: Hauptmenü / Einstellungen / Speicherliste / In-Game-HUD / Dialoge / Fehler
[ ] Player.log keine neuen Ausnahmen (RT1 = 0)
[ ] Backup-Originalpfad in Release Notes dokumentieren (nur so kann man wiederherstellen)
[ ] MD5 der Produkte notieren, in Versionshinweise aufnehmen
Fehlersuche bei Fehlern: Zuerst nur die Original-DLL zurücklegen, andere Dateien behalten – wenn das normal läuft, liegt das Problem im Assembly-Patch. Dann nur die DLL patchen, andere Original. Binäre Suche ist schneller als Code-Lesen.
Referenzdokumente
references/flow.md– Detaillierte Implementierung und Datenstrukturen jeder Phasereferences/audit-spec.md– Vollständige Definition der 31 Prüfungen, Stufen, Reparaturanleitungenreferences/risk-rules.md– Risikoregel-Tabelle und reale Vorfälle dahinterassets/l10n.example.json/assets/glossary.example.csv– Konfigurationsvorlagen