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.

playbooki18nlocalizationno-code
monito

Internationalization testing: the bugs live in the layout, not the translations

playbooki18nlocalizationno-code
August 12, 2026

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:

  1. Layout under text expansion. The single biggest source of real i18n bugs, and the most mechanical to find.
  2. Locale-formatted data. Dates, numbers, currency, sort order — right value, wrong shape, quietly wrong meaning.
  3. RTL. If you support Arabic, Hebrew, Farsi, or Urdu: a whole second layout that gets a fraction of the attention.
  4. 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 sourceAverage expansion
Up to 10200–300%
11–20180–200%
21–30160–180%
31–50140–160%
Over 70130%

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.

Go to the app's German site at https://staging.yourapp.com/de and
log in with test@example.com / Password123!

Walk through these pages: the dashboard, the settings page, the
billing page, and the main navigation menu.

On each page, look carefully for text that does not fit its
container:
- Button labels that are cut off, ellipsised, or overflow their button
- Navigation or tab labels that wrap awkwardly, collide, or push
  the layout sideways
- Table headers that are truncated
- Form field labels that overlap the field or each other
- Any text that spills outside a card, badge, or modal
- Any horizontal scrollbar that appears where there shouldn't be one

You do not need to read or judge the German. Only report where text
does not fit. Screenshot every instance and describe exactly which
element is broken.

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:

Go to https://staging.yourapp.com/de and log in with
test@example.com / Password123!

Open the Billing page and the Orders list.

For every date, number, and price you can see, report back the exact
rendered string and where it appears. I need to check them against
German locale conventions, so be literal — copy exactly what is on
screen, including separators and symbols.

Specifically flag anything that looks like US formatting on a German
page:
- Dates in MM/DD/YYYY form
- Numbers using a comma as the thousands separator (German uses a
  period)
- Prices with the currency symbol before the number, or using a
  period as the decimal separator
- Times in 12-hour AM/PM form
- Any date or number that is formatted differently from the others
  on the same page

Report every inconsistency, even if you're unsure whether it's wrong.

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:

Go to https://staging.yourapp.com/ar and log in with
test@example.com / Password123!

This is the Arabic (right-to-left) version of the app. Compare the
dashboard, the settings page, and the main navigation against the
English version at https://staging.yourapp.com/en.

Report:
- Whether the overall layout is mirrored (navigation, sidebars, and
  content on the opposite side from English)
- Any element that did NOT mirror and should have: back/forward
  arrows still pointing the English direction, progress bars filling
  the wrong way, sidebars or menus stuck on the left
- Any element that mirrored and should NOT have: the logo, phone
  numbers, code snippets, or anything with Latin text reading
  backwards
- Any text alignment that is still left-aligned in a right-aligned
  layout
- Icons or buttons that overlap text now that the direction flipped

Screenshot both versions side by side for anything you flag.

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.

Go to https://staging.yourapp.com/de and log in with
test@example.com / Password123!

Deliberately trigger error and edge-case states across the app and
report any English text appearing on this German page:
- Submit the profile form with an empty required field and read
  the validation message
- Submit the profile form with an invalid email and read the message
- Search for a nonsense string that returns no results and read the
  empty state
- Open a date picker and read the month and day names
- Visit https://staging.yourapp.com/de/this-page-does-not-exist and
  read the 404 page
- Open any confirmation dialog (e.g. delete something) and read
  the buttons

For each, report the exact text you see and whether it is German or
English. Also flag any raw translation keys leaking through, like
"settings.profile.title" or "%s" placeholders shown literally.

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:

Go to https://staging.yourapp.com/de and log in with
test@example.com / Password123!

Walk through the dashboard, settings, billing, and the main
navigation.

I do not speak German and I am not asking you to check the
translations. I am asking you to find text that does not fit.

Report every button label that is cut off or overflows, every tab
or nav item that wraps or collides, every truncated table header,
every label overlapping its field, every element with text spilling
outside it, and any unexpected horizontal scrollbar.

Screenshot each one and tell me exactly which element is broken.
Flag anything else that looks wrong while you're in there.

Your first run is free — point it at your German site and find out how many of your buttons fit.