Skip to main content

Your translation platform exported a string without its placeholder

A locale file that comes back from Crowdin, Weblate or Lokalise is missing an interpolation the source has. Why the round trip loses it, why platform QA does not catch it, and how to check the files you actually ship.

JSON (i18next)JSON (vue-i18n)Rails YAMLgettext .po

What you see

  • A string renders the raw token, or renders a sentence with a value missing, in one or two languages only.
  • The translation platform reports the string as translated and passes its own QA checks.
  • Someone fixes the locale file in the repo, and the next sync quietly puts the broken value back.

Why it happens

Placeholders survive a translation platform only as long as every step treats them as code rather than text. They cross an import parser, a translator or machine-translation engine, a review UI that often renders them as chips, and an export template. Any one of those can drop, reorder or "correct" a token, and the file that lands back in your repo is the artifact nobody reviews line by line.

1The export drops the token the source still has

The platform is the system of record, so a token lost anywhere upstream is exported as a complete, translated-looking string. Nothing in the pull request diff says "this one lost its variable" — it looks like ordinary translated prose.

nocodb/nocodb55k★

The Arabic upgrade prompt had lost `{activePlan}`, `{limit}` and `{plan}`. We reported the one locale we had verified; the maintainer traced it to the Crowdin export and swept all 39 locale files in a single PR.

Our report — merged in under 24 hours, +300/-300 across 39 locales →

2Plural forms collapse on the round trip

A vue-i18n string carries its plural forms in one pipe-separated value. Platforms that model plurals as separate units can bring back a single form, so the runtime picks a branch that is not there and renders the wrong text or the key itself.

chatwoot/chatwoot22k★

Chinese had reversed placeholders in one string, and pipe plurals were collapsed across every locale in the export, not just one.

Our report — reported in the maintainers' existing thread →

3Fixing the repo file does not fix the problem

On a platform-managed project the next sync overwrites a hand-edited locale file. The fix has to go into the platform, or into the source string, or it lasts until the following export. This is why a direct pull request is often the wrong instinct here, and why the check belongs in CI on the files you ship.

excalidraw/excalidraw132k★

A Spanish string had `{{mix}}` where the source has `{{max}}`. Rather than patch the exported file, a contributor added a placeholder-parity test suite covering every locale, so the next bad export fails CI.

Our report — the durable fix: a test, not a string edit →

Find every instance

Check the files in your repository, which is what actually ships, rather than the platform's view of them. No API key, no account, nothing leaves your machine:

npx @shipi18n/cli@latest check ./locales -s en --severity 'missing-key=off,untranslated=off,orphan-key=off'

Those flags narrow it to placeholder and plural problems, which is what a sync breaks. Missing and untranslated keys are the platform's job and it already tracks them.

✗ ar  coverage 100.0%  3 error(s), 0 warning(s)
    error  upgrade.prompt  placeholder-missing — dropped {activePlan}, {limit}, {plan}

How to fix it

  1. 1Correct the string in the platform, not in the repository, or the next export undoes it. If the source string itself changed name, update the source and let the translations re-sync.
  2. 2Sweep every locale rather than the reported one. In both cases above the reported language was not the only one affected, and the maintainers found more once they looked.
  3. 3Run the check in CI on the synced files, so a bad export fails a pull request instead of shipping. `--baseline` records what exists today and fails only on newly introduced drift, which keeps the first run green on a large tree.

The rules that catch this

How often this actually happens

Of 64 public repositories we scanned, 21 ship at least one broken translation, and the ones managed by a translation platform were no better off than the hand-maintained ones: https://shipi18n.com/oss