Referenz / CLI
Maschinell übersetzte Seite
verbatra types
Erzeuge TypeScript-Deklarationen für deine Katalog-Keys und deren Nachrichtenargumente, ohne einen Provider aufzurufen oder einen API-Key zu lesen.
Mach aus einem vertippten Key oder einem vergessenen Interpolationsargument einen Compile-Fehler. types liest deinen Quellkatalog über den konfigurierten Format-Adapter und schreibt eine Deklarationsdatei: eine Union aller Keys und, pro Key, die Argumente, die dessen Nachricht nimmt. Der Befehl ruft keinen Provider auf, liest keinen API-Key und fasst nie eine Ziel-Locale-Datei an.
Synopsis
verbatra types [flags]Beispiele
# write verbatra-types.d.ts from the source catalog
verbatra types
# write it where the app already imports from
verbatra types --out src/generated/messages.d.ts
# CI gate: exit 1 if the committed declaration is stale
verbatra types --check
# machine-readable result for a script
verbatra types --jsonFlags
| Flag | Argument | Standard | Wirkung |
|---|---|---|---|
--out | <path> | verbatra-types.d.ts | wohin die Deklaration geschrieben wird, relativ zum Arbeitsverzeichnis und innerhalb der Projektwurzel; fehlende Verzeichnisse werden angelegt |
--check | keins | aus | die committete Deklaration Byte für Byte mit einer frischen Generierung vergleichen, ohne etwas zu schreiben; Exit 1, wenn sie veraltet ist oder fehlt |
Außerdem nimmt der Befehl die globalen Flags --cwd, --config und --json.
Verhalten
Was erzeugt wird
Aus einer locales/en.json wie dieser:
{
"app": { "title": "Verbatra" },
"greeting": "Hello {{name}}",
"cart": { "item_one": "{{count}} item", "item_other": "{{count}} items" }
}wird das hier:
// Generated by verbatra from locales/en.json (i18next-json). Do not edit by hand.
// Re-run `verbatra types` after the source catalog changes.
/** An argument whose type the source catalog does not record. */
export type VerbatraArgument = string | number;
/** The arguments of a message that takes none: passing any argument is a type error. */
export type VerbatraNoArguments = Record<string, never>;
/**
* The arguments of a message verbatra could not analyse, so nothing is claimed about them.
* Every key carrying this type is listed by `verbatra types` when it generates this file.
*/
export type VerbatraUnknownArguments = Record<string, VerbatraArgument>;
/** Every key in the source catalog, mapped to the arguments its message takes. */
export interface VerbatraMessages {
"app.title": VerbatraNoArguments;
"greeting": { readonly "name": VerbatraArgument };
"cart.item": { readonly "count": VerbatraArgument };
"cart.item_one": { readonly "count": VerbatraArgument };
"cart.item_other": { readonly "count": VerbatraArgument };
}
/** Every key in the source catalog. */
export type VerbatraMessageKey = keyof VerbatraMessages;
/** The arguments the message at `Key` takes. */
export type VerbatraMessageArguments<Key extends VerbatraMessageKey> =
VerbatraMessages[Key];
/** The keys the source catalog marks as carrying plural forms. */
export type VerbatraPluralMessageKey = "cart.item_one" | "cart.item_other";Typisiere deine eigene Übersetzungsfunktion dagegen, den Rest erledigt der Compiler:
import type { VerbatraMessageArguments, VerbatraMessageKey } from "./verbatra-types.js";
declare function t<Key extends VerbatraMessageKey>(
key: Key,
args: VerbatraMessageArguments<Key>,
): string;
t("greeting", { name: "Ada" }); // fine
t("greting", { name: "Ada" }); // error: not a key in the catalog
t("greeting", {}); // error: the name argument is required
t("app.title", { name: "Ada" }); // error: this message takes no argumentWas behauptet wird und was nicht
Die Keys sind genau das, was der Format-Adapter erzeugt hat, in Dokumentreihenfolge, deshalb erzeugt derselbe Katalog immer dieselben Bytes. Die Argumente stammen aus den Platzhalter-Tokens, die der Adapter extrahiert hat; Nachrichten in next-intl-json und arb werden mit dem ICU-Parser des Adapters gelesen, sodass auch ein Argument deklariert wird, das nur einige Zweige verwenden. Geraten wird nichts, und nichts wird als any deklariert.
| Deine Nachricht | Was deklariert wird |
|---|---|
| keine Platzhalter | VerbatraNoArguments, jedes übergebene Argument ist also ein Typfehler |
benannte Platzhalter ({{name}}, {name}, %(name)s) | eine Objektform mit genau diesen Namen |
nummerierte oder anonyme Platzhalter ({0}, %@, %1$d) | ein readonly-Tupel, eine Position pro Argument |
ein Argument, das nur einige select- oder plural-Zweige verwenden (next-intl-json, arb) | eine optionale Eigenschaft, name?:, oder eine optionale Tupel-Position am Ende ([number, VerbatraArgument?]); eines, das jeder Zweig verwendet, bleibt Pflicht |
ein ICU-Argument vom Typ number, plural oder selectordinal in next-intl-json oder arb | number |
ein ICU-Argument vom Typ date oder time ({d, date, short}) in next-intl-json oder arb | Date | number |
ein ICU-Argument vom Typ select ({gender, select, ...}) in next-intl-json oder arb | string |
ein i18next-Formatter number ({{count, number}}) in i18next-json, ngx-translate-json oder yaml | number |
ein i18next-Formatter datetime ({{d, datetime}}) in denselben drei Formaten | Date | number |
eine unescapte i18next-Interpolation ({{- name}}) | der Name hinter dem Präfix, name |
ein MessageFormat-Argument vom Typ number, plural oder choice ({0,number}) in properties | number |
eine numerische printf-Konvertierung (%d, %1$d) | number |
eine printf-String-Konvertierung (%s, %(name)s) | string |
jeder andere Platzhalter, auch ein schlichtes ICU-Argument ({name}) | VerbatraArgument, ein Alias für string | number |
ungültige Nachrichtensyntax, die nur next-intl-json und arb prüfen | VerbatraUnknownArguments, und der Key wird in der Ausgabe genannt |
| eine Nachricht, die benannte, nummerierte und anonyme Argumente mischt | VerbatraUnknownArguments, und der Key wird in der Ausgabe genannt |
ein nummeriertes Argument unter Position 0 oder ab Position 64 (%0$s, {64}) | VerbatraUnknownArguments, und der Key wird in der Ausgabe genannt |
Ein Format, dessen Platzhalter keinen Namen tragen (Apple .strings, Android strings.xml, positionale gettext-Konvertierungen), bekommt ein Tupel, nie erfundene Namen. Ein Name, der mit verschiedenen Typen vorkommt, bekommt deren Vereinigung: {d, date, short} neben einem schlichten {d} wird zu Date | number | string. Keys sind immer String-Literale in Anführungszeichen, ein Key mit Punkt, reserviertem Wort, führender Ziffer oder Anführungszeichen wird also wortwörtlich deklariert. Ein Key, den der Adapter von der Übersetzung ausgeschlossen hat (ein Blatt, das kein String ist, eine Android-Ressource mit translatable="false"), wird nie deklariert und in der Ausgabe genannt.
Ein Key, dessen eigener Name einen Punkt enthält
Der deklarierte Key wird so geschrieben, wie verbatra ihn in verbatra.lock.json schreibt, nicht in einer Lookup-Syntax, die deine i18n-Bibliothek versteht. Ein JSON-Katalog mit {"a.b": "..."} trägt den Key a\.b, bewusst verschieden vom verschachtelten Pfad a.b:
export interface VerbatraMessages {
"a\\.b": VerbatraNoArguments;
}i18next kennt kein Escape für sein Key-Trennzeichen, t("a\\.b") schlägt diesen Key also nicht nach. Trägt dein Katalog solche Keys, setz keySeparator auf ein anderes Zeichen (oder bei einem flachen Katalog auf false) und bilde den deklarierten Key in deinem eigenen typisierten Wrapper auf den Laufzeit-Key ab.
Der eine Fall, in dem die Deklaration falsch statt nur still ist
Nur next-intl-json und arb prüfen die Nachrichtensyntax. In jedem anderen Format liefert ein kaputter Wert wie "Hello {name" kein Platzhalter-Token. Die Nachricht wird deshalb als argumentlos deklariert, und das Argument zu übergeben, das sie wirklich braucht, ist ein Compile-Fehler. Gemeldet wird das nicht, weil es nicht erkannt wurde. Reparier den Katalog, nicht die Deklaration.
Plural-Keys
Nichts wird zusammengefasst. i18next trägt den Plural als Key-Suffix, deshalb werden cart.item_ und cart.item_ so deklariert, wie sie geschrieben sind, und daneben der Basis-Key cart.item, den t("cart.item", { count }) nachschlägt; er verlangt count und jedes Argument, das eine der Formen nimmt. Auch die eigenen Typen von i18next akzeptieren beide Schreibweisen. Eine Ordinalgruppe wie place_ deklariert place, und ein Basis-Key, den der Katalog schon enthält, behält seine eigene Deklaration. VerbatraPluralMessageKey listet die Keys, die der Adapter als Träger von Pluralformen markiert hat, also die mit Suffix, damit du darauf einschränken kannst.
Wohin die Deklaration geht
Standardmäßig landet sie als verbatra-types.d.ts im Wurzelverzeichnis deines Projekts. Committe sie: Eine Kollegin oder ein CI-Job typprüft dagegen, ohne verbatra laufen zu lassen, und --check braucht eine committete Datei zum Vergleichen. Nichts trägt sie in .gitignore ein.
Ein Ausgabepfad wird mit TYPES_ zurückgewiesen, wenn er:
- keine Datei benennt, auch wenn er auf ein Pfadtrennzeichen endet wie
generated/(ein leeres--outscheitert früher, mitINVALID_),OUT - absolut ist,
- mit
..aus der Projektwurzel herausklettert, - keine TypeScript-Datei ist, sein Name also nicht auf
.ts,.mtsoder.ctsendet, - eine konfigurierte Locale-Datei benennt, Quelle oder Ziel, damit die Generierung nie einen Katalog überschreibt,
- die Lock-Datei
verbatra.lock.jsonist, - die Herkunftsdatei
verbatra.provenance.jsonist, - der Übersetzungsspeicher-Cache
verbatra.cache.jsonist, - eine Datei ist, in der verbatra nach seiner Konfiguration sucht:
package.json,.verbatrarc,.verbatrarc.json,.verbatrarc.yaml,.verbatrarc.yml,.verbatrarc.js,.verbatrarc.cjs,.verbatrarc.ts,verbatra.config.js,verbatra.config.cjsoderverbatra.config.ts, - die Konfigurationsdatei ist, die der Lauf tatsächlich geladen hat, auch eine, die du mit
--configangegeben hast, - die Glossardatei ist, auf die
glossaryin deiner Konfiguration zeigt, - eine bestehende Datei ist, die nicht mit der Kopfzeile
// Generated by verbatra frombeginnt, die verbatra an den Anfang jeder Deklaration schreibt (eine vorangestellte Byte-Order-Mark und Leerzeilen werden ignoriert), oder zu groß ist, um das zu prüfen.
Dateinamen werden ohne Rücksicht auf Groß- und Kleinschreibung verglichen, und die Prüfungen laufen nach dem Auflösen symbolischer Links noch einmal, sodass ein verlinktes Verzeichnis die Deklaration weder aus der Projektwurzel hinaus noch auf eine geschützte Datei tragen kann. Die letzte Prüfung läuft nur, wenn ein Lauf schreiben würde, und lässt deine Datei unangetastet; --check weist eine bestehende Datei nie zurück. Ein Pfad, der alle Prüfungen besteht, kann beim Schreiben trotzdem scheitern (ein schreibgeschütztes Verzeichnis, eine volle Platte): Der Lauf bricht dann mit TYPES_ ab und nennt die Datei und den Dateisystem-Code, etwa EACCES.
Ehrlich bleiben in der CI
Lass types --check im selben Job neben verbatra check laufen: check fängt Locales ab, die hinter der Quelle zurückbleiben, types --check deine Typen. Der Vergleich geht Byte für Byte, und die Deklaration wird immer mit Line-Feed-Zeilenenden geschrieben. Leg die Zeilenenden deshalb in .gitattributes fest, sonst lässt ein Checkout, der sie umschreibt, die Datei dauerhaft veraltet zurück:
verbatra-types.d.ts text eol=lfJSON-Ausgabe
Schema: types-envelope.json validiert den ganzen Envelope; Schemas listet die übrigen auf.
Mit --json trägt der Envelope ein Ergebnis:
interface GenerateTypesResult {
path: string; // absolute path of the declaration file
sourcePath: string; // the source catalog, relative to the project root
keys: number; // how many keys are declared
withArguments: number; // keys taking at least one determined argument
unresolved: { key: string; reason: "invalid-message-syntax" | "mixed-argument-styles" | "argument-index-out-of-range" }[];
excluded: string[]; // keys the adapter excluded, never declared
plural: string[]; // keys carrying plural forms
written: boolean; // false under --check and when the file already matched
stale: boolean; // the file on disk differed when the run started
missing: boolean; // no file existed when the run started
check: boolean; // whether this was a --check run
}Rückmeldung im Terminal
Die Zusammenfassung geht auf stdout; eine Spinner-Zeile und nach dem Schreiben ein Hinweis, die Datei zu committen, gehen auf stderr (--check schlägt nichts vor, und --quiet lässt beides weg). Ein erzeugender Lauf, ein unveränderter zweiter Lauf und ein veraltetes --check:
verbatra: generating the declarations... done (0.2s)
verbatra types
214 keys declared, 63 of them taking arguments, from locales/en.json
arguments not determined (1):
legal.notice invalid-message-syntax
excluded by the adapter (1): meta.version
wrote verbatra-types.d.ts
next: commit verbatra-types.d.ts (verbatra types --check compares against it in CI)
verbatra types
214 keys declared, 63 of them taking arguments, from locales/en.json
unchanged verbatra-types.d.ts
verbatra types
215 keys declared, 63 of them taking arguments, from locales/en.json
verbatra-types.d.ts is out of date, re-run verbatra typesFarbe, Spinner und die Variablen dazu findest du unter Terminalausgabe.
Exit-Codes
| Code | Bedeutung |
|---|---|
0 | die Deklaration wurde erzeugt, egal ob sich die Datei geändert hat; oder --check hat sie als aktuell befunden |
1 | --check hat die committete Deklaration als veraltet oder fehlend befunden (is missing, run verbatra types to create it) |
2 | konnte nicht laufen: ein Verwendungsfehler, ein Problem mit Konfiguration oder Quelle, ein zurückgewiesener Ausgabepfad (TYPES_) oder eine Deklaration, die nicht geschrieben werden konnte (TYPES_) |
Den Vertrag, dem jeder Befehl folgt, findest du unter Exit-Codes.
Siehe auch
verbatra extractträgt die Keys, die dein Code benutzt, in den Katalog ein;typesdeklariert sie dann.verbatra checkist das CI-Gate für Locale-Drift, direkt neben diesem für Typ-Drift.- Formate listet die Platzhaltersyntax jedes Formats.
- Exit-Codes und JSON-Ausgabe behandelt den Envelope und den Exit-Code-Vertrag.