Internationalization (i18n)
Internationalization (i18n) is the engineering work that lets a product take any language and region without code changes: text lives in resource files, dates and amounts come from locale-aware formatting, layout does not assume a direction, and every layer stores Unicode. Localization (l10n) then adapts the product to one locale, such as Arabic for Saudi Arabia (ar-SA) or Hindi for India (hi-IN).
A concrete case: checkout code builds the total as "Total: SAR " + amount. English looks right. In Arabic and Hindi the word "Total" stays English, and the order of label, code and amount and the number format stay the way the developer typed them instead of following the locale’s own currency pattern. No translator can fix it, because the text never reached the translation files. That is an i18n defect, and this lesson is about finding that class early.
Why it matters
Most i18n defects are invisible in the source language, so a team that tests only in English ships them to every other market at once. They also cost more to fix late than translation defects: a hard-coded string or a fixed date pattern means a code change, a new build and a retest in every locale, while a wrong translation is one line in a resource file.
For the tester, i18n knowledge decides where a report goes. A plain English label in the Arabic interface is not a translation bug for the translator; it is a code defect, and no string key exists.
Two different jobs: once in the code, then per locale
The localization testing guide introduced the terms; what matters for test design is where the work happens. Internationalization is done once, in the code, by developers: it is a property of the product. Localization is done per locale, in content, by translators and local experts: translated strings, local formats, legal texts, payment methods. Some teams call the two together globalization (g11n).
Because the work happens in different places, so do the defects. An i18n defect usually appears in every locale except the source language, and any tester with the right data can reproduce it. An l10n defect appears in one locale and often needs a native speaker to judge. One screen can carry both: a price built from a string template (i18n) and a wrong Arabic term for "cart" (l10n).
- i18n question: can the product take any locale? Test before translation exists, with a pseudo-locale, Unicode data, right-to-left direction and non-default formats.
- l10n question: is this locale right? Test after translation exists, with the real strings, real formats and a native speaker.
- Locale, not just language:
arselects the Arabic text;ar-SAadds the region that decides formats, currency and calendar defaults. Language tags follow RFC 5646.
Internationalization (i18n)
Make the product able to take any locale
- Done where, by whom:
- Once, in the code, by developers
- A defect lives in:
- A template, formatting code, a layout rule or a storage path
- Who sees it:
- Usually every locale except the source language
- On the checkout screen:
"Total: SAR " + amount: "Total" stays English and the amount ignores the locale format- When it is found:
- Before translation: pseudo-locale, Unicode data, right-to-left pseudo-locale
- The report goes to:
- Developers, with the layer and the string key if one exists
Localization (l10n)
Adapt the product to one locale
- Done where, by whom:
- Per locale, in content, by translators and local experts
- A defect lives in:
- One locale’s resource file or content
- Who sees it:
- Users of that one locale only
- On the checkout screen:
- A wrong Arabic term for "cart" in the Arabic resource file
- When it is found:
- After translation: localized build, real formats, native speaker
- The report goes to:
- Translator or localization lead, with the key and a screenshot
Externalised strings and locale data
Externalising a string means moving it out of the code into a resource file under a key: the code asks for checkout.total and the runtime returns the text of the current locale. Each sentence is one message with named parameters, never fragments glued together, because word order and agreement differ between languages. Counts use plural categories: the Unicode CLDR rules give English two, Hindi two and Arabic six, so the message carries one form per category and the formatter picks it; in the ICU MessageFormat syntax below, # stands for the number.
Locale data is everything the runtime knows about a locale that is not a translation: date and number patterns, digits, currency symbols and their position, calendar, first day of the week, plural rules and sort order. On most platforms it comes from CLDR and reaches the code through locale-aware APIs such as the ECMAScript Intl objects (ECMA-402). Internationalized code never hard-codes a pattern like DD/MM/YYYY; it passes the user’s locale tag to the API and shows what comes back. CLDR data changes with new releases, so a test should take its expected format from the platform’s formatter, not from a value copied once from a website.
- Missing keys: compare every locale file with the source file; a key in
en.jsonthat is absent fromar.jsonfalls back to English or shows the raw key. - Parameters:
{name}and{amount}must survive translation unchanged; a translated placeholder shows raw braces or breaks the formatter, and a deleted one silently drops the value.
en.json
"checkout.total": "Total: {amount}"
"checkout.nights": "{count, plural, one {# night} other {# nights}}"
"checkout.greeting": "Welcome back, {name}"
ar.json (same keys; Arabic carries all six plural categories)
"checkout.total": "الإجمالي: {amount}"
"checkout.nights": "{count, plural, zero {لا ليالي} one {ليلة واحدة} two {ليلتان}
few {# ليالٍ} many {# ليلة} other {# ليلة}}"
"checkout.greeting": "أهلاً بعودتك يا {name}"
What the tester checks
every visible string has a key -> nothing is typed in the template
{amount} arrives already formatted -> by the locale API, not by the message
{count} hits every plural category -> 0, 1, 2, 3, 11 and 100 for ArabicWorked example: a checkout screen in a pseudo-locale
Pseudo-localization generates a fake locale from the source strings: letters get accents, every message grows and is wrapped in brackets, and optionally the base direction flips to right-to-left. The team can still read the screen, and any interface text that is not transformed was never externalised. No translator is needed, so it can run in CI on every build. Android, for example, offers the pseudo-locales en-XA (accented, longer text) and ar-XB (right-to-left).
Below is a hotel checkout screen in English and in the pseudo-locale, with what each line tells the tester. Look for plain English, for several bracket pairs where one sentence should be, for a missing closing bracket, and for question marks where accented letters should be.
- Expansion: every string grows by a factor the team chooses, because many translations are longer than English and short labels grow most. A layout that survives it usually survives the real Arabic and Hindi strings.
- Right-to-left pseudo-locale: it flips the base direction without an Arabic translation, so unmirrored icons, misplaced back arrows and text aligned to the wrong side show weeks before the Arabic file exists.
English Pseudo-locale What the tester sees
------------------------ ----------------------------- --------------------------------
Checkout [Çĥéçķöüţ ····] fine: externalised
Book now Book now hard-coded in the template
You have 2 guests [Ýöü ĥàṽé] 2 [ĝüéšţš] three pieces: concatenated
Total: SAR 1,250.00 Total: SAR 1,250.00 hard-coded: never had a key
Room type [?ööm ?ý?é ····] question marks: non-UTF-8 path
Cancel booking [Çàñçéļ ḃööķí truncated: fixed-width button
Welcome back, Ahmed [Ŵéļçömé ḃàçķ, Ahmed ····] fine: {name} survivedTypical i18n defects and where they live
Most i18n defects fall into a few classes. Each has a symptom, a place in the code and a cheap way to provoke it; knowing the class lets you report the cause, not only the symptom.
- Hard-coded strings in validation messages, API errors, emails, labels and image text. Provoke: pseudo-locale, then Arabic, reading every message including the ones that need an error.
- Concatenation and
if-based plurals:"You have " + n + " items",count === 1 ? "night" : "nights". Symptom: bracketed fragments, "2 ليلة" instead of "ليلتان". Provoke: counts 0, 1, 2, 3, 11 and 100. - Fixed formats:
DD/MM/YYYYtemplates, hand-placed currency codes, a decimal point typed in code. Symptom: the same format in every locale. Provoke:ar-SAandhi-IN, compared with the platform’s own formatter. - Physical direction in layout:
margin-leftandtext-align: leftinstead of logical properties that follow the text direction (margin-inline-start,text-align: start) and thedirattribute. Symptom: an Arabic page whose layout stays English. Provoke: the right-to-left pseudo-locale. - Non-Unicode paths: a column, API or export that uses a legacy encoding instead of Unicode, or a length limit counted in bytes. Symptom: question marks, mojibake, a 100-character field that rejects 60 Arabic letters (120 bytes in UTF-8). Provoke: Unicode datasets in every field, then check the stored value.
- Locale lost in transit: the interface is Arabic but the email, PDF receipt or SMS is English. Provoke: finish a flow in Arabic and open every output that is not a screen.
Try it: Unicode Dataset Generator Try it: String Length Counter Try it: Arabic Test Data Generator Try it: Hindi Test Data Generator
Common mistakes
- Waiting for the translation before testing anything; i18n defects show in the pseudo-locale weeks earlier.
- Filing a hard-coded English label as a translation request; the translator cannot fix text that has no key.
- Testing only the pages: emails, PDFs, SMS, API errors and exports are where the locale usually gets lost.
- Taking an English pass as proof; most i18n defects never show in the source language.
- Using Latin-only test data in forms, so the non-Unicode path is never exercised.
- Treating the pseudo-locale as a substitute for the real Arabic and Hindi builds and for linguistic review.
The QA perspective
In practice i18n testing starts in the requirements review with three questions: which locales must ship, where the locale comes from (user setting, Accept-Language, device, URL), and what is deliberately not translated (brand names, codes, legal text). In code review, or with the developer, ask whether strings are externalised, whether formatting goes through the locale API and whether layout uses logical properties.
In execution, run the pseudo-locale build first, then the Arabic and Hindi builds with Unicode test data, then every output that is not a screen. In the report record the locale tag, the string key if one exists, the screen or output, a screenshot and the layer where the value went wrong: template, resource file, API, database or email service. "Hard-coded in the checkout template, reproduces in every locale" is fixed in one place; "Arabic translation missing" may go to a translator who can do nothing.
Practice exercise
Classify the findings of a pseudo-locale run
A hotel booking app was run in the pseudo-locale (en-XA) and then in Arabic (ar-SA). For each of the eight findings below, decide whether it is an i18n or an l10n defect, name where it lives (code, resource file or locale content) and who should receive the report.
Then write a one-line bug title for finding 3 that names the cause, not only the symptom.
1 "Book now" is plain Book now in the pseudo-locale; every other button shows
[Ḃööķ ñöẃ ····]-style text.
2 The guest line reads [Ýöü ĥàṽé] 2 [ĝüéšţš]: three bracketed pieces.
3 In Arabic the total line reads "Total: SAR 1,250.00", English text with Western
digits, while every other amount on the page uses Arabic-Indic digits and ر.س.
4 The room type "Deluxe" is "ديلوكس" on the search page and "فاخرة" on the invoice.
5 The check-in date field accepts only MM/DD/YYYY in every locale.
6 After an Arabic booking the confirmation email arrives in English, although the
Arabic email template is complete.
7 Two nights show "2 ليلة" in Arabic instead of "ليلتان"; English is correct. The
Arabic file has the same two entries as English for this text, one for 1 and one
for any other count, and the code reads only those two.
8 The Arabic cancellation policy allows free cancellation up to 24 hours before
check-in; the English source text says 48 hours.- Ask whether the finding appears in every locale or only in Arabic.
- Ask whether a translator with the resource file could fix it.
- Name the layer: template, formatting code, resource file, email service or locale content.
- Write the title for finding 3 as symptom, place and cause.
Show the model answer
i18n, in the code, to the developers: 1 hard-coded string in the template; 2 concatenated sentence; 3 hard-coded label and an amount built by a string template instead of the locale API; 5 fixed date pattern instead of a locale-aware date input; 6 locale not passed to the email service; 7 plural chosen by a two-way check such as count === 1, while Arabic needs six categories. Each is fixed once in the code, for every locale.
l10n, in Arabic content, to the translator or localization lead with the string keys and screenshots: 4 inconsistent terminology between two screens, in the Arabic resource file; 8 a mistranslation that changes the meaning of the cancellation policy, in the localized policy text.
Finding 7 shows only in Arabic, so step 1 points to l10n, but step 2 decides: no translator can write "ليلتان" into a file that offers one form for 1 and one for everything else. The fix is a plural message with the CLDR categories, in the code.
Title for 3: "Checkout total in ar-SA shows Total: SAR 1,250.00 in English with Western digits: the label is hard-coded and the amount is formatted by a string template instead of the locale API".
Frequently asked questions
Is internationalization the developers’ job or the testers’ job?
Building it is engineering work; checking it is test work. Testers find i18n defects earliest by reviewing requirements for locale decisions, running the pseudo-locale on every build and feeding Unicode data through every layer, and they report each defect with its cause so it is fixed once in the code rather than patched per locale.
Can pseudo-localization replace testing with real Arabic and Hindi?
No. It finds the defects that do not depend on a real translation: hard-coded strings, concatenation, truncation, encoding and direction problems. Terminology, grammar, cultural fit and real locale formats still need the localized builds, localized test data and the native-speaker review covered in the lesson on translation and linguistic testing.
What is the difference between a language and a locale?
The language subtag, such as ar, selects which translation to show. A locale adds a region, as in ar-SA or ar-EG (both are language tags under RFC 5646), and the region decides formats, currency, calendar and other defaults. Two users with the same language and different regions can see different dates and amounts from the same code.
Do I need to speak Arabic or Hindi to find i18n defects?
No. i18n defects show as plain English, bracketed fragments, wrong digits, a broken layout direction or lost characters, and any tester can see them with pseudo-localization and generated test data. Judging the quality of a translation does need a native speaker.
References
- Internationalization (i18n) ActivityW3C's internationalization articles, tutorials and checks for building and testing multilingual web content.
- RFC 5646: Tags for Identifying LanguagesBCP 47 language tags (ar, ar-SA, hi-IN, en-GB) — the identifiers used in lang attributes, Accept-Language and locale settings.
- Unicode CLDR ProjectThe Common Locale Data Repository: the locale data for date, time, number and currency formats and plural rules that most platforms use.
- ECMAScript Internationalization API Specification (ECMA-402)The JavaScript Intl API specification — locale-aware formatting of dates, numbers, currencies, lists and plural rules in the browser.
Practise with Mutqan tools
Generate the same records in multiple languages (English, Arabic, Hindi, and more) to test i18n and RTL/LTR mixing.
Generate Unicode test strings across scripts (Arabic, Devanagari, CJK, Cyrillic), emoji, combining marks and normalisation forms.
Measure string length in characters, code points, UTF-8 bytes and UTF-16 units.
Compare two JSON documents semantically and highlight added, removed and changed values.
Generate Arabic names, addresses, sentences, numbers (Arabic-Indic digits) and RTL edge cases for localisation testing.
Generate Hindi (Devanagari) names, addresses, sentences and numerals for localisation testing.
Go deeper
Your progress on this topic
Not started
Mark the topic complete when you have studied it and done the exercise.
Sign in to track your progress
Mark topics complete, continue where you left off and unlock specialisation exams.