GitLab CI
The template at the repository root defines three hidden jobs you extend, each
installing @the-i18n-kit/cli and running one command. .i18n-translate commits and
pushes translated locale files; .i18n-cleanup reports orphan keys and .i18n-check
keys that render raw. Only the first writes to your repository.
Including the Template
Include the v8 ref: it follows the CLI's major, so template changes reach you with the release they were written for. main can run ahead of the CLI your job installs.
include:
- remote: 'https://raw.githubusercontent.com/fabkho/the-i18n-kit/v8/gitlab-ci.yml'
stages:
- lint
i18n-check:
extends: .i18n-check
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
Declare a lint stage: every job sets stage: lint, and GitLab rejects a pipeline
naming a stage that does not exist. The URL tracks main, so pin it to a tag, and
pin the CLI with I18N_CLI_VERSION.
Each job takes its outcome from the CLI's
exit code. The scanning jobs carry
allow_failure.exit_codes: [2]: findings warn yellow and still reach the merge
request widget, a scan that fell over fails red. allow_failure: false on your job
makes findings block the merge.
Translating
i18n-translate:
extends: .i18n-translate
variables:
I18N_PROVIDER: google
I18N_MODEL: gemini-2.5-flash
I18N_API_KEY: $GEMINI_API_KEY
I18N_LOCALE_PATHS: "i18n/locales/ app-*/i18n/locales/"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes: ['i18n/locales/en.json']
Those three variables are required; without them the job exits 1 before calling
anything. The key reaches the CLI as the environment variable of the provider you
chose, never on the command line.
| Variable | Default | Description |
|---|---|---|
I18N_LAYER | (all) | One layer, or every one in a single commit |
I18N_LOCALES, I18N_SOURCE_LOCALE, I18N_KEYS | (all) | Targets, reference, key subset |
I18N_BATCH_SIZE | 50 | Keys per LLM call |
I18N_DRY_RUN | false | Preview, writing nothing |
I18N_FAIL_ON_FAILED | (off) | Exit 2 when a key failed |
I18N_LOCALE_PATHS | i18n/locales/ | Globs staged and committed — must cover every layer |
I18N_MEMORY_FILE | .i18n-kit.lock.json | The translation memory, staged with the translations it describes |
I18N_COMMIT_MESSAGE | (generated) | Replaces the message, [skip ci] included |
I18N_PUSH_TOKEN | (empty) | Pushes with this instead of the job token |
I18N_CLI_VERSION, I18N_INSTALL_PEER_DEPS | 8 (the template's major), (empty) | CLI version, extra packages |
I18N_LOCALE_PATHS are written on the runner and
never committed: a clean-looking run, and the keys still missing on the next one.The job also stages the
translation memory at
I18N_MEMORY_FILE, which is what a later run reads to tell a translation still
made from the current source from one whose source has changed since. It is
skipped when the file does not exist — translationMemory: false — and when the
project's .gitignore covers it, in which case nothing records what each
translation was made from and stale stays empty.
The job allows failure, and its gate trips only after the commit and push, so a red
job never costs you translations. The result stays as .i18n-reports/translate.json.
Letting it push. A CI token cannot write unless you allow it: Settings → CI/CD →
Job token permissions → Allow Git push requests to the repository, or a token with
write_repository in I18N_PUSH_TOKEN. Give that token the api scope too — a
token push starts no pipeline, and the job uses api to start one at the new head,
which keeps the merge request widgets alive after an auto-commit.
Scanning
.i18n-cleanup reports orphan keys and passes no flag
that deletes any; I18N_FAIL_ON_ORPHANS: "true" makes them trip a gate. .i18n-check
has nothing to opt into — a key that renders raw is a defect, so its gate always
runs. Both take I18N_LAYER and keep their report and gl-codequality.json for 7
days. missing, status and find-duplicates write the same report format with
--codequalityOutput <file> if you add a job for them.
gl-codequality.json renders as a diff against the newest default-branch report,
so a job running only on merge requests has no baseline and the widget stays
blank. Give every job a second rule for the default branch, without a changes:
filter. Baselines expire with the artifacts; raise expire_in on a quiet branch.Overriding
image, before_script, tags and cache are overridable on your job — the
supported path for private registries; I18N_INSTALL_PEER_DEPS adds packages without
replacing anything. A replacement before_script keeps the-i18n-cli on the PATH,
keeps the || exit 1 that stops a failed install from reading as findings, and
installs git and curl. Keep it POSIX sh.