← back to the section

The shop launched in Germany, and within a week support brings three tickets: the label has run past the edge of the button, and half the button is not clickable; a customer expected the parcel on March 4th and it arrived on April 3rd, because the email said "03.04.2026"; the cart says "Found 1 items".

The translation is flawless in all three. What broke is everything around it: a layout measured against the length of English words, a date assembled from an English template, a sentence glued out of three pieces. Checking that the product stays correct in every language version is what localization testing is.

one order card, three locales — the same code behind all three Order #10482Delivery 04/03/2026$1,299.00tax added at checkoutPlace order04/03/2026 is April 3rd; $ goes before the number, comma marks thousands Bestellung Nr. 10482Lieferung 03.04.20261.299,00 €inkl. MwSt.Zahlungspflichtig bestellen✗ the label is a third wider than the buttonsame date, another currency, a longer label — and it no longer fits Заказ № 10482Доставка 03.04.20261 299,00 ₽цена с НДСОплатить03.04.2026 — the same day, day first; thousands split by a space en-US de-DE ru-RU length format currency layout

Same code, same data — only the locale changes. With it change the length of the string, the separator inside the date, the currency symbol and its position. The German button label is a third longer than the English one, and in a layout measured against English text it no longer fits.

Internationalization prepares, localization applies

Which of the two similar words you say decides who receives the bug.

A product with labels typed straight into the code cannot be translated at all: there is nowhere for the translator to put the translation. Preparing it — labels into dictionaries, dates and money into libraries that know the rules of different countries — is called internationalization (i18n), and developers do it once. Localization (l10n) comes afterwards for each language and country: translation, formats, currency, images, emails. That is what a tester checks.

The key word is locale, a language together with a region: en-US and en-GB disagree on dates, de-DE and de-CH on the separators inside a number, which is why a bug report says de-DE rather than "the German version". Hence the rule of ownership: a string that refuses to translate in every language is unfinished internationalization, a task for a developer; a string that translates but comes out wrong in one language is localization.

The key, the dictionary and three kinds of untranslated text

Before filing a bug on an untranslated chunk, work out where it came from.

After internationalization there are no labels left in the code — in their place stand keys such as cart.button.pay, with dictionaries next to them, one per language. Three symptoms follow, and different people fix them: German is expected and English shows up — there is no translation for this locale, a task for the translator; the button reads the key itself — the entry is missing from the dictionary, a task for a developer; the text is identical in every locale — the string was hard-coded past the dictionary, and this is the most common cause.

Hard-coded strings hide where nobody gets around to looking: error messages from the server, emails, text baked into images, receipts. The main screen is translated first — so hunt on the rare ones: an empty state, a wrong-password error, a "nothing found" page.

Glued sentences and plural forms

"Found 1 items" is not a translation mistake: both words are correct. The phrase was assembled from three parts — "Found ", a number and " items" — and language does not work that way.

The number of plural forms differs by language: two in English, three in Russian (1 товар, 2 товара, 5 товаров), six in Arabic, a single one for every case in Japanese and Chinese. And the rule is harder than "look at the last digit": in Russian 11, 12, 13 and 14 take the "many" form. So the set of numbers to check is always the same: 0, 1, 2, 5, 11, 21, 101, 111 — plus zero on its own, where you expect "Nothing found". A library picks the form itself, provided it was given a template like "Found {count} items" with every variant.

The same glue breaks grammatical gender: substitute an entity name into a template in Russian and you get "Ваш подписка отменён" — the wrong ending on both words.

Dates, numbers and money

The string 03.04.2026 means nothing on its own — the reader gives it meaning. In Germany and Russia it is April 3rd, while an American expects 04/03/2026 and reads the first version as March 4th: the application never lied, and the customer showed up on the wrong day. The order of the parts changes from country to country — day-month-year, month-day-year, year-month-day in Japan and in the technical form 2026-04-03.

Numbers swap the decimal and thousands separators around: 1,299.00 in the US, 1.299,00 in Germany, 1 299,00 in Russia. The string 1,299 is one thousand two hundred and ninety-nine to an American and slightly more than one to a German: a factor of a thousand, with no warning attached.

A mistake in a price is the most expensive of all. The symbol and its position are part of the format: $1,299.00 goes before the number and tight against it, 1.299,00 € after it. The number of decimal places is not always two: the yen has none, the Kuwaiti dinar has three, and rounding to two regardless gets whole countries wrong. Tax differs too: European Union law requires the catalogue price to include VAT, while in the US sales tax is added at checkout — "1,299 in the catalogue, 1,411 at the till" is normal for an American and a violation for a German. Reconcile the amount in three places at once: the screen, the email and the receipt.

Translation length, mirrored layout and forms

The button was drawn for "Place order" — eleven characters. In Germany the same action is labelled "Zahlungspflichtig bestellen", twenty-seven: the law requires the button to say outright that an obligation to pay is being taken on. The button kept its width, and the right-hand part of the label ran outside it and stopped being clickable.

German text runs about a third longer than English, and short labels suffer most: they have no slack and can double. So you check the longest translation in existence, straight away on a narrow screen and at 200 % zoom: localization and accessibility break layout the same way. The rest about layout is in cross-browser and mobile testing.

Arabic and Hebrew versions mirror the whole layout — blocks, padding, column order, arrows — but not numbers, phone numbers or logos; the most fragile spot is mixed text, where a full stop inside an Arabic phrase with a Latin brand name drifts to the wrong side. And an order form is broken by fields pinned to one country: a five-digit postcode against the British SW1A 1AA and its complete absence in the UAE, a mandatory state against European addresses that carry none. Check it with a real address from the target country.

Time zones, sorting and case

An order was placed on April 3rd at 01:30 Moscow time, and it is not in the April 3rd report. Translation has nothing to do with it: the server stores time on a single scale, UTC, and Moscow runs three hours ahead — so 01:30 on the 3rd is 22:30 on the 2nd. Hence you check the day boundary, 23:50 and 00:10 rather than a comfortable midday, and daylight saving, which Europe has and Russia does not: a night with 23 or 25 hours in it breaks "exactly one day later" calculations. Switch the zone in the sensors panel of DevTools and repeat the scenario.

Sorting breaks the same way, with a flawless translation: a computer compares letters by their numbers in the character table rather than by the alphabet, and the Russian "ё" sits after "я" — so "Ёлкин" lands at the bottom of the list, below "Яковлева". The correct order is defined by the language: the German ä sits next to a, the Swedish one after z. Case is a trap of the same kind: in Turkish the capital of i is İ, so code that upper-cases input before comparing it turns id into İD. What breaks is not a label but sign-in and search.

How to check a language you don't speak

The quality of the translation is checked by a native speaker. Your job is everything else, and for that you don't need the language.

The strongest technique is pseudolocalization: a "language" in which the text is not translated but deliberately mangled — letters swapped for accented look-alikes, the string padded by about a third and wrapped in brackets: [!!! Ρļãçé öŕðéŕ !!!]. From there it is simple: anything left in plain letters without brackets is hard-coded, and anything clipped or overflowing will not survive German.

The second technique is the key dictionary: the translation file shows empty values and strings where the {count} placeholder went missing, meaning the number will not appear at all.

The order of checks is set by the price of the mistake: money, the dates people act on, the main buttons, input forms, emails and receipts. A bug report names the locale exactly: "in de-DE the payment button label is wider than its container, the right part is not clickable; in en-US on the same build there is no issue".

In short

  • Developers do internationalization once, a tester checks localization: a string that refuses to translate anywhere is a problem of the first.
  • A locale is a language plus a region, and a bug report names it in full: de-DE, not "the German version".
  • English where German was expected is a missing translation, the key itself is a missing dictionary entry, identical text everywhere is hard-coded.
  • Plural forms number three in Russian, two in English, six in Arabic; the numbers to check are 0, 1, 2, 5, 11, 21, 101, 111.
  • 03.04.2026 and 04/03/2026 are the same day; the yen has no decimals, the dinar has three; the EU shows prices with VAT, the US adds tax at the till.
  • German runs a third longer than English: check the longest translation on a narrow screen. Zones and sorting lie with a flawless translation.