Zum Inhalt springen

Docs / Erste Schritte

Maschinell übersetzte Seite

Diese Seite wurde automatisch übersetzt und kann daher Fehler oder seltsame Formulierungen enthalten. Die englische Version ist die maßgebliche Quelle. Das englische Original lesen.

SDK-Schnellstart

Installiere @verbatra/sdk, sieh dir einen Lauf ohne API-Schlüssel in der Vorschau an, übersetze aus deinem Skript, behandle Fehler und sichere die CI ab.

Am Ende dieser Seite übersetzt ein TypeScript-Skript von dir eine englische Locale-Datei ins Deutsche, meldet, was passiert ist, und lässt einen CI-Job scheitern, sobald eine Locale nicht mehr synchron ist. Du brauchst Node.js ^22.18.0 || >=24 und etwa zehn Minuten. Die ersten drei Schritte brauchen keinen API-Schlüssel.

Jeder Schritt ist ein vollständiges Skript, das du mit node ausführst. Node.js führt TypeScript-Dateien direkt aus, ein zusätzliches Flag brauchst du nicht. Die Skripte nutzen import und Top-Level-await, deshalb markiert der erste Schritt das Projekt auch als ES-Modul.

Schritte

Das SDK installieren

npm init -y
npm pkg set type=module
npm install --save-dev @verbatra/sdk

Führ das in einem leeren Verzeichnis aus: npm init -y legt die package.json an, die npm pkg set type=module als ES-Modul markiert. In einem bestehenden Projekt lässt du npm init -y weg. Wenn pnpm mit ERR_PNPM_IGNORED_BUILDS endet, sieh unter Installation nach.

Eine Quelldatei und eine Konfiguration anlegen

locales/en.json
{
  "greeting": "Hello, {{name}}!",
  "cart": {
    "empty": "Your cart is empty."
  }
}
verbatra.config.ts
import { defineConfig } from "@verbatra/sdk";

export default defineConfig({
  sourceLocale: "en",
  targetLocales: ["de"],
  format: "i18next-json",
  files: { pattern: "locales/{locale}.json" },
  provider: { id: "gemini", options: { model: "gemini-2.5-flash", maxOutputTokens: 4096 } },
});

loadConfig findet diese Datei ausgehend vom Arbeitsverzeichnis und validiert sie. Unter Wähle deinen Stack steht das format für dein Framework, und Provider listet jeden Provider mit seinen Optionen. Willst du ganz ohne Datei auskommen, übergib dasselbe Objekt als configOverride; siehe SDK-Rezepte.

Den Lauf ohne Schlüssel in der Vorschau ansehen

preview.ts
import { loadConfig, translate } from "@verbatra/sdk";

const config = await loadConfig();
const preview = await translate({ config, dryRun: true });

for (const locale of preview.locales) {
  console.log(`${locale.locale}: would translate ${locale.translated.join(", ")}`);
}
node preview.ts
Ausgabe
de: would translate cart.empty, greeting

Ein Probelauf baut keinen Provider, braucht also keinen Schlüssel, kostet nichts und schreibt nichts. Übergib stattdessen estimate: true, um eine Token- oder Kostenzahl zu bekommen; siehe Kosten schätzen.

Übersetzen und die Zusammenfassung lesen

translate.ts
import { loadConfig, translate } from "@verbatra/sdk";

const config = await loadConfig();
const summary = await translate({
  config,
  onProgress: (event) => {
    if (event.type === "locale-finished") console.log(`${event.locale}: ${event.translated} keys`);
  },
});

const { succeeded, partial, failed, usage } = summary;
console.log(`${succeeded.length} ok, ${partial.length} partial, ${failed.length} failed`);
if (usage !== undefined) {
  console.log(`tokens: ${usage.inputTokens} in, ${usage.outputTokens} out`);
}

for (const locale of summary.locales) {
  if (locale.status === "failed") {
    console.error(`${locale.locale}: ${locale.error?.code} ${locale.error?.message}`);
    continue;
  }
  for (const entry of locale.needsReview) {
    console.warn(`review ${locale.locale}/${entry.key}: ${entry.reasons.join(", ")}`);
  }
}

if (failed.length > 0 || partial.length > 0) {
  process.exitCode = 1;
}

Der Provider liest seinen Schlüssel aus der Umgebung, und anders als die CLI lädt das SDK keine .env-Dateien, also reichst du ihn selbst weiter. Provider nennt die Variable für jeden Provider.

node --env-file=.env translate.ts

locales/de.json enthält jetzt beide Keys, und {{name}} ist unversehrt. Eine Locale mit partial wurde geschrieben, obwohl noch Keys fehlen, deshalb scheitert das Skript daran genauso wie an failed. Probleme einzelner Locales kommen als Daten in der Zusammenfassung an; nur ein Problem des ganzen Laufs wirft. translate listet jede weitere Option.

Fehler behandeln Verfügbar ab 0.12.0

Ein Problem des ganzen Laufs wirft einen SdkError. Verzweige über seinen stabilen code und gib errorHint aus, um den nächsten Schritt zu zeigen.

safe-translate.ts
import { errorHint, loadConfig, SdkError, translate } from "@verbatra/sdk";

try {
  const summary = await translate({ config: await loadConfig() });
  console.log(`${summary.succeeded.length} locales translated`);
} catch (error) {
  if (!(error instanceof SdkError)) {
    throw error;
  }
  switch (error.code) {
    case "CONFIG_NOT_FOUND":
      console.error(`${error.code}: ${error.message} (searched from ${process.cwd()} upward)`);
      break;
    case "PROVIDER_CONSTRUCTION_FAILED":
      console.error(`${error.code}: the provider could not be built, most often for a missing key`);
      break;
    default:
      console.error(`${error.code}: ${error.message}`);
  }
  const hint = errorHint(error);
  if (hint !== undefined) {
    console.error(`next: ${hint}`);
  }
  process.exitCode = 2;
}

Führst du es ohne Schlüssel aus, meldet es PROVIDER_CONSTRUCTION_FAILED und nennt die fehlende Variable, nie einen Schlüsselwert. Fehlercodes listet jeden Code.

Die CI mit check absichern

check.ts
import { check, loadConfig } from "@verbatra/sdk";

const summary = await check({ config: await loadConfig() });

for (const locale of summary.locales) {
  console.log(`${locale.locale}: ${locale.missing} missing, ${locale.stale} stale`);
}

if (!summary.inSync) {
  process.exitCode = 1;
}
node check.ts

check ruft keinen Provider auf und schreibt nichts, braucht in der CI also keinen Schlüssel. Das Skript endet mit 1, wenn ein Quelltext keine Übersetzung hat oder sich seit der Übersetzung geändert hat.

CommonJS

In einem CommonJS-Projekt liefert require("@verbatra/sdk") dieselben Exporte. Top-Level-await gibt es dort nicht, also packst du die Aufrufe in eine async-Funktion.

Nächste Schritte

Auf GitHub bearbeiten

Auf dieser Seite