Mehrsprachigkeit von Anfang an wäre billiger gewesen
Über hundert Befehle mit Texten mitten im Code, dazu Zahlen, Daten und Pluralformen. Was das Nachrüsten von zwei Sprachen tatsächlich gekostet hat.
1000 Wörter · 5 Min. Lesezeit
- yurna
- i18n
- architektur
Der Bot war deutsch. Nicht aus einer Entscheidung heraus, sondern weil ich deutsch bin und die ersten Nutzer auch. Als der Bot auf Servern landete, auf denen englisch geschrieben wurde, kam die Frage nach einer zweiten Sprache — und ich habe unterschätzt, wie tief das geht.
Der naive Ansatz und warum er nicht reicht
Der offensichtliche erste Schritt: Texte in Dateien auslagern.
{
"befehl.warn.erfolg": "{nutzer} wurde verwarnt.",
"befehl.warn.fehlerRechte": "Dafür fehlen dir die Rechte."
}antworten(t("befehl.warn.erfolg", { nutzer: ziel.tag }));Das ist die halbe Miete und war bei mir mehrere Wochen Arbeit, weil über hundert Befehle betroffen waren. Aber es reicht nicht, und die Gründe dafür sind der eigentliche Inhalt dieses Eintrags.
Was Übersetzung wirklich schwierig macht
Pluralformen. „1 Verwarnung" und „3 Verwarnungen" ist im Deutschen einfach. Andere Sprachen haben mehr als zwei Fälle — Polnisch etwa unterscheidet zwischen 1, 2–4 und 5+. Wer das mit zahl === 1 ? "x" : "y" löst, hat eine Sprache im Code eingebaut.
{
"verwarnungen": {
"one": "{zahl} Verwarnung",
"other": "{zahl} Verwarnungen"
}
}Satzbau. Die Verlockung ist groß, Sätze aus Teilen zusammenzusetzen: "Du hast " + zahl + " von " + gesamt + " erreicht." Sobald das übersetzt werden soll, ist es unbrauchbar, weil die Reihenfolge in anderen Sprachen anders ist. Ein Text muss immer ein ganzer Satz mit Platzhaltern sein.
Zahlen und Daten. 1234.56 ist im Deutschen 1.234,56. Ein Datum ist mal 18.08.2026, mal 08/18/2026. Das gehört nicht in Übersetzungsdateien, sondern in die Formatierungsfunktionen der Laufzeitumgebung:
new Intl.NumberFormat(sprache).format(betrag);
new Intl.DateTimeFormat(sprache, { dateStyle: "medium" }).format(datum);Textlängen. Deutsche Texte sind im Schnitt spürbar länger als englische. Eine Schaltfläche, die auf Englisch passt, bricht auf Deutsch um. Das ist der Grund, warum Oberflächen mit fester Breite nach der Übersetzung plötzlich schlecht aussehen.
Die Wahl der Sprache selbst. Woher weiß der Bot, welche Sprache er nehmen soll? Discord liefert die Spracheinstellung des Nutzers und die des Servers mit. Beide können abweichen. Ich habe mich für eine Reihenfolge entschieden: ausdrückliche Einstellung im Dashboard, sonst Serversprache, sonst Nutzersprache, sonst Englisch.
Infobox
Die Entscheidung, ob eine Bot-Antwort in der Sprache des Nutzers oder des Servers erfolgt, ist keine technische. Auf einem deutschen Server, auf dem ein Nutzer sein Discord auf Englisch gestellt hat, wirkt eine englische Antwort merkwürdig für alle Mitlesenden. Bei öffentlichen Antworten gewinnt deshalb die Serversprache, bei privaten die Nutzersprache. Das ist ein Detail, das niemand bemerkt, wenn es richtig ist.
Der Teil, der mich am meisten Zeit gekostet hat
Nicht die Übersetzung. Die Suche nach den Texten.
Nach Wochen war ich sicher, alles erwischt zu haben — bis in einem seltenen Fehlerfall ein deutscher Satz auftauchte. Dann noch einer. Und noch einer. Texte verstecken sich in Fehlermeldungen, in Standardwerten, in Feldnamen von Einbettungen, in Beschreibungen der Befehle selbst.
Was am Ende geholfen hat, war eine Prüfung, die den Code nach Zeichenketten mit deutschen Umlauten und typischen Wörtern durchsucht und meldet, wenn sie außerhalb der Übersetzungsdateien stehen. Grob, aber wirksam.
Was ich beim zweiten Mal anders gemacht habe
Die Übersetzungen lagen zunächst in einem Ordner beim Bot. Als das Dashboard dieselben Texte brauchte — Einstellungsnamen, Fehlermeldungen — hätte ich sie kopiert. Genau der Fehler, den ich einen Monat zuvor beim Monorepo abgeschafft hatte.
Also wurden sie ein eigenes Paket. Mit einer Besonderheit, die wichtig war: getrennte Einstiegspunkte für Server und Client. Das Dashboard soll nicht sämtliche Sprachen an den Browser schicken, sondern nur die aktive.
Was ich beim Übersetzen über meine eigenen Texte gelernt habe
Der unerwartete Nebeneffekt: Beim Herausziehen der Texte habe ich zum ersten Mal alle meine Meldungen nebeneinander gesehen. Das war unangenehm.
Sie waren uneinheitlich. Mal „Du hast keine Berechtigung", mal „Fehlende Rechte", mal „Zugriff verweigert" — für dieselbe Situation, an drei Stellen, weil ich sie zu drei Zeitpunkten geschrieben hatte.
Sie waren teilweise unhöflich. „Falsche Verwendung!" ist ein Vorwurf. „So funktioniert dieser Befehl: …" ist eine Hilfe. Beide sind gleich lang.
Sie waren technisch, wo sie fachlich sein sollten. „Datenbankfehler beim Speichern" sagt einem Nutzer nichts — außer, dass ihm gerade nicht geholfen wird.
Die Sammlung aller Texte an einem Ort hat diese Muster sofort sichtbar gemacht. Ich habe danach mehr Zeit mit dem Vereinheitlichen der deutschen Texte verbracht als mit dem Übersetzen ins Englische.
Die Struktur, die sich bewährt hat
Nach mehreren Anläufen sind die Schlüssel bei mir hierarchisch nach Herkunft aufgebaut, nicht nach Bedeutung:
befehl.warn.erfolg
befehl.warn.fehlt.ziel
allgemein.fehler.keineRechte
allgemein.fehler.unbekannt
Der Grund für „nach Herkunft" ist praktisch: Wenn ich einen Befehl entferne, kann ich seine Texte mitentfernen, ohne zu prüfen, ob sie woanders benutzt werden. Bei einer Sortierung nach Bedeutung — alle Fehlermeldungen zusammen — geht das nicht, und die Datei sammelt Leichen.
Die Ausnahme sind wirklich allgemeine Texte, und die haben einen eigenen Bereich. Als Faustregel: Ein Text, der an mehr als drei Stellen vorkommt, ist allgemein. Alles darunter gehört zu seinem Befehl.
Was Übersetzungen mit Wartung machen
Ein Punkt, der beim Einführen nicht offensichtlich ist: Jede Sprache ist ein zusätzlicher Zustand, der auseinanderlaufen kann.
Ein neuer Text wird in der Hauptsprache geschrieben. Bis er übersetzt ist, fehlt er in der anderen — und was passiert dann? Drei Möglichkeiten, alle mit Nachteilen:
Der Schlüssel wird angezeigt. Ehrlich, aber unbrauchbar für Nutzer.
Ein Rückfall auf die Hauptsprache. Die beste Lösung, und sie versteckt fehlende Übersetzungen — man merkt monatelang nicht, dass etwas fehlt.
Der Text fehlt ganz. Nie eine gute Idee.
Ich benutze den Rückfall und dazu eine Prüfung, die meldet, welche Schlüssel in einer Sprache fehlen. Ohne diese Prüfung wächst die Lücke, und irgendwann ist die zweite Sprache eine Mischung aus beiden — was schlechter ist als eine Sprache.
Was ich daraus mitnehme
Es ist kein Feature, sondern eine Eigenschaft. Mehrsprachigkeit lässt sich nicht sinnvoll „später hinzufügen", so wie man eine Statistikseite später hinzufügt. Sie betrifft jede Stelle, an der Text entsteht.
Trotzdem hätte ich es nicht von Anfang an gebaut. Wenn ich 2020 mit Übersetzungsschlüsseln angefangen hätte, hätte ich langsamer gebaut und in Schlüsseln gedacht statt in Sätzen. Was ich mir gewünscht hätte, ist eine Zwischenstufe: Texte nicht mitten im Code, sondern gesammelt in einer Datei pro Modul — auch bei nur einer Sprache. Das kostet fast nichts und halbiert die spätere Arbeit.
Der billigste Zeitpunkt ist der, an dem der zweite Nutzer nach etwas fragt. Nicht vorher, nicht viel später.