---
title: "Run verbatra in CI"
description: "Fail a pull request on translation drift, broken values or unreviewed machine output, and translate on a push, with the CLI in any CI system."
type: "how-to"
---
# Run verbatra in CI

Fail a pull request on translation drift, broken values or unreviewed machine output, and translate on a push, with the CLI in any CI system.

Use this guide when a pipeline should catch locale files that drifted from the source, or should translate them for you on a push. Every step runs the same CLI you run locally, so it works in any CI system; the examples use GitHub Actions.

Before you start, you need:

- `@verbatra/cli` as a dev dependency, with your lockfile committed. The CLI needs Node `^22.22.0 || >=24`.
- A committed config, locale files and [`verbatra.lock.json`](https://verbatra.kreitz-webdev.de/docs/the-lock-file.md).
- For translating only: the API key of your provider in the CI secret store.

If you use GitHub Actions and want annotations and a job summary without writing the job yourself, use the [GitHub Action](https://verbatra.kreitz-webdev.de/docs/github-action.md) instead.

## Add a drift gate [#add-a-drift-gate]

[`verbatra check`](https://verbatra.kreitz-webdev.de/docs/cli/check.md) compares the source, the target files and the lock file, and exits `1` when a locale has missing or stale keys. It calls no provider, so the job needs no key and runs on pull requests from forks:

```yaml title=".github/workflows/i18n.yml"
name: i18n
on: pull_request

permissions:
  contents: read

jobs:
  check-translations:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@<commit-sha>
      - uses: pnpm/action-setup@<commit-sha>
      - uses: actions/setup-node@<commit-sha>
        with:
          node-version: 22
      - run: pnpm install --frozen-lockfile
      - run: pnpm exec verbatra check
```

Swap `check` for [`verbatra diff`](https://verbatra.kreitz-webdev.de/docs/cli/diff.md) when you want the key names behind the drift, for example to post them in a pull request comment. It exits `1` on the same condition; orphaned keys show up in its output but never fail it on their own.

To gate only some locales, pass `--locales de,fr`. A `--locales` value naming a locale you have not configured exits `2`, even next to valid ones, so a typo never turns the gate green.

### Gate on translation quality [#gate-on-translation-quality]

<AvailableFrom version="0.12.0" />

Drift is not the only failure worth catching. Add `--qa` to also run the integrity gate and the review flags over every committed translation, so a hand-edited value with a broken placeholder, broken markup or unfit ICU plural arms fails the job:

```bash
verbatra check --qa
```

- Add `--strict` to fail on review warnings and missing plural categories too.
- In an edit hook, `verbatra check --file <path>` checks one locale file on its own.

See [the quality check](https://verbatra.kreitz-webdev.de/docs/cli/check.md#quality-check) and [One file](https://verbatra.kreitz-webdev.de/docs/cli/check.md#one-file). The [GitHub Action](https://verbatra.kreitz-webdev.de/docs/github-action.md#checking-translation-quality) runs the same gate with `qa: "true"`.

### Gate on review decisions [#gate-on-review-decisions]

<AvailableFrom version="0.12.0" />

Add `--require-reviewed` to fail while any machine-written translation is not approved in the committed `verbatra.provenance.json`, the file [Studio's Review queue](https://verbatra.kreitz-webdev.de/docs/review-in-studio.md) and the MCP review tools write to:

```bash
verbatra check --require-reviewed
```

It needs no key either. See [Require reviewed translations](https://verbatra.kreitz-webdev.de/docs/cli/check.md#require-reviewed-translations) for the codes it reports.

## Translate on a push [#translate-on-a-push]

Run [`verbatra translate`](https://verbatra.kreitz-webdev.de/docs/cli/translate.md) and map the provider key from your secret store into the step's environment. The CLI takes no key argument and reads no key from the config; [Providers](https://verbatra.kreitz-webdev.de/docs/providers.md#keys-come-from-the-environment) lists the variable each provider reads.

```yaml
      - run: pnpm exec verbatra translate --json
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
```

- The step writes locale files into the checkout and stops there. Add your own step that commits them or opens a pull request, and grant `contents: write` or `pull-requests: write` to that job only.
- To preview first, run `verbatra translate --dry-run` or `--estimate`: neither calls a provider nor needs a key. See [Estimating cost](https://verbatra.kreitz-webdev.de/docs/estimating-cost.md).
- Against a rate-limited provider, translate one locale per step with `--locales`.

## Preflight with doctor [#preflight-with-doctor]

Put [`verbatra doctor`](https://verbatra.kreitz-webdev.de/docs/cli/doctor.md) before either job to fail early on a broken setup. It checks the config, the format, the provider, the key variable, the network policy, the source file and locale support, and never calls a provider or reads a key value. It does report an unset key variable as a failed check, so map the key into any job where that check has to pass.

## Exit codes [#exit-codes]

Branch on the exit code, never on the text. What each code means, with its edge cases, is on [Exit codes and JSON output](https://verbatra.kreitz-webdev.de/docs/cli/output.md#exit-codes); an error code in the output is explained on [Error codes](https://verbatra.kreitz-webdev.de/docs/error-codes.md).

## JSON output [#json-output]

Pass `--json` for one envelope per record on stdout. Its fields are on [Exit codes and JSON output](https://verbatra.kreitz-webdev.de/docs/cli/output.md#json-output), and each command's `result` is on its command page. [Script verbatra with JSON](https://verbatra.kreitz-webdev.de/docs/agent-recipes.md) has recipes that parse it.

## Keys and installs [#keys-and-installs]

- Install with `pnpm install --frozen-lockfile` or `npm ci`, so the job runs the exact `@verbatra/cli` release your lockfile records.
- Keep secrets out of jobs that never call a provider: `check`, `diff`, `report`, `export`, `import`, `extract`, `pseudo`, `types` and `tmx`.
- In CI, rely on `env:`. Which commands also load `.env.local` and `.env` is on [Environment files](https://verbatra.kreitz-webdev.de/docs/cli.md#environment-files).

## Related [#related]

- [Use the GitHub Action](https://verbatra.kreitz-webdev.de/docs/github-action.md): the same gates with annotations and a job summary.
- [`verbatra check`](https://verbatra.kreitz-webdev.de/docs/cli/check.md) and [`verbatra diff`](https://verbatra.kreitz-webdev.de/docs/cli/diff.md): every flag of the two gates.
- [Exit codes and JSON output](https://verbatra.kreitz-webdev.de/docs/cli/output.md): the contract a CI step reads.
- [Error codes](https://verbatra.kreitz-webdev.de/docs/error-codes.md): what each code means and its next step.
