Internationalization & Localization Testing
What it is
Section titled “What it is”Internationalization (i18n) testing proves the app can be localised: strings are externalised, encodings survive the round trip, and layout accommodates translated text. Localization (l10n) testing proves a specific translation is correct: locale formats, pluralisation, gender and region conventions.
source string ──▶ message catalog ──▶ rendered locale (en) (vi, ar, ja) + formats date · number · currency · pluralThe high-risk areas are rarely the words themselves. They are the edges: right-to-left mirroring, text expansion (German and Finnish run long), Unicode normalisation, time zones, and languages with complex plural rules such as Arabic, Russian and Polish.
When to use it / when to avoid it
Section titled “When to use it / when to avoid it”Use it for
- Every release that ships more than one locale
- New UI strings, especially those with placeholders, plurals or gender
- Layouts that must mirror for right-to-left languages (Arabic, Hebrew, Persian)
- Any flow involving dates, times, numbers, currency or sorting/collation
Avoid it when
- The product is genuinely single-locale and will stay that way
- You are duplicating a functional test only to change the language — check the format layer instead
- The concern is pure encoding detection with no user-visible output
Frameworks & tools
Section titled “Frameworks & tools”| Tool | Ecosystem | Notes |
|---|---|---|
| ICU MessageFormat | Multi | Plural, select and gender in one message syntax |
| CLDR / Intl | Multi | Platform-standard locale data for formatting |
| pseudo-localization | Multi | Fake locale to reveal unexternalised and hard-coded strings |
| i18next / i18n ally | JS/TS | Runtime i18n and editor-time missing-key linting |
| Babel / gettext | Python | Message extraction and catalog tooling |
| go-i18n / golang.org/x/text | Go | Locale-aware formatting and messages |
| Fluent | Multi (Rust, JS, Python) | Asymmetric localisation syntax for rich grammar |
| RTL screenshot diffing | Multi | Visual regression for mirrored layouts |
Quick setup
Section titled “Quick setup”npm install -D vitestnpm install intl-messageformatimport { IntlMessageFormat } from 'intl-messageformat';
new Intl.NumberFormat('de-DE').format(1234.5); // "1.234,5"new Intl.DateTimeFormat('ja-JP').format(new Date()); // locale orderpip install babelfrom babel.numbers import format_currencyformat_currency(1234.5, "EUR", locale="de_DE") # '1.234,50 EUR'go get golang.org/x/textNumberFormat nf = NumberFormat.getNumberInstance(Locale.GERMANY);nf.format(1234.5); // "1.234,5"using System.Globalization;var de = new CultureInfo("de-DE");value.ToString("C", de); // "1.234,50 €"Example
Section titled “Example”const pr = new Intl.PluralRules('ar');// Arabic has six plural categoriespr.select(0); // "zero"pr.select(1); // "one"pr.select(2); // "two"pr.select(11); // "many"from babel.messages.catalog import Catalog# Verify a catalog for each target locale parses# and that plural forms exist for every count category.for locale in ("en", "vi", "ar", "ru", "pl"): catalog = Catalog(locale=locale) assert catalog.locale is not Noneimport "golang.org/x/text/language"import "golang.org/x/text/message"
p := message.NewPrinter(language.German)p.Sprintf("%d", 1234) // "1.234"@Testvoid formatsPerLocale() { assertEquals("1.234,5", NumberFormat.getNumberInstance(Locale.GERMANY).format(1234.5)); assertEquals("1,234.5", NumberFormat.getNumberInstance(Locale.US).format(1234.5));}[Fact]public void FormatsArabicCurrency(){ var ar = new CultureInfo("ar-EG"); var text = (1234.5).ToString("C", ar); Assert.Contains("ج.م", text); // EGP symbol Assert.True(ar.TextInfo.IsRightToLeft);}Anti-patterns & best practices
Section titled “Anti-patterns & best practices”- Never concatenate sentences from fragments. Word order differs per language; use full ICU messages with placeholders.
- Test with pseudo-localization. A fake locale exposes hard-coded strings and text expansion before translators do.
- Assert encodings explicitly. UTF-8 end to end; store and serve with the declared charset.
- Test RTL as a first-class layout. Mirror the whole UI, not just flip the text direction.
- Cover plural categories, not just one and many. Arabic, Russian and Polish have several forms.
- Pin the time zone and locale in tests. Ambiguous environments produce false failures around dates.
Self-assessment quiz
Section titled “Self-assessment quiz”Self-assessment quiz
Pick one answer per question, then press Check answers.