Localization Testing
Localization testing checks that a product works, reads correctly and looks right for users of a specific locale — a language plus a region, such as Arabic in Saudi Arabia (ar-SA) or Hindi in India (hi-IN). It covers translated text, but most of its defects are not translation mistakes: they are dates in the wrong calendar, amounts with the wrong separators, sentences cut in half, layouts that break when text changes length or direction, and letters that turn into question marks somewhere between the form and the database.
This guide is the entry point to the domain. It separates internationalization, localization and globalization, maps the areas a tester has to cover, and gives concrete checks, data and defect examples for Arabic, Hindi and English — the three languages Mutqan Tools itself runs in. Right-to-left layout and bidirectional text have their own guide, RTL testing for Arabic interfaces; this page summarises them and goes deep on everything else.
i18n, l10n and g11n: what is being tested
Internationalization (i18n) is an engineering property: the product is built so that it can be adapted to any locale without changing the code. Text lives in resource files, not in templates; dates, numbers and currencies go through locale-aware formatting APIs; text direction is not assumed; storage and transport are Unicode end to end. Localization (l10n) is the adaptation to one locale: translated strings, locale formats, local content, legal texts, payment methods, address and phone formats. Globalization (g11n) is the umbrella term for the business and engineering effort that combines both so one product can be offered in many markets.
The distinction matters because the defects differ. An i18n defect lives in the code and appears in every locale — a hard-coded currency symbol, a concatenated sentence, a date parsed with a fixed pattern. An l10n defect lives in one locale’s content — a wrong term, a missing translation, a wrong address format. i18n defects are cheapest to find before any translation exists, with pseudo-localization; l10n defects need the real translation and, for language quality, a native speaker.
- Locale — identified by a BCP 47 language tag (RFC 5646): ar, ar-SA, hi-IN, en-IN. The language subtag selects the translation; the region subtag selects formats, currency and calendar defaults.
- Internationalization testing — can the product support locales at all? Run it with pseudo-localized text, Unicode data, right-to-left direction and non-default formats.
- Localization testing — is this particular locale right? Translation in context, formats, cultural fit, layout with real strings.
The map of localization testing
Localization testing spans areas that need different skills and different data. Use this map to plan coverage per locale; the sections below go through each area, and right-to-left layout is covered in depth in the RTL testing guide.
- Language and translation quality — terminology, context, completeness, plural and gender forms, message parameters, truncation.
- Locale formats — dates and calendars (Gregorian and Hijri), times and time zones, numbers and digits, currencies, units, addresses, phone numbers and personal names.
- Text and script handling — encoding, Unicode normalization, grapheme clusters, fonts, sorting, searching and input methods.
- Layout — text expansion and contraction, line height for scripts with marks above and below the line, and for Arabic the whole right-to-left mirroring and bidirectional-text area.
- Locale selection and fallback — how the product chooses a locale (user setting, Accept-Language header, device, URL), what happens for an unsupported locale, and whether the choice carries over to emails, PDFs, SMS and push notifications.
- Accessibility per locale — the lang attribute that tells a screen reader which voice and pronunciation rules to use, and translated accessible names for icons and controls.
Language and translation quality
Translators usually work from a spreadsheet or a translation-management system without seeing the screen, and many translation defects come from that. The English "Save" can be a verb (save a file) or a noun (a discount); "Open" can be a status or a command; a translator without context picks one. The tester’s job is to review every string on the real screen and to report the string key together with the screenshot, so the fix lands in the right resource.
Plurals are the most common structural defect. English has two cardinal plural forms. The Unicode CLDR plural rules give Arabic six categories — zero (0), one (1), two (2), few (numbers whose last two digits are 3–10, such as 3 or 103), many (last two digits 11–99) and other (for example 100–102) — and Hindi two, where the "one" form covers 0 and 1. Code that chooses the form with count === 1 is wrong for Arabic by construction. The fix is a message format with plural categories; the test is a table of counts that hits every category.
- Terminology consistency — one term per concept across screens, help pages and emails, checked against the glossary. Calling the cart "سلة" on one screen and "عربة" on another is a defect.
- Untranslated strings — English left inside the Arabic or Hindi interface; typical in validation messages, API errors, emails and strings added late in a sprint.
- Concatenated strings — sentences assembled from fragments ("You have" + n + "items") cannot be translated correctly because word order and agreement differ; each sentence must be one message with parameters.
- Message parameters — the {name} or %s inside a translation must survive unchanged; a translated or deleted parameter shows raw braces or crashes the formatter.
- Gender and register — Arabic verbs and adjectives agree with gender; decide how messages address the user and check that the decision is applied consistently.
# ICU MessageFormat, Arabic: one message, six CLDR plural categories
cart.items = {count, plural,
zero {السلة فارغة}
one {منتج واحد في السلة}
two {منتجان في السلة}
few {# منتجات في السلة}
many {# منتجًا في السلة}
other {# منتج في السلة}
}
# Counts that hit every Arabic category
0 (zero) · 1 (one) · 2 (two) · 3, 10, 103 (few) · 11, 99 (many) · 100, 101, 102 (other)
# Hindi has two categories: one (0 and 1) and other
0, 1 (one) · 2, 5, 1.5 (other)count === 1 check is right only at 1 and 100–102 among the test counts.Arabic (ar): CLDR category
- 0
zero - 1
one - 2
two - 3–10
few - 11–99
many - 100–102
other - 103–110
few
count === 1 check: one form, else other
- 0wrong, needs zeroFail
- 1rightPass
- 2–99wrong, needs two, few or manyFail
- 100–102rightPass
- 103–110wrong, needs fewFail
Hindi (hi): CLDR category
- 0, 1
one - 1.5, 2, 5 …
other
Not to scale: each test count from the sample, and 110 where few ends, gets its own slot. The check is read as English-style code: the one form for 1, the other form for every other count. In Hindi it is wrong at 0.
Pseudo-localization: find i18n defects before translation
Pseudo-localization replaces every UI string with an automatically transformed version that the team can still read: accented letters, extra length, and brackets marking the start and end of each message. Run the product in the pseudo-locale and every defect class that does not need a real translation shows at once — hard-coded text stays plain English, a concatenated sentence shows several bracketed pieces, truncation cuts off the closing bracket, and characters outside ASCII expose encoding problems.
Android provides two pseudo-locales that can be enabled for debug builds: English (XA), which adds accents, expands the text and brackets each message, and AR (XB), which switches the direction to right-to-left and reverses the characters, so a team can find mirroring and bidirectional problems without knowing an RTL language. On the web, a pseudo-locale is usually one more resource file generated during the build from the source strings.
Source (en): Save changes
Pseudo (expanded): [Ŝàṽé çĥàñĝéš ·····]
RTL pseudo: same text, base direction switched to right-to-left
What each symptom on screen means:
Save changes -> hard-coded string, never externalised
[Ŝàṽé] [çĥàñĝéš] -> sentence assembled by concatenation
[Ŝàṽé çĥàñĝ -> truncated: no room for longer translations
[Ŝ?ṽ? ?ĥ?ñĝ?š] -> characters lost in an encoding conversionDates, calendars and time zones
A date format is more than the order of day and month. The locale decides the calendar, the month names, the digits, the separators, the first day of the week and the 12- or 24-hour clock. In Saudi Arabia two calendars are in everyday use: the Gregorian calendar and the Hijri calendar, which Saudi Arabia standardises as the Umm al-Qura calendar. Products often show both or let the user choose; every place that shows, accepts, stores or compares a date must be clear about which one it uses.
Do not assume what "ar-SA" produces. Locale defaults come from the CLDR data inside the runtime and change between versions: older runtimes formatted ar-SA dates in the Hijri calendar by default, while recent ones use the Gregorian calendar with Arabic-Indic digits. Request the calendar and numbering system explicitly — for example with the Unicode locale extensions ar-SA-u-ca-islamic-umalqura or ar-SA-u-nu-latn — and test the result on every runtime you ship: server, browsers and mobile. Different Hijri calculations (Umm al-Qura, tabular civil) can place the same day on different dates, so a conversion test must name the calendar, not just "Hijri".
Time zones come from the IANA tz database. Asia/Riyadh is UTC+03:00 and Saudi Arabia does not observe daylight saving time; Asia/Kolkata is UTC+05:30, also without daylight saving. That makes DST defects invisible in local testing and real for users in Europe or North America. Store instants in UTC or with an explicit offset (ISO 8601), convert to the user’s zone only for display, and test a half-hour offset, a zone that observes DST, and times close to midnight, where the calendar day shifts.
- Each date field states its calendar; choosing a date in a Hijri picker stores the same value as choosing the equivalent Gregorian date.
- Month and year ends: Umm al-Qura months have 29 or 30 days; test the last day of a month and of a year in both calendars.
- Midnight shift: an order placed at 01:30 Riyadh time falls on the previous day in UTC; a report grouped by UTC day shows it on the wrong date.
- Week and weekend: the first day of the week varies by locale, and the weekend in Saudi Arabia is Friday and Saturday; calendars, schedules and business-day calculations must follow the product’s rule, not a hard-coded one.
One instant: 2026-09-28T09:05:00Z
(example output from Node.js 22 with ICU 78 / CLDR 48; other runtimes may differ)
ar-SA ٢٨ سبتمبر ٢٠٢٦
ar-SA-u-nu-latn 28 سبتمبر 2026
ar-SA-u-ca-islamic-umalqura ١٧ ربيع الآخر ١٤٤٨ هـ
hi-IN 28 सितंबर 2026
en-US September 28, 2026
Time in Asia/Riyadh (UTC+03:00) 12:05
Time in Asia/Kolkata (UTC+05:30) 14:35Try it: Date Test Data Generator Try it: Timezone Converter Try it: Timestamp Converter Try it: ISO 8601 Converter
Numbers, digits and currencies
Arabic text can use Western digits (0–9) or Arabic-Indic digits (٠١٢٣٤٥٦٧٨٩), and with Arabic-Indic digits the separators change too: the Arabic decimal separator ٫ (U+066B) and the Arabic thousands separator ٬ (U+066C). Hindi text normally uses Western digits, although Devanagari digits (०–९) exist and can be requested. India groups large numbers differently — 12,34,567, the lakh (1,00,000) and crore (1,00,00,000) system — in both en-IN and hi-IN. Decide per locale which digits and grouping the product uses, then test display and input: a user may type Arabic-Indic digits into an amount or phone field, and the product must either accept and normalise them or reject them with a clear message.
Currencies are identified by ISO 4217 codes — SAR for the Saudi riyal and INR for the Indian rupee, both with two minor units. Format amounts with the locale, not with string templates: the symbol, its position and the spacing vary by locale. The Saudi Central Bank introduced a dedicated riyal symbol in 2025, and Unicode 17.0 encodes it as U+20C1 SAUDI RIYAL SIGN; if your design uses it, check which fonts on your target devices can render it, because a font without the glyph shows an empty box.
Locale-formatted strings contain invisible characters. Formatting an SAR amount for ar-SA produces a right-to-left mark (U+200F) at both ends and a no-break space before the currency, so a test that compares with a hand-typed string fails although the screen looks right. Compare with the output of the same formatting API, or strip invisible characters in the assertion deliberately.
- Parsing: "1.234" is one thousand two hundred and thirty-four in some locales and one point two three four in others; a value formatted for display must be parsed with the same locale.
- Phone numbers, IBANs and ID numbers are identifiers, not quantities: never group them or convert their digits for display.
1234567.5 as currency, per locale
(example output from Node.js 22 with ICU 78 / CLDR 48)
ar-SA SAR ١٬٢٣٤٬٥٦٧٫٥٠ ر.س.
(U+200F at start and end, U+00A0 before ر.س.)
ar-SA-u-nu-latn SAR 1,234,567.50 ر.س.
hi-IN INR ₹12,34,567.50
en-IN INR ₹12,34,567.50
en-US SAR SAR 1,234,567.50
1234567 with Devanagari digits (hi-IN-u-nu-deva): १२,३४,५६७Try it: Arabic Test Data Generator Try it: Hindi Test Data Generator Try it: Decimal Dataset Generator
Text, Unicode and scripts
Every layer must agree on UTF-8: the HTML meta charset and the HTTP Content-Type, form submission, the API, message queues, database columns and connections, logs, and CSV or PDF exports. When one layer reads UTF-8 bytes as a legacy single-byte encoding, the result is mojibake — "مرحبا" turns into something like "Ù…Ø±ØØ¨Ø§" — and a round trip can make the damage permanent. Question marks in place of letters usually mean characters were lost in a conversion; empty boxes usually mean the font lacks the glyph. In MySQL, the legacy utf8 (utf8mb3) character set stores Arabic and Devanagari but not supplementary (four-byte) characters, which include most emoji; utf8mb4 stores all of Unicode.
The same visible text can be stored as different code point sequences — "é" as one precomposed code point or as "e" plus a combining accent. Unicode normalization (NFC, NFD, NFKC, NFKD) makes them comparable, so search, uniqueness checks and user names must normalise consistently. Length is ambiguous too: the Hindi word "नमस्ते" is 6 code points, 6 UTF-16 units, 18 UTF-8 bytes and 3 user-perceived characters (grapheme clusters: न, म, स्ते). A limit counted in bytes, a limit counted in characters and a cursor that moves by code point will all disagree.
Devanagari joins consonants into conjuncts (क्ष, स्त) through the virama, and some vowel signs are drawn before the consonant they follow in memory — the i-sign in "कि" is stored after क but displayed to its left. Truncating, highlighting or moving the cursor inside a cluster leaves a vowel sign without its base, which rendering engines typically show with a dotted circle (◌ि). A font without full Devanagari shaping shows conjuncts as separate letters with visible viramas.
Sorting and search are locale-specific: use the platform’s collation (Intl.Collator in JavaScript, collations in the database) and test with names that begin with the different forms of alif (ا أ إ آ), with and without diacritics, and with mixed scripts. Input needs its own checks: Arabic keyboards, Hindi typed with the InScript layout or a phonetic transliteration keyboard, and input method editors that compose text over several keystrokes — validation that fires on every keystroke can interrupt a composition.
- Text expansion and contraction: a translation can be much longer or shorter than English, especially for short labels; test buttons, tabs, table headers, toasts and badges with the real strings of every locale.
- Vertical space: Devanagari has vowel signs above and below the headline and Arabic has diacritics above and below letters; fixed line heights with hidden overflow clip them.
- SMS: a message containing Arabic or Devanagari is usually sent in UCS-2, 16 bits per character; with 140 octets of user data per SMS (3GPP TS 23.040), a single message then holds 70 characters instead of 160 in the GSM 7-bit alphabet, so OTP and notification templates can split into several messages.
Try it: Unicode Dataset Generator Try it: String Length Counter Try it: Character Counter Try it: Special Character Dataset Generator
How to run localization testing
Localization testing works best as a series of gates rather than one pass at the end: the cheapest defects (i18n) are found before translation, and the most visible ones (layout and language) need the translation. A workable order, which fits the test levels described in the end-to-end quality assurance guide:
- Review requirements and designs for locale decisions: which locales, which calendar and digits per locale, which formats the user can change, the fallback language, and what is deliberately not translated (brand names, legal texts).
- Review the code for internationalization: externalised strings, locale-aware formatting APIs, no physical left/right in layout code, Unicode storage end to end.
- Run pseudo-localization in CI: every build runs in the pseudo-locale, and any plain-English string is a defect.
- Test functionally per locale with localized test data — names, addresses, phone numbers, amounts and dates in the target scripts and formats — through every layer to the database and back out into exports, emails and SMS.
- Hold a linguistic review in context with native speakers, using screenshots and string keys, against the glossary and style guide.
- Regress per release: localized smoke tests, the missing-key check and per-locale screenshot comparison on every build, and a full localization pass when a feature changes text-heavy screens.
Localization test checklist, per locale (ar-SA, hi-IN, en-US ...)
[ ] Locale is picked correctly (setting, Accept-Language, URL) and persists
[ ] Unsupported locale falls back to the agreed default, never to raw keys
[ ] No untranslated or hard-coded strings (pseudo-locale run is clean)
[ ] Plurals: counts 0, 1, 2, 3, 11, 100 give the correct Arabic forms
[ ] Dates: calendar, digits, month names, time zone, midnight shift
[ ] Numbers: digits, decimal and grouping separators, lakh/crore in hi-IN
[ ] Currency: ISO 4217 code, symbol, position, minor units, input parsing
[ ] Text: Arabic and Devanagari intact through DB, API, CSV, PDF, email, SMS
[ ] Length limits enforced in the unit the backend uses (chars vs bytes)
[ ] Sorting and search work for the locale (alif forms, diacritics)
[ ] Layout: no truncation, clipping or overlap with real strings
[ ] RTL: direction, mirroring, bidirectional text (see the RTL guide)
[ ] lang attribute on the page and on inline foreign-language textTry it: Multilingual Test Data Generator Try it: Arabic Test Data Generator Try it: Hindi Test Data Generator Try it: Saudi Address Test Data
Automating localization checks
Automated tests should run in more than one locale. Parameterise the suite by locale and time zone, and compute expected values with the platform’s Intl APIs instead of typing formatted strings into assertions — typed strings miss invisible marks and break when the CLDR data is updated. Compute the expected value in the same runtime that produced the actual one: a Node.js test runner and a browser can carry different ICU versions and disagree.
Three inexpensive checks catch most regressions: a missing-key check that compares every locale file with the source locale in CI, an untranslated check that flags values identical to the source (with an allow-list for brand names and codes), and a screenshot comparison per locale for key screens.
// Locale-parameterised UI check (Playwright Test)
for (const locale of ["en-US", "ar-SA", "hi-IN"]) {
test(`order total is formatted for ${locale}`, async ({ browser }) => {
const context = await browser.newContext({
locale,
timezoneId: locale === "hi-IN" ? "Asia/Kolkata" : "Asia/Riyadh",
});
const page = await context.newPage();
await page.goto("/orders/ORD-2026-0042");
// expected value computed with Intl in the same browser, not typed by hand
const expected = await page.evaluate(
(l) => new Intl.NumberFormat(l, { style: "currency", currency: "SAR" }).format(1500),
locale,
);
await expect(page.getByTestId("order-total")).toHaveText(expected);
await expect(page).toHaveScreenshot(`order-${locale}.png`);
});
}
// Missing and untranslated keys (Node.js, runs in CI)
const flat = (o, p = "") => Object.entries(o).flatMap(([k, v]) =>
v && typeof v === "object" ? flat(v, p + k + ".") : [[p + k, v]]);
const en = new Map(flat(require("./locales/en.json")));
const ar = new Map(flat(require("./locales/ar.json")));
const missing = [...en.keys()].filter((k) => !ar.has(k));
const same = [...en].filter(([k, v]) => ar.get(k) === v);
if (missing.length) { console.error("Missing in ar:", missing); process.exit(1); }
if (same.length) console.warn("Identical to en, check:", same.map(([k]) => k));Typical localization defects, written as bug titles
The examples below are illustrative, not real incidents. Each follows the bug-report pattern of symptom, place and condition; the how to write a bug report guide covers the rest of the report.
- Cart badge shows "2 منتج" instead of "منتجان" — plural chosen with a count === 1 check
- Order confirmation email arrives in English for users who selected Arabic in the app
- Hijri date picker stores the previous day when the date is chosen between 00:00 and 03:00 Riyadh time
- Amount field rejects "١٥٠٠" typed on an Arabic keyboard with "Invalid number"
- hi-IN sales report shows 1,234,567 instead of 12,34,567
- Hindi product name truncated inside a conjunct, leaving a dotted-circle vowel sign in search results
- Validation message "Field is required" not translated on the Arabic registration form
Frequently asked questions
What is the difference between internationalization testing and localization testing?
Internationalization testing checks that the product can support any locale — externalised strings, locale-aware formatting, Unicode everywhere, no fixed text direction — and is best done with pseudo-localization before translation. Localization testing checks one specific locale: its translation in context, formats, layout and cultural fit.
Does localization testing need a native speaker?
For language quality, yes: terminology, grammar, register and naturalness need a qualified native speaker. Most other checks — formats, truncation, encoding, plurals, missing strings, layout — can be done by any tester with the right test data and a checklist.
What is pseudo-localization?
An automatic transformation of the source strings into accented, expanded and bracketed text (and optionally right-to-left text) used as a fake locale. It reveals hard-coded strings, concatenation, truncation and encoding problems before any real translation exists.
Which calendar should software for Saudi Arabia use?
That is a product decision to be written into the requirements. Saudi Arabia standardises its Hijri calendar as Umm al-Qura, and the Gregorian calendar is also widely used, so many products show both or let the user choose. Whatever is chosen, request the calendar explicitly in code and test it on every runtime, because locale defaults change between CLDR versions.
How many plural forms does Arabic have?
The Unicode CLDR plural rules define six cardinal categories for Arabic: zero, one, two, few, many and other. Test counts such as 0, 1, 2, 3, 11 and 100 to hit all of them. Hindi has two categories, one (used for 0 and 1) and other.
References
- Internationalization (i18n) ActivityW3C's internationalization articles, tutorials and checks for building and testing multilingual web content.
- Unicode CLDR ProjectThe Common Locale Data Repository: the locale data for date, time, number and currency formats and plural rules that most platforms use.
- The Unicode StandardThe universal character set and its encoding forms (UTF-8, UTF-16, UTF-32), normalization and the properties of every code point.
- 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.
- Intl — MDNThe Intl namespace in practice: DateTimeFormat, NumberFormat, Collator, PluralRules and how locale negotiation works.
- Time Zone DatabaseThe tz database of the world's time zones and their historical offset and daylight-saving rules, updated several times a year.
- ISO 4217 — Currency codesThe three-letter currency codes (SAR, INR, USD…) and their minor units — the reference for currency fields and formatting tests.
- 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.
- Indic Layout RequirementsThe layout requirements of Indic scripts such as Devanagari — conjuncts, vowel signs, line breaking — relevant to Hindi interfaces (a W3C Working Draft).
Practice exercise
Judge the findings of a first localization pass
You test a grocery delivery service (fictional) with an Android app and a website in two markets: Saudi Arabia, in ar-SA and English with prices in SAR, and India, in hi-IN and English with prices in INR. Release 2.0 adds Arabic and Hindi to a product that was English only. The material gives the locale decisions from the requirements, an excerpt of the resource files and ten observations from the first localization pass on staging, including a pseudo-locale run.
Work on paper with the guide’s map of localization testing and its checklist. The locale decisions are your test basis: judge each observation against them, and against the guide where they say nothing. The String Length Counter and the Character Counter answer two of the questions, and the Timezone Converter helps with a third. Allow 25–30 minutes.
Grocery delivery service (fictional) - Android app and website, two markets
Saudi market locales ar-SA (default) and en SAR zone Asia/Riyadh (UTC+03:00)
India market locales hi-IN (default) and en INR zone Asia/Kolkata (UTC+05:30)
Release 2.0 adds Arabic and Hindi; until now the service was English only.
Today: Mon 12 Oct 2026. Staging, build 2.0.0-rc1, first localization pass.
Locale decisions (requirements)
LOC-1 ar-SA: Western digits (0-9) and Gregorian dates on every screen. The order
confirmation also shows the Umm al-Qura Hijri date.
LOC-2 India market: amounts in INR with Indian grouping (12,34,567), in hi-IN and en.
LOC-3 Amount, phone and code fields accept Arabic-Indic digits and convert them.
LOC-4 Emails, SMS and push notifications use the language chosen in the app.
LOC-5 Product name: at most 40 characters as the user sees them.
LOC-6 Daily sales report: orders grouped by the local calendar day of the market.
Umm al-Qura reference: 1 Rabi al-Akhir 1448 = Sat 12 Sep 2026,
1 Jumada al-Ula 1448 = Mon 12 Oct 2026.
Resource files (excerpt; delivery codes have 6 digits)
cart.items
en {count, plural, one {# item in your cart} other {# items in your cart}}
ar {count, plural,
one {منتج واحد في السلة}
other {# منتج في السلة}}
hi {count, plural, one {कार्ट में # आइटम है} other {कार्ट में # आइटम हैं}}
slot.notice
en Delivery to {area} between {from} and {to}
ar التوصيل إلى {المنطقة} بين {from} و{to}
wallet.invalidAmount
en Invalid number
ar (no entry)
sms.deliveryCode
en Your delivery code is {code}. Share it only with the courier at your door.
hi आपका डिलीवरी कोड {code} है। यह कोड केवल डिलीवरी पार्टनर को बताएँ, किसी और को नहीं।
Observations (staging, build 2.0.0-rc1)
O1 ar-SA, cart: 0 items -> "0 منتج في السلة", 2 -> "2 منتج في السلة",
3 -> "3 منتج في السلة", 11 -> "11 منتج في السلة".
O2 ar-SA, delivery slot screen: "التوصيل إلى {المنطقة} بين 18:00 و20:00".
O3 Pseudo-locale run, Android English (XA: accents, padding words, brackets):
checkout button Place order
order summary [Ýöû šåVé one two] 15.00 SAR [öñ ţĥîš öŕðéŕ one two]
4th tab of the tab bar [Öŕðéŕ ĥîšţ
O4 ar-SA, wallet top-up: "١٥٠" typed on an Arabic keyboard is rejected with
"Invalid number" (in English); "150" is accepted.
O5 India market, app language Hindi, phone language English: the delivery-code
SMS arrives in English.
O6 Admin web, Hindi UI: the product name "बासमती सेला चावल" is rejected with
"नाम 40 अक्षरों से लंबा है" ("the name is longer than 40 characters").
O7 Saudi daily sales report for Tue 6 Oct 2026 lists order ORD-48213. The customer
placed it on Wed 7 Oct at 01:30 Riyadh time; created_at 2026-10-06T22:30:00Z.
O8 ar-SA, confirmation of an order placed on Sun 11 Oct 2026:
"الأحد، ١١ أكتوبر ٢٠٢٦ (٢٨ ربيع الآخر ١٤٤٨ هـ)"
O9 India market, monthly sales report total: hi-IN "₹1,234,567.50",
en "₹12,34,567.50". Ravi (tester) has logged the en value as "wrong grouping".
O10 Saudi market, courier delivery slip (PDF): the customer name "نورة القحطاني"
prints as "???? ????????". The admin web shows the same name correctly.- For each observation decide: is it a defect (name the locale decision or the guide’s rule), is it an internationalization (i18n) or a localization (l10n) defect, and which area of the guide’s map does it belong to? Some observations hold more than one finding, and one holds a report that is not a defect.
- For O1, write the corrected Arabic message and its plural test table: each count, the CLDR category it hits and the expected text. Then list the counts the Hindi table needs.
- Measure: the product name in O6 with the String Length Counter (why was it rejected?), and the Hindi SMS of O5 with a 6-digit code with the Character Counter (what does its length mean?).
- Work out the expected values for O7 and O8: the report day of ORD-48213, the hours affected in each market, and the Umm al-Qura date of 11 Oct 2026.
- Write a bug title for every defect (symptom, place, condition), then name the cheapest earlier check that would have caught each group of defects.
Show the model answer
Test basis and overall result. LOC-1 to LOC-6 are the test basis; where they say nothing, the guide’s rules apply. The ten observations hold 14 defects, one report that is not a defect (the en grouping in O9) and one open question for the product owner (the SMS length in O5). Eleven defects are internationalization (i18n) defects: they live in code — a hard-coded string, a concatenation, or a parsing, formatting or conversion step that ignores the locale — and a developer fixes them. Three are localization (l10n) defects: they live in one locale’s resources (O1, O2 and the missing Arabic message in O4), and the translator fixes them.
O1 — defect, l10n, language and translation quality (plurals). The message format is right: en and hi use ICU plural categories, and ICU picks the CLDR category for each count. But the Arabic resource provides only one and other, so every count except 1 falls back to other: 2 needs the dual "منتجان", 3 the plural "منتجات", 11 the form "منتجًا", and 0 its own form. Fix ar.json with the six categories from the guide: {count, plural, zero {السلة فارغة} one {منتج واحد في السلة} two {منتجان في السلة} few {# منتجات في السلة} many {# منتجًا في السلة} other {# منتج في السلة}}. Title: Arabic cart message shows "2 منتج في السلة" instead of "منتجان في السلة" — ar cart.items has only the one and other forms.
Plural test table for ar-SA (count, CLDR category, expected text): 0, zero, "السلة فارغة"; 1, one, "منتج واحد في السلة"; 2, two, "منتجان في السلة"; 3, 10 and 103, few, "3 منتجات في السلة" (each with its own number); 11 and 99, many, "11 منتجًا في السلة"; 100, 101 and 102, other, "100 منتج في السلة". The Hindi table needs both of its categories: 0 and 1 hit one ("कार्ट में 1 आइटम है"), and two counts such as 2 and 5 hit other ("कार्ट में 2 आइटम हैं"). In Hindi 0 takes the one form, so ask the translator whether "कार्ट में 0 आइटम है" reads well, or add an exact case such as =0 {कार्ट ख़ाली है}.
O2 — defect, l10n, language and translation quality (message parameters). The translator translated the parameter name {area} into {المنطقة}. The app passes a value named area, so the formatter finds no value for {المنطقة} and prints the braces. Fix ar.json so that the message keeps {area} unchanged. Then look for the same problem everywhere: a check that every translation contains exactly the placeholders of the English source finds all such messages at once. Title: Delivery slot notice shows raw "{المنطقة}" instead of the area name (ar-SA) — parameter name translated in slot.notice.
O3 — three i18n defects, found before anyone reads a translation, exactly as the guide’s pseudo-localization table predicts: two in language and translation quality (a hard-coded string, a concatenated sentence) and one in layout (text expansion). "Place order" in plain English is hard-coded: it never went through the resource files and will stay English in Arabic and Hindi. The order summary shows two bracketed pieces around the amount: the sentence is concatenated from "You save", the amount and "on this order"; it needs one message with a parameter, such as You save {amount} on this order, with the amount formatted for the locale. The fourth tab has lost its closing bracket: the label is truncated, so there is no room for longer translations; check the tab bar with the real Arabic and Hindi labels as well. Titles: "Place order" button text is hard-coded, not in the resource files; "You save … on this order" line in the order summary is built by concatenation; fourth tab label (Order history) is truncated in the pseudo-locale.
O4 — two defects. The first is i18n, in locale formats (numbers and digits): LOC-3 says Arabic-Indic digits are accepted and converted, and the field rejects them. Test the phone and code fields too, which LOC-3 also covers, and ask the product owner whether the Arabic decimal separator is accepted (١٥٠٫٥٠). The second is l10n, in language and translation quality (an untranslated string): wallet.invalidAmount exists in en.json but not in ar.json, so the app falls back to English. A missing-key check finds every key like it: the guide’s script fails the CI build on them, and JSON Diff between en.json and ar.json shows them as removed keys (every translated value shows as changed). Titles: Wallet top-up amount field rejects "١٥٠" typed in Arabic-Indic digits (ar-SA); Validation message "Invalid number" not translated on the ar-SA wallet top-up screen.
O5 — defect, i18n, locale selection: LOC-4 requires the language chosen in the app, but the SMS follows the phone language. The Hindi template exists, so this is not a missing translation: the SMS service does not receive the user’s app language. Check email and push notifications for the same user, and Arabic users with an English phone. Then measure the Hindi SMS with a 6-digit code in the Character Counter: the SMS (Unicode) bar shows 82 / 70, over by 12, because it counts UTF-16 units (the Characters figure is lower: it counts grapheme clusters). Devanagari text is usually sent in UCS-2, where one message holds 70 characters (UTF-16 units), so this SMS goes out as two messages; the English text, 74 characters in the GSM 7-bit alphabet (74 / 160), fits in one. No requirement sets a limit, so this is not a failure: raise it as a question for the product owner, because providers usually count every part as a message, and offer a Hindi wording of at most 70 characters. Title: Delivery-code SMS sent in English to users whose app language is Hindi (phone language English).
O6 — defect, i18n, text and script handling (length). The String Length Counter gives 11 grapheme clusters, 16 code points, 16 UTF-16 units and 44 UTF-8 bytes. Only the byte count is above 40, so the check counts UTF-8 bytes, while LOC-5 counts characters as the user sees them, that is grapheme clusters: the name must be accepted. Add boundary tests with Devanagari names of 40 and 41 grapheme clusters, and ask which unit and size the database column uses, because a Hindi name of 40 clusters can have many more code points, and up to three UTF-8 bytes for each of them. Title: Admin product form rejects an 11-character Hindi product name as "longer than 40 characters" — the limit is counted in UTF-8 bytes.
O7 — defect, i18n, locale formats (times and time zones). 2026-10-06T22:30:00Z is 01:30 on Wed 7 Oct in Riyadh (UTC+03:00), as the Timezone Converter confirms. The report groups orders by UTC date, while LOC-6 requires the local day of the market, so ORD-48213 belongs in the 7 Oct report. Affected are all orders placed between 00:00 and 02:59 Riyadh time and, in the India market, between 00:00 and 05:29 Kolkata time (UTC+05:30). Test data at the edges: 02:59 and 03:00 in Riyadh, 05:29 and 05:30 in Kolkata, and 23:59 and 00:00 local time in both; compare each day’s total with the sum of that day’s orders. Title: Saudi daily sales report lists orders placed between 00:00 and 02:59 Riyadh time under the previous day — orders grouped by UTC date.
O8 — two i18n defects in locale formats (dates and calendars). First, the digits are Arabic-Indic although LOC-1 requires Western digits. The code most likely asks for plain ar-SA, whose default in recent CLDR data is Arabic-Indic digits; it has to request the numbering system explicitly (ar-SA-u-nu-latn, and ar-SA-u-ca-islamic-umalqura-nu-latn for the Hijri date) and be tested on every runtime that formats dates: the server for emails and PDFs, Android and the browsers. Second, the Hijri date is wrong. 1 Rabi al-Akhir 1448 is 12 Sep and 1 Jumada al-Ula is 12 Oct, so 11 Oct is the last day of Rabi al-Akhir: day 30, 29 days after 12 Sep. 28 is what the tabular civil Hijri calculation (the ICU calendar islamic-civil) gives for that day, so the code uses a calculation other than Umm al-Qura. Expected: "الأحد، 11 أكتوبر 2026 (30 ربيع الآخر 1448 هـ)". Add month ends and a year end in both calendars to the test data. Titles: ar-SA order confirmation shows dates in Arabic-Indic digits instead of Western digits; Order confirmation shows the Hijri date 28 Rabi al-Akhir 1448 for 11 Oct 2026 instead of the Umm al-Qura date, 30 Rabi al-Akhir 1448.
O9 — one defect and one invalid report. Under LOC-2 hi-IN must show ₹12,34,567.50; ₹1,234,567.50 is Western grouping, so the report formats hi-IN amounts with another locale or a fixed pattern (i18n, locale formats). The en value, ₹12,34,567.50, is exactly what LOC-2 requires for the India market: reject Ravi’s report as working as specified and quote LOC-2. Title: hi-IN monthly sales report shows ₹1,234,567.50 instead of ₹12,34,567.50.
O10 — defect, i18n, text and script handling (encoding). Question marks mean the characters were lost in a conversion to an encoding that has no Arabic letters; a font without the glyphs would show empty boxes, and UTF-8 read as a single-byte encoding would show Latin garbage such as "Ø". One question mark per letter (4 and 8) fits that loss. The admin web shows the name correctly, so the stored value is most likely intact and the loss happens on the way to the PDF, in the slip service or its PDF library. Test Hindi names on the India market slip and a name that mixes scripts as well. Title: Courier delivery slip (PDF) prints Arabic customer names as question marks.
The cheapest earlier checks. Pseudo-localization in CI would have caught all of O3 on the first build, before any translation. Automated checks on the resource files would have caught the l10n defects on the day the translations arrived: missing keys (O4), placeholders that differ from the source (O2) and plural messages without all the CLDR categories of the target language (O1). The other i18n defects (the digit parsing in O4, and O5 to O10) are locale-unaware code: a code review for internationalization finds them, and so do functional tests per locale with localized test data — Arabic-Indic digits, Devanagari names, times close to midnight, an Umm al-Qura month end, large INR amounts, Arabic and Hindi names carried through to the PDF and the SMS — before any manual pass; keep those tests as regression tests. The SMS length belongs in requirements review: a maximum length per template and language.
If your answers differ, check them against two rules from the guide: an i18n defect lives in code and a developer fixes it, an l10n defect lives in one locale’s content and is fixed in that resource; and an observation is a defect only when it contradicts the locale decisions or a rule the guide states. Calling O8 or O9 l10n because they appear in one locale is acceptable if your report puts the fix in the code; reporting the en grouping in O9 or the SMS length in O5 as a failure is not.
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.
Part of the QA Roadmap
Before this topic
Next topics
Tools mentioned in this guide
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.
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.
Generate date datasets: random dates in a range, leap days, month ends, DST edges and multiple formats.
Convert a date and time between timezones (Riyadh, Amman, Delhi, UTC, New York, London…).
Measure string length in characters, code points, UTF-8 bytes and UTF-16 units.
Generate Saudi National Address–style test data: building number, street, district, city, postal code and additional number.
Generate inputs full of special characters: quotes, SQL/HTML/JSON metacharacters, control chars, emoji and path tricks.
Compare two JSON documents semantically and highlight added, removed and changed values.