Check translations in GitHub Actions
A translation that drops {name} or skips a key breaks the app at runtime, and review rarely catches it. This workflow runs kaeris check on every pull request and turns the job red. It is offline and free: no API key, no account.
The recipe
The workflow
Save it as .github/workflows/i18n-check.yml. Change source to your base-language file and languages to the locales you ship.
name: i18n check
on: pull_request
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: kaeris-io/kaeris-action@v1
with:
mode: check
source: locales/en.json
languages: de,fr
Without Python: npx
The npm package kaeris runs the same check on Node 18 or newer; npx fetches it, so there is no install step. It has only check: translating needs the Python package.
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npx kaeris check --source locales/en.json --langs de,fr
The same without the Action
Plain steps with Python and pip. The log then shows every finding line by line instead of the table the Action prints.
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install kaeris
- run: kaeris check --source locales/en.json --langs de,fr --ci
Findings on the exact line of the pull request (optional)
The Action can also write a SARIF report; uploading it turns each finding into an annotation in the diff. Code-scanning uploads are free on public repositories; private ones need GitHub Advanced Security.
name: i18n check
on: pull_request
permissions:
contents: read
security-events: write
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: kaeris-io/kaeris-action@v1
with:
mode: check
source: locales/en.json
languages: de,fr
sarif-file: kaeris.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: kaeris.sarif
What it prints, on real runs
Output of kaeris 0.2.35, captured 27 October 2026. The last line of each box is the exit code the CI job sees.
What the job prints
The Action prints a summary table per language and ends with the error line when something is wrong. Left: files that are fine. Right: de.json with {nam} instead of {name} and the checkout key missing.
## KAERIS i18n — CI check Placeholder-loss: **0** (should be 0). | Language | Status | Missing | Placeholder issues | Translated | |---|---|---|---|---| | de | ✓ complete | 0 | 0 | – | | fr | ✓ complete | 0 | 0 | – | _Generated from `kaeris check --json` — no fabricated numbers._ step exit=0 --- GITHUB_OUTPUT (without summary body): missing=0 placeholder-issues=0 changed=false check-exit-code=0 translate-exit-code=0
## KAERIS i18n — CI check Placeholder-loss: **1** (should be 0). Missing keys remaining: **1**. | Language | Status | Missing | Placeholder issues | Translated | |---|---|---|---|---| | de | ⚠ 1 missing, 1 placeholder issue | 1 | 1 | – | | fr | ✓ complete | 0 | 0 | – | _Generated from `kaeris check --json` — no fabricated numbers._ ::error::kaeris check found incomplete/broken translations — see the job summary for the language matrix step exit=1 --- GITHUB_OUTPUT (without summary body): missing=1 placeholder-issues=1 changed=false check-exit-code=1 translate-exit-code=0
What the npx step prints
The same project checked with npx: kaeris 0.2.37 from npm, an empty npm cache and CI=true, as on a fresh runner. The first line is npm fetching the package.
$ npx kaeris check --source locales/en.json --langs de,fr npm warn exec The following package was not found and will be installed: [email protected] ✓ de: complete & placeholder-safe ✓ fr: complete & placeholder-safe ✓ all locales complete & placeholder-safe exit code: 0
$ npx kaeris check --source locales/en.json --langs de,fr npm warn exec The following package was not found and will be installed: [email protected] ✗ de: 1 missing key(s): checkout ✗ de: 1 placeholder mismatch(es): greeting: missing ['{name}'], added ['{nam}'] ✗ de: 2 translation fault(s): greeting: lost placeholder {name} greeting: invented placeholder {nam} (not in source) ✓ fr: complete & placeholder-safe ✗ 1 missing key, 1 placeholder mismatch, 2 translation faults exit code: 1
Set it up in three steps
Save the workflow
Create
.github/workflows/i18n-check.ymlwith the workflow above, with your ownsourceandlanguages.Open a pull request
Push a branch that changes a locale file. The job runs on every pull request and goes red on a lost placeholder or a missing key.
Make the check required
In the repository settings, add the job as a required status check in branch protection. A red job then blocks the merge.
How we checked this
Every command on this page was run before it was written here. What was not run is listed too.
What ran
The step script of
kaeris-io/kaeris-actionat tagv1, copied from GitHub on 27 October 2026 and run in bash on a test project, with the CLI installed from PyPI (kaeris 0.2.35). Clean files: exit code 0. The brokende.json: exit code 1 and the error line shown above.npx
npx kaeris checkfrom the npm package kaeris 0.2.37 was run on 28 October 2026 on the same test project, with an empty npm cache,CI=trueand no--yes. Clean files: exit code 0. The brokende.json: exit code 1.SARIF
The
--sarifflag was run on the broken project and wrote a report with 4 results; on clean files it wrote a report with 0. The upload to GitHub was not tried.What did not run
The workflow file itself was not run on a GitHub runner, and neither were
actions/checkout,actions/setup-python,actions/setup-nodeandupload-sarif. The syntax of the workflows above was not checked by GitHub.Which CLI the Action installs
The Action runs
pip install "kaeris>=0.2.12", so a job picks up the newest release at that moment. If you need a fixed version, use the plain-steps variant and pin it:pip install kaeris==0.2.35.
Options worth knowing
Fail on stale keys too
Set
strict: trueon the Action (or--strictin the plain variant).Translations in another folder
Set
out: path/to/translationson the Action; by default it uses the folder of the source file.Keep brand names as written
--glossary 'KAERIS,GitHub'fails a target that dropped one of the terms. The same list can live underkeepinkaeris.json.Different file layout
--outis the folder with the translations and--pattern '{lang}/common.json'their file names. Default: the layoutkaeris translatewrites.
Questions
Does it need an API key or an account?
No. kaeris check is described in its own help as static, local and no-API, so the job needs no secret. Translating is a separate command that does need a key.
Which file formats does it read?
The help of kaeris check lists JSON, ARB, YAML, .strings, .xcstrings, .po, Android XML, CSV, XLIFF, .properties, .resx, Fluent and Markdown as source formats (kaeris 0.2.35 and newer). Guides by format
What makes the job fail?
The exit code of kaeris check: 0 clean, 1 a problem was found, 2 wrong usage or a file it cannot read. A problem the check calls red fails the job; strict makes stale keys fail as well. GitHub Action reference
Keep going
Other recipes: GitLab CI · pre-commit · Make and npm scripts
CLI reference · Check reference · GitHub Action reference · Free check in the browser · Translate in the browser