DEV Community

Cover image for Two translation bugs your build will pass, and one command that catches them
Artem Hetman
Artem Hetman

Posted on AI-assisted

Two translation bugs your build will pass, and one command that catches them

You ask an AI to translate your app. It could be ChatGPT, or the coding agent in your editor. It writes the Ukrainian file, the Flutter build passes, and you ship.

Two of those strings are broken. Neither one stops the build, and you can't see either one unless you read Ukrainian.

Here are both, in English terms.

Bug 1: the total with no number

Your English string has a placeholder for the amount:

"checkoutTotal": "Total: {amount}"
Enter fullscreen mode Exit fullscreen mode

The Ukrainian translation came back like this:

"checkoutTotal": "Разом до сплати"
Enter fullscreen mode Exit fullscreen mode

It means "Total to pay". The {amount} is gone. flutter gen-l10n exits 0 and says nothing about it. The generated method still takes the amount as a parameter and never puts it in the text, so your Ukrainian users see a checkout total with no number.

Bug 2: "2 items" in the wrong grammar

English has two plural forms: 1 item, 2 items. Ukrainian has four, and the number decides which one you need. 2 to 4 take one form, 5 to 20 take another.

The AI translated the two forms English has, and stopped:

"itemsInCart": "{count, plural, one{{count} товар} other{{count} товарів}}"
Enter fullscreen mode Exit fullscreen mode

So for 2 items the badge falls back to the only other form it has, the one for 5 to 20. It reads "2 товарів" where a Ukrainian speaker says "2 товари". In English terms, it's a badge that says "2 of items". The build passes.

You don't need Ukrainian to catch either one

Both bugs are structural. The English string has a placeholder and the translation doesn't. The language needs four plural forms and the file has two. Neither needs anyone to understand the words.

I build a free CLI that checks exactly this, called StringLane. Run it in the project folder:

$ npx @stringlane/cli check
flutter · arb · single-origin · base en
3 locales · 3 keys · 86% complete

  de      4/4        complete
  en      4/4        complete
  uk      4/6        2 missing

3 errors · 0 warnings · 0 info
  error    placeholder_mismatch  uk/checkoutTotal
  error    missing_key           uk/itemsInCart[few]
  error    missing_key           uk/itemsInCart[many]
Enter fullscreen mode Exit fullscreen mode

That output is trimmed: each error also says in a sentence what is wrong. It exits 1, so the same line in CI stops the merge:

- run: npx @stringlane/cli check
Enter fullscreen mode Exit fullscreen mode

It reads Flutter ARB, iOS String Catalogs and .strings, Android XML and i18next JSON. It needs no account and no key, and CI runs send no usage data.

If the AI is your coding agent

The CLI also runs as an MCP server, so an agent can translate through it and have each translation checked before it is written. Agents still write locale files directly sometimes, with their own edit tool or a quick script, and the server never sees those writes. So keep the check in CI either way. How that works per agent, and what each one can and can't guard, is in the full write-up.

What it won't tell you

Whether a translation is good. It checks structure: missing keys and plural forms, placeholders, ICU syntax. Whether "2 товари" is the right word for your badge still needs someone who understands Ukrainian.

Top comments (1)

Collapse
 
launchgatecheck profile image
Launch Gate •

Does placeholder matching work per ICU branch, or only over the whole message? A useful negative fixture would keep {amount} in one select branch but drop it from another. The message-wide placeholder set would still match while one checkout path loses the total.

I'd pair that with a valid translation that moves a shared placeholder outside the select, so the check doesn't require identical sentence structure. I haven't run StringLane; this is a parser-fixture suggestion for the structural boundary you describe, not a translation-quality check.