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

1

Your pipeline downloads the Mergua CLI from the Mergua API and runs a command (works on GitLab, GitHub & Gitea — the provider is auto-detected)

2

The CLI checks if your Git branch has a matching branch in Mergua

3

push — Uploads translation keys and all locale translations to Mergua

Push
4

notify — Detects if a Git branch was just merged and notifies Mergua (optionally auto-merges the Mergua branch)

Notify
5

pull — Downloads translations (based on --mode: always, reviewed, or never)

Pull
6

If 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 api scope
  • GitHub: Use GITHUB_TOKEN (automatic) or a PAT
  • Gitea: Token with repo + API scope
  • Only needed for the sync and notify commands — pull / push work without it. See Git Providers.

4. CI/CD variables

Add these as masked variables in your CI/CD settings:

VariableValueOptions
MERGUA_API_KEYYour Mergua API keyMasked
MERGUA_GIT_TOKENGit 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]
CommandPush keysNotify mergePullCommit & pushGit token
sync
pullyou commit
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 pull command and add your own git commit / git push step 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-latest

Precedence: 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 -- pull

Git 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.

WhatGitLab CIGitHub / Gitea Actions
BranchCI_COMMIT_REF_NAMEGITHUB_REF_NAME
Commit SHACI_COMMIT_SHAGITHUB_SHA
Repo (owner/repo)CI_PROJECT_PATHGITHUB_REPOSITORY
Merge detectionMerge Requests APIPull 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 branchMergua branch exists?PullKey Sync
feature/loginYes Uses feature/login Syncs to feature/login
feature/loginNo Falls back to --fallback-branch Creates feature/login branch
developYesUses develop Syncs to develop
main—Uses mainSkipped

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 -- pull

Custom 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 flat

Context 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-sync

Full 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-merge

Key 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/i18n

Push-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 -- push

Notify-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-merge

JSON 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 sync

Key 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_KEY branch 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/master branches (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.

LayoutSupported
{locale}/{namespace}.json Yes
{namespace}/{locale}.json what Transloco scaffolds — see aboveNo
{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:--path contains subdirectories matching locale codes (e.g. en/, fr/, de/) with .json files inside
  • Single mode:--path contains flat locale.json files (no subdirectories)

Quick Start

1

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.json
2

Push 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 en

Keys are synced from en/*.json, translations from de/*.json, fr/*.json etc.

3

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/i18n
4

CI/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 -- sync

Single-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.json
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- sync

Multi-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.json

Push 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 en

Keys 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 medication

Pull a single JSON file

curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- pull --path src/assets/i18n --namespace common

FAQ

Can I mix namespaces and flat files?

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.

What happens to existing keys when I switch to namespaces?

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.

Can two namespaces have the same key?

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.

Do I need --namespace in CI?

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).

Which Transloco versions are supported?

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-merge

MERGUA_API_KEY and MERGUA_GIT_TOKEN as masked CI/CD variables; MERGUA_PROJECT_ID in the variables: block.

Flow

  1. YouPush a feature branch

    e.g. feature/login, with new i18n keys added in your source locale.

  2. CIPipeline runs "sync" command

    Provider auto-detected; branch resolved from the CI environment.

  3. MerguaBranch auto-created, keys pushed

    A matching Mergua branch is created and the new keys appear for translators.

  4. CIExisting translations pulled & committed

    Any translations already in Mergua are written back to your branch.

  5. TeamTranslators work in Mergua

    They translate and review on the feature branch, isolated from main.

  6. GitYou merge the MR/PR into main

    The next pipeline on main detects which branch was merged.

  7. MerguaMergua branch auto-merged

    --auto-merge triggers automatic merging of the Mergua branch.

  8. 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

  1. YouPush with changed locale files

    e.g. en.json gains keys, de.json gains some translations.

  2. MerguaKeys synced from the source locale

    Folder mode reads {folder}/{sourceLocale}.json as the key manifest.

  3. MerguaAll other locales pushed as translations

    de.json, fr.json, … are uploaded for their respective languages.

  4. 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 -- pull

Only 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

  1. CIPipeline runs the "pull" command

    Branch resolved from CI; no commits are made by the CLI.

  2. MerguaTranslations downloaded

    Locale files are written to --path (default: src/assets/i18n).

  3. CIFiles left in the working tree

    The CLI prints a hand-off message and exits.

  4. 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_KEY branch 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_VALUE branch 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)

ModeBehavior
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 -- pull
git add src/assets/i18n/
git diff --cached --quiet || git commit -m "chore: update translations [skip ci]"
git push origin HEAD

Token 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_KEY conflict does.
  • Merging with force goes 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_at from 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 matches
  • Status: 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-merge

Command 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_KEY

Mergua project API key

--project <uuid>Env: MERGUA_PROJECT_ID

Mergua project UUID

--provider <name>Env: MERGUA_GIT_PROVIDER

Git provider: gitlab, github, or gitea (auto-detected)

--branch <name>Env: MERGUA_GIT_BRANCH

Git branch (auto-detected from CI)

--json

Machine-readable JSON output (result on stdout, logs on stderr)

--no-color

Disable colored output

--no-failEnv: MERGUA_FAIL_ON_ERROR=false

Continue on errors instead of failing the pipeline

--version, -v

Print 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/i18n

Where 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: always

When to download translations

--fallback-branch <name>Default: main

Fallback when no matching Mergua branch exists

--auto-create / --no-auto-createDefault: auto-create

Auto-create Mergua branch if it does not exist

--namespace <name>Env: MERGUA_NAMESPACE

Limit to a single namespace (only in namespace mode)

--key-pullDefault: off

Update manifest file with keys from Mergua

--key-manifest <path>Default: same as --path

Path to key manifest file (required for --key-pull)

--min-progress <pct>Env: MERGUA_MIN_PROGRESS · Default: off

Whole 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-detected

Source locale code (e.g. "en")

--key-manifest <path>Default: same as --path

Path to locale folder or single manifest file

--path <dir>Default: src/assets/i18n

Where locale files are read from

--no-key-sync

Skip key sync entirely

--namespace <name>Env: MERGUA_NAMESPACE

Limit to a single namespace (only in namespace mode)

--include-translationsEnv: MERGUA_INCLUDE_TRANSLATIONS

Also upload locale translations from the --path directory (useful with a single-file manifest)

--auto-create / --no-auto-createDefault: auto-create

Auto-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: off

Read 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_TOKEN

Git host token (required — used to query merge status)

--auto-merge / --no-auto-mergeDefault: no auto-merge

Automatically 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: off

Walk 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

VariableUsed forDefaultRequired
MERGUA_API_KEYAuthenticates the CLI with your Mergua project — requiredRead more
MERGUA_GIT_TOKENLets the CLI push commits & query the Git host — conditionalRead more
MERGUA_PROJECT_IDSelects which Mergua project to sync — requiredRead more
MERGUA_API_URLPoints the CLI at the right Mergua server auto (from server) optionalRead more
MERGUA_LOCALE_PATHWhere translation files are read & written src/assets/i18n optionalRead more
MERGUA_FILE_FORMATShape of the JSON output (nested vs flat) nested optionalRead more
MERGUA_ARB_PATH off optionalRead more
MERGUA_ARB_PUSH false optionalRead more
MERGUA_FALLBACK_BRANCHSource branch when no matching Mergua branch exists main optionalRead more
MERGUA_GIT_PROVIDERWhich Git host integration to use auto-detected optionalRead more
MERGUA_GIT_BRANCHWhich branch to sync auto (CI var / git) optionalRead more
MERGUA_GIT_REMOTEWhere translation commits are pushed auto (per provider) optionalRead more
MERGUA_GIT_REPOIdentifies the repo to the host API auto (CI var) optionalRead more
MERGUA_KEY_SYNCTurns on pushing keys from code to Mergua true deprecatedRead more
MERGUA_KEY_MANIFEST_PATHWhere keys & locale files are discovered MERGUA_LOCALE_PATH optionalRead more
MERGUA_SOURCE_LOCALEWhich locale holds the canonical keys auto-detected optionalRead more
MERGUA_AUTO_CREATE_BRANCHCreates the Mergua branch on first sync true optionalRead more
MERGUA_INCLUDE_TRANSLATIONSUploads locale files alongside a single-file key manifest false optionalRead more
MERGUA_KEY_PULLWrites new keys back into your manifest false optionalRead more
MERGUA_NAMESPACELimits sync to a single namespace all optionalRead more
MERGUA_PULL_MODEWhen translations get pulled always deprecatedRead more
MERGUA_MIN_PROGRESSBlocks the pull below a translation percentage — optionalRead more
MERGUA_FAIL_ON_ERRORWhether API errors fail the pipeline true optionalRead more
MERGUA_AUTO_MERGE_BRANCHMerges translations when the Git branch merges false optionalRead 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_KEYRequired

Mergua project API key — must be masked in GitLab. Also available as --api-key flag for non-CI usage.

MERGUA_GIT_TOKENConditional

Git 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_IDRequired

Mergua 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/i18n

Path where translation files are written. CLI flag: --path

MERGUA_FILE_FORMATDefault: nested

File 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: off

Write 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: false

Push 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: main

Fallback 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-detected

Git 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: true

Key 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_PATH

Path to locale folder or single file. CLI flag: --key-manifest

MERGUA_SOURCE_LOCALEDefault: auto-detected

Source locale code, e.g. "en". Auto-detected from your Mergua project settings. CLI flag: --source

MERGUA_AUTO_CREATE_BRANCHDefault: true

Auto-create Mergua branch if it does not exist. CLI flags: --auto-create / --no-auto-create

MERGUA_INCLUDE_TRANSLATIONSDefault: false

Also upload locale translations from the --path directory when pushing a single-file manifest. CLI flag: --include-translations

MERGUA_KEY_PULLDefault: false

Update the manifest file with keys from Mergua after each translation pull. CLI flag: --key-pull

MERGUA_NAMESPACEDefault: all

Limit 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: always

When 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_PROGRESS

Minimum translation percentage per locale before the pull is allowed. Off by default — progress is only logged. CLI flag: --min-progress

MERGUA_FAIL_ON_ERRORDefault: true

Fail pipeline on Mergua API errors. CLI flag: --no-fail (to disable)

MERGUA_AUTO_MERGE_BRANCHDefault: false

Auto-merge Mergua branch when Git branch is merged. CLI flags: --auto-merge / --no-auto-merge

Troubleshooting

Setup & Configuration

ERROR: MERGUA_API_KEY is not set

The API key is not configured. Set it as a masked CI/CD variable or pass --api-key.

ERROR: MERGUA_PROJECT_ID is not set

Set MERGUA_PROJECT_ID as an env var or pass --project <uuid>. Copy the UUID from your project header in Mergua.

ERROR: jq is required but not installed

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.

Unknown command '...'

Available commands: pull, push, notify, sync, version. Example: sh -s -- sync

Unknown option '--...' for ... command

Run the command with --help to see available options for that command.

ERROR: MERGUA_GIT_TOKEN is not set

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.

ERROR: Could not determine the git branch

Running outside a recognised CI environment. Pass --branch <name> or set MERGUA_GIT_BRANCH.

Authentication & Access

ERROR: Authentication failed

Your API key is invalid or expired. Go to Mergua > Project Settings > API Keys and generate a new one.

ERROR: Access denied

The API key belongs to a different project. Check that --project (or MERGUA_PROJECT_ID) matches the project the API key was created for.

ERROR: Not found

The project UUID is wrong. Copy it from Mergua > Project Settings.

Key Sync

ERROR: Source locale file not found

The source locale file (e.g. en.json) was not found. Check --path / --key-manifest or MERGUA_LOCALE_PATH / MERGUA_KEY_MANIFEST_PATH.

ERROR: Manifest path not found

The folder or file specified in --key-manifest does not exist. Check the path is relative to your repository root.

Namespaces

WARNING: No namespaces found in '...'

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.

ERROR: Unknown namespace '...'

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.

Locale subdirectory not detected

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.

A namespace file is never downloaded

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.

Keys turn up in the wrong namespace

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

WARNING: Mergua API returned 5xx

Mergua is temporarily unavailable. The pipeline fails by default. Pass --no-fail to continue with existing translations.

Push fails / permission denied

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).

ERROR: Translation progress below --min-progress

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.

WARNING: Progress check failed

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.

Skipping: this pipeline was triggered by a Mergua translation sync.

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 variableStill read?Use instead
MERGUA_MODE noA subcommand: sync, pull, push or notify

No 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-sync

Key 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|never

Still read as the fallback for --mode, but the flag is the documented way to set it.

Which command each mode became

MERGUA_MODECommand
full (default)sync
pullpull
pushpush
notifynotify
pull,notifypull, 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 reviewed

MERGUA_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.