All articles
11 min read

Designing an AI Localization Workflow That Humans Still Control

Where machine translation belongs in a release process, how to gate AI output behind review, and the branching model that keeps unreviewed copy out of production.

AI translationWorkflowLocalization

Machine translation is now good enough that the interesting question is no longer "is the output usable?" but "at what point does a human take responsibility for it?" Teams that skip that question ship confident, fluent, wrong copy – and usually find out from a support ticket.

This article describes a pipeline where AI does the mechanical bulk of the work and humans own the last mile: the stages, the review states, the branching model that keeps unreviewed strings off production, what should never go near a machine, and how to measure and pay for the whole thing without guessing.

The failure mode: shipping raw machine output

Raw MT fails in specific, repeatable ways. Knowing them tells you exactly where to put your gates.

Placeholders and interpolation

Every framework has its own placeholder syntax, and translation engines love to "help". A model that has never been told the rules will happily translate the variable name, reorder ICU arguments, or convert {{ count }} into {{ nombre }}. The result compiles, passes CI, and renders {{ nombre }} to a customer.

Treat placeholder integrity as a machine-checkable invariant, not a review task:

const PLACEHOLDER = /\{\{?\s*[\w.]+\s*\}?\}|%[sd]|\$\{[\w.]+\}/g;

export function placeholdersMatch(source: string, target: string): boolean {
  const a = (source.match(PLACEHOLDER) ?? []).sort();
  const b = (target.match(PLACEHOLDER) ?? []).sort();
  return a.length === b.length && a.every((tok, i) => tok === b[i]);
}

The sort is deliberate: the set of placeholders must survive, but their order legitimately changes when word order changes, so position is not something you can assert on. Extend the pattern to whatever syntaxes your stack actually uses – this one covers the common curly-brace, printf and template-literal forms and nothing else. Run the check on every AI-produced value before it is even offered to a reviewer, and mark a failure rejected automatically.

Plurals

English has two plural forms. Russian, Polish and Arabic have more, and Japanese effectively has one. An engine given only one and other will produce a grammatically impossible Russian string for 5 файла. Plural rules are a data problem – expand the CLDR categories for each target language before translating, and give the model each form separately with the numeric context spelled out.

Brand voice and false friends

Models default to a neutral, slightly formal register. If your product speaks casually in English, MT will quietly promote it to corporate. Worse are false friends: German eventuell is "possibly", not "eventually"; Spanish actualmente is "currently", not "actually". Fluent output hides these – that is exactly what makes them expensive.

The staged pipeline

The shape that works is a short chain of stages, each of which can fail loudly:

extract → pre-translate (AI) → human review → approve → merge → release

Extract. Source strings come out of the codebase into a single system of record. The important property is that extraction is automated and idempotent – a developer adding a key in a feature branch should not have to remember to tell anyone.

Pre-translate. AI fills only missing values. It never overwrites an approved translation. This is the single most important rule in the pipeline, because it means the system converges: every human decision is permanent, and every re-run only touches the gaps.

Human review. A native speaker sees source, machine suggestion, and any glossary hits, and either approves, edits, or rejects. Review is fastest when the reviewer is editing rather than composing – which is the actual value of MT in this workflow.

Approve. An explicit state change, not an implicit one. "Nobody complained" is not approval.

Merge and release. Approved strings move into the branch that production builds from. Nothing else does.

The stages are easy to name and hard to hold together, because what moves between them is files. A fr.json emailed to a translator and emailed back is not a stage, it is an untracked fork. Extraction, suggestion, review and merge have to read and write one store, with the repo as a client of it.

None of this is exotic. Most mature translation management platforms – Crowdin, Phrase, Lokalise, Weblate and others – model review states and can gate MT behind them; the arrangement below is one way to wire it up, not a claim that only one tool can.

Where review status fits

Four states are enough to run this: draft, review, approved, rejected. Map them onto the pipeline deliberately.

StateSet byMeaningShippable
draftAI pre-translation, or a translator mid-editA suggestion exists, nobody has vouched for itNo
reviewTranslator finishing a passReady for a second pair of eyesNo
approvedReviewer or adminA human is accountable for this stringYes
rejectedReviewer, or an automated placeholder/plural checkKnown bad, do not re-suggest without changesNo

Two rules make the states load-bearing rather than decorative. First, AI output enters as draft – never approved. Second, editing a source string demotes every dependent translation out of approved, because the thing that was approved no longer exists. Skip the second rule and your approval flag slowly becomes a lie.

Translatize models exactly these four statuses per label, which is what lets a release gate ask a single question: are all strings for the shipping languages approved?

Branching keeps unreviewed copy out of production

Status flags tell you what is safe. Branching makes unsafe copy physically absent from the build.

Give each feature a translation branch that matches the code branch. The AI pre-translation and the review cycle happen there, in isolation. main only ever receives approved strings, via merge, and only from people allowed to perform that merge.

# feature work, isolated from what production builds
npx @translatize/cli pull --branch feature/checkout-v2
# ... developers add keys, AI fills gaps, reviewers approve ...
npx @translatize/cli status --branch feature/checkout-v2 --fail-on-missing

This solves the problem that status flags alone do not: a half-translated feature under a flag can sit in the system for weeks without any risk of a stray export picking it up. It also gives you a clean answer to "who can ship a translation?" – in Translatize, developer-and-above can create branches, only the creator or an admin can delete one, and only an admin or owner can merge to main. The permission boundary sits on the merge, which is where the risk actually is.

When you do merge, pick the strategy consciously. Translatize offers four: overwrite is a non-destructive union where the source branch wins on conflict, and it is the sane default for feature work; replace is destructive and makes the target a copy of the source, which is right when you are restoring a known-good snapshot and wrong almost every other time; keep-newer resolves each conflicting label by comparing per-label timestamps, which is convenient and occasionally surprising; manual has you fetch the conflict list and resolve it key by key, which is what you want for a high-stakes surface like billing.

Every member of the project is notified over a WebSocket connection when a label is added, edited or merged, which removes the most common coordination failure in this loop – a translator polishing a string that a developer deleted an hour ago.

Glossary and translation memory

Consistency is where AI is weakest across a long project. The same source term gets three different renderings in three different runs, because each request is independent.

A glossary (termbase) pins terms that must not drift: product names, UI nouns your docs rely on, and terms that must stay in English. Feed the relevant entries into the translation request as constraints.

{
  "workspace": { "de": "Workspace", "fr": "espace de travail", "doNotTranslate": ["de"] },
  "branch":    { "de": "Branch",    "fr": "branche" },
  "label":     { "de": "Label",     "fr": "libellé" }
}

A translation memory is the complementary half: reuse of previously approved translations for identical or near-identical source. TM is cheaper than MT, more consistent than MT, and it carries your reviewers' past decisions forward. The ordering that works is TM exact match → TM fuzzy match offered as a draft → AI for the remainder.

Translatize covers both halves of this layer. A project glossary holds your terms with per-language translations and do-not-translate flags, managed in project settings, and a check endpoint scans a proposed translation for terms that were rendered inconsistently. Translation memory is exposed as a suggest call from the label editor, so a translator sees what was approved for matching source text before typing anything. Underneath it all sits the durable record of which translations reached approved – the raw material any memory is built from.

One honest limitation worth planning around: memory lookup matches on source text rather than offering scored fuzzy matches, so near-identical strings will not surface the way a dedicated CAT tool surfaces them. For terminology-critical content, treat the glossary as the enforcement mechanism and the check endpoint as the gate, rather than relying on memory to catch drift.

What to never machine-translate

Some categories should be routed to a human from the start, with MT available only as a reference:

  • Legal text – terms of service, privacy policies, consent language, disclaimers. These have jurisdiction-specific meaning that a translation engine has no way to know, and getting them wrong is a regulatory problem, not a copy problem.
  • Pricing, tax and billing copy – currency conventions, VAT wording and "per seat" semantics vary by market and are frequently a compliance matter.
  • Safety, medical and security instructions – anything where a misreading causes harm.
  • Marketing headlines and taglines – these need transcreation, not translation. A literal rendering of a pun is worse than a plain sentence.
  • Anything with a legal name or address in it – leave it alone entirely.

Mark these keys in your source of truth so the pre-translation step skips them. A flag on the key itself is more reliable than a policy in a wiki; if your platform has no such flag, a naming convention plus a filter in whatever script triggers pre-translation gets you most of the way.

Measuring quality honestly

You cannot review everything forever, so measure a sample and be honest about what the measurement is worth.

Spot-check sampling. Take a random sample per language per release – a few dozen strings is enough to notice a systemic problem, nowhere near enough to certify a corpus. Score each on a small fixed rubric (accuracy, terminology, fluency, formatting) and track the trend. A sudden drop usually means a prompt, a model, or a glossary changed.

Back-translation is a weak signal. Translating the target back to the source language catches gross errors – dropped negations, wrong subjects. It does not catch register, false friends, or terminology drift, and it produces false alarms on any legitimate idiomatic choice. Use it as a cheap smoke test, never as an acceptance criterion.

Native reviewer time is the honest metric. Track minutes spent per 100 strings. If MT is genuinely helping, that number falls over time as your glossary and TM mature. If it stays flat, your reviewers are rewriting rather than editing, and you are paying twice.

Count what reaches users. Locale-tagged support tickets and per-language funnel drop-off are the only signals that reflect real customers.

Cost control

AI translation is metered by characters, so the cost model is simple and the optimisation is obvious: only translate deltas.

  • Never re-translate approved strings. Pre-translation should query for missing values only.
  • Deduplicate before sending. Repeated source strings across keys are one request.
  • Check TM first. An exact match costs nothing.
  • Batch by language, not by key. Fewer requests, more shared context, better consistency.
  • Skip the do-not-translate set – legal and marketing copy is often your longest text, and it is exactly the text a machine should not touch.

Translatize includes AI auto-translation on the Professional and Agency plans, metered by characters per month, and pre-translation fills missing values rather than overwriting – so a re-run after adding ten keys costs ten keys, not the whole project. The Free and Starter plans have no AI translation at all, which is worth knowing before you design a pipeline around it: on those tiers the platform is the review and branching layer and the machine pass happens elsewhere.

Automating it in CI

The pipeline only holds if it runs without anyone remembering to run it. Two jobs are enough: push new source keys when a feature branch changes, and block the release when the shipping languages are not approved.

name: translations
on:
  pull_request:
    paths: ['locales/**']

jobs:
  sync:
    runs-on: ubuntu-latest
    env:
      TRANSLATIZE_API_TOKEN: ${{ secrets.TRANSLATIZE_API_TOKEN }}
      BRANCH: ${{ github.head_ref }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      # Upload new and changed source keys. Never deletes remote keys.
      - name: Push source strings
        run: npx @translatize/cli push --branch "$BRANCH"

      # Gate: fail the PR if any target language is missing strings.
      - name: Check completeness
        run: npx @translatize/cli status --branch "$BRANCH" --fail-on-missing --json

There are GitHub Action and GitLab CI templates that wrap exactly these commands if you would rather not manage the npx invocation yourself, and a @translatize/core SDK plus a @translatize/mcp MCP server if you want to script the same operations from your own code or drive them from an AI coding agent.

One limitation to design around: the CLI syncs JSON files on disk – nested or flat – and nothing else. If your build consumes ARB, RESX, Android XML or .strings, the CI loop above still works for the JSON your tooling can produce, but the full nine-format import and export runs through the app and the REST API rather than through pull/push. In practice that means a Flutter or .NET project either generates JSON in CI and converts it downstream, or drives export from the API directly.

Scope the CI credential to a single project and branch so a leaked token cannot touch main – integration tokens are prefixed mcni_ and are bound that way by construction. For the release build, run pull against main and treat a non-zero exit as a hard stop: a build that silently falls back to English strings in a French locale is the failure mode this whole pipeline exists to prevent.

If your source files are JSON, the placeholder and nesting mechanics are covered in more depth in translating JSON files with AI; framework-specific guides for Next.js, React and Laravel cover extraction. The exact CLI flags are documented in the product docs.

Where Translatize fits

Translatize is built around this pipeline rather than bolted onto it. Import and export are symmetric across nine formats – JSON nested or flat, YAML, CSV, XLIFF 1.2, gettext PO, Flutter ARB, .NET RESX, Android XML, iOS .strings – so the store speaks whatever your build already reads. Labels carry exactly the four statuses used above; AI auto-translation fills only missing values, as drafts, never over an approval; feature branches keep unreviewed copy out of what production builds from, and merges to main are restricted to admins and owners. Label context fields – description, screenshot, charLimit, location – travel with the string, so a reviewer is not guessing what they are approving.

Wrapping up

The workflow that survives contact with a real release schedule is unglamorous: extract automatically, let AI fill only the gaps as drafts, make approval an explicit human act, keep unapproved copy on a branch production cannot see, and put a machine check in front of every placeholder and plural. Route legal, pricing and marketing copy to people from the start. Measure with samples and reviewer time rather than a quality score you cannot defend.

Do that, and MT stops being a risk you tolerate and becomes what it should be – a very fast first draft that a human signs off on.

Keep reading