Ask your question and get a summary of the document by referencing this page and the AI provider of your choice
The content of this page was translated using an AI.
See the last version of the original content in EnglishIf you have an idea for improving this documentation, please feel free to contribute by submitting a pull request on GitHub.
GitHub link to the documentationCopy doc Markdown to clipboard
Automating translations in CI/CD without shipping bad copy
Manual translation does not survive contact with a release cadence. Someone adds a string on Friday, the export happens next sprint, and by then three more locales are behind. Automating it is straightforward. Automating it without quietly publishing machine output to customers is the part worth thinking about.
Table of Contents
You do not have to migrate to automate
The pipeline shapes below are library-agnostic, and so is the tooling. If your messages are JSON catalogues for i18next, next-intl, react-intl, vue-i18n or next-translate, the Sync JSON plugin reads and writes those files in place:
Copy the code to the clipboard
Your app keeps importing what it imports. The CI jobs below then fill and gate your existing catalogues, and the diff a reviewer sees is a change to locales/fr/checkout.json, not a migration. There is a Sync PO plugin for gettext workflows, and compat adapters if you also want the runtime API to stay unchanged.
Separate the gate from the fill
Two different jobs get confused constantly.
A gate is a check that fails. It says this build must not ship because required locales are missing. It writes nothing.
A fill is a mutation. It generates the missing translations and commits them. It never fails a build.
Running only a fill means nothing ever blocks, and machine output flows to production unreviewed. Running only a gate means the build goes red and a human has to unblock it every time. Most teams want both, wired to different triggers: fill on a pull request, gate on merge to the release branch.
Where automation can live
Open the table in a modal to view all data content clearly
| Stage | Trigger | Good for | Cost |
|---|---|---|---|
| Pre-push hook | Local git | Fast feedback, no CI minutes | Runs on the developer's machine and their API key |
| Pull request | CI job | Review before merge, one place for secrets | CI minutes plus model calls per PR |
| Release branch | CI job | Hard gate on coverage | Cheap, no model calls |
| Runtime | CMS | Content changes without a rebuild | Hosted dependency |
Pre-push: fastest loop
Husky runs the fill before the code leaves the machine, so the translations arrive in the same push as the strings that needed them.
Copy the code to the clipboard
--unpushed limits the work to content that has not been pushed yet, which is what keeps this from taking a minute on every push. --mode complete fills what is missing without rewriting entries that already have a value, so a reviewed translation is never silently replaced.
For a monorepo, scope each app:
Copy the code to the clipboard
The downside is real: every developer needs an API key, and the cost lands on whoever pushes. That is why most teams move this to CI once there are more than a few of them.
Pull request: fill where the review is
The same work in GitHub Actions, scoped to the diff:
Copy the code to the clipboard
Four details in there are load-bearing:
fetch-depth: 0is required for--git-diffto work. A shallow clone has no base to diff against, and the fill silently covers nothing.[skip ci]in the commit message stops the workflow retriggering itself. Without it the job commits, which opens a run, which commits again. This is the classic way to burn a CI budget overnight.concurrencywithcancel-in-progressstops two pushes racing to write the same files.--git-diffscopes the fill to what changed in the PR. Omit it and you re-translate the whole catalogue on every run.
The translations land as a commit on the PR branch, which means a reviewer sees them in the diff. That is the entire point of doing it here rather than after merge.
Release branch: the gate
The gate needs no model access and should be fast.
Copy the code to the clipboard
Backed by a test that asserts coverage rather than by the CLI report:
Copy the code to the clipboard
npx intlayer content test prints a report but exits zero, so it informs and does not gate. Use it locally; use the assertion in CI. More on the distinction in finding missing translations.
requiredLocales is what makes the gate survivable
A gate that demands all eighteen locales blocks every release until the slowest language lands, and gets disabled within a month.
Copy the code to the clipboard
Declare the locales you serve, require the ones that block a release. The rest are filled asynchronously and never hold up a deploy.
Taking translations out of the repo entirely
The other model is to declare one locale in code and manage the rest remotely, through the CMS with Live Sync. Content changes then do not require a rebuild at all, which decouples the copy cadence from the deploy cadence.
Copy the code to the clipboard
This suits teams where non-developers own the copy. It is a trade, not an upgrade: you gain editor autonomy and lose the property that a git checkout fully describes what the app renders. Details in the CMS documentation.
Note that clientSecret is a server-side credential. It belongs in CI secrets and in your server environment, never in anything that reaches a client bundle.
The honest limitation
Everything above automates coverage, not quality. A machine fill turns a visible gap into an invisible one: the audit goes green because the key now has a value, and nobody read it.
That is acceptable for an internal tool, a changelog, or a beta locale. It is not acceptable for pricing, legal copy, error messages that tell someone their payment failed, or anything a customer reads before deciding. Route those through a human, and use --mode complete everywhere so a reviewed string is never overwritten by a later run.
Give the model context so its output is at least consistent:
Copy the code to the clipboard
Common mistakes
- No
[skip ci]on the auto-commit. The job retriggers itself in a loop. - Shallow clone with
--git-diff. No base to diff, so nothing is filled and nothing complains. - Filling the whole catalogue every run. Scope with
--git-diffor--unpushedor watch the bill. - Using the CLI report as a gate. It exits zero.
- Requiring every locale. The gate gets removed the first time it blocks a release.
- A fill job with no gate anywhere. Nothing ever fails, so machine copy reaches production unreviewed.
- Model API keys in the repo. They belong in CI secrets, same as
clientSecret.
Going further
- CI/CD: auto-generating translations with Husky, GitHub Actions and the CMS
- Testing your content and gating a build on coverage
- autoFill: generating per-locale declaration files
- Configuration reference:
locales,requiredLocales,editor - Benchmark reports across frameworks
- Drop-in i18next compat adapter
- How to find missing translations
- How to test translations without brittle tests
Comments
No comments yet. Be the first to share your thoughts.
