Your first translation

Install the CLI, scaffold a config with verbatra init, preview the run with no API key, then set your key and run your first translate. Under ten minutes from nothing to a translated locale file.

Five steps take you from an empty project to a translated locale file. All you need is Node.js >=22.14.0, and an API key for one provider once you are ready to translate for real. The first three steps cost nothing and need no account.

1. Install

verbatra is a dev dependency:

npm install --save-dev @verbatra/cli

pnpm and yarn work too: pnpm add -D @verbatra/cli then pnpm verbatra ..., or yarn add -D @verbatra/cli then yarn verbatra .... The code blocks below use npx, matching the npm install above.

pnpm takes one extra step. pnpm add -D @verbatra/cli installs correctly but exits 1 with ERR_PNPM_IGNORED_BUILDS, and leaves an unanswered pnpm-workspace.yaml behind that makes every later pnpm command in the project fail the same way. Run pnpm approve-builds once and approve neither entry, or see Troubleshooting for the non-interactive fix.

2. Scaffold a config

npx verbatra init --provider gemini

With a terminal attached, init prompts for anything you did not pass as a flag, each with a default:

  • the provider: one of anthropic, openai, gemini, or deepl (flag --provider, the only input without a default)
  • the source locale, default en (--source)
  • the target locales, comma-separated, default de (--targets)
  • the locale file pattern, default locales/{locale}.json (--path)

Pass --yes to skip the prompts and take the defaults. Without a terminal (in CI, for example) init never prompts; it uses the defaults and only requires --provider.

init writes three things:

  • verbatra.config.ts: your project config, validated against the real schema before it is written
  • .env.example: names the provider's key variable, never a key value
  • .gitignore entries for .env, .env.local, .verbatra-local/, and verbatra.cache.json, created or appended so a real key, local state, or the regenerable cache never lands in a commit

Re-running init skips files that already exist; --force overwrites them. It also pre-fills format by looking at your dependencies: a project using i18next, vue-i18n, next-intl, or @ngx-translate/core gets the matching JSON format, anything else defaults to i18next-json with a TODO comment to change it. For Gemini the scaffold looks like this:

import { defineConfig } from "@verbatra/cli";

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,
    },
  },
});

3. Preview without an API key

Start with verbatra doctor, the preflight for exactly this setup:

npx verbatra doctor

It runs five checks: the config loads and validates, the format resolves to a file adapter, the provider ID resolves to a provider, the environment variable that provider reads its key from is set, and the source locale file exists and parses. Every check runs even when an earlier one failed, so one run reports every problem rather than stopping at the first. It constructs no provider, makes no network request, writes nothing, and never reads a key value. It is also the only check that works before the source file exists, so run it now and again after step 4.

If you do not have a source file yet, create one at the configured pattern, for example locales/en.json:

{
  "greeting": "Hello, {{name}}!",
  "cart": {
    "empty": "Your cart is empty."
  }
}

Now preview the run:

npx verbatra translate --dry-run

A dry run constructs no provider at all, so it needs no API key and cannot spend anything. It reads your files, diffs them, and prints the same per-locale summary a real run would, without sending a single string or writing a single file. Use it to confirm your format, file pattern, and locales are right before you sign up for anything.

verbatra check and verbatra diff are provider-free in the same way: check reports per-locale counts, diff lists the exact keys. Unlike doctor, all three read the source file, so they need it in place first.

4. Set your API key

Keys come from the environment, never from the config file. Each hosted provider reads exactly one variable; for Gemini that is GEMINI_API_KEY. Copy the scaffolded example and fill in your key:

cp .env.example .env

Then open .env and paste your key after GEMINI_API_KEY=. translate, watch, doctor, and studio load .env.local and then .env from the working directory before a run, and a variable already set in your shell always wins. See Providers for every provider's variable.

No key yet? Gemini has a free tier.

Gemini's API has a genuinely free tier, which makes it the cheapest way to try verbatra. Grab a key from Google AI Studio and set GEMINI_API_KEY. The free tier has per-minute and per-day request limits, so pace a large first-time translation.

5. Translate

With the key in place, run the same command for real:

npx verbatra translate

verbatra reads the source locale, sees that every key is missing from de, sends them to the provider in batches, runs each result through the integrity gate, and writes locales/de.json. The run ends with a per-locale summary: keys translated, keys unchanged, orphaned keys, and any notices. The exit code is 0 when every locale came out complete and 1 when one failed or came out partial (written, but with keys still missing); add --json for a machine-readable summary.

What just happened

Three things are now on disk:

  • locales/de.json: the target locale file. It has the same keys as your source, in the same document order, and {{name}} survived translation intact; a result that dropped it would have been withheld, not written.

  • verbatra.lock.json: the lock file. For each target locale it maps every translated key to a hash of the source string that translation came from:

    {
      "version": 1,
      "locales": {
        "de": {
          "cart.empty": "<source content hash>",
          "greeting": "<source content hash>"
        }
      }
    }
  • .verbatra-local/: process-local state (the run-status snapshot and per-locale write locks). init gitignored it; never commit it.

The lock file is the baseline for every future run. Run verbatra translate again without editing anything and nothing is sent: every key is already current. Edit one source string and only that key is retranslated.

Commit the lock file

Commit verbatra.lock.json alongside your locale files, so every machine and your CI diff against the same baseline. See The lock file.

Next

Edit on GitHub