Lokalisierung
Jeder Text, den der Spieler sieht, wird über einen Schlüssel nachgeschlagen, einen kurzen Namen wie missions/coastal_patrol/mission_name. Das Spiel sucht den Text für diesen Schlüssel in der Sprache des Spielers. Diese Seite zeigt, wie eine Mod ihren Schlüsseln Text gibt, in einer Sprache oder in vielen, ohne eine einzige Datei des Spiels anzufassen.
Der einfachste Weg: Text im Missions-JSON
Eine Mission kann ihren Text direkt in ihrer JSON-Datei tragen. Kein Skript, keine Schlüssel:
{
"_rawMissionName": "The Lost Bombers",
"_rawMissionDescription": "A flight of our own bombers is limping home from France."
}
Das ist nur eine Sprache und deckt genau neun Slots pro Mission ab. Siehe Text im JSON statt eines Terms weiter unten. Für alles andere registrierst du Text aus einem Skript.
Text aus einem Skript definieren
localization.SetTerm("en", "mymod/greeting", "Good morning, gentlemen.")
SetTerm(language_code, key, text) gibt einem Schlüssel seinen Text in einer Sprache. Ein Schlüssel mit seinen Texten heißt Term.
Ruf es auf der obersten Ebene eines Skripts auf, nicht in einem Handler. Die oberste Ebene läuft einmal, wenn das Spiel das Skript lädt, der Text existiert also, bevor irgendetwas ihn braucht.
Ein Skript, das nur Text definiert, ist völlig in Ordnung. Es braucht keinen Handler, kein JSON und keine Missions-Id. Leg es wie jedes andere Skript in den Missions/-Ordner der Mod; das Spiel führt jedes Skript aus, das es dort findet.
Warum das sicher ist
Text, den du registrierst, landet nicht in den Übersetzungsdateien des Spiels. Das Spiel führt einen eigenen Speicher für Text aus Skripten, fügt ihm Schlüssel hinzu, sobald Skripte sie registrieren, und schaut dort ebenfalls nach. Nichts, was du mit SetTerm machst, ändert eine Datei auf der Festplatte, und es gibt keinen Übersetzungsschritt, den du ausführen musst.
Gibt es den Schlüssel schon in den eigenen Texten des Spiels, ersetzt SetTerm diesen Text, solange deine Mod geladen ist. So kann eine Mod eine eingebaute Zeile umformulieren. Deshalb solltest du auch jeden eigenen Schlüssel mit deinem Mod-Namen versehen: Ein versehentlicher Treffer überschreibt den Text des Spiels stillschweigend.
Missionstexte
Eine Mission verwendet neun Schlüssel, alle nach dem Muster missions/<missionId>/<slot>.
Drei liest das Spiel selbst, und sie lassen sich nicht umbenennen:
| Schlüssel | Angezeigt in |
|---|---|
missions/<id>/mission_name |
der Missionsliste und dem Ergebnisfenster |
missions/<id>/mission_description |
dem Missionsfenster |
missions/<id>/intel_threat_report |
der Bedrohungszeile im Missionsfenster |
Fehlt einer, erscheint im Spiel der rohe Schlüssel.
Sechs verwendet das Missionsskript. Das gemeinsame Standard-Missionsskript verwendet genau diese Namen, also braucht auch eine Mission ohne eigenes Skript sie:
| Schlüssel | Wann |
|---|---|
missions/<id>/ap_transmission |
im Flug, läuft nach Plan |
missions/<id>/gw_transmission |
im Flug, schiefgelaufen |
missions/<id>/ap_success_transmission |
wie geplant, Fertigkeit bestanden |
missions/<id>/ap_failure_transmission |
wie geplant, Fertigkeit gescheitert |
missions/<id>/gw_success_transmission |
schiefgelaufen, Fertigkeit bestanden |
missions/<id>/gw_failure_transmission |
schiefgelaufen, Fertigkeit gescheitert |
ap steht für "as planned" (wie geplant), gw für "gone wrong" (schiefgelaufen). Die vier Schlüssel *_success und *_failure entsprechen eins zu eins den vier Ausgangs-Slots im Missions-JSON.
Eine kleine Hilfsfunktion hält ein Missionsskript lesbar:
MISSION_ID = "mymod_coastal_patrol"
def _term(slot, text):
localization.SetTerm("en", "missions/{}/{}".format(MISSION_ID, slot), text)
_term("mission_name", "Coastal Patrol")
_term("mission_description", "Fly the shoreline. Report anything that moves.")
Ein Missionsskript darf beliebig viele zusätzliche Schlüssel registrieren (zum Beispiel missions/<id>/veteran_departure) und sie mit event_log.Create verwenden.
Text im JSON statt eines Terms
Die neun _raw*-Felder eines Missions-JSON bedienen die neun Slots oben direkt. Ist ein Raw-Feld gefüllt, verwendet das Spiel es, bevor es den Schlüssel nachschlägt. Eine Mission mit Raw-Text braucht also keinen SetTerm-Aufruf und überhaupt kein Skript.
| JSON-Feld | Slot, den es bedient |
|---|---|
_rawMissionName |
mission_name |
_rawMissionDescription |
mission_description |
_rawIntelThreatReport |
intel_threat_report |
_rawApTransmission |
ap_transmission |
_rawGwTransmission |
gw_transmission |
_rawApSuccessTransmission |
ap_success_transmission |
_rawApFailureTransmission |
ap_failure_transmission |
_rawGwSuccessTransmission |
gw_success_transmission |
_rawGwFailureTransmission |
gw_failure_transmission |
Ein leeres Feld zählt als nicht gefüllt, und das Spiel greift auf den Schlüssel zurück.
Nur diese neun Slots funktionieren so. Es gibt kein zehntes _raw*-Feld: event_log.Create("missions/mymod_patrol/my_own_line") ohne Term dahinter gibt den rohen Schlüssel aus.
Was du verwenden solltest
_raw* im JSON |
SetTerm in einem Skript |
|
|---|---|---|
| Braucht ein Skript | nein | ja |
| Anzahl der Texte | genau neun | unbegrenzt |
| Sprachen | eine | alle, die das Spiel in seinen Einstellungen anbietet |
| Gut für | eine Mission mit festem Text | Text, der sich danach richtet, was im Flug passiert ist |
Beides lässt sich kombinieren: Raw-Text deckt die neun Standard-Slots ab, und ein Skript ergänzt eigene Terms für alles darüber hinaus. Das Beispiel raf_collection macht genau das: Drei Missionen liefern nur Raw-Text, und greenhorn_watch fügt eigene Terms hinzu, damit es je nach Erfahrung der Crew eine andere Geschichte erzählen kann.
Questtexte
Quest-Dateien tragen Schlüssel, keinen Text: _questTitle, _subQuestTitle und _subQuestDescription enthalten Schlüssel, und das Spiel schlägt sie nach. Die eigenen Quests des Spiels verwenden dieses Muster:
quest/<questId>/title
quest/<questId>/subQuest<N>_title
quest/<questId>/subQuest<N>_description
Um einer Mod-Quest ihren Text zu geben, liefere ein kleines Skript im Missions/-Ordner der Mod mit, das diese Schlüssel registriert:
QUEST_ID = "mymod_first_steps"
localization.SetTerm("en", "quest/{}/title".format(QUEST_ID), "First Steps")
localization.SetTerm("en", "quest/{}/subQuest0_title".format(QUEST_ID), "Clear the field")
localization.SetTerm("en", "quest/{}/subQuest0_description".format(QUEST_ID), "Fell five trees.")
Die Schlüssel müssen nur mit dem übereinstimmen, was in der Quest-Datei steht. Siehe Quests.
Ortsnamen
Eine Mod kann eigene Orte zur Kriegskarte hinzufügen (siehe Orte mit einer Mod hinzufügen). Der Spieler sieht den _locationName eines solchen Orts genau so, wie er geschrieben ist, denn nur die mitgelieferten Orte haben Übersetzungen. Damit dein Ort einen richtigen Namen bekommt, registrierst du einen Term namens mission_location/<name>, wobei <name> der _locationName ist:
localization.SetTerm("en", "mission_location/mymod_scapa_flow", "Scapa Flow")
localization.SetTerm("de", "mission_location/mymod_scapa_flow", "Scapa Flow")
Platzhalter
event_log.Create nimmt ein Dictionary, das {[NAME]}-Platzhalter füllt:
localization.SetTerm("en", "mymod/pilot_tired",
"{[PILOT]} came back exhausted after {[HOURS]} hours in the air.")
event_log.Create("mymod/pilot_tired", {"PILOT": str(pilot_id), "HOURS": "6"})
Bei Platzhalternamen zählt Groß- und Kleinschreibung, und sie müssen exakt übereinstimmen. Werte müssen Strings sein, pack Zahlen und Ids also in str(...).
Inline-Formatierung
Vier Tags funktionieren in jedem Text:
| Tag | Wirkung |
|---|---|
[character:<id>] |
ein anklickbarer Link zu diesem Charakter, angezeigt als sein Name |
[resource:<name>] |
Symbol und Farbe der Ressource |
[morale:<name>] |
Symbol und Farbe der Moralkategorie |
[CharacterStat:<name>] |
Symbol und Farbe einer Crew-Fähigkeit (Flying, Shooting, Bombing, Endurance, Engineering, Navigating) |
CharacterStat schreibt sich mit großem C und S - bei diesem Tag kommt es auf Groß- und Kleinschreibung an.
Kombiniere sie mit Platzhaltern, um einen Charakter aus einem Event zu nennen:
localization.SetTerm("en", "mymod/chat",
"[character:{[FIRST]}] talked to [character:{[SECOND]}] until dawn.")
event_log.Create("mymod/chat", {"FIRST": str(first_id), "SECOND": str(second_id)})
Für Ressourcen, Moral und Crew-Fähigkeiten setzt du den Spielwert beim Laden des Skripts in den Text ein:
from Blindflug.GameResources import ResourceType
from Blindflug.Game.Characters.Morale import MoraleCategory
from BlindGame.CharacterStats.Data import CharacterStatType
localization.SetTerm("en", "mymod/mice",
"Mice got into the [resource:{}] again.".format(ResourceType.FoodRations))
localization.SetTerm("en", "mymod/good_chat",
"Their [morale:{}] improved.".format(MoraleCategory.Mood))
f-Strings funktionieren auch, und die eigenen Skripte des Spiels verwenden sie. Denk daran, die geschweiften Klammern eines Platzhalters in einem f-String zu verdoppeln: f"[character:{{[FIRST]}}] shared the [resource:{ResourceType.FoodRations}]".
Sprachen
Definiere immer Englisch ("en"). Hat ein Term keinen Text in der Sprache des Spielers, zeigt das Spiel den Text einer Sprache, die du registriert hast - mit en bist du also auf der sicheren Seite. Ein Schlüssel, der in keiner einzigen Sprache registriert ist, erscheint als roher Schlüssel.
Zum Übersetzen rufst du SetTerm einmal pro Sprache auf:
localization.SetTerm("en", "mymod/greeting", "Good morning, gentlemen.")
localization.SetTerm("de", "mymod/greeting", "Guten Morgen, meine Herren.")
localization.SetTerm("fr", "mymod/greeting", "Bonjour, messieurs.")
Das Spiel liefert diese Sprachcodes mit:
en, en-GB, de-DE, de, ar, bg, ca, zh-CN, zh-TW, cs, da, nl, fi, fr, el, hu, id, it, ja, ko, ms, nb, fa, pl, pt, pt-BR, ro, ru, es, es-419, sv, gsw-CH, th, tr, uk, vi
Deutsch ist de - das ist die Sprache, die Spieler in den Einstellungen wählen (de-DE in der Liste ist eine Testsprache, die du ignorieren kannst). Der Sprachcode muss einer sein, den das Spiel in seinen Einstellungen anbietet.
Ein praktisches Muster für mehrere Sprachen:
TEXTS = {
"mymod/greeting": {
"en": "Good morning, gentlemen.",
"de": "Guten Morgen, meine Herren.",
},
}
for key, translations in TEXTS.items():
for language, text in translations.items():
localization.SetTerm(language, key, text)
Wenn die Mod entfernt wird
Text, der mit SetTerm registriert wurde, existiert nur, solange das Skript geladen ist, das ihn registriert hat. Er wird nie in den Spielstand oder in die Dateien des Spiels geschrieben.
Wenn ein Spieler deine Mod entfernt oder ausschaltet und einen Spielstand lädt:
- Deine Terms sind weg. Alles im Spielstand, was noch auf einen deiner Schlüssel verweist, zeigt statt des Texts den rohen Schlüssel. Das gilt für alte Zeilen im Ereignisprotokoll, die deine Skripte geschrieben haben, und für Missionen aus deiner Mod, die noch auf der Kriegskarte warteten.
- Ein Schlüssel des Spiels, den deine Mod umformuliert hatte, zeigt wieder den Originaltext des Spiels.
- Nichts geht kaputt; wenn du die Mod neu installierst, ist der Text wieder da.
Missionstext aus _raw*-Feldern wird beim Erstellen in die Mission kopiert. Eine Mission, die schon auf der Kriegskarte lag, behält ihren Namen also auch, nachdem die Mod weg ist.
Deine Schlüssel benennen
Setz deinen Mod-Namen vor jeden Schlüssel (mymod/...), damit er nicht mit dem Spiel oder einer anderen Mod kollidieren kann: Ein vorhandener Schlüssel wird stillschweigend überschrieben. Missionsschlüssel sind die Ausnahme, weil sie missions/<missionId>/... folgen müssen. Mach stattdessen die Missions-Id selbst unverwechselbar, was du ohnehin tun solltest.