trans-mgm keeps a codebase's UI copy translated across many languages without hand-managing files. You push the English source; a model translates and self-reviews each string; finished translations come back to your repo as a pull request. This page explains how it works and how to use it.
The design rests on one rule: the source language is owned by your repo, translations are owned by the platform, and data flows one way. You never hand-edit a translated JSON file in the repo, and you never edit the English source inside the platform — so the two sides can never conflict.
your repo (web-ui, docs-site, …) trans-mgm platform ┌──────────────────────────┐ push source ┌────────────────────────────┐ │ messages/en.json │ ─────────────▶ │ diff → translate queue │ │ (source · repo-owned) │ webhook / CLI │ 1. translate (model A) │ │ │ │ 2. ICU placeholder gate │ │ messages/zh.json … │ ◀───────────── │ 3. AI review (model B) │ │ (translations · │ auto pull-req │ 4. final / needs_attention│ │ platform-owned) │ │ web UI: browse · edit · retry│ └──────────────────────────┘ └────────────────────────────┘
added / changed / removed. Only new and changed strings enter the queue — unchanged ones are skipped, so a push is cheap and idempotent. A changed source string resets all of its translations (even human-edited ones) back to pending.{name}, {count, plural, …}, <b>…</b>) must exactly match the source. This is a hard, mechanical check — a translation that drops or renames a placeholder is rejected and retried before any model ever sees it.needs_attention for a human.Translation runs as a background job that processes one batch, then chains itself to the next, with a lease so two runners never touch the same job. A cron sweep resumes anything that stalls. This keeps long runs alive within serverless time limits.
When you fix a translation by hand it is marked manual and the automated pipeline will never overwrite it — unless the English source itself changes, which resets everything for that key so nothing goes stale.
One codebase / message catalog. Holds its source and target locales, the translator & reviewer model config, a glossary, a style prompt, the target repo for PRs, and its CLI tokens.
A key is one source string (dashboard.apiKeys.copy). Each key has one translation row per target locale, each moving through its own status.
Onboarding is a joint effort: steps marked operator are done by a platform operator (model config and GitHub wiring involve credentials); everything after that is self-service for your team.
Projects → New project: slug, name, source locale (e.g. en), target locales (e.g. zh, ko, ja), the GitHub owner/repo/base branch/file path template, and the translator model (an OpenAI-compatible base URL, a model name, and the name of the environment variable holding its API key — the raw key is never stored). Optionally a Reviewer model, a glossary (one term = translation per line), and a style prompt. Your team can refine locales, glossary, and style later — those fields are self-service.Install with npm i -g @0gfoundation/trans-mgm-cli (or run ad-hoc via npx @0gfoundation/trans-mgm-cli …), then add a trans-mgm.config.json to your repo pointing at the platform and your source file:
{
"apiUrl": "https://trans-mgm.vercel.app",
"project": "web-ui",
"sourcePath": "messages/en.json",
"outputTemplate": "messages/{locale}.json"
}Provide the token via TRANS_MGM_TOKEN (never commit it), then run one of three commands:
export TRANS_MGM_TOKEN=tm_…
trans-mgm push # upload the source file → diff → start translating
trans-mgm status # per-locale progress (final / pending / needs_attention)
trans-mgm pull # download finished translations → write {locale}.jsonpush and pull operate on flat or nested JSON, including arrays (e.g. FAQ lists). The source file is the single source of truth for keys — removing a key from it archives its translations.
Recommended: the GitHub App — your repo carries zero configuration. Once the trans-mgm App can see your repo, every push to the project's base branch is ingested automatically: the platform reads the source, translates only the diff, and opens (or updates) a pull request with the new translations. No repo webhook, no CI job, no secrets on your side.
Org → Settings → GitHub Apps → trans-mgm→ Repository access → add your repo. That single grant covers everything: push events reach the platform through the App's own webhook, and the platform reads sources and opens PRs with a short-lived, repo-scoped installation token — no personal tokens involved.
The project's Settings must have the GitHub owner/repo, base branch, and locale file path template filled in — deliveries are matched to projects by repo + branch.
By default the source is the single file at the path template (e.g. messages/{locale}.json with the source locale filled in). Large catalogs can instead set Source shards directory in Settings (e.g. messages/en): the platform reads every *.json in it and merges them with filename = top-level namespace — so developers edit small per-namespace files and no merged file needs to exist in the repo at all.
A failed ingest (malformed JSON, refused mass-archive) never advances the change cursor — the next push or a manual Sync from reporetries the same content. The failure shows as a red banner on the Keys tab, appears in the Jobs tab's ingest history, and pings the ops Slack channel if configured.
Alternative: push from CI with the CLI — useful when the App can't be granted the repo or the source needs a build step first (e.g. merging shards).
# .github/workflows/i18n.yml
name: i18n sync
on:
push:
branches: [main]
paths: ['messages/en.json']
jobs:
push-source:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npx -y @0gfoundation/trans-mgm-cli push
env:
TRANS_MGM_TOKEN: ${{ secrets.TRANS_MGM_TOKEN }}For the platform to open the PR back, the project must have its GitHub repo configured and the App must have access to it (the installation token carries Contents + Pull-requests permissions). Deployments without the App fall back to a server-side GITHUB_PAT.
Filter by locale, status, namespace, or free-text search — including archived keys. Each row shows the key, the English source, and the translation with its status badge. Edit a translation inline (Save marks it manual), Re-translate a single row, or Pin it so a source change never auto-queues it for retranslation.
Import from repobackfills existing translations from the repo's locale files at onboarding (never overwrites platform finals; idempotent). Sync from repo re-ingests the source right now — the manual counterpart of the webhook. Sync now opens or updates the translation PR with the current finals.
The key browser pre-filtered to needs_attentionrows. Each shows the reviewer's note so you know why it was flagged before fixing it.
Every push/translate/PR run appears here with its status and stats. A failed job can be retried in place — a translate retry only reprocesses the items still pending.
Every situation you can hit, and exactly what the platform does about it. The through-line: the source file decides which keys exist, and a translation ships only once it has passed the ICU gate — everything below is the system protecting those two rules.
added; one pending row is created per target locale and a full translation run starts.changed: all of its translations — including hand-edited manual ones — reset to pending and re-translate. Nothing stale survives a source edit, with one exception: pinned rows keep their human-approved value (unpin or re-translate explicitly when ready).GET /api/v1/projects/<slug>/readiness (project token auth) returns {ready, total, final, pending, …} — ready is true only when every active key is final in every targeted locale. Curl it in CI before promoting a deploy.{locale}.json — pending or flagged strings are never pulled.pull instead of receiving a PR.