Translatize CLI

The command-line client for Translatize. It downloads a branch into per-language JSON files with pull, sends local source keys back with push, and reports drift with status – so a pull request can be gated on translation completeness. It also gates on translation quality with qa, uploads screenshot captures, publishes over-the-air releases, and writes the Translatize rules for coding agents into AGENTS.md.

Install

Add the CLI as a dev dependency. The binary is named translatize – run it with npx translatize <command> or from an npm script. It is built on @translatize/core and requires Node.js 18.17 or newer. This page describes version 0.2.0.

terminal
npm install -D @translatize/cli
On npm
@translatize/cli is published on npm under the @translatize scope, so the install above and npx @translatize/cli@latest resolve from the public registry. The qa, screenshots upload and release commands and init --agents need 0.2.0 or newer.

Quickstart

terminal
# 1. Scaffold translatize.config.json (records your project + file layout)
npx translatize init

# 2. Provide a branch-bound API token
export TRANSLATIZE_API_TOKEN=mcni_xxx

# 3. Download translations into locales/<lang>.json
npx translatize pull

init writes a translatize.config.json you commit to your repo. The token is never written to that file – it is read from the environment or the --token flag at run time.

Authentication

The CLI authenticates with an API token – a string beginning with mcni_, not your login. Create one under Project Settings → Integrations in the app. A token is bound to a single project and a base branch, and by default translatize acts on that bound branch.

A token with the Agent workflow branch scope (create-own) may also target branches it created: pass --branch <name> (or set branch in the config) to operate on one of them. Naming a branch the token cannot touch fails with a branch_not_allowed error that lists the branches the token may target.

The token is resolved in this order:

  1. the --token <mcni_...> flag, then
  2. the TRANSLATIZE_API_TOKEN environment variable.

If neither is set, every command except init stops with a usage error (exit code 2).

Keep tokens out of version control
Never commit the token to translatize.config.json or any file. In CI, store it as a secret and expose it as the TRANSLATIZE_API_TOKEN environment variable.

What each command needs

A token has a role – translator, developer or admin – and some commands need a paid plan:

CommandToken rolePlan
initAny, or no token at allAny
pull, push, statusTranslator or higherAny
qaTranslator or higherAny
screenshots uploadDeveloper or higherStarter, Professional or Agency
releaseDeveloper or higherPublishing works on any plan; apps get the release from our CDN once the account has delivery blocks, an add-on from Starter up (see Delivery)

Configuration

translatize.config.json lives at the root of your project, or wherever you point --config. File patterns are resolved relative to the directory that holds the config file.

translatize.config.json
{
  "apiUrl": "https://api.translatize.com/v1",
  "projectId": "your-project-id",
  "format": "json-nested",
  "files": "locales/{lang}.json",
  "namespace": "app",
  "branch": "feature-x"
}
FieldRequiredDescription
apiUrlnoAPI base URL including the /v1 segment. Defaults to https://api.translatize.com/v1.
projectIdnoThe project id. May be left empty – the CLI then takes the project from the token. Set it to pin the project; a mismatch with the token is reported as a warning and the token wins.
formatyesOn-disk shape: json-nested (dotted keys become nested objects) or json-flat (dotted keys stay flat).
filesyesPath pattern for each language file. Must contain the {lang} placeholder, e.g. locales/{lang}.json.
namespacenoRestrict pull, push and status to keys that start with <namespace>.; pull then writes only those keys.
branchnoDefault branch for pull, push, status, qa and screenshots upload. Must be in the token's allowed set and is overridden by --branch. When omitted, commands use the token's bound branch.
githubnoSettings for the GitHub App (sourceLanguage, autoTranslate, commitTranslations, requireComplete, syncDefaultBranch). The CLI ignores this block, so one file serves both.

Other top-level keys are accepted and kept as they are, so init --force keeps another tool's settings. qa, screenshots upload and release only talk to the API, so they also run without a config file; the API URL then comes from --api-url or the default.

json-nested vs json-flat

The API stores keys in flat, dotted form (app.nav.home). format decides how they are written to disk:

locales/en.json
// json-nested
{
  "app": {
    "nav": { "home": "Home" },
    "title": "Hello"
  }
}

// json-flat
{
  "app.nav.home": "Home",
  "app.title": "Hello"
}

Both are read back losslessly, and files are written with keys sorted and a trailing newline so re-running pull stays diff-friendly. If a key is both a value and a parent (both a and a.b exist), json-nested can't represent it and the CLI asks you to switch to json-flat.

Commands

Every command accepts these global flags:

FlagDescription
-c, --config <path>Path to the config file. Default translatize.config.json.
--token <token>API token, overriding TRANSLATIZE_API_TOKEN.
--api-url <url>API base URL, overriding the config value.

translatize -v (or --version) prints the version, and translatize <command> --help lists a command's flags. Running translatize with no arguments prints the help.

translatize init

Create translatize.config.json (or the file named with --config). It refuses to overwrite an existing file unless --force is passed. If a token is available, init checks it, prints the project name, bound branch, role and configured languages, and records the token's project id when you don't pass one. If the check fails, it writes the config anyway and warns. A URL given with --api-url is saved as apiUrl.

FlagDescription
--project-id <id>Project id to record in the config.
--files <pattern>File pattern with a {lang} placeholder. Default locales/{lang}.json.
--format <format>json-nested (default) or json-flat.
--forceOverwrite an existing config file.
--agentsWrite the coding-agent rules into AGENTS.md instead (see below). Leaves the config alone.

translatize init --agents

terminal
npx translatize init --agents

Writes the Translatize rules for coding agents into a marked section of AGENTS.md (created when missing) and of CLAUDE.md when that file exists, in the config file's directory. The rules tell an agent to:

  • never hardcode user-visible strings and use the project's i18n lookup instead;
  • create keys through the Translatize MCP server with the source text, a description, a character limit only when the UI clearly limits the length, and the code location;
  • use dotted key names grouped by feature, and reuse a key only when the meaning matches;
  • work on a Translatize branch, never directly on main;
  • run the quality checks and the merge gate before asking for a merge, and never merge into main without your approval;
  • never put secrets or personal data in translations.

The section sits between <!-- translatize:agents:start --> and <!-- translatize:agents:end -->. Re-running replaces only that section and leaves the rest of the file alone, so run it again after upgrading the CLI. To give the agent the tools those rules name, connect it to the MCP server.

translatize pull

Download the branch and write one file per project language at the files pattern, creating parent directories as needed. A key whose value is empty or missing for a language is omitted from that file. When namespace is set, only keys under it are pulled.

FlagDescription
--branch <name>Branch to operate on. Overrides the config branch field and the token's bound branch. Must be in the token's allowed set.
--dry-runReport the per-file changes (new file / keys added / changed / removed locally) without writing anything.
--jsonEmit machine-readable JSON.

translatize push

Read each language file, compare it with the branch, and upload only new and changed keys. Values are assembled per key across all language files, and large uploads are split into several requests for you. A missing language file is skipped with a warning. push never deletes keys: keys that exist on the branch but not locally are reported and left untouched. It exits 1 if the server rejects any key, and lists each one with its error.

FlagDescription
--branch <name>Branch to operate on. Overrides the config branch field and the token's bound branch. Must be in the token's allowed set.
--dry-runPrint the pending create/update list without sending anything.
--jsonEmit machine-readable JSON.

translatize status

Compare local files against the branch without changing either side – this is the CI gate. It prints per-language completeness (non-empty translations over total branch keys) and the counts of keys that are only local, only remote, or whose values differ, with up to 10 example keys each. Without a --fail-* flag it reports differences but still exits 0.

FlagDescription
--branch <name>Branch to operate on. Overrides the config branch field and the token's bound branch. Must be in the token's allowed set.
--fail-on-missingExit 1 if any language has missing translations.
--fail-on-diffExit 1 if local differs from the branch in any way.
--jsonEmit machine-readable JSON.

translatize qa

Run the quality checks on the branch – placeholders, HTML tags, ICU syntax, ICU plural categories, glossary terms, length against the key's character limit, numbers, punctuation, untranslated values and whitespace – and print the counts per severity with the first 20 issues. Checks switched off for the project and findings someone ignored in the app are not counted.

On a branch other than the token's base branch (for example an agent's own branch), qa also reads the merge gate for merging it into the base and prints whether the merge would be blocked.

It exits 1 on issues at the --fail-on severity or above, and whenever the merge gate would block the merge, whatever --fail-on says. Info-level issues never fail it. AI review findings are at most warnings, so they can only fail it with --fail-on warning. A very large branch is checked in the background on the server; qa then reports the issues already stored for the branch, which are kept up to date as labels change.

FlagDescription
--branch <name>Branch to check. Overrides the config branch field and the token's bound branch.
--fail-on <severity>error (default) or warning (warnings and errors). Any other value is a usage error.
--jsonEmit the full report as JSON.

translatize screenshots upload

Upload the captures written by @translatize/playwright (<name>.json + <name>.png pairs, by default in .translatize/captures) as one capture run, so Translatize can link each screen to the keys shown on it. The CLI asks the server which images it already has and uploads only the rest; the details of every screen, such as the texts and their positions, are always sent. It then completes the run and prints how many images went up and how many key links the run made.

A JSON file without its PNG is skipped with a warning. A screen the server rejects is listed and the others still go up; a full storage quota stops the upload, and the screens already sent are kept. A run holds at most 500 screens – with more captures the command stops before uploading and asks you to split them into several directories. It exits 1 when any screen failed or the upload stopped.

Argument / flagDescription
[dir]Capture directory. Default .translatize/captures.
--branch <name>Branch whose keys the screens are matched against. Overrides the config branch field; must be in the token's allowed set.
--run-name <name>Name of the run. Defaults to the CI branch or tag name and the time.
--commit-sha <sha>Commit the screens come from. Defaults to GITHUB_SHA or CI_COMMIT_SHA.
--jsonEmit machine-readable JSON.

translatize release

Publish an over-the-air distribution as a new version on our CDN, and print the version, the number of keys and languages, and the manifest URL. The token sees only distributions whose branch it may use: if it sees one, that one is published; otherwise name it with --distribution or narrow the choice with --branch. A disabled distribution can still be published – the CDN serves it again once it is enabled in the app.

If the branch has nothing to publish yet – no translated values, or no approved labels for a distribution that publishes approved labels only – the command says so and exits 1.

FlagDescription
--distribution <id>Distribution id, public id or name. Needed when the token sees more than one.
--branch <name>Pick the distribution that publishes this branch. The config branch field is not used here.
--jsonEmit machine-readable JSON.
Releases are public
Anything you publish is public – never put secrets in translations.

Selecting a branch

pull, push, status, qa and screenshots upload all accept --branch <name>. The branch is resolved in this order:

  1. the --branch <name> flag, then
  2. the branch field in translatize.config.json, then
  3. the token's bound (base) branch.

A standard token can only ever act on its bound branch, so --branch must name that same branch. A create-own agent token may additionally target any branch it created. Targeting a branch outside the token's allowed set fails with a branch_not_allowed error listing the branches the token may use. release uses --branch only to pick a distribution.

Agent tokens
The create-own scope lets an AI agent open and manage its own translation branches – it can act on its base branch and the branches it creates, never on any other. See the MCP Server and Integration API docs for the full agent workflow.

Machine-readable output

Every command except init accepts --json and prints one JSON document to stdout. Warnings and errors go to stderr. Colours are off when stdout is not a terminal or when NO_COLOR is set, so CI logs stay plain.

Environment variables

VariableUsed for
TRANSLATIZE_API_TOKENThe API token when --token is not given.
NO_COLORAny non-empty value turns coloured output off.
GITHUB_SHA, CI_COMMIT_SHADefault commit for screenshots upload (GitHub Actions, GitLab CI).
GITHUB_REF_NAME, CI_COMMIT_REF_NAMEPart of the default run name for screenshots upload.

Exit codes

CodeMeaning
0Success (including status differences when no --fail-* gate is set), and --help or --version.
1A gate tripped (status --fail-on-*, qa issues or a blocked merge gate), a push or screenshots upload had server-side failures, or an API request failed (for example an expired token or a missing plan feature).
2Usage or configuration error: an unknown command or flag, a bad value such as --fail-on info, a missing or invalid config, or no token.

Use in CI

status is designed to fail a build when translations fall behind. This GitHub Actions job blocks merges when any language is missing translations:

.github/workflows/translations.yml
name: translations
on: [pull_request]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 24
      - run: npm ci
      - run: npx translatize status --fail-on-missing
        env:
          TRANSLATIZE_API_TOKEN: ${{ secrets.TRANSLATIZE_API_TOKEN }}

Add a QA step to fail the build on translation quality errors too:

.github/workflows/translations.yml
      - run: npx translatize qa --fail-on error
        env:
          TRANSLATIZE_API_TOKEN: ${{ secrets.TRANSLATIZE_API_TOKEN }}

After your Playwright tests have written their captures, upload them in the same job. The token needs the developer role:

.github/workflows/e2e.yml
      - run: npx playwright test
      - run: npx translatize screenshots upload
        env:
          TRANSLATIZE_API_TOKEN: ${{ secrets.TRANSLATIZE_API_TOKEN }}

release needs no checkout and no config file. This workflow publishes a distribution named "Web app" when you start it by hand:

.github/workflows/publish-translations.yml
name: Publish translations
on: workflow_dispatch

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-node@v4
        with:
          node-version: 24
      - run: npx --yes @translatize/cli@0.2.0 release --distribution "Web app"
        env:
          TRANSLATIZE_API_TOKEN: ${{ secrets.TRANSLATIZE_API_TOKEN }}

To push newly added source keys after a merge to main, run npx translatize push in a job with a token bound to that branch. Rather than script npx by hand, you can use the ready-made GitHub Action or GitLab CI component for pull, push, status and qa.