Lesson 01·Unit 1 · Getting oriented·What localization work is made of·Lv 101·~6 min
Orientation: i18n, l10n, and the standards stack
One order confirmation (a count, a date, a price) rendered in four locales by the browser's own Intl APIs. Four layers did the work: Unicode supplied the characters, CLDR the locale data, the ICU/Intl engine applied it, and a BCP 47 tag named what was asked for. This lesson puts each layer in place before the rest of the course uses them.
By the end
- ·Separate i18n work (one codebase that can render any locale) from l10n work (the content for one market), and budget them apart.
- ·Name the four layers behind any formatted string, and who maintains each one.
- ·Send a locale bug to the right layer: bad data, broken engine, character properties, or the wrong tag.
- ·Read a tag like pt-BR the way the runtime does: a language, then a region.
The problem
Most teams defer this with one sentence: “we’ll localize later.” By the time later arrives, the codebase has hardcoded the assumptions English allowed: dates concatenated field by field, an s appended when count !== 1, layouts that only work left-to-right, prices formatted with toFixed(2). None of these are translation problems, so translators can’t fix them; they are design problems, and they are expensive precisely because they were cheap to avoid. The other cost of skipping orientation is vocabulary: a team that can’t distinguish i18n work from l10n work can’t scope, staff, or schedule either one.
How it works
Four words, two jobs
The field compresses its own vocabulary into numeronyms: take the first letter, count the letters in between, keep the last. "Internationalization" is i, then 18 letters, then n: i18n. "Localization" is l10n, "globalization" is g11n, "translation" is t9n. The abbreviations look interchangeable, so teams use the words interchangeably. They name different jobs.
Internationalization is engineering, done once. You design the product so a team can adapt it later: no hardcoded date order, no count !== 1 plural logic, no layout that only works left to right. Localization is production, done per market: you write the German strings, choose the Egyptian date conventions, and adapt imagery and legal text. Translation is the part of localization that converts text, so t9n is the narrowest of the four terms. Globalization (g11n, and you will also see GILT) is the business umbrella term for all of it.
The split makes the work schedulable. i18n is a fixed cost that lives in the codebase. l10n recurs with every market you add. A team that mixes them budgets neither: it over-scopes translation ("we need engineers for the French launch") or under-scopes engineering ("the translators will handle the dates"). Translators cannot fix a hardcoded date order. That bug appeared at design time, when the fix was cheap.
i18n = i + 18 letters + n internationalization
l10n = l + 10 letters + n localization
g11n = g + 11 letters + n globalization
t9n = t + 9 letters + n translationHow it works
The stack: who owns what
Every formatted string in the demo above passed through four layers, each with a different owner. At the bottom, the Unicode Consortium defines the characters: every character gets a code point and properties, including the bidirectional class that makes Arabic run right to left. The same consortium maintains CLDR, the Common Locale Data Repository. That is the data layer: month names per language, preferred digit shapes, plural rules, currency symbol placement, separators. Unicode owns *what the characters are*. CLDR records *how each locale uses them*.
Data needs an engine. ICU (International Components for Unicode) is the C/C++ and Java library that applies CLDR data, and it is the industry's reference implementation. In a browser you reach that machinery as Intl.*, an API that ECMA-402 standardizes. The locale data ships inside the JavaScript engine. That is why new Intl.PluralRules('ru') knows Russian's four cardinal shapes without a download: the rules shipped inside the browser, the same way they ship inside your phone's OS.
The top layer does no formatting at all. BCP 47 (an IETF standard, currently RFC 5646 for the tags and RFC 4647 for matching them) defines the identifier you pass in: pt-BR is the language subtag pt plus the region subtag BR. It is the lookup key for every other layer, and many bugs live there: ask for the wrong tag, and three working layers give you the wrong answer. The design keeps the name of the request separate from the data and the engine. That is what lets one codebase serve any locale through a change to a single string.
"ar-EG" BCP 47: names the request
plural rules, digits, month names CLDR: the data for that locale
Intl.NumberFormat / PluralRules ICU / Intl (ECMA-402): applies it
٣ عناصر Unicode: the characters on screenSee it yourself
Try these, in order
Each step triggers a specific failure you should recognize on sight.
- 1Start on the ar-EG tab (selected when the page loads) and read the whole sentence. The digits are Arabic-Indic (٣, ٢٠٢٦), the month is أغسطس, and the currency amount reads right to left. Nobody authored any of it by hand. The count, date, and price all came out of the stack below.
- 2Under “Who did what”, select CLDR. The count, date, and price light up while the surrounding words dim. The dimmed words are translation: a person wrote them (l10n). The highlighted parts are locale data.
- 3Under Count, click 3, then 11. In “How the count resolved”, ar-EG moves from category few to many. The Arabic noun changes form (عناصر → عنصرًا), because 11 takes a singular-form noun in Arabic. Your code never branched. A CLDR rule decided.
- 4Switch to the ja-JP tab and click every Count button, 1 through 100. The category never leaves other. Japanese has one cardinal category, so a single arm covers every count. The number of arms is a property of the language, not of your UI.
- 5Select BCP 47. The highlight leaves the sentence entirely and lands on the active tab. The tag formats nothing: ja is a language subtag, JP a region subtag, and together they are the lookup key for the other three layers.
- 6Select Unicode, then flip between the en-US and ar-EG tabs. The static text highlights, and the direction note under the sentence changes: the Arabic characters carry a right-to-left bidirectional property. That is why ar-EG needs dir="rtl" and en-US does not.
One sentence, four locales
Same order: a count, a ship date, a price in USD. Only the locale tag changes.
طلبك المكوّن من ٣ عناصر يُشحن في ٥ أغسطس ٢٠٢٦ مقابل ٤٢٫٥٠ US$.
Arabic (Egypt) · dir="rtl"
Count
How the count resolved
Intl.PluralRules("ar-EG").select(3) → "few"
ar-EG has 6 cardinal categories (a zero category also exists: 0 resolves to it). The arms are translations. Which arm renders is a CLDR rule, applied by the engine.
Who did what · select a layer
Each layer highlights the part of the sentence it is responsible for.
Four layers cooperated to render the sentence above. Select one to see its share of the work.
Inspect any BCP 47 tag
A tag is a lookup key: language, then optional script, region, variants, and a -u- extension. Type one and watch it canonicalize, split, and expand through CLDR likely-subtags.
Language
zhChinese
Script
HantTraditional
Region
HKHong Kong SAR China
Maximize (add likely subtags)
zh-Hant-HK
Minimize (drop the redundant)
zh-HK
Maximize adds the script and region CLDR considers most likely (so en becomes en-Latn-US). Minimize strips whatever is implied. Negotiation uses these to match zh-HK against a zh-Hant bundle.
If you remember one thing
Four layers render every localized string: Unicode supplies the characters, CLDR the locale data, the ICU/Intl engine applies it, and a BCP 47 tag names the request. Route every locale bug to its layer.
What to do about it
- Learn the four layers before touching the APIs. Unicode (characters and their properties), CLDR (the locale data), ICU / Intl (the engine that applies it), BCP 47 (the tag that names the request). Every later lesson is one of these layers up close.
- Route every bug to its layer. A wrong month name is data (CLDR); a crash or missing API is engine (Intl); text running the wrong direction is characters (Unicode); the wrong language entirely is usually the tag (BCP 47) or how you negotiated it.
- Split the work by the two words: i18n is engineering that makes one codebase capable of rendering any locale; l10n is producing the content for one market. Budget them separately; one is a fixed cost, the other recurs per market.
- Default to the platform. The browser already ships this entire stack; most of the failures in this course come from reimplementing some layer of it by hand.
Use this with
Stakeholders
Moments
- ·Onboarding anyone who will touch international work
- ·Scoping a first-market launch: splitting i18n from l10n line items
- ·Triaging a locale bug to the layer that owns it
Field note
The W3C internationalization FAQ exists because teams keep asking whether i18n and l10n are the same thing. Its definitions draw the line this course uses: internationalization is design that enables localization, and localization is the adaptation itself. It is blunt about the cost of deferring the first: a retrofit of a single-culture product for a global market is “much more difficult and time-consuming” than designing for it. It illustrates the point with the Y2K effort to undo two-digit year fields.
Quick check
3 questions · pass at 2+
Question 1/3
A Russian UI shows “22 файла” using the few plural form. Which layer holds the rule that put 22 in few?
Question 2/3
In the tag pt-BR, what do the two subtags name?
Question 3/3
Your team ships message-formatting code that can select among plural arms at runtime, and hires translators to write the Russian strings. Which job is which?