Every i18n setup I've shipped has the same slow rot: you add a feature, you add the English string, and you fully intend to backfill the other languages "later." Later never comes, the non-English files drift behind, and your French users see raw key strings or fall back to English. So I built a thing that just fills the missing keys in on the PR that introduced them.
The problem
You add a confirmation dialog. You touch en.json because that's the language you're thinking in:
// locales/en.json
{
"cart": {
"checkout": "Checkout",
"confirm_title": "Confirm your order",
"confirm_body": "We'll charge {{count}} item(s) to {cardLast4}. Continue?"
}
}
// locales/fr.json — still the old version
{
"cart": {
"checkout": "Commander"
// confirm_title and confirm_body don't exist here
}
}
Two new keys, zero new French. Depending on your i18n library, the French user now sees the literal cart.confirm_title, or an empty string, or a silent English fallback. None of those are what you wanted, and your test suite is perfectly happy because the keys all "exist somewhere."
The annoying part is it's invisible. The PR looks complete. CI is green. The gap only surfaces in production, in a language you don't read, reported by a user weeks later — if at all.
And the manual fix is its own trap: hand-translating {{count}} and {cardLast4} is exactly where placeholders get mangled, pluralized, or dropped.
How it works
i18n Autopilot is a GitHub Action that runs on every pull request:
- It diffs your locale files and finds keys present in your base/source locale but missing in the others.
- It translates only those missing keys with AI — it doesn't touch existing translations.
- It preserves placeholders like
{name}and{{count}}exactly, so interpolation and pluralization don't break. - It commits the filled-in translations back to the same PR, so the branch that added the English string ships with every other language already done.
After it runs on the PR above, fr.json comes back complete:
// locales/fr.json — after i18n Autopilot
{
"cart": {
"checkout": "Commander",
"confirm_title": "Confirmez votre commande",
"confirm_body": "Nous débiterons {{count}} article(s) sur {cardLast4}. Continuer ?"
}
}
Note that {{count}} and {cardLast4} survived untouched — that's the part hand-translation and naive find-and-replace get wrong.
Try it
It's a free GitHub Action on the Marketplace: https://github.com/marketplace/actions/i18n-autopilot — add it to a workflow and your next PR comes back with the missing locale keys filled in. There's also a hosted Pro app if you don't want to manage the Action and API key yourself: https://i18n.useautopilot.dev
If you maintain anything multilingual, I'd love to hear how far behind your non-English files have quietly drifted. That number is always bigger than people expect.
Top comments (0)