Formate
Die acht unterstützten Formate, wie jedes seine Dateien liest und schreibt, und die Garantie der Dokumentreihenfolge der Keys.
Maschinell übersetzte Seite
verbatra arbeitet über einen Format-Adapter auf deinen Locale-Dateien. Jeder Adapter liest eine Datei in eine formatneutrale Form, auf der Vergleich, Hashing und Integritätsprüfungen laufen, und schreibt sie dann in der ursprünglichen Form der Datei zurück. Du wählst das Format über das format-Feld in deiner Konfiguration. Acht werden unterstützt: vier JSON-Varianten plus XLIFF, YAML, ARB und Java/Spring-Properties.
Die vier JSON-Formate
format | Für | Platzhalter | Plurale | ICU |
|---|---|---|---|---|
i18next-json | i18next | {{name}} und $t(...)-Verschachtelungsreferenzen | CLDR-Plural-Suffix am Key | nein |
vue-i18n-json | vue-i18n | {name}, {0} | Pipe im Wert | nein |
next-intl-json | next-intl | ICU-Argument- und Tag-Namen | ICU plural oder selectordinal | ja |
ngx-translate-json | ngx-translate | {{name}} | keine | nein |
Alle vier lesen verschachtelte JSON-Objekte mit String-Blättern. Die Unterschiede liegen in der Nachrichtensyntax:
- i18next nutzt
{{double-brace}}-Interpolation und leitet den Plural aus dem CLDR-Suffix am Key ab (_zero,_one,_two,_few,_many,_other). Zusätzlich extrahiert und schützt es$t()-Verschachtelungsreferenzen (die den Inhalt eines anderen Keys in den Wert einfügen), zum Beispiel$t(common.foo)und$t(common.foo, { options }), als Platzhalter, sodass eine Übersetzung, die eine davon verwirft oder verändert, die Integritätsprüfung nicht besteht. Zwei Einschränkungen: Verschachtelte Klammern innerhalb der$t()-Optionen werden nicht unterstützt, und nur das Standard-Präfix$t(wird erkannt. - Einzelklammer-Text in den Doppelklammer-Formaten. In
i18next-json,ngx-translate-jsonundyamlist ein Token der Form{name}Literaltext und keine Interpolation. Es wird deshalb nicht als Platzhalter extrahiert, und eine Übersetzung darf es fallen lassen oder umformulieren. Geschützt ist es trotzdem, aber nur in eine Richtung: Ein{name}-förmiges Token, das in einer Übersetzung auftaucht und in der Quelle nie vorkam, wird als Erfindung abgelehnt. Das deckt sowohl einen frei erfundenen Platzhalter ab als auch einen, der in einen Namen verändert wurde, den die Quelle nie hatte. verbatra hat keine Einstellung für i18nexts eigeneinterpolation.prefixundinterpolation.suffix. Wenn du also auf Einzelklammer-Delimiter umgestellt hast, wird eine Übersetzung, die einen deiner Platzhalter stillschweigend fallen lässt, nicht erkannt. - vue-i18n nutzt Einfach-Klammer-Tokens
{name}und{0}und leitet den Plural aus einer Pipe im Wert ab. - next-intl-Werte sind ICU MessageFormat: Die Platzhalter sind die ICU-Argument- und Rich-Text-Tag-Namen, der Plural folgt einem ICU-plural- oder selectordinal-Argument, und der ICU-Körper wird wortgetreu durch die Pipeline getragen. Ein Wert, der nicht als ICU parst, wird als ungültig gemeldet und übersprungen, nie geworfen.
- ngx-translate teilt i18nexts
{{double-brace}}-Interpolation, hat aber kein$t()-Nesting, keinen eingebauten Plural und kein ICU. Seine Dateien dürfen flach (gepunktete Keys) oder verschachtelt sein, und verbatra erhält beim Schreiben den Stil, den die Datei verwendet; eine neue Zieldatei wird verschachtelt geschrieben. Einzigartig: Eine Datei, die flache gepunktete Keys mit verschachtelten Objekten mischt, lässt das Lesen mitMIXED_STRUCTUREfehlschlagen, weil so eine Datei mehrdeutig statt erratbar ist.
Namespace-Layouts
Strings über mehrere Namespace-Dateien pro Locale zu verteilen, ist in i18next-Projekten üblich: common.json, auth.json und so weiter liegen nebeneinander in einem Locale-Verzeichnis. Eine verbatra-Konfiguration adressiert genau eine Datei pro Locale: files.pattern ist ein wörtlicher Pfad mit eingesetztem {locale}, er wird nie als Glob expandiert, und es gibt kein Namespace-Token. Ein Muster wie public/locales/{locale}/*.json wird als Datei mit dem wörtlichen Namen *.json gesucht und schlägt mit SOURCE_UNREADABLE fehl.
Ein Layout mit einem einzigen Namespace funktioniert wie konfiguriert:
files: {
pattern: "public/locales/{locale}/common.json",
},Eine Konfiguration pro Namespace
Schreibe für ein Projekt mit mehreren Namespaces eine Konfiguration pro Namespace und starte verbatra einmal pro Konfiguration. Jeder Dateiname funktioniert, da --config einen expliziten Pfad anhand seiner Endung lädt:
verbatra translate --config verbatra.common.config.ts
verbatra translate --config verbatra.auth.config.tsJeder Lauf behandelt seinen eigenen Namespace: Keys, die in einer Zieldatei fehlen, werden wie gewohnt übersetzt.
Nur der zuletzt gelaufene Namespace behält die Änderungserkennung
Alle Konfigurationen im selben Arbeitsverzeichnis teilen sich eine verbatra.lock.json, und jeder translate-Lauf ersetzt die aufgezeichnete Baseline dieser Locale durch die Keys, die er gerade verarbeitet hat. Nach dem zweiten Lauf hat der erste Namespace keine Baseline mehr, eine Änderung an einem seiner Quellstrings gilt für check und diff also als synchron und wird nicht neu übersetzt. Eine andere Reihenfolge der Läufe hilft nicht; wer zuletzt läuft, überschreibt den anderen. Um einen geänderten String in diesem Aufbau neu zu übersetzen, lösche diesen Key aus der Zieldatei (oder lösche die ganze Zieldatei), damit er als fehlend statt als geändert zählt.
XLIFF
Das xliff-Format deckt .xlf- und .xliff-Dateien ab, XLIFF 1.2 (file/body/trans-unit) und 2.0 (file/unit/segment). Anders als die Baum-Formate ist ein XLIFF-Dokument eine flache Liste von Trans-Units.
- Keys. Jeder Eintrag wird über die
idseiner Trans-Unit identifiziert, mitresnameals Rückfallebene. Eine Unit ohne beides fällt auf einen Positions-Key zurück, der sich verschiebt, wenn frühere Units hinzukommen oder wegfallen, gib also jeder Unit eine stabileid. Zwei Units, die zum selben Key auflösen (typischerweise eine doppelteid), lassen das Lesen mitINVALID_STRUCTUREfehlschlagen, statt still eine zu behalten. - Werte. Der Wert kommt aus
<target>, wenn vorhanden, sonst aus<source>. - Schreiben aktualisiert Targets an Ort und Stelle. verbatra schreibt jeden Wert in sein
<target>und lässt<source>, Attribute und<note>-Elemente unangetastet, damit das Dokument den Roundtrip übersteht. Weil eine flache Key/Wert-Zuordnung kein XLIFF-Dokument rekonstruieren kann, muss die Zieldatei bereits existieren: verbatra aktualisiert Targets in einer vorbereiteten Zieldatei (der Standard-XLIFF-Workflow) und legt keine fehlende an; ein fehlendes Ziel schlägt mitINVALID_STRUCTUREfehl. - Inline-Markup. Inline-Platzhalterelemente (
x,g,bx,ex,ph,it,mrk) und Einfach-Klammer-{name}-Interpolation werden als Platzhalter extrahiert und über die Übersetzung hinweg geschützt. Beim Schreiben überleben nur diese per Allowlist zugelassenen Elemente (mit ihren eigenen minimalen, nicht ausführbaren Attributen) als lebendiges Markup in<target>; alles andere in einem übersetzten Wert, einschließlich eines unerwarteten Elements oder eines Namespace-Konflikts, wird stattdessen als reiner Text geschrieben. - Notes als Kontext. Die
<note>einer Trans-Unit (in 2.0 die<notes><note>der Unit, geteilt von jedem Segment der Unit) wird als Entwicklerkontext gelesen: Der Inhalt erreicht den Provider als Kontext zur Begriffsklärung und erscheint in derContext-Spalte einer exportierten Arbeitsmappe. Er ist schreibgeschützt und wird nie zurückgeschrieben.
YAML
Das yaml-Format deckt .yml- und .yaml-Dateien ab: ein verschachtelter Baum in YAML-Syntax, dieselbe Form wie eine verschachtelte JSON-Datei, verarbeitet von derselben Baum-Pipeline. Es nimmt i18next-kompatible {{double-brace}}-Interpolation an, einschließlich des oben beschriebenen Schutzes gegen erfundene Einzelklammer-Token, und erkennt nur über die Dateiendung.
- YAML-Kommentare überstehen einen Schreibvorgang nicht, so wie JSON kein Kommentarkonzept hat.
- Skalare Nicht-String-Keys behalten ihre String-Form (
1:wird als"1"gelesen,true:als"true"). - Ein zusammengesetzter Key (eine Map oder Sequenz als Mapping-Key) hat keine treue String-Form, das Lesen schlägt also mit
INVALID_STRUCTUREfehl, statt ihn still zu Text zu verflachen. - Fehlerhafte Syntax wird als
INVALID_YAMLgemeldet, und die Anker-Alias-Expansion ist begrenzt, ein bösartiges Dokument kann das Parsen also nicht sprengen.
ARB
Das arb-Format deckt Flutters .arb-Dateien ab: JSON mit einem flachen Objekt aus Nachrichten-Keys neben @-präfixierten Metadaten (@key-Metadaten pro Nachricht und @@-präfixierte globale Einträge wie @@locale).
- Metadaten werden erhalten, nie übersetzt.
@-präfixierte Keys werden vor der Übersetzung entfernt und beim Schreiben an ihrer Dokumentposition wieder eingefügt. Eine Zieldatei, die existiert, aber beschädigt ist, lässt den Schreibvorgang fehlschlagen, statt ihre Metadaten still zu löschen. - Nachrichten sind ICU. Platzhalter, Plural-Behandlung und ICU-Validierung funktionieren genau wie bei
next-intl-json. - Beschreibungen werden Kontext. Jede
@key.descriptionwird als Entwicklerkontext für diese Nachricht gelesen: Die Beschreibung erreicht den Provider als Kontext zur Begriffsklärung (nie als zu übersetzender Text) und erscheint in derContext-Spalte einer exportierten Arbeitsmappe. Auch sie ist schreibgeschützt und wird nie zurückgeschrieben.
Properties
Das properties-Format deckt Javas und Springs .properties-Dateien ab, erkannt an der Endung .properties: eine flache Liste von Key/Wert-Zeilen. Keys bleiben als flache Keys wortgetreu erhalten und werden nie in einen Baum aufgeteilt.
- Trennzeichen und Kommentare. Ein Key wird von seinem Wert durch
=,:oder Whitespace getrennt, wobei Leerzeichen rund um das Trennzeichen ignoriert werden, wie beijava.util.Properties. Eine Zeile, deren erstes Nicht-Leerzeichen#oder!ist, ist ein Kommentar. - Fortsetzungen und Escapes. Eine Zeile, die auf einen Backslash endet, wird auf der nächsten fortgesetzt. Die Standard-Escapes (
\t,\n,\r,\f,\\, ein escaptes Trenn- oder Kommentarzeichen) und\uXXXXwerden beim Lesen dekodiert. Die Eingabe wird als UTF-8 gelesen; beim Schreiben wird jedes Nicht-ASCII-Zeichen als ASCII-sicheres\uXXXX-Escape ausgegeben, damit die Datei auch unter einem alten ISO-8859-1-Reader lädt. - Reihenfolge, Kommentare und Leerzeilen bleiben erhalten. Ein Schreibvorgang liest das Ziel erneut ein und behält dessen Key-Reihenfolge, Kommentare und Leerzeilen: Jede vorhandene Key-Zeile wird an Ort und Stelle mit ihrem neuen Wert neu geschrieben, und ein Key, den die Datei noch nicht hat, wird in Quellreihenfolge angehängt. Ein doppelter Key behält seine erste Position und nimmt den letzten Wert, wie
Properties.load. - Zeilenenden folgen dem Ziel. Eine Datei, die irgendwo CRLF enthält, wird komplett mit CRLF zurückgeschrieben, eine Datei nur mit CR mit CR, und alles andere, auch eine noch nicht existierende Datei, mit LF. Eine gemischte Datei läuft damit auf einen Stil zusammen, statt zeilenweise erhalten zu bleiben, und ein
\roder\ninnerhalb eines Werts wird weiterhin escapt statt als Zeilenumbruch ausgegeben. So bleibt eine Übersetzungsänderung an zwei Keys ein Diff über zwei Zeilen, auch in den CRLF-Repositories, in denen diese Dateien oft liegen. - Platzhalter sind MessageFormat. Werte werden als
java.text.MessageFormatgelesen, sodass{0}, die typisierte Form{0,number}, die stilisierte Form{0,number,integer}und benannte Argumente wie{count}extrahiert und über die Übersetzung hinweg geschützt werden. Unternachrichten-Argumente (plural,select,selectordinal,choice) werden erkannt, sodass das Übersetzen des Zweigtexts eine Übereinstimmung bleibt, das Entfernen oder Umbenennen eines Arguments jedoch nicht. - Einschränkung: Einfache Anführungszeichen werden nicht interpretiert. Die Single-Quote-Maskierung von MessageFormat wird nicht beachtet, sodass ein maskiertes Literal wie
'{0}'weiterhin als Platzhalter gelesen wird. Das ist Absicht, damit ein gewöhnlicher Apostroph im übersetzten Text nie einen folgenden Platzhalter verschluckt.
Dokumentreihenfolge der Keys
Schreibvorgänge erhalten die Key-Reihenfolge deines Dokuments. Die JSON-Familie, YAML und ARB tragen Keys exakt in Dokumentreihenfolge durch den Roundtrip:
- Ein ganzzahlartiger Key wie
"2","10"oder"404"behält seine Position, statt nach vorn gehoben und neu sortiert zu werden, eine Datei mit numerischen Ids, HTTP-Statuscodes oder Jahreszahlen als Keys bleibt also in ihrer eigenen Reihenfolge. - Ein Key, den ein
translate-Lauf einer Zieldatei hinzufügt, wird nach den bestehenden Keys des Ziels angehängt, in der Reihenfolge des Quelldokuments, statt alphabetisch einsortiert zu werden. - ARB-Metadatenblöcke überstehen den Roundtrip ebenfalls an ihrer Dokumentposition.
XLIFF ist nicht betroffen: Es aktualisiert bestehende <target>-Elemente an Ort und Stelle, die Reihenfolge des Dokuments wurde also nie neu aufgebaut.
Gepunktete Keys und Kollisionen
Für i18next-json, vue-i18n-json und next-intl-json übersteht ein wörtliches gepunktetes Blatt (ein Key wie "foo.bar" als einzelnes Blatt) den Roundtrip verlustfrei: verbatra liest ihn und schreibt ihn mit seiner Form auf der Platte zurück, nicht neu verschachtelt in foo und dann bar. Echte verschachtelte Pfade bleiben verschachtelt.
Der eine Fall, der bewusst fehlschlägt, ist eine echte Kollision, bei der eine Datei denselben effektiven Pfad sowohl als wörtliches gepunktetes Blatt als auch als echten verschachtelten Pfad ausdrückt (zum Beispiel "foo.bar" neben "foo": { "bar": ... }). Dieses Lesen schlägt mit INVALID_STRUCTURE fehl, statt zu raten oder Daten zu beschädigen.
ngx-translate-json behandelt einen gepunkteten Key als verschachtelten Pfad statt als wörtliches Blatt, es gibt also keine Mehrdeutigkeit zwischen gepunktet und wörtlich zu erhalten, aber es bekommt dieselbe Kollisionssicherheit: Ein gepunkteter Key, dessen Pfad mit einem echten verschachtelten Pfad kollidiert, auch wenn einer der Vorfahr des anderen ist, schlägt mit INVALID_STRUCTURE fehl, statt still einen Wert zu überschreiben.
Wie Dateien gelesen und geschrieben werden
verbatra begrenzt beim Lesen Eingabegröße und Verschachtelungstiefe und widersteht einer Datei, die sich unter ihm ändert. Es schreibt atomar: erst in eine temporäre Datei, dann per Umbenennen an den Zielort, ein unterbrochener Schreibvorgang hinterlässt also nie eine halbfertige Locale-Datei. Wenn verbatra eine Datei nicht verarbeiten kann, erzeugt es einen strukturierten, geheimnisfreien Fehler mit stabilem Code statt eines rohen: INVALID_JSON, INVALID_YAML oder INVALID_XML für fehlerhafte Syntax, INVALID_STRUCTURE für eine parsebare Datei der falschen Form, MAX_DEPTH_EXCEEDED und INPUT_TOO_LARGE für die Obergrenzen und MIXED_STRUCTURE für ngx-translates Mischfall.
Eine baumbasierte Locale-Datei (die JSON-Familie, YAML und ARB) kann ein Blatt tragen, das kein String ist, zum Beispiel ein count: 5 oder enabled: true neben den übersetzbaren Keys. verbatra akzeptiert das: Ein Blatt darf String, Zahl, Boolean oder null sein, und nur ein Blatt eines anderen Typs, etwa ein Array, schlägt mit INVALID_STRUCTURE fehl. Ein Nicht-String-Blatt ist vom übersetzbaren Bestand ausgeschlossen: Es wird nie übersetzt, gehasht, verglichen oder auf Platzhalter oder ICU geprüft. Ausschluss ist keine Erhaltung: Schreibt verbatra später dieselbe Locale-Datei, wird der Schreibvorgang aus den Strings aufgebaut, die es verwaltet, ein beim Lesen vorhandenes Nicht-String-Blatt wird also nicht in die Ausgabe übernommen. Muss so ein Wert einen Neuschrieb überleben, halte ihn aus den Dateien heraus, in die verbatra schreibt. XLIFF-Trans-Unit-Werte und .properties-Werte sind immer Strings, dort gilt das also nicht.
Warum das Format wichtig ist
Das Format sagt verbatra zwei Dinge, die es für einen sicheren Lauf braucht. Erstens die Platzhaltersyntax, damit die Platzhalter-Integritätsprüfung weiß, was sie vor und nach der Übersetzung vergleichen soll. Zweitens, für die ICU-Formate, welche Werte zu validieren sind, damit ein Quell-Key mit ungültigem ICU übersprungen statt in kaputtem Zustand gesendet wird. Das falsche Format vergleicht die falschen Tokens, wähle es also passend zu deiner i18n-Bibliothek: i18next-json für i18next, vue-i18n-json für vue-i18n, next-intl-json für next-intl, ngx-translate-json für ngx-translate, xliff für XLIFF-Dateien, yaml für YAML-basierte i18n, arb für Flutter und properties für Java- oder Spring-.properties-Dateien.
Provider
Die fünf Übersetzungs-Provider, ihre Optionen und API-Schlüssel und das Verhalten, das sie alle teilen.
Kosten abschätzen
Schätze den Umfang eines Übersetzungslaufs ab, bevor du Geld ausgibst: zähle die Keys mit einem Dry Run, rechne sie in Requests und Tokens um und bepreise sie mit den Raten deines Providers.