Strukturierte i18n-JSON Dateien für Übersetzungen – ist das möglich?

Thorsten Rintelen

Seit über 20 Jahren Software-Developer, Spezialisiert auf Angular, Teamleiter Entwicklung bei der traperto GmbH

Große Projekte sind oft mehrsprachig. Auch in meinem Fall, einem größeren Angular Produkt / Projekt ist dies so. Für Übersetzungen verwenden wir die verbreitete Erweiterung ngx-translate und setzen wie viele andere auch auf JSON Dateien, die die Übersetzungen enthalten.
Mit steigender Größe der Dateien wächst auch das Chaos in diesen Dateien. Die Struktur wird unübersichtlich und Texte werden mehrfach mit der gleichen Bedeutung hinterlegt / übersetzt.

Welche Möglichkeiten bieten sich an, um die Probleme in den Griff zu bekommen? Können wir dem Chaos entgegentreten? 

Möglicher Aufbau einer JSON Datei

Nested vs. Flat

Grundsätzlich wird zwischen einer nested- und einer flat-Variante unterschieden, wobei pro Sprache eine Datei erzeugt wird, z.B. de.json, en.json etc.
In beiden Varianten ist es empfehlenswert den Inhalt alphabetisch zu sortieren, z.B. über einen commit hook (Siehe https://dev.to/teamhive/automatically-alphabetize-translation-json-files-56na)

// Nested
{
    "FEATURE": {
        "COMPONENT": {
            "TITLE": "Mein Titel"
        }
    }
}
 
//Flat
{
    "FEATURE.COMPONENT.TITLE": "Mein Titel"
}

// Plaintext
{
    "Project": "Projekt",
    "Name of the project.": "Name des Projekts",
    "Empty! No entries added yet.": "Leer! Es sind noch keine Einträge vorhanden.",
    "Ok.": "Ok."
}

Vor- / Nachteile

  • Bei der Flat-Variante ist sehr einfach möglich, den exakten key zu suchen
  • Die Datei der Flat-Variante wird auf Grund der Dopplungen etwas größer sein
  • Die nested-Variante ist meiner Recherche nach weiter verbreitet und beliebter

Eigentlich wollte ich noch mehr Vor- bzw. Nachteile hervorheben, aber gibt es noch welche? Ist der Unterschied der beiden Varianten wirklich so marginal? Bitte gerne melden (Lächeln)

Multi language JSON

Eine weitere Variante wäre es, alle Übersetzungen aller Sprachen in eine Datei zu packen. Wenn aber nicht jedes Projekt immer alle Sprachen benötigt, so wird die Datei schnell unnötig groß.
Für mich persönlich ist diese Variante (daher) keine gute Lösung.

{
  "de": {
    "SALE": "Verkauf",
    "ADDRESS": "Adresse"
  },
  "fr": {
    "SALE": "soldes",
    "ADDRESS": "adresse"
  }
}

Mögliche Namespaces einer JSON Datei

Unabhängig von der Struktur der Datei, stellt sich die Frage nach dem Aufbau der Namespaces.

Context vs. Kategorie vs. Plaintext

Die „kurioseste“ Variante ist die Plaintext. Diese habe ich als eine Option in den unendlichen Weiten des Internets gefunden. Von dieser Variante wird aber oft direkt wieder abgeraten (https://www.codeandweb.com/babeledit/documentation/file-formats#plaintext-json), auch wenn diese Vorteile bringt (Im Code gut zu lesen, keine Diskussion über Namen).
Spätestens wenn Platzhalter im Texte enthalten sind oder der Text sehr lang wird, fühlt es nicht richtig an. Und was wären wir Entwickler schon, wenn wir nicht über Namen diskutieren könnten?!

Die Context Variante ist am weitesten verbreitet. Hier kann der Namespace nach Wunsch über Feature und / oder Komponenten Ebene definiert werden. Der Bezug eines Textes zu einer Komponente ist somit sehr leicht zu erkennen. Wird die Komponente gelöscht, lassen sich sehr einfach die passenden Texte finden.

Aber auch die Kategorie Variante hat ihre Vorteile. Werden allgemein gültige Texte wie „Ok“ oder „Abbrechen“ mehrfach verwendet, so bietet es sich an diese in Kategorien zu sammeln. Es kann z.B. sinnvoll sein, eine Kategorie für „BUTTON“ oder „ERROR“ zu haben.

Tatsächlich werden die Context und die Kategorie-Variante oft als Hybrid-Lösung verwendet. Die Texte werden nach Context gruppiert, außer bei ganz allgemeinen Texten, für diese gibt es dann eine entsprechende Kategorie.

// Context
{
    "FEATURE": {
        "COMPONENT": {
            "TITLE": "Mein Titel"
        }
    },
    "BUTTON": {
        "OK": "Ok"
    }
}
 
// Category
{
    "TITLE": {
        "FEATURE_COMPONENT1": "Mein Titel 1",
        "FEATURE_COMPONENT2": "Mein Titel 2",
        "FEATURE_COMPONENT3": "Mein Titel 3"
    }
}

Doppelte Übersetzung bei gleicher Bedeutung

Insbesondere bei einfachen Texten wie z.B. „Abbrechen“ oder „Vorname“ sollten diese in einer Kategorie abgelegt werden (z.B. COMMON.FIRST_NAME).
Die Bedeutung des Wortes wird sich nicht ändern. Sollte es dennoch einmal nötig sein, dass der Text nicht „Abbrechen“ sondern „Wirklich Abbrechen“ heissen soll, so kann für diesen Fall noch immer ein neuer Key als Context verwendet werden.

Manchmal hat ein Text nur einen kleinen Unterschied, z.B. „bearbeiten“ und „Bearbeiten“. Je nach dem wo ich den Text verwende, muss dieser groß oder klein geschrieben werde. Schreiben wir den Text an dieser Stelle klein, so kann die Angular Pipe TitleCasePipe diesen auf Wunsch umschreiben.

Wo die Grenze eines „einfachen“ Textes liegt, muss / darf jeder für sich und sein Projekt entscheiden.

Hierbei spielt es keine Rolle, ob die nested oder die flat Variante verwendet wird – es gibt kein richtig und kein falsch.

Tools für ngx-translate

Was ngx-tranlsate von Haus aus nicht kann, ist die Unterscheidung von Ein- und Mehrzahl. Dafür gibt es zwei empfohlene Lösungen

Für VSCode gibt es eine Erweiterung, die nicht verwendete Keys findet: https://marketplace.visualstudio.com/items?itemName=seveseves.ngx-translate-zombies

ngx-translate bietet in GitHub eine Liste mit weiteren Möglichkeiten. Einige davon wurden aber lange nicht mehr aktualisiert und konnten von mir in einem Test nicht verwendet werden.

Fazit

Ob und wie eine JSON Datei für Übersetzungen aufgebaut und verwendet wird, ist am Ende reine Geschmacksache.
Es gibt für alle Varianten Vor- und Nachteile und wie so oft hängt eine gute Struktur von der Disziplin ab, mit der die eigene Definition umgesetzt wird – und wie gut jeder sein Team im Griff hat (Lächeln)

Ich persönlich werde die Hybrid-Flat-Variante versuchen, da für mich in dieser Konstellation die Vorteile überwiegen.

Im übrigen können die meisten von mir gefundenen Übersetzungstools und Anbieter mit allen hier genannten Varianten umgehen – auch hier spielt die Variante am Ende keine entscheidende Rolle.

Idee

In Angular bin ich es gewohnt, alle Dateien einer Komponente an einer Stelle zu haben. Die Dateien TS, HTML, CSS und SPEC liegen alle zusammen in einem Ordner. Wäre es jetzt nicht schön, auch die i18n JSON Datei hier zu finden?
Ein prebuild in npm oder ein Plugin für die Angular CLI könnten diese Dateien sammeln, zusammenführen und als finale Übersetzungsdatei für ngx-translate zur Verfügung stellen.

Vielleicht findet sich jemand der Zeit für diesen Versuch hat…

my.component.ts
my.component.html
my.component.css
my.component.spect.ts
my.component.i18n.json