JavaScript Intl Table
| Constructor | What it formats | Field note |
|---|---|---|
Intl.NumberFormat(locale, opts) | Numbers, currency, units | 1,234.56 in en-US is 1.234,56 in de-DE - grouping is locale law |
Intl.DateTimeFormat(locale, opts) | Dates and times | dateStyle/timeStyle presets; calendar choice follows locale |
Intl.RelativeTimeFormat | "3 days ago" | numeric: auto drops the "in 0 days" awkwardness |
Intl.ListFormat | Natural lists | A, B, and C - conjunction per language, no hand-built join |
Intl.Collator | Locale-aware sorting | sort() with a collator - accents and case sort correctly |
Intl.PluralRules | Select the plural form | One/few/many is locale grammar - never hardcode s |
Intl.DisplayNames | Region/language names | region code 'DE' becomes Deutschland/Germany per locale |
new Intl.Locale(s) | Parse and extend tags | languageScript-REGION-u-nu-... - BCP 47 as an object |
Intl is the internationalization engine built into the platform: every constructor takes a locale (de-DE, ar-EG, zh-CN) and renders by that locale's LAW - grouping separators, calendar names, plural categories, sort order. The data ships with the browser; no library, no CDN.
Bottom line: hand-rolled formatting dies at the grammar wall - Russian plurals have one/few/many forms, German groups thousands with dots, Japanese lists join with different particles. Intl.PluralRules, NumberFormat, DateTimeFormat and ListFormat exist because string templates cannot express any of it. Construct formatters once and reuse: they parse locale data eagerly.
The honest part: Intl output varies across engines and versions by design - it renders the platform's CLDR data, which updates. Snapshot tests on exact formatted strings are brittle; test the shape (a currency symbol present, the right plural category chosen), not the exact glyphs.
How to use
- Format money correctly: new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(1234.5) - symbol placement and separators follow the locale, not your string concat.
- Sort like a human: users.sort(new Intl.Collator('sv').compare) - Swedish sorts Å after Z; the default sort() is codepoint order and gets every accented alphabet wrong.
- Build natural lists: new Intl.ListFormat('en', { type: 'conjunction' }).format(names) - 'A, B, and C' with the right comma-and grammar per locale.
Frequently asked questions
Why can't I just add an 's' for plurals in my UI strings?
Because plural is grammar, not suffix arithmetic. English has singular/plural; Russian has one/few/many (2 дня, 5 дней, 21 день); Arabic has six categories; Chinese does not inflect at all. The mapping from number to category is locale data - which is exactly what Intl.PluralRules.select(n) returns ('one', 'few', 'many', 'other'). The production pattern: translation files carry one string per CATEGORY (keyed by those names), the code asks PluralRules for the category, and the right string follows in every language. Any hand-rolled n === 1 check is English-only by accident.
What does locale 'law' actually decide in NumberFormat and DateTimeFormat?
Everything visual: de-DE renders 1.234,56 (dot thousands, comma decimal) while en-US renders 1,234.56 - same number, opposite conventions, both correct. Currency style moves the symbol (€ before in Germany, after in many locales, $ before with its own spacing rules in en-US); DateTimeFormat picks MDY versus DMY, 12 versus 24-hour clocks, month names and first-week rules. This is why formatted strings must never be parsed back - they are display artifacts. The rule: store data as raw values (or ISO), format at the edge, and never concatenate your own separators.
Why does array.sort() mangle accented names, and how does Collator fix it?
The default sort compares UTF-16 code units - so É (U+00C9) sorts after z (U+007A), putting every accented name at the end of the list regardless of alphabet. Real dictionaries sort by language rules: Swedish treats å/ä/ö as letters AFTER z; German phone books expand ä to ae. Intl.Collator encodes those rule tables: new Intl.Collator('sv').compare is a ready-made comparator for sort(), with options for case ordering, numeric sorting (file2 after file10) and sensitivity (ignore accents when matching). It is also fast - collators cache their rule tables, so construct once and reuse.
When do RelativeTimeFormat, ListFormat and DisplayNames earn a library replacement?
Almost never in modern engines. RelativeTimeFormat produces '3 days ago' / 'in 3 days' with correct grammar; ListFormat joins with locale-appropriate conjunctions (English's Oxford comma, others without); DisplayNames turns region and language codes into properly capitalized names in any locale (translation dropdowns without shipping name tables). These used to require date-fns, i18next helpers or hand-maintained data - the platform versions are sufficient for display purposes and maintained by the engine's CLDR updates. Reach for a library when you need message management (ICU MessageFormat bundles, extraction pipelines) - not for any single format() call.