---
title: "verbatra watch"
description: "Re-translate on every source change until interrupted, so locale files stay in sync while you edit the source; each run sends only new and changed keys."
type: "reference"
---
# verbatra watch

Re-translate on every source change until interrupted, so locale files stay in sync while you edit the source; each run sends only new and changed keys.

Watch the source locale file and re-run a translation after each debounced change, until you interrupt it. `watch` runs an initial translation at startup, then keeps your target locales current as you save. Each run works like [`verbatra translate`](https://verbatra.kreitz-webdev.de/docs/cli/translate.md), except that `watch` has no `--dry-run`, `--prune`, `--include-human`, `--estimate` or `--max-tokens` and never exits `3`. At startup it also adds missing entries to an existing `.gitignore`, see [.gitignore entries](https://verbatra.kreitz-webdev.de/docs/cli.md#gitignore-entries).

## Synopsis [#synopsis]

```bash
verbatra watch [flags]
```

## Examples [#examples]

```bash
# watch and re-translate on each change
verbatra watch

# calmer in a busy editor: wait 1s after the last change, stream NDJSON
verbatra watch --debounce 1000 --json

# keep only German current while you work
verbatra watch --locales de
```

## Flags [#flags]

| Flag | Argument | Default | Effect |
| --- | --- | --- | --- |
| `--locales` | `<list>` | every configured target locale | watch only this comma-separated subset; checked once at startup, where an unconfigured locale fails with `UNKNOWN_LOCALE` |
| `--debounce` | `<ms>` | `300` | wait this many milliseconds after the last change before translating; a whole number from `1` to `60000` |
| `--lock-timeout` | `<seconds>` | `600` | wait this long for a held per-locale write lock in each run; a whole number from `1` to `3600` |
| `--concurrency` | `<n>` | `1` | translate up to `n` target locales at once per run; a whole number from `1` to `100`, refused under a `maxTokens` budget |
| `--no-cache` | none | off (cache on) | bypass the [translation-memory cache](https://verbatra.kreitz-webdev.de/docs/the-cache.md) (`verbatra.cache.json`) on every run |

Also takes the [global flags](https://verbatra.kreitz-webdev.de/docs/cli.md#global-flags) `--cwd`, `--config` and `--json`; here `--json` prints one record per run. Before it starts, `watch` loads the [environment files](https://verbatra.kreitz-webdev.de/docs/cli.md#environment-files).

## Behavior [#behavior]

### Each run [#each-run]

Every run translates the `--locales` subset, or every target, with a fresh token budget. A locale whose [write lock](https://verbatra.kreitz-webdev.de/docs/the-lock-file.md) stays contended past `--lock-timeout` fails with `LOCK_CONTENDED` in that run's summary while the other locales still run; a lock left by a verbatra process that is no longer running is reclaimed at once. A failed run never stops the watcher. A wait for a lock prints the lock-wait line described under [`translate`](https://verbatra.kreitz-webdev.de/docs/cli/translate.md#write-locks-and-interrupts).

A `--concurrency` above `1` together with a configured `maxTokens` budget is refused at startup with exit `2`, before the initial run, because which locale loses its remaining work would depend on how the locales interleave.

A provider that cannot be built, most often because its API key variable is unset, is refused at startup too: `watch` exits `2` with `PROVIDER_CONSTRUCTION_FAILED` and a `next:` line naming the variable, exactly like `translate`, instead of waiting for changes no run could translate. Before 0.12.0 it kept waiting.

### Stopping [#stopping]

<AvailableFrom version="0.12.0" />

The first interrupt (Ctrl-C or SIGTERM) lets the current run finish, stops the watcher, and exits `0`. A second interrupt while that shutdown is still in flight force-stops with exit `130`, after releasing the session's write locks. Without `--json`, stderr says `verbatra: waiting for changes...` after every run, `verbatra: press Ctrl-C to stop` after the startup line on an interactive terminal, and `verbatra: stopped` once the shutdown has finished. `--quiet` drops these lines, but not the startup line or `verbatra: stopping, finishing current run...` on the first interrupt: those are written in every mode, `--json` included.

### JSON output [#json-output]

Schema: [`watch-envelope.json`](https://verbatra.kreitz-webdev.de/schema/v1/watch-envelope.json) validates the whole envelope; [Schemas](https://verbatra.kreitz-webdev.de/docs/cli/output.md#schemas) lists the others.

With `--json`, stdout is NDJSON: one [envelope](https://verbatra.kreitz-webdev.de/docs/cli/output.md#json-output) per run, with `command: "watch"`. A succeeded run is an `ok: true` record carrying that run's [`RunSummary`](https://verbatra.kreitz-webdev.de/docs/cli/translate.md#json-output); a failed run is an `ok: false` record with the failure's own code, or `WATCH_RUN_FAILED` when it had none, plus its `hint` when it has one. A failed run is only a record: it neither stops the watcher nor changes the exit code. The startup line, the stopping notice and progress go to stderr. Without `--json`, a run's summary goes to stdout and a failed run's `verbatra: error [CODE] message` line to stderr.

### Terminal feedback [#terminal-feedback]

<AvailableFrom version="0.12.0" />

Without `--json`, `watch` names the source file once a change to it has settled, then reports the run as `translate` does, with the same spinner on an interactive terminal:

```text output
verbatra: change detected: locales/en.json
verbatra: translating de
verbatra: de batch 1/1
verbatra: de: writing
verbatra: de done, 1 translated
verbatra: run finished, 1 locale processed
verbatra: waiting for changes...
```

These lines go to stderr, and `--quiet` drops them. See [Terminal output](https://verbatra.kreitz-webdev.de/docs/cli/output.md#terminal-output).

## Exit codes [#exit-codes]

| Code | Meaning |
| --- | --- |
| `0` | stopped cleanly by a single interrupt |
| `2` | could not start or stop: a config error, a startup or shutdown failure, or a usage error such as an invalid `--debounce` |
| `130` | force-stopped by a second interrupt |

A failed run during the session never changes the exit code. See [the exit-code contract](https://verbatra.kreitz-webdev.de/docs/cli/output.md#exit-codes).

## Related [#related]

- [`verbatra translate`](https://verbatra.kreitz-webdev.de/docs/cli/translate.md) is the one-shot run `watch` repeats.
- [The cache](https://verbatra.kreitz-webdev.de/docs/the-cache.md) explains what `--no-cache` bypasses on each run.
- [How it works](https://verbatra.kreitz-webdev.de/docs/how-it-works.md) explains what each run does.
- [Protecting human translations](https://verbatra.kreitz-webdev.de/docs/protecting-human-translations.md) explains why a run keeps a stale value a person wrote.
- [The SDK reference](https://verbatra.kreitz-webdev.de/docs/sdk.md) documents the `WatchRunResult` and `RunSummary` shapes.
