CI/CD Integration
Mergua CLI
Automatically sync translations between your codebase and Mergua via CI/CD — on GitLab, GitHub or Gitea. Use subcommands to push keys, pull translations, detect merges, or run a full sync.
Do you even need the CLI?
The Mergua CLI is a convenience layer for CI/CD pipelines (committing pulled translations, key sync, merge notifications). If you only want to read translations, you don't need it at all — the public REST API lets you pull any locale directly:
curl -H "X-API-Key: $MERGUA_API_KEY" \
"https://api.mergua.com/v1/$PROJECT_ID/translations/fr?branch=develop&format=nested"Reach for the CLI when you want a pipeline to commit translations back, push keys, or report merges automatically.
Everything on this page is pull: your pipeline asks Mergua. If you want the other direction — Mergua telling your pipeline that something changed, so it does not have to run on a schedule and guess — that is a webhook. The two are made to be used together.
How it works
Your pipeline downloads the Mergua CLI from the Mergua API and runs a command (works on GitLab, GitHub & Gitea — the provider is auto-detected)
The CLI checks if your Git branch has a matching branch in Mergua
push — Uploads translation keys and all locale translations to Mergua
Pushnotify — Detects if a Git branch was just merged and notifies Mergua (optionally auto-merges the Mergua branch)
Notifypull — Downloads translations (based on --mode: always, reviewed, or never)
PullIf translations changed: commits and pushes directly to your branch (sync command only)
Prerequisites
Before setting up the pipeline sync, you need to prepare a few things.
1. Mergua project
- Create a project in Mergua (or use an existing one)
- Add at least one locale (e.g. English as source, German as target)
- Copy your Project ID from the project header (click the UUID to copy)
2. Mergua API key
- Go to your project in Mergua
- Scroll down to API Keys
- Create a new key and copy it immediately (it won't be shown again)
3. Git host access token
- GitLab: Preferences > Access Tokens with
apiscope - GitHub: Use
GITHUB_TOKEN(automatic) or a PAT - Gitea: Token with repo + API scope
- Only needed for the
syncandnotifycommands —pull/pushwork without it. See Git Providers.
4. CI/CD variables
Add these as masked variables in your CI/CD settings:
| Variable | Value | Options |
|---|---|---|
| MERGUA_API_KEY | Your Mergua API key | Masked |
| MERGUA_GIT_TOKEN | Git host token (for sync/notify) | Masked |
Important: Secrets must be set as masked variables in your CI settings, not in your pipeline file. Secrets in YAML are visible to everyone with repo access.
Quickstart
After completing the prerequisites above, add this to your .gitlab-ci.yml:
# MERGUA_API_KEY and MERGUA_GIT_TOKEN must be set as masked CI/CD variables
# (Settings > CI/CD > Variables) — never put secrets in this file.
variables:
MERGUA_PROJECT_ID: "your-project-uuid"
stages:
- .pre
- build
# ... your other stages
mergua-sync:
stage: .pre
image: alpine:latest
before_script:
- apk add --no-cache curl jq git
script:
- curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- sync
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: never
- if: $CI_COMMIT_BRANCH
when: always
allow_failure: false Commit and push. The mergua-sync job appears in the .pre stage of your pipeline.
Commands
The Mergua CLI uses subcommands to select which operation to run. Each command can be customised with flags — run any command with --help for details.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- <command> [flags]| Command | Push keys | Notify merge | Pull | Commit & push | Git token |
|---|---|---|---|---|---|
sync | |||||
pull | |||||
push | |||||
notify |
MERGUA_API_KEY (your Mergua project key) is always required — every command talks to the Mergua API. MERGUA_GIT_TOKEN (your Git host token) is only needed for sync and notify, because those push commits or query the Git host API. pull and push never need MERGUA_GIT_TOKEN.
Just need the files once, without any pipeline? Download translations directly from the REST API — Import & Export › API access.
The sync command triggers a second pipeline
The sync command commits and pushes translation changes to your branch. This push triggers a second pipeline run on your CI. The CLI detects its own commits (starting with Mergua: sync translations) and skips automatically — no infinite loops. But the second pipeline still shows up in your CI history.
Alternatives to avoid the second pipeline:
- Pull + own commit step: Use the
pullcommand and add your owngit commit/git pushstep with[skip ci]in the commit message. - GitLab workflow rules: Exclude pipelines when the commit message starts with
Mergua::
workflow:
rules:
- if: $CI_COMMIT_MESSAGE =~ /^Mergua:/
when: never
- when: always- GitHub: skip push events from Mergua: Add a condition:
jobs:
mergua-sync:
if: ${{ !startsWith(github.event.head_commit.message, 'Mergua:') }}
runs-on: ubuntu-latestPrecedence: CLI flag > env var > default
Every option can be passed as a CLI flag or set as an environment variable. CLI flags always take precedence over environment variables. Environment variables are useful for CI/CD pipeline defaults, while flags let you override per-run.
# All equivalent:
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- pull --path src/locale --format flat
MERGUA_LOCALE_PATH=src/locale MERGUA_FILE_FORMAT=flat curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- pullGit Providers
The same CLI runs on GitLab, GitHub and Gitea. The provider is auto-detected from the CI environment (GITLAB_CI, GITEA_ACTIONS, GITHUB_ACTIONS — Gitea is checked before GitHub because it sets both). Override with --provider if detection is wrong.
| What | GitLab CI | GitHub / Gitea Actions |
|---|---|---|
| Branch | CI_COMMIT_REF_NAME | GITHUB_REF_NAME |
| Commit SHA | CI_COMMIT_SHA | GITHUB_SHA |
| Repo (owner/repo) | CI_PROJECT_PATH | GITHUB_REPOSITORY |
| Merge detection | Merge Requests API | Pull Requests API |
GitHub Actions
MERGUA_GIT_TOKEN is the host access token (only needed for sync / notify commands).
name: Mergua Sync
on:
push:
branches: ['**']
jobs:
mergua-sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sudo apt-get update && sudo apt-get install -y jq
- run: curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- sync
env:
MERGUA_API_KEY: ${{ secrets.MERGUA_API_KEY }}
MERGUA_PROJECT_ID: ${{ secrets.MERGUA_PROJECT_ID }}
MERGUA_GIT_TOKEN: ${{ secrets.GITHUB_TOKEN }}Gitea Actions
The same workflow works unchanged — --provider auto-detects gitea, and the API base is derived from GITHUB_SERVER_URL. Pass a token with repo + API scope as MERGUA_GIT_TOKEN.
Branch Matching
The CLI automatically matches your Git branch to a Mergua branch by name. This keeps translation work isolated per feature — just like Mergua branches isolate translation changes from main. Override with --branch.
Feature branches
When you push a branch like feature/login, the CLI looks for a Mergua branch with the same name.
- Branch exists: Translations are pulled from that branch, keys are synced to it
- Branch doesn't exist: Translations fall back to
--fallback-branch(default:main). Key Sync auto-creates the branch (enabled by default, disable with--no-auto-create).
Main / master
On main or master, translations are always pulled from Mergua's main branch. Key Sync is skipped — keys reach main via branch merges, not direct pushes.
| Git branch | Mergua branch exists? | Pull | Key Sync |
|---|---|---|---|
| feature/login | Yes | Uses feature/login | Syncs to feature/login |
| feature/login | No | Falls back to --fallback-branch | Creates feature/login branch |
| develop | Yes | Uses develop | Syncs to develop |
| main | — | Uses main | Skipped |
Configuration Examples
Common setups for different use cases. Pass options as CLI flags or set them as environment variables (flags take precedence).
Minimal — Translation pull only
Downloads translations on every push. Good for projects where keys are managed in Mergua directly.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- pullCustom path and format
Override the locale path and JSON format via CLI flags.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- pull --path src/locale --format flatContext notes in the repo
Each key's context note and character limit start out only in Mergua. Add --arb-path and the pull writes a second, ARB-formatted set carrying them as @key metadata — the runtime JSON stays exactly where your app loads it from.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- pull --arb-path src/locale-arb The way back is push --arb: point it at that same directory and a note edited in your editor lands on the key in Mergua. Attributes Mergua has no field of its own for are kept as they were, so nothing is lost on the way through.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- push --arb --key-manifest src/locale-arb The flag is the whole opt-in: without it push still refuses an ARB file, so a stray one in your locale directory cannot be sent by accident.
Without Key Sync
Key Sync is enabled by default in the sync and push commands. Disable it if keys are managed in Mergua directly.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- sync --no-key-syncFull setup — with Auto-Merge
Fully automated: keys and translations are synced, and when you merge your Git branch, the matching Mergua branch is auto-merged too.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- sync --auto-mergeKey Manifest File
If your project uses a separate key structure file (e.g. main.json) instead of a source locale file, point the manifest path directly to it.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- sync --key-manifest src/assets/i18n/main.json The CLI extracts all string leaf nodes as keys, regardless of what the values contain. To also upload the locale translations from the --path directory, add --include-translations.
Push keys + translations (file manifest)
When using a single-file manifest, only keys are pushed by default. Add --include-translations to also upload all locale files from the --path directory.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- push --key-manifest src/assets/i18n/en.json --include-translations Only files matching a project locale are uploaded. Alternatively, point --key-manifest at a directory — then translations are always included.
Two-way Key Sync (Key Pull)
When your team also creates keys directly in Mergua, enable Key Pull to keep your manifest file in sync. Without it, keys added in Mergua would be deleted on the next pipeline run.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- sync --key-manifest src/assets/i18n/main.json --key-pull Requires --key-manifest to point to a file (not a directory).
Reviewed pull mode
Only pull translations when all locales are fully reviewed and conflict-free.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- pull --mode reviewed Available modes: always (default), reviewed, never.
Debian/Ubuntu CI image
If your CI image is Debian-based instead of Alpine, use apt-get to install dependencies:
mergua-sync:
stage: .pre
image: node:22
before_script:
- apt-get update && apt-get install -y jq
script:
- curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- sync
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: never
- if: $CI_COMMIT_BRANCH
when: always Most Debian/Ubuntu images already include curl and git. Only jq typically needs to be installed.
Pull-only — commit it yourself
Writes translation files and stops. No MERGUA_GIT_TOKEN needed — your own steps commit and push.
curl -sf https://api.mergua.com
/v1/mergua.sh | sh -s -- pull --path src/assets/i18nPush-only — keys up, nothing back
Uploads keys/translations to Mergua without pulling or committing.
curl -sf https://api.mergua.com
/v1/mergua.sh | sh -s -- pushNotify-only — react to merges
A main-branch job that only tells Mergua a branch was merged (and optionally auto-merges it), without pushing or pulling.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- notify --auto-mergeJSON output
Pass --json before the command for machine-readable output. Human-readable logs go to stderr, JSON result to stdout — so a pipeline can capture the result and still show progress in the job log.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- --json pull Errors are JSON too, in one envelope with the same four parts the human-readable output prints — what failed, why, the fix, and the page that explains it. why, fix and docs are only present when the CLI has something to say, so read them as optional; what is always there:
{
"error": {
"what": "Authentication failed",
"why": "Your API key is invalid or expired",
"fix": "Check your MERGUA_API_KEY variable",
"docs": "https://mergua.com/docs/pipeline-sync"
}
}The error envelope goes to stderr, not stdout — stdout carries results only, so a step that parses it never has to tell a failure apart from a result.
Two exit codes, and --no-fail only touches one of them. A malformed invocation — an unknown flag, a flag missing its value, an unusable --format or --min-progress value — exits 2, and keeps doing so with --no-fail set: a typo in the pipeline file is not a transient failure to ride out. Everything else exits 1, and that is the code --no-fail rewrites to 0.
Which means --no-fail covers more than the outage it is meant for: a missing MERGUA_API_KEY also exits 1, so a job carrying the flag goes green whether Mergua was down or the variable was never set. Reach for it on jobs where stale translations are better than a red pipeline, and read the log rather than the badge when a sync quietly stops arriving.
Continue on errors
Pass --no-fail to continue the pipeline even when the Mergua API returns an error.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- --no-fail syncKey Sync (Push)
Automatically push translation keys from your source code to Mergua. New keys appear for translators immediately, deleted keys are tracked as branch operations.
Enabled by default
Key Sync is enabled by default in the sync and push commands. No extra configuration needed — keys are pushed automatically.
The source locale and locale folder are auto-detected from your Mergua project settings. Override with --source and --key-manifest if needed.
To disable Key Sync, pass --no-key-sync.
How it works
- Reads all locale files from your locale folder (e.g.
en.json,de.json) and syncs them to Mergua - Keys are determined from the source locale file — new keys are created with translations from all locale files
- Removed keys are tracked as
DELETE_KEYbranch operations - Existing keys and translations are left untouched
Branch behavior
- Keys are synced to a Mergua branch matching your Git branch name
- With
--auto-create(default), the branch is created automatically - Key sync is skipped on
main/masterbranches (keys reach main via merge)
Pipeline order
In the sync command, Key Sync runs before the translation download. This means newly pushed keys are immediately available when translations are pulled back.
Key Pull (Two-way Sync)
By default, Key Sync is one-way: code → Mergua. Enable --key-pull to also update your manifest file with keys added in Mergua. Without it, keys created via the dashboard are deleted on the next sync because they're missing from your manifest.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- sync --key-manifest src/assets/i18n/main.json --key-pull Requires --key-manifest to point to a file, not a directory. Enable when translators or PMs add keys directly in Mergua.
Namespaces
Multiple JSON files per language
Split translations into multiple JSON files per language — the way Transloco scopes, i18next namespaces and vue-i18n message files all divide a locale. Instead of one en.json, you have en/common.json, en/errors.json, etc.
Directory Layout
Mergua expects the locale-first layout — the language directory, then the file:
{locale}/{namespace}.json Relative to your --path directory (default src/assets/i18n):
src/assets/i18n/
├── en/
│ ├── common.json
│ ├── errors.json
│ └── pages.json
├── fr/
│ ├── common.json
│ ├── errors.json
│ └── pages.json
└── de/
├── common.json
├── errors.json
└── pages.json Each JSON file is one namespace. The filename (without .json) is the namespace name. Every locale must have the same set of namespace files.
| Layout | Supported |
|---|---|
| {locale}/{namespace}.json | Yes |
| {namespace}/{locale}.json what Transloco scaffolds — see above | No |
| {locale}.{namespace}.json | No |
| {namespace}.{locale}.json | No |
Transloco scaffolds the other way round
Transloco calls one of these files a scope and asks the loader for checkout/en — the scope first. Leave that as it comes and the files on disk are checkout/en.json, which is the row marked No above. The CLI then finds nothing to push and stops with ERROR: No namespaces found in …, naming the layout it expected — the job fails rather than syncing half of it.
In Transloco you write the loader yourself, so this is two lines in it: swap the segments, and the scopes you already have sync untouched.
// A scope is a second file for the same language, and a Mergua
// project holds as many of those as you have scopes. Its layout
// puts the language first:
//
// src/assets/i18n/en/default.json
// src/assets/i18n/en/checkout.json
//
// Transloco asks the loader for "checkout/en" — swap the two
// segments and the scopes you already have sync untouched.
getTranslation(path: string) {
const [scope, lang] = path.includes('/')
? path.split('/')
: ['default', path];
return this.http.get<Translation>(
`/assets/i18n/${lang}/${scope}.json`,
);
}The same loader as the “scopes” tab on Angular & Transloco, which walks through the rest of the setup.
Auto-Detection
The CLI automatically detects namespace mode based on your directory structure. No configuration flag is needed.
- Namespace mode:
--pathcontains subdirectories matching locale codes (e.g.en/,fr/,de/) with.jsonfiles inside - Single mode:
--pathcontains flatlocale.jsonfiles (no subdirectories)
Quick Start
Organize your files
Place your JSON files in the {locale}/{namespace}.json layout. The CLI auto-detects namespace mode when it finds locale subdirectories.
src/assets/i18n/
├── en/
│ ├── common.json
│ └── errors.json
├── fr/
│ ├── common.json
│ └── errors.json
└── de/
├── common.json
└── errors.jsonPush keys and translations to Mergua
Point --key-manifest at the directory. The CLI discovers all namespaces from the source locale and pushes keys + translations for all languages.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- push --key-manifest src/assets/i18n --source enKeys are synced from en/*.json, translations from de/*.json, fr/*.json etc.
Pull translations back
The CLI downloads translations into {locale}/{namespace}.json — maintaining the directory structure.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- pull --path src/assets/i18nCI/CD: no changes needed
The sync command handles namespaces automatically. Your existing pipeline configuration works as-is.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- syncSingle-Namespace (Backwards Compatible)
Projects with the flat layout continue to work without changes. All keys belong to the default namespace internally. No migration required.
src/assets/i18n/
├── en.json
├── fr.json
└── de.jsoncurl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- syncMulti-Namespace Example
A project with three namespaces: common, medication, and patients.
src/assets/i18n/
├── en/
│ ├── common.json
│ ├── medication.json
│ └── patients.json
├── de/
│ ├── common.json
│ ├── medication.json
│ └── patients.json
└── fr/
├── common.json
├── medication.json
└── patients.jsonPush all files & all languages
Point --key-manifest at the directory to push keys from the source locale and translations from all other languages — for every JSON file.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- push --key-manifest src/assets/i18n --source enKeys from en/common.json, en/medication.json, en/patients.json — translations from de/*.json, fr/*.json etc.
Push a single JSON file
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- push --key-manifest src/assets/i18n --source en --namespace medicationPull a single JSON file
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- pull --path src/assets/i18n --namespace commonFAQ
No. The CLI operates in either single mode (flat locale.json) or namespace mode ({locale}/{namespace}.json), never both. The mode is auto-detected from your directory structure.
Existing keys in Mergua use the "default" namespace. When you restructure your files into {locale}/default.json, they will continue to sync normally. You can then add new namespace files alongside default.json.
Yes. Keys are unique per namespace — common.json and errors.json can both contain a key called "title". In Mergua, each namespace maintains its own key set.
Usually no. Without --namespace, the CLI processes all namespaces automatically. Use --namespace only when you want to limit operations to a single namespace (e.g. testing or partial deployments).
All of them — the version is not what decides it. Transloco hands the loader "checkout/en", scope first, while Mergua reads en/checkout.json. Swapping those two segments in your loader is the whole adaptation, and you write that loader yourself in every Transloco version. See "Transloco scaffolds the other way round" above for the code.
Workflows
End-to-end scenarios showing what happens at each step and what you configure where. Pick the one closest to your setup.
Feature branch → review → merge (sync command)
The default, fully automated flow. Keys flow from code to Mergua per branch, translators work in isolation, and translations flow back when the branch merges.
Configure
# .gitlab-ci.yml
variables:
MERGUA_PROJECT_ID: "your-project-uuid"
mergua-sync:
stage: .pre
image: alpine:latest
before_script:
- apk add --no-cache curl jq git
script:
- curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- sync --auto-mergeMERGUA_API_KEY and MERGUA_GIT_TOKEN as masked CI/CD variables; MERGUA_PROJECT_ID in the variables: block.
Flow
- YouPush a feature branch
e.g. feature/login, with new i18n keys added in your source locale.
- CIPipeline runs "sync" command
Provider auto-detected; branch resolved from the CI environment.
- MerguaBranch auto-created, keys pushed
A matching Mergua branch is created and the new keys appear for translators.
- CIExisting translations pulled & committed
Any translations already in Mergua are written back to your branch.
- TeamTranslators work in Mergua
They translate and review on the feature branch, isolated from main.
- GitYou merge the MR/PR into main
The next pipeline on main detects which branch was merged.
- MerguaMergua branch auto-merged
--auto-merge triggers automatic merging of the Mergua branch.
- CImain pipeline replaces the locale files
The final, merged translations are committed to main.
Monorepo / multi-locale folder
Point the manifest at a folder to sync keys from the source locale and push every locale file in one run — handy for repos that keep all languages side by side.
Configure
# .gitlab-ci.yml
mergua-sync:
script:
- curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- sync --path libs/i18n/src/assets --key-manifest libs/i18n/src/assets--key-manifest points to the locale folder; the source locale is auto-detected and all other locale files are pushed as translations.
Flow
- YouPush with changed locale files
e.g. en.json gains keys, de.json gains some translations.
- MerguaKeys synced from the source locale
Folder mode reads {folder}/{sourceLocale}.json as the key manifest.
- MerguaAll other locales pushed as translations
de.json, fr.json, … are uploaded for their respective languages.
- CITranslations pulled & merged into each file
Every locale file in the folder is refreshed and committed.
Pull-only — you own the commit (any provider)
The pipeline only writes translation files; your own steps commit and push. No git token required, so it works with the most restricted CI tokens.
Configure
# .gitlab-ci.yml
mergua-sync:
script:
- curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- pullOnly MERGUA_API_KEY is needed (no MERGUA_GIT_TOKEN). Add your own git add/commit/push (or open a PR) after the script runs.
Flow
- CIPipeline runs the "pull" command
Branch resolved from CI; no commits are made by the CLI.
- MerguaTranslations downloaded
Locale files are written to --path (default: src/assets/i18n).
- CIFiles left in the working tree
The CLI prints a hand-off message and exits.
- YouCommit & push your way
Custom message, signing, formatters, or open a dedicated PR — your call.
Sync Behavior
Understanding when keys get created, updated, deleted, or left unchanged. The push and sync commands compare your manifest file against the keys in Mergua and apply the difference.
Example: First sync
You push this manifest file for the first time:
en.json{
"home": {
"title": "Welcome",
"subtitle": "Get started"
},
"auth": {
"login": "Log in"
}
}Result:+3 created — All three keys (home.title, home.subtitle, auth.login) are new.
Example: Key added and removed
On the next push, you added a key and removed another:
en.json (updated){
"home": {
"title": "Welcome",
"subtitle": "Get started",
"cta": "Try it free"
}
}+1 createdhome.cta is new · -1 deletedauth.login is missing from the manifest · =2 unchanged
Example: Value changed
If the source value of an existing key changes:
en.json (value changed){
"home": {
"title": "Welcome back",
"subtitle": "Get started",
"cta": "Try it free"
}
}~1 updatedhome.title changed from "Welcome" to "Welcome back" — tracked as a MODIFY_VALUE branch operation · =2 unchanged
When are keys deleted?
- A key is marked for deletion when it exists in Mergua but is missing from your manifest
- Deletions are tracked as
DELETE_KEYbranch operations — they are not permanent until the branch is merged - If you re-add the key to your manifest, the deletion is automatically undone on the next sync
- The Sync Dashboard shows which keys were deleted in each sync run
- An empty source file skips key sync altogether — it is read as nothing to push, not as every key having been removed. So a manifest that failed to build, or a path that resolved to the wrong file, cannot empty the project on the next pipeline run
When are keys updated?
- A key is updated when its source locale value changes between syncs
- Updates are tracked as
MODIFY_VALUEbranch operations with the new value - Other locale translations are not affected — only the source value is compared
Who is shown as "created by"?
When the CI pipeline creates branches, adds keys, or edits translations, the API key owner's name is shown as the creator. This is the team member who generated the API key in the project settings.
Pipeline-created items are marked with a via CI Pipeline badge so you can distinguish manual edits from automated syncs.
API response
The sync endpoint returns the affected key names. Pass --json for machine-readable output:
{
"created": ["home.cta"],
"deleted": ["auth.login"],
"updated": ["home.title"],
"unchanged": 2,
"summary": {
"created": 1, "deleted": 1,
"updated": 1, "unchanged": 2
},
"branch": "feature/redesign",
"branchCreated": false
}Translation Pull
The pull command downloads translations from Mergua. In sync, translations are also committed directly to your branch. Control when translations are pulled with --mode.
Pull modes (--mode)
| Mode | Behavior |
|---|---|
| always | Downloads translations on every pipeline run, even if some locales are incomplete. This is the default. |
| reviewed | Checks branch readiness first. Only downloads if all translations are reviewed and no conflicts exist. |
| never | Skips translation download entirely. Useful when you only want Key Sync and Merge Notifications (via sync). |
Smart merge
Translations from Mergua are merged with your local files, not overwritten. Mergua values take precedence where they exist, but local values are preserved for keys that Mergua doesn't have translations for yet.
Direct commit (sync command)
In the sync command, translations are committed and pushed directly to your branch — no separate merge request. The CLI automatically skips itself when it detects its own sync commits, so there are no infinite pipeline loops.
Pull command — where do the files go?
The pull command only writes translation files to disk — it does not commit or push. Where the files end up depends on how you run the CLI:
- In CI/CD: The files are written to the runner's working directory. Add a subsequent step to commit and push them.
- Locally: Run the command in your project folder and the files are written directly into your working tree.
Example CI step to commit after pull:
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- pullgit add src/assets/i18n/git diff --cached --quiet || git commit -m "chore: update translations [skip ci]"git push origin HEADToken requirements
Downloading translations only needs your MERGUA_API_KEY. Committing the pulled translations back to your repo (sync command) additionally needs MERGUA_GIT_TOKEN with write access.
Renamed keys reach every branch at once
A key rename is stored on the key itself, and that key is shared by main and every branch. The rename stays pending on its branch, but the moment that branch is merged into main, the new name is what every open branch sees — in the editor, in a download and in a pull.
- Merging into main is refused when another open branch has operations on a key being renamed. The response lists the affected branches, the same way a
DELETE_KEYconflict does. - Merging with
forcegoes ahead regardless — use it when you know the other branch should follow the new name. - A branch that holds no operation on the key is not reported, because every open branch in the project would be on every rename. Its next pull returns the new name even though nothing on that branch changed, so treat a rename as landing everywhere.
- A deleted key keeps its name, so nothing else can be renamed to it — not even with
force, since this is a naming rule rather than a review one. The key is in no list and no download, so the rename dialog and the merge both name the case explicitly; pick another name.
Readiness Check (--mode reviewed)
With --mode reviewed, the CLI checks branch readiness before downloading. Translations are only pulled when all locales are fully reviewed and conflict-free. hasConflicts signals that the merge target changed a value away from what the branch forked — the same check the merge itself enforces, for main and for a branch target alike. It compares the values, not when they were written: approving or resetting a status on the target is never a conflict, and neither is a value the target changed and then changed back. The target is the project's default branch, or main when none is set, and comes back as target.
Git Merge Notification
When a Git branch is merged, the notify command (also part of sync) detects the merge and notifies Mergua. A banner appears on the matching Mergua branch card.
How it works
- Runs on the target branch after a merge — checks whether the current commit was a merge
- Asks your git host which PR/MR produced this commit
- If a merge is found, calls the Mergua notification endpoint
- The Mergua branch is marked as "git-merged" and a readiness summary is printed
What Mergua records about the merge
- When — the
merged_atfrom the host - Where — the target branch name
- Branch existence — whether the source branch was deleted after the merge
When it's not clean
Merge notification is best-effort — it never fails your pipeline (independent of --no-fail). You may see:
- Nothing — the commit wasn't a merge, or no PR/MR was found (expected on normal commits)
No matching Mergua branch— merge detected but no Mergua branch matchesStatus: NOT READY— marked git-merged, but translations still need review
Auto-Merge
With --auto-merge, the CLI automatically merges the Mergua branch when the Git branch is merged. If all operations are reviewed, a full merge is performed. If only some locales are ready, a partial merge is attempted.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- notify --auto-merge
# or as part of full sync:
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- sync --auto-mergeCommand Reference
Complete flag reference for each command. Global flags (before the command) work with all commands.
Global Flags
Place before the command name. Apply to all commands.
--api-key <key>Env: MERGUA_API_KEYMergua project API key
--project <uuid>Env: MERGUA_PROJECT_IDMergua project UUID
--provider <name>Env: MERGUA_GIT_PROVIDERGit provider: gitlab, github, or gitea (auto-detected)
--branch <name>Env: MERGUA_GIT_BRANCHGit branch (auto-detected from CI)
--jsonMachine-readable JSON output (result on stdout, logs on stderr)
--no-colorDisable colored output
--no-failEnv: MERGUA_FAIL_ON_ERROR=falseContinue on errors instead of failing the pipeline
--version, -vPrint version and exit
--help, -h Print usage and exit. Every command takes it too — put it after the command to get that command's flags: sh -s -- pull --help.
pull
Download translations from Mergua
--format nested|flat|arbDefault: nested File format for translation files. arb writes <locale>.arb instead of .json and replaces the file on every pull rather than merging into it — ARB is flat, and a merge would leave an older nested file's keys standing beside it. Transloco reads JSON at runtime, so only pick this if your build converts ARB itself; otherwise use --arb-path.
--format xliff is the fourth format Mergua has and the one this script does not carry: it exits 2 and says where the document comes from instead. XLIFF is a handoff document for a translation agency, not a file your app loads — no Transloco runtime reads it, and the script does all of its work with jq, which cannot read XML. Download it from the app, or ask the API for translations/<locale>?format=xliff — see XLIFF. xlf and MERGUA_FILE_FORMAT=xliff get the same answer.
--path <dir>Default: src/assets/i18nWhere translation files are written
--arb-path <dir>Env: MERGUA_ARB_PATH · Default: off Write an extra ARB set to this directory, next to the runtime files rather than instead of them. ARB carries each key's context note and character limit as @key metadata, so translators and external tools see them in the repo while the app keeps loading the JSON. Pull-only: these files are never uploaded (see push below).
--mode always|reviewed|neverDefault: alwaysWhen to download translations
--fallback-branch <name>Default: mainFallback when no matching Mergua branch exists
--auto-create / --no-auto-createDefault: auto-createAuto-create Mergua branch if it does not exist
--namespace <name>Env: MERGUA_NAMESPACELimit to a single namespace (only in namespace mode)
--key-pullDefault: offUpdate manifest file with keys from Mergua
--key-manifest <path>Default: same as --pathPath to key manifest file (required for --key-pull)
--min-progress <pct>Env: MERGUA_MIN_PROGRESS · Default: offWhole number 0–100. Aborts the pull when any locale is translated below that percentage, so a pipeline never commits a half-translated state.
--dry-runDefault: off Resolve the branch, report which locale files would be written, and write none of them. The run header prints DRY RUN, and --json adds "dry_run": true to the result — so a pipeline can check a path or a namespace filter before it touches the working tree.
Before downloading, pull reports the translation progress of the resolved Mergua branch — one line per locale as translated/total (percentage%). It is informational: if the progress endpoint fails, the run only warns and continues. Pass --min-progress to turn it into a gate.
=== Translation Progress ===
Translation progress on branch 'feature-checkout':
de: 128/128 (100%)
fr: 54/128 (42%)push
Upload keys/translations to Mergua
--source <locale>Default: auto-detectedSource locale code (e.g. "en")
--key-manifest <path>Default: same as --pathPath to locale folder or single manifest file
--path <dir>Default: src/assets/i18nWhere locale files are read from
--no-key-syncSkip key sync entirely
--namespace <name>Env: MERGUA_NAMESPACELimit to a single namespace (only in namespace mode)
--include-translationsEnv: MERGUA_INCLUDE_TRANSLATIONSAlso upload locale translations from the --path directory (useful with a single-file manifest)
--auto-create / --no-auto-createDefault: auto-createAuto-create Mergua branch if it does not exist
--arbEnv: MERGUA_ARB_PUSH · Default: off Push ARB documents instead of the runtime JSON, so the context notes and character limits in @key reach Mergua. Reads <locale>.arb from the manifest directory rather than <locale>.json — for the source locale and for every translation file beside it — so point --key-manifest at the directory the pull wrote with --arb-path. Without the flag an ARB file is still refused, so an accidental ARB push cannot slip through.
--dry-runDefault: offRead the manifest and report the key counts it would push, without sending anything. Worth a run before the first real push of a restructured manifest: key sync reconciles, so a manifest pointed at the wrong directory would mark every existing key for deletion — this shows that as a number first.
An ARB file needs --arb. Key sync reads ARB only when asked: it splits the document, creates the real keys and writes the @key descriptions and character limits onto them. Without the flag, push detects an ARB document by its @ keys and aborts before sending anything — pushing one by accident is the case that guard is still there for.
Translation files follow the same rule. With the flag, every <locale>.arb in the manifest directory is pushed and its @key entries land on the keys the upload was allowed to write — a key another branch owns, one renamed on the branch, or one the branch has marked for deletion keeps the notes it had, exactly as it keeps its value. Without the flag a translation file carrying @ keys is refused, and the run tells you which flag would have sent it. Descriptions are not language-dependent, so the same block arrives in every locale file a pull wrote and the last push of a changed one wins.
notify
Report git merges to Mergua
--git-token <token>Env: MERGUA_GIT_TOKENGit host token (required — used to query merge status)
--auto-merge / --no-auto-mergeDefault: no auto-mergeAutomatically merge the Mergua branch when the Git branch is merged
sync
Full sync: push + notify + pull + commit. Combines all flags from pull, push, and notify.
Accepts all flags from pull (--format, --path, --arb-path, --mode, --fallback-branch, --namespace, --key-pull, --key-manifest, --auto-create, --min-progress), push (--source, --namespace, --no-key-sync, --include-translations, --arb), and notify (--git-token, --auto-merge), plus --dry-run.
--dry-runDefault: offWalk every phase and report what each would do, then stop: no keys pushed, no merge reported, no files written, and — the part that matters on sync — no commit and no push. This is the flag to reach for when adding the job to a pipeline for the first time, because it is the only way to see the plan without a commit landing on the branch.
version
Print the CLI version. With --json, outputs as JSON.
Environment Variables
All environment variables are fallbacks for CLI flags. Precedence: CLI flag > env var > built-in default. Secrets should always be passed as env vars in CI/CD pipelines.
At a glance
| Variable | Used for | Default | Required | |
|---|---|---|---|---|
| MERGUA_API_KEY | Authenticates the CLI with your Mergua project | — | required | Read more |
| MERGUA_GIT_TOKEN | Lets the CLI push commits & query the Git host | — | conditional | Read more |
| MERGUA_PROJECT_ID | Selects which Mergua project to sync | — | required | Read more |
| MERGUA_API_URL | Points the CLI at the right Mergua server | auto (from server) | optional | Read more |
| MERGUA_LOCALE_PATH | Where translation files are read & written | src/assets/i18n | optional | Read more |
| MERGUA_FILE_FORMAT | Shape of the JSON output (nested vs flat) | nested | optional | Read more |
| MERGUA_ARB_PATH | off | optional | Read more | |
| MERGUA_ARB_PUSH | false | optional | Read more | |
| MERGUA_FALLBACK_BRANCH | Source branch when no matching Mergua branch exists | main | optional | Read more |
| MERGUA_GIT_PROVIDER | Which Git host integration to use | auto-detected | optional | Read more |
| MERGUA_GIT_BRANCH | Which branch to sync | auto (CI var / git) | optional | Read more |
| MERGUA_GIT_REMOTE | Where translation commits are pushed | auto (per provider) | optional | Read more |
| MERGUA_GIT_REPO | Identifies the repo to the host API | auto (CI var) | optional | Read more |
| MERGUA_KEY_SYNC | Turns on pushing keys from code to Mergua | true | deprecated | Read more |
| MERGUA_KEY_MANIFEST_PATH | Where keys & locale files are discovered | MERGUA_LOCALE_PATH | optional | Read more |
| MERGUA_SOURCE_LOCALE | Which locale holds the canonical keys | auto-detected | optional | Read more |
| MERGUA_AUTO_CREATE_BRANCH | Creates the Mergua branch on first sync | true | optional | Read more |
| MERGUA_INCLUDE_TRANSLATIONS | Uploads locale files alongside a single-file key manifest | false | optional | Read more |
| MERGUA_KEY_PULL | Writes new keys back into your manifest | false | optional | Read more |
| MERGUA_NAMESPACE | Limits sync to a single namespace | all | optional | Read more |
| MERGUA_PULL_MODE | When translations get pulled | always | deprecated | Read more |
| MERGUA_MIN_PROGRESS | Blocks the pull below a translation percentage | — | optional | Read more |
| MERGUA_FAIL_ON_ERROR | Whether API errors fail the pipeline | true | optional | Read more |
| MERGUA_AUTO_MERGE_BRANCH | Merges translations when the Git branch merges | false | optional | Read more |
Full details by category:
Secrets (CI/CD Variables)
Set these in GitLab > Settings > CI/CD > Variables. Never put them in .gitlab-ci.yml. Secrets are always passed as env vars, never as CLI flags.
MERGUA_API_KEYRequiredMergua project API key — must be masked in GitLab. Also available as --api-key flag for non-CI usage.
MERGUA_GIT_TOKENConditionalGit host token with api + write scope. Only required for sync and notify commands — pull and push do not need it. Must be masked. Also available as --git-token flag.
Project Configuration
Set these in the variables block or pass them as CLI flags (flags take precedence).
MERGUA_PROJECT_IDRequiredMergua project UUID (copy from the project header). CLI flag: --project
MERGUA_API_URLDefault: auto (from server)Rarely needed. Override the Mergua API base URL — useful for local testing. Automatically baked into the script by the server.
MERGUA_LOCALE_PATHDefault: src/assets/i18nPath where translation files are written. CLI flag: --path
MERGUA_FILE_FORMATDefault: nestedFile format: nested, flat or arb. arb writes .arb files instead of .json and replaces them on every pull. xliff is refused with exit 2 — it is a handoff document, not a file your app loads. CLI flag: --format
MERGUA_ARB_PATHDefault: offWrite an extra ARB set with context notes and character limits to this directory, next to the runtime JSON. CLI flag: --arb-path
MERGUA_ARB_PUSHDefault: falsePush ARB documents instead of the runtime JSON, so context notes and character limits edited in the repo reach Mergua. Reads <locale>.arb from the manifest directory, for the source locale and the translation files alike. Without it an ARB file is refused. CLI flag: --arb
MERGUA_FALLBACK_BRANCHDefault: mainFallback branch when no matching Mergua branch exists. CLI flag: --fallback-branch
Git Provider
Auto-detected from the CI environment — set these only when running outside CI or overriding detection.
MERGUA_GIT_PROVIDERDefault: auto-detectedGit host: gitlab, github, or gitea. CLI flag: --provider
MERGUA_GIT_BRANCHDefault: auto (CI var / git)Branch to sync. Falls back to the CI branch variable, then `git rev-parse`. CLI flag: --branch
MERGUA_GIT_REMOTEDefault: auto (per provider)Full authenticated push remote URL. Derived per provider by default; set on unusual hosting.
MERGUA_GIT_REPODefault: auto (CI var)Repository path owner/repo. Derived from CI_PROJECT_PATH (GitLab) or GITHUB_REPOSITORY (GitHub/Gitea).
Key Sync
These env vars are fallbacks for CLI flags on push and sync commands.
MERGUA_KEY_SYNCDeprecatedDefault: trueKey Sync — push keys from code to Mergua. Enabled by default, so setting this to true does nothing. Still honoured, but the CLI prints a [migration] hint when it is set: use --no-key-sync to disable instead.
MERGUA_KEY_MANIFEST_PATHDefault: MERGUA_LOCALE_PATHPath to locale folder or single file. CLI flag: --key-manifest
MERGUA_SOURCE_LOCALEDefault: auto-detectedSource locale code, e.g. "en". Auto-detected from your Mergua project settings. CLI flag: --source
MERGUA_AUTO_CREATE_BRANCHDefault: trueAuto-create Mergua branch if it does not exist. CLI flags: --auto-create / --no-auto-create
MERGUA_INCLUDE_TRANSLATIONSDefault: falseAlso upload locale translations from the --path directory when pushing a single-file manifest. CLI flag: --include-translations
MERGUA_KEY_PULLDefault: falseUpdate the manifest file with keys from Mergua after each translation pull. CLI flag: --key-pull
MERGUA_NAMESPACEDefault: allLimit operations to a single namespace (only in namespace mode). CLI flag: --namespace
Pipeline Behavior
These env vars are fallbacks for CLI flags and command selection.
MERGUA_PULL_MODEDeprecatedDefault: alwaysWhen to pull translations: always (every run), reviewed (only when fully reviewed), never (skip pull). Still honoured, but the CLI prints a [migration] hint when it is set: pass --mode instead.
MERGUA_MIN_PROGRESSMinimum translation percentage per locale before the pull is allowed. Off by default — progress is only logged. CLI flag: --min-progress
MERGUA_FAIL_ON_ERRORDefault: trueFail pipeline on Mergua API errors. CLI flag: --no-fail (to disable)
MERGUA_AUTO_MERGE_BRANCHDefault: falseAuto-merge Mergua branch when Git branch is merged. CLI flags: --auto-merge / --no-auto-merge
Troubleshooting
Setup & Configuration
The API key is not configured. Set it as a masked CI/CD variable or pass --api-key.
Set MERGUA_PROJECT_ID as an env var or pass --project <uuid>. Copy the UUID from your project header in Mergua.
Your CI image is missing jq. Add "apk add --no-cache curl jq git" (Alpine) or "apt-get update && apt-get install -y jq" (Debian/Ubuntu) to before_script.
Available commands: pull, push, notify, sync, version. Example: sh -s -- sync
Run the command with --help to see available options for that command.
sync and notify commands need a token to push commits / query the host API. Either set MERGUA_GIT_TOKEN or pass --git-token, or use pull/push which do not require it.
Running outside a recognised CI environment. Pass --branch <name> or set MERGUA_GIT_BRANCH.
Authentication & Access
Your API key is invalid or expired. Go to Mergua > Project Settings > API Keys and generate a new one.
The API key belongs to a different project. Check that --project (or MERGUA_PROJECT_ID) matches the project the API key was created for.
The project UUID is wrong. Copy it from Mergua > Project Settings.
Key Sync
The source locale file (e.g. en.json) was not found. Check --path / --key-manifest or MERGUA_LOCALE_PATH / MERGUA_KEY_MANIFEST_PATH.
The folder or file specified in --key-manifest does not exist. Check the path is relative to your repository root.
Namespaces
The CLI detected namespace mode (subdirectories exist) but found no .json files in the source locale directory. Ensure your source locale directory (e.g. src/assets/i18n/en/) contains at least one .json file.
You passed --namespace with a name that does not match any .json file in the source locale directory. Check the spelling — the CLI suggests a close match ("Did you mean ...?") and lists the available namespaces.
The CLI matches subdirectory names against known locale patterns (e.g. en, de, fr-FR, zh_CN). A directory named "english/" will not be recognised. Use standard locale codes as directory names.
pull only writes namespaces that exist in Mergua. A namespace file you added locally but never pushed has nothing to download. Run push first, then pull.
A key belongs to the namespace of the file it was pushed from — if "title" shows up under errors instead of common, that is where it sits locally. Move it to the right file and push again: the next run deletes it from the old namespace and creates it in the new one.
Pipeline & Push
Mergua is temporarily unavailable. The pipeline fails by default. Pass --no-fail to continue with existing translations.
Check that MERGUA_GIT_TOKEN (or --git-token) has api scope and the token owner has at least Developer role (or Maintainer for protected branches).
A locale is translated below the threshold you set. Finish the translations in Mergua, lower --min-progress, or drop the flag to only log progress.
The progress endpoint was unreachable or returned an error. This is only informational — the pull continues. Any --min-progress gate is skipped for that run.
This is expected. The sync command detects its own commits (starting with "Mergua: sync translations") and skips to avoid infinite pipeline loops.
Migrating from v2
Version 2 of the script had no subcommands: it read MERGUA_MODE to decide what to do, and you piped it into a bare sh. Everything after that is a flag on an explicit command. If the CLI prints a [migration] line in your pipeline log, one of the three variables below is still set.
| Legacy variable | Still read? | Use instead |
|---|---|---|
| MERGUA_MODE | no | A subcommand: sync, pull, push or notifyNo longer read. The value used to select the phase to run; it is now the argument after --, e.g. sh -s -- pull. |
| MERGUA_KEY_SYNC | as fallback | --no-key-syncKey sync is on by default, so setting it to true changes nothing. Pass --no-key-sync to turn it off. |
| MERGUA_PULL_MODE | as fallback | --mode always|reviewed|neverStill read as the fallback for --mode, but the flag is the documented way to set it. |
Which command each mode became
| MERGUA_MODE | Command |
|---|---|
| full (default) | sync |
| pull | pull |
| push | push |
| notify | notify |
| pull,notify | pull, then notify as a second step |
Before (v2)
script:
- curl -sf https://api.mergua.com/v1/mergua.sh | sh
variables:
MERGUA_MODE: "pull"
MERGUA_KEY_SYNC: "true"
MERGUA_PULL_MODE: "reviewed"After
script:
- curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- pull --mode reviewedMERGUA_KEY_SYNC: "true" drops out entirely — key sync is on by default, and pull does not push keys anyway.
The -s -- is what makes the difference: it tells sh to read the script from the pipe and pass everything after it to that script rather than to sh itself. Leave it out and the command name is silently swallowed, which is the v2 behaviour — the script runs, and it runs the wrong phase.