Übersetzter Text erscheint als Kästchen
Du hast das Spiel übersetzt, der Export lief sauber durch, und die Dialogbox ist voller □□□. Das ist fast nie ein Übersetzungsproblem — es ist ein Font-Problem, und es wird anders behoben als die zwei Fehler, mit denen man es verwechselt. So unterscheidest du eine fehlende Glyphe von einem verworfenen Zeichen und von Mojibake, und so wird jeder Fall in Unity, TextMeshPro, Ren'Py und den Laufzeit-Werkzeugen behoben.
Der Export ist fertig, das Spiel gestartet, und jede Dialogzeile ist eine Reihe identischer leerer Rechtecke. An der Übersetzung ist nichts falsch. Der Text in der Datei ist genau das, was der Anbieter zurückgegeben hat, und wenn du diese Datei in einem Texteditor öffnest, liest sie sich korrekt. Gescheitert ist der letzte Schritt der Kette: Der Font, den das Spiel dem Renderer übergibt, hat für diese Zeichen keine Zeichnung.
Was ein Tofu-Kästchen tatsächlich ist
Ein Font ist eine Nachschlagetabelle von Unicode-Codepoint zu Form. Wird ein Renderer gebeten, einen Codepoint zu zeichnen, den der Font nicht enthält, setzt er die Ersatzglyphe des Fonts ein — meist ein leeres oder durchgestrichenes Rechteck, formal .notdef genannt und überall sonst als Tofu-Kästchen bekannt. Ein Kästchen entspricht einem Zeichen, das der Font nicht zeichnen konnte. Das ist der ganze Mechanismus.
Daraus folgen zwei Dinge, und beide helfen bei der Diagnose. Erstens entspricht die Zahl der Kästchen der Zahl der Zeichen: Fünf thailändische Zeichen ergeben fünf Kästchen, nicht eine kürzere verstümmelte Folge. Zweitens ist der String im Speicher intakt — das Spiel kann ihn weiterhin vermessen, vergleichen, speichern und neu laden. Nur die Pixel fehlen. Deshalb kann ein übersetztes Menü völlig unleserlich sein und sich trotzdem korrekt bedienen lassen.
Drei Symptome, drei verschiedene Probleme
Die meiste Verwirrung um "meine Übersetzung ist kaputt" entsteht, weil jemand die Lösung für eines dieser Probleme auf ein anderes anwendet. Lies die Form des Schadens, bevor du irgendetwas änderst.
- □□□-Kästchen — die Zeichen stimmen, der Font kann sie nicht zeichnen. Repariere den Font.
- ???? — die Zeichen sind weg. Etwas hat den String in einen engeren Zeichensatz umkodiert und alles ersetzt, was es nicht darstellen konnte. Kein Font holt sie zurück. Repariere die Kodierung oder wähle eine Zielsprache, die das Format halten kann.
- Verstümmeltes Latein wie
テã‚ストoderテキストdargestellt alsテキスト— Mojibake. Die Bytes stimmen, der Dekoder ist falsch: In einer Kodierung geschriebener Text wird als eine andere gelesen. Repariere, welche Kodierung gelesen wird.
Eine schnelle Abgrenzung: Kästchen und Mojibake lassen die Byte-Zahl beide ungefähr proportional zum Original, aber Mojibake erzeugt sichtbaren, wechselnden Zeichenmüll, während Tofu identische, gleichförmige Rechtecke erzeugt. Gleichförmig heißt "Font". Wechselnd heißt "Kodierung".
Kästchen: eine fehlende Glyphe
Das Spiel wurde mit einem Font ausgeliefert, der nur abdeckt, was es brauchte. Nichts in der Kette ist kaputt — du hast einen Codepoint verlangt, den der Grafiker nie eingeschlossen hat. Die Lösung besteht immer darin, dem Renderer einen Font mit der Glyphe zu geben, entweder indem du den Font ersetzt oder indem du einen Fallback hinzufügst, den der Renderer heranzieht, wenn der Hauptfont nichts liefert.
Fragezeichen: Die Zeichen wurden verworfen
Wenn ein String in ein Format zurückgeschrieben werden muss, das Text in einer alten Ein- oder Doppelbyte-Kodierung ablegt, muss jedes Zeichen außerhalb dieser Kodierung zu irgendetwas werden. Üblicherweise ist dieses Irgendetwas ?. Shift-JIS und cp932 können Japanisch, ASCII und sehr wenig sonst halten; ein striktes ASCII-Feld hält weder Griechisch noch Kyrillisch, Thai oder CJK. Das ist eine Eigenschaft des Dateiformats, nicht des Fonts, und es passiert beim Export — wenn das Spiel die Datei liest, ist die Information längst weg.
RuneTranslate hat eine Engine, bei der das eine reale Grenze ist, und sie wird genannt statt versteckt: YU-RIS kodiert seine Strings als cp932 neu, was Englisch und andere lateinische Ausgaben gut trifft, Thai aber als ? ausliefert. Nicht-cp932-Ziele brauchen auf dieser Engine einen Font-Hack, den es noch nicht gibt.
Verstümmeltes Latein: Mojibake
Japanischer Text, der als Shift-JIS geschrieben und dann als UTF-8 gelesen wird (oder umgekehrt), erzeugt lange Folgen akzentuierter lateinischer Buchstaben und Rahmenzeichen. Das tritt auf, wenn ein Werkzeug eine Datei in der falschen Kodierung schreibt oder wenn der spieleigene Loader eine Kodierung annimmt, die dein Export nicht verwendet hat. Die Lösung ist, die Datei in der Kodierung zu schreiben, die die Engine erwartet — niemals den Font zu tauschen, das ändert nichts.
Warum es CJK und Thai am härtesten trifft
Latein braucht rund hundert Glyphen. Japanisch braucht mehrere Tausend, Chinesisch mehr. Font-Dateien sind groß, und Spiele liefern die kleinste aus, die ihre eigene Schrift abdeckt — die Abdeckung, die du erbst, hängt also vollständig davon ab, für welche Sprache das Spiel gebaut wurde.
- Ein für Japanisch gebautes Spiel trägt typischerweise Kana, gängige Kanji und ASCII. Übersetze es nach Russisch, Griechisch, Thai oder Koreanisch, und jedes Nicht-ASCII-Zeichen fällt hinten runter.
- Ein für Englisch gebautes Spiel trägt typischerweise nur Latein — Japanisch, Chinesisch, Koreanisch, Thai, Griechisch und Kyrillisch scheitern also alle auf einmal.
- Thai ist in der Praxis der schlimmste Fall: Es liegt außerhalb jedes gängigen Spiele-Fonts und braucht zusätzlich kombinierende Vokal- und Tonzeichen über und unter dem Grundzeichen, sodass ein Font, der die Codepoints technisch hat, es trotzdem schlecht darstellen kann.
- Latein ist nicht automatisch sicher. Polnisch, Tschechisch, Ungarisch, Türkisch, Vietnamesisch und Rumänisch nutzen Latin-Extended-Zeichen, die ein für Englisch oder Japanisch gebauter Font häufig auslässt. RuneTranslate bündelt für genau diese Ziele auf Ren'Py einen breiteren Font, weil sich "Latein ist in Ordnung" als falsch herausstellte.
Unity und TextMeshPro im Besonderen
Unity hat zwei Textsysteme, und sie scheitern unterschiedlich. Das alte uGUI-Text nutzt ein Font-Objekt. TextMeshPro — was die meisten modernen Spiele nutzen — nutzt zur Laufzeit gar keine Font-Datei. Es nutzt ein TMP-Font-Asset: einen Textur-Atlas vorgerenderter Glyphenbilder plus eine Tabelle, die Codepoints auf Positionen in diesem Atlas abbildet.
Deshalb hat ein TMP-Font-Asset einen festen Zeichensatz. Als der Entwickler es erzeugte, wählte er einen Zeichenbereich — meist nur die Sprache, die er ausliefern wollte — und TMP backte genau diese Glyphen in den Atlas. Ein statisches Font-Asset kann nie etwas außerhalb dieses Satzes darstellen, egal was du ihm gibst. Ein dynamisches Font-Asset behält einen Verweis auf die Quell-Font-Datei und kann neue Glyphen bei Bedarf rastern, und das ist der einzige Modus, der auf eine Sprache wachsen kann, an die der Entwickler nie gedacht hat.
TMPs Notausgang ist die Fallback-Liste. Jedes Font-Asset hat eine m_fallbackFontAssets-Liste, und die TMP Settings haben eine globale Fallback-Liste; hat das primäre Asset keine Glyphe für einen Codepoint, geht TMP diese Listen durch und sucht eines, das sie hat. Das ist die richtige Stelle zum Eingreifen, weil sie den spieleigenen Font für alles zuständig lässt, was er ohnehin zeichnen kann — die UI behält ihr beabsichtigtes Aussehen, und nur die Zeichen, die sie nicht bewältigt, kommen von woanders.
Mit einem Laufzeit-Werkzeug beheben
XUnity.AutoTranslator hookt das Spiel im Lauf und kann Fonts aus seiner Konfigurationsdatei tauschen. Die relevanten Einstellungen stehen unter [Behaviour]:
OverrideFont— ersetzt den Font, den die alten Unity-UI-Text-Komponenten nutzen. Erwartet den Namen eines Fonts, den das Spiel oder das Betriebssystem auflösen kann.OverrideFontTextMeshPro— ersetzt das TMP-Font-Asset komplett. Zeige damit auf eine TMP-Font-AssetBundle-Datei oder auf den Namen eines Assets, das das Spiel bereits hat.FallbackFontTextMeshPro— die sicherere der beiden: fügt einen Font hinzu, den TMP nur für Glyphen heranzieht, die dem spieleigenen Asset fehlen, und lässt die ursprüngliche Schrift überall sonst stehen.- tmp_font_assetbundles — die von der Community gepflegte Sammlung vorgebauter TMP-Font-AssetBundles, auf die
OverrideFontTextMeshProundFallbackFontTextMeshProüblicherweise zeigen. Sie werden pro Unity-Version gebaut, und ein Bundle für eine andere Unity-Version als die des Spiels lädt nicht — die Version abzugleichen ist der Schritt, den man übersieht.
Ein Laufzeit-Werkzeug wirkt pro Rechner und pro Konfiguration: Es repariert das Spiel auf dem PC, auf dem es installiert ist, und jeder andere, der dieses Build spielt, braucht dieselbe Einrichtung. RuneTranslate kann die plugin-eigene _AutoGeneratedTranslations.txt lesen und schreiben, ein Projekt, das sie bereits nutzt, ist also keine Sackgasse — siehe den Vergleich dazu, wo welcher Ansatz passt.
Auf Dateiebene beheben
RuneTranslate geht den Fallback-Weg direkt in den Daten des Spiels. Beim Unity-Export findet es die TMP Settings des Spiels, ein Font-Asset, das es als Vorlage nutzen kann, und einen Font, baut dann ein neues TMP-Font-Asset im Dynamic-Modus um einen mitgelieferten Font, der die Zielschrift abdeckt, und hängt es an die Fallback-Liste an. Vorhandene Fallback-Einträge bleiben unangetastet, und der spieleigene Font bleibt primär — ein japanisches, ins Thailändische übersetztes Spiel behält also sein ursprüngliches Aussehen für alles Lateinische und zieht die thailändischen Glyphen aus dem injizierten Asset.
Zwei ehrliche Anmerkungen dazu. Es braucht TMP Settings, eine brauchbare Font-Asset-Vorlage und ein Font-Objekt im selben Container der Spiel-Assets; ein Spiel, das diese anders anordnet, kann herausfallen. Und wenn die Injektion übersprungen wird, sagt der Export das in seinen Warnungen, statt grün durchzulaufen — ein Export, der als Kästchen ausgeliefert wird, darf nicht genauso aussehen wie einer, der funktioniert. Siehst du diese Warnung, ist der Weg über das Laufzeit-Werkzeug oben der Ersatzplan.
Ren'Py erreicht dasselbe Ergebnis über einen anderen Mechanismus, weil Ren'Py kein TMP hat: RuneTranslate kann einen CJK- oder nicht-lateinischen Font in das exportierte Build packen und ihn über Ren'Pys eigene config.font_replacement_map registrieren, sodass die Engine ihn überall dort einsetzt, wo der Font des Spiels nicht ausreicht.
Checkliste für die Praxis
- Sieh dir den Schaden an. Gleichförmige Rechtecke,
?-Zeichen oder verstümmelte Buchstaben — entscheide, welchen der drei Fälle du hast, bevor du irgendetwas anfasst. - Ist es
?, öffne die exportierte Datei. Stehen die Fragezeichen in der Datei, gingen die Zeichen beim Schreiben verloren und kein Font hilft; prüfe, ob die String-Kodierung der Engine deine Zielsprache halten kann. - Sind es verstümmelte Buchstaben, prüfe die Kodierung, die die Engine für diese Datei erwartet, statt den Font.
- Sind es Kästchen, prüfe, ob der Dateiinhalt korrekt ist — das ist er fast immer — und behandle es als Font-Aufgabe.
- Ziehe unter Unity das Hinzufügen eines Fallbacks dem Ersetzen des Spiel-Fonts vor. Ein Ersatz ändert das Aussehen jedes Strings, auch derer, die einwandfrei dargestellt wurden.
- Nutzt du ein TMP-Font-AssetBundle, passe es zur Unity-Version des Spiels.
- Exportiere neu und prüfe die konkreten Zeichen, die gescheitert sind, nicht nur die erste Dialogzeile. Latin-Extended und thailändische Zeichen überstehen eine Stichprobe oft und scheitern anderswo.
Wie es weitergeht
Für den vollständigen Unity-Ablauf — welchen Text Unity tatsächlich offenlegt und was außer Reichweite bleibt — lies Ein Unity-Spiel übersetzen und die Unity-Engine-Seite. In Grafiken statt in Daten eingebrannter Text ist ein eigenes Problem mit einem eigenen Werkzeug: siehe Bildübersetzung. Und die FAQ deckt den Rest der häufigen Überraschungen beim ersten Lauf ab.
RuneTranslate ist kostenlos nutzbar, mit jeder Engine und jedem Anbieter freigeschaltet, darunter drei, die gar keinen API-Schlüssel brauchen. Es zerlegt die spieleigenen Dateien und exportiert ein spielbares übersetztes Build, das dir bleibt. Lade es herunter oder sieh dir die unterstützten Engines an.
Bereit, RuneTranslate auszuprobieren?
Der Free-Tarif schaltet jede Engine + jeden Übersetzungsanbieter frei. Supporter ($3/Mon.) schaltet volle Geschwindigkeit frei.
Für Windows herunterladen
