Internationalization testing: the bugs live in the layout, not the translations
Internationalization testing isn't proofreading translations. It's finding the button your German text overflows, the date that reads 03/04, and the RTL layout nobody looked at. Here's the agent-driven playbook, in plain-English prompts.
Internationalization testing: the bugs live in the layout, not the translations
Every article about internationalization testing opens by carefully distinguishing i18n from l10n, gives you a table of "key testing areas," and leaves you exactly where you started: with an app in eleven languages and no idea which of them is broken right now.
Here's the useful framing instead. Your translators are fine. The strings are almost certainly correct — a translation vendor is good at translating. What's broken is everything around the strings: the button that fit "Save" and doesn't fit "Speichern unter", the date that says 03/04 and means two different days depending on who's reading, the price that renders 1,299 to a German user who reads that as one point two nine nine, the Arabic page where the text flipped and the icons didn't.
None of that is a translation bug. All of it ships. And it ships because internationalization testing, done the normal way, means one person on the team who doesn't read German opens the German site, sees German words, and concludes it works.
This is the version an agent can run on every locale, on every release, without anyone needing to read German.
What you're actually looking for
Four categories, in descending order of how often they ship broken:
- Layout under text expansion. The single biggest source of real i18n bugs, and the most mechanical to find.
- Locale-formatted data. Dates, numbers, currency, sort order — right value, wrong shape, quietly wrong meaning.
- RTL. If you support Arabic, Hebrew, Farsi, or Urdu: a whole second layout that gets a fraction of the attention.
- Untranslated leakage. English strings hardcoded in a page that's supposed to be in Japanese.
Notice that three of the four are visible to someone who doesn't speak the language. That's the whole reason this is automatable.
1. Text expansion: why your buttons break
The W3C's Text size in translation is the primary source here, and it's worth internalizing the actual numbers rather than the vibe. Their headline finding, quoting IBM's published expansion rates for English into European languages:
| Characters in English source | Average expansion |
|---|---|
| Up to 10 | 200–300% |
| 11–20 | 180–200% |
| 21–30 | 160–180% |
| 31–50 | 140–160% |
| Over 70 | 130% |
Read that table again, because it says something counterintuitive. As the W3C puts it: "text will normally expand, but note carefully how the smaller the source message, the higher the likely translation length."
The short strings expand the most. And short strings are exactly the ones you put in buttons, tabs, table headers, and next to form fields. The W3C names the trap directly: "the problem tends to be that the smaller the English text, the more likely it is to be squeezed into a small space, such as alongside a form entry field, or inside a graphic, or a set of width restricted tabs."
Their Flickr example is the one to remember. The English "views" becomes "visualizzazioni" in Italian — a 300% expansion, in a string that lives inline next to a number.
Two complications on top of raw length, both from the same source:
Compound nouns don't wrap. German, Finnish, and Dutch "create single large 'words' to replace what is a sequence of smaller words in other languages." The W3C's example: "Input processing features" becomes "Eingabeverarbeitungsfunktionen" in German. Your English wraps across two lines in a narrow tab. The German is one word and "may not wrap automatically."
Character count is not width. "The English 'desktop' becomes 'デスクトップ' in Japanese. The Japanese has one less character, but will typically take up much more horizontal space." A length check on the string passes. The layout still breaks.
This is why the test has to be visual, and why it has to be per locale, not on a length assertion.
That prompt is doing something a person doesn't reliably do: looking at a language they don't speak with no comprehension bias, purely as shapes in boxes. It's genuinely one of the tasks an agent is better at than a human reviewer, for the same reason an agent finds bugs your scripts miss — it has no expectation about what should be there, so it reports what is.
Run it against your longest language first. If you only test one, test German.
2. Formatted data: right value, wrong meaning
This is the category that costs money, because unlike a broken button it doesn't look broken.
03/04/2026 is March 4th in the US and April 3rd nearly everywhere else. 1,299 is one thousand two hundred ninety-nine in English and roughly one-point-three in German. €1.299,00 and $1,299.00 are the same number wearing different clothes. A user reading the wrong one doesn't file a bug — they just make a decision based on a number that isn't what you meant.
The correctness bar here is the platform's own: Intl.DateTimeFormat and Intl.NumberFormat do this correctly, per locale, in every browser you care about. If a date in your UI doesn't match what Intl would produce for that locale, someone hand-rolled a formatter, and hand-rolled formatters are where this bug lives. Usually it's one component — the one written in a hurry, the one in the email template, the one in the CSV export.
The agent can't run Intl for you, but it doesn't need to. It can read the rendered page and tell you the shape, and you know what the shape should be:
The last instruction is the important one. Inconsistency within a page is the highest-signal finding you can get here, because it proves two code paths formatted the same kind of value differently — which means at least one of them isn't going through Intl. You don't need to know which is correct to know something is.
3. RTL: the second layout
If you ship Arabic or Hebrew, you ship a mirrored layout, and it gets tested roughly never.
RTL breaks in a specific, predictable way: things that should mirror don't, and things that shouldn't mirror do. A back arrow that still points left in Arabic is pointing forward. A progress bar that fills left-to-right is running backwards. Meanwhile a logo that got mirrored along with everything else is now wrong in a way that will end up on social media.
The tell is almost always a hardcoded margin-left or left: that should have been a logical property. You don't need to know that to test it, though — you need to look:
That side-by-side comparison instruction is what makes this work. The agent isn't judging whether Arabic is correct — it's diffing two layouts and reporting the asymmetries. Same technique as visual regression testing, but comparing across locales rather than across time.
4. Untranslated leakage
The easy one, and the only category where the agent is genuinely reading the language rather than looking at boxes.
Hardcoded English strings hide in the places nobody translated because nobody thought of them: validation error messages, empty states, tooltips, confirmation dialogs, the 404 page, date pickers, the checkout error path. All the surfaces you only reach by doing something wrong.
Raw translation keys and unfilled placeholders are the bonus catch. Those mean a missing key in the locale file, and they're pure signal.
What this doesn't cover
Two honest limits, because pretending otherwise is how you get a false green.
Locale detection from the browser is not tested here. All of these prompts reach a locale by navigating to a locale-prefixed URL or using the app's own language switcher — because that's what a browser agent does: it navigates and it clicks. It is not sending a German Accept-Language header. If your app auto-detects locale from the browser's language preference, that specific redirect logic needs a request-level test, not a browser agent. Test the code paths the agent can reach, and know which one you're leaving out.
The agent is not a translation reviewer. It will tell you the German string is German. It will not tell you the German is good, or that your marketing copy is culturally tone-deaf. That's a human with the language, and no amount of automation replaces them. What automation does is make sure they're reviewing a page that isn't visibly broken.
Running it per locale
Save each prompt as a Test Scenario with the locale-prefixed URL, and duplicate it once per language you ship. That's the whole matrix: four Scenarios × N locales, each run about 8–13 credits — roughly $0.08–$0.13. Eleven locales at four checks is under six dollars a release, which is meaningfully less than one afternoon of one engineer opening the German site and seeing German words.
Every failure gives you the full Monito Session — screenshot timeline, console output, the agent's reasoning — so an overflow bug arrives as a picture of the overflowing button rather than a ticket saying "German looks off." Wire the set into CI and the locale matrix runs on every release instead of on the release before the one where someone complains.
The same structural instinct applies here as in testing search and filter: the interesting bugs aren't in the component you're looking at, they're in the combination — locale × page × state. You find them by walking the combination, not by checking a box.
The one to run first
If you run exactly one i18n check, make it text expansion in your longest language. It's the highest-yield, and it needs nothing from you but a URL:
Your first run is free — point it at your German site and find out how many of your buttons fit.