GitLab CI

Keep your repository translation files in sync with Translatize straight from your GitLab pipelines. Wire it up with a CI/CD Catalog component you include:, or drop a copy-paste job into any .gitlab-ci.yml – both run the Translatize CLI through npx, so there is nothing to vendor into your repo.

What the CLI does

The translatize CLI reads a config file (default translatize.config.json, committed to your repo) that names your Translatize project, the file format and where the language files live.

CommandPurpose
pullDownload translations from Translatize into your repo locale files.
pushUpload local source strings to Translatize.
statusCompare local vs. remote and report drift.
qaRun the quality checks on the branch and read its merge gate; fail on errors (or warnings).

Useful flags: --config <path>, --api-url <url>, --json, --dry-run (pull and push); for status, --fail-on-missing / --fail-on-diff (turn findings into a non-zero exit so a pipeline gate fails); and for qa, --fail-on error|warning. The token is never passed on the command line: the CLI reads TRANSLATIZE_API_TOKEN from the environment, which you provide as a masked CI/CD variable.

Exit codes

CodeMeaningJob result
0Success, including differences that status reports without a --fail-* flag.pass
1A gate condition tripped (missing translations / drift under --fail-on-missing / --fail-on-diff, QA issues at the --fail-on severity, a blocked merge gate), or an API/network/auth error (bad or expired token, unreachable API).fail
2Usage or configuration error (unreadable/invalid config, missing token, bad flags).fail

Any non-zero code fails the job. Add allow_failure: true to a job to make it advisory instead of blocking.

Component usage

.gitlab-ci.yml
include:
  - component: gitlab.com/translatize/gitlab/translatize@0.2.0
    inputs:
      command: status
      cli_version: "0.2.0"
      config: translatize.config.json
      extra_args: "--fail-on-missing"

That adds one job, translatize-sync, to your pipeline. Its inputs:

InputDefaultDescription
stagetestStage the job runs in.
commandstatusOne of pull, push, status, qa.
fail_onerrorOnly for qa: the lowest severity that fails the job, error or warning. A blocked merge gate always fails it.
cli_versionlatestnpm dist-tag or exact version of @translatize/cli. qa needs 0.2.0 or newer.
configtranslatize.config.jsonPath to the CLI config file, relative to the repo root. With the default path, qa also runs when the file is missing.
api_url""Override the API base URL (empty = CLI default).
extra_args""Extra flags appended verbatim, e.g. --fail-on-missing --json.
node_imagenode:24-alpineImage that provides Node.js and npx.
Pin the versions
Version 0.2.0 of the component added the qa command and the fail_on input. Include it as gitlab.com/translatize/gitlab/translatize@0.2.0, and pin the CLI with cli_version: "0.2.0" for reproducible pipelines. The component is published in the GitLab.com CI/CD Catalog at gitlab.com/translatize/gitlab; on GitLab.com, $CI_SERVER_FQDN/translatize/gitlab/translatize@0.2.0 resolves to the same component.
One component job per pipeline
Every include of the component adds a job with the same name, translatize-sync, so including it twice (say, for status and qa) leaves you with one job. Use the component for one command and copy-paste jobs, with their own names, for the rest.

Copy-paste job (no component needed)

Drop this into your project .gitlab-ci.yml. It runs the exact same npx invocation as the component defaults:

.gitlab-ci.yml
translatize-status:
  stage: test
  image: node:24-alpine
  script:
    - npx --yes @translatize/cli@latest status --config translatize.config.json

Swap status for pull, push or qa, pin @latest to an exact version such as @0.2.0 for reproducible builds, and append flags such as --fail-on-missing or --json as needed.

CI/CD variable setup

  1. In Translatize, open your project and go to Project Settings → Integrations. Create an API token – it looks like mcni_... and is shown only once.
  2. Tokens are branch-bound: each token is tied to one project and a base branch (default main). A standard token acts only on that branch; a create-own-scope token may also target branches it created (select one with --branch in extra_args). If you sync more than one branch with standard tokens, mint one token per branch and store them under distinct variable names.
  3. Any token role can run pull, push, status and qa; screenshots upload and release need the developer role or higher.
  4. In GitLab, go to Settings → CI/CD → Variables and add the token as a masked variable:
KeyValueFlags
TRANSLATIZE_API_TOKENmcni_...Masked

The CLI reads this automatically – never echo it or pass it as a --flag.

Masked vs. protected
Protected variables are not exposed to merge-request pipelines (those run on unprotected refs). If you want the MR gate below to work, keep TRANSLATIZE_API_TOKEN masked but not protected. A push token used only by branch or scheduled pipelines can be both masked and protected.

Merge-request status gate

Fail the MR pipeline when translations are missing, so drift cannot merge to your default branch:

.gitlab-ci.yml
translatize-mr-gate:
  stage: test
  image: node:24-alpine
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
  script:
    - npx --yes @translatize/cli@latest status --config translatize.config.json --fail-on-missing

--fail-on-missing makes the CLI exit 1 when any target language is missing translations, which fails the job and blocks the merge. Add allow_failure: true to make it advisory instead.

Merge-request QA gate

Fail the pipeline on translation QA errors – broken placeholders, invalid ICU messages, text over its character limit, glossary violations. With the component:

.gitlab-ci.yml
include:
  - component: gitlab.com/translatize/gitlab/translatize@0.2.0
    inputs:
      command: qa
      fail_on: error

The component job has no rules, so it runs in branch pipelines. To run the check in merge-request pipelines, use the job below instead:

.gitlab-ci.yml
translatize-qa:
  stage: test
  image: node:24-alpine
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
  script:
    - npx --yes @translatize/cli@latest qa --fail-on error

qa exits 1 on issues at the --fail-on severity or above (warning includes errors; info never fails). It checks the Translatize branch the token works on, not the git branch of the merge request. With a create-own token working on its own branch, add --branch <name> and it also reads that branch's merge gate into the token's base branch – a gate that would block the merge fails the job too. The checks are described under Quality checks.

Publish a delivery release

The component covers pull, push, status and qa. Other CLI commands run as plain jobs. This one publishes your delivery distribution when you start it from a pipeline on the default branch. It needs no config file and a token with the developer role, and allow_failure: true keeps the pipeline from waiting on the manual job:

.gitlab-ci.yml
translatize-release:
  stage: deploy
  image: node:24-alpine
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
      when: manual
  allow_failure: true
  script:
    - npx --yes @translatize/cli@0.2.0 release

Uploading Playwright screenshot captures works the same way: run npx --yes @translatize/cli@0.2.0 screenshots upload in the job that runs your tests, after they have written the captures (see the CLI docs).

Scheduled nightly pull that commits back

Pull the latest translations every night and commit them to the branch. Because a bot needs to write to the repo, this uses a GitLab project access token with the write_repository scope and a role that may push to that branch, stored as a masked and protected CI/CD variable TRANSLATIZE_GIT_TOKEN. This is a different token from TRANSLATIZE_API_TOKEN – the GitLab token authorizes the git push, the Translatize token authorizes the pull. Create the schedule under Settings → CI/CD → Pipeline schedules, targeting the branch your Translatize token is bound to.

.gitlab-ci.yml
translatize-nightly-pull:
  stage: test
  image: node:24-alpine
  rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule"'
  variables:
    GIT_STRATEGY: clone
    GIT_DEPTH: "0"
  script:
    - apk add --no-cache git
    - npx --yes @translatize/cli@latest pull --config translatize.config.json
    - |
      if [ -z "$(git status --porcelain)" ]; then
        echo "Translations already up to date."
        exit 0
      fi
      git config user.email "bot@translatize.com"
      git config user.name "Translatize Bot"
      git add -A
      git commit -m "chore(i18n): sync translations from Translatize [skip ci]"
      git push "https://oauth2:${TRANSLATIZE_GIT_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git" "HEAD:${CI_COMMIT_BRANCH}"
  • [skip ci] in the commit message stops the push from triggering another pipeline.
  • git status --porcelain detects both edited and newly-created locale files, so a no-op night makes no empty commit.
  • HEAD:${CI_COMMIT_BRANCH} pushes the detached CI checkout back onto the scheduled branch.