The JavaScript Internationalization API (Intl)
Summary
Internationalization (i18n) in JavaScript historically relied on massive external libraries like Moment.js or date-fns. The Intl API (natively available across all modern browsers) solves this by providing number formatting, date manipulation, string sorting, and segmentation directly within the JavaScript engine.
This article explores the core constructors of the Intl namespace, teaching you how to adapt your applications to multiple cultures with zero dependencies.
1. The Global Intl Object
The Intl global object acts as a namespace (similar to Math). It cannot be instantiated with new. Instead, it hosts several specialized constructors.
Almost all of these constructors accept two parameters:
locales: a string with the BCP 47 language tag (e.g.,"es-US","en-GB") or an array of them. If omitted, it uses the user’s system locale.options: a configuration object to fine-tune the constructor’s behavior.
2. Number and Currency Formatting (Intl.NumberFormat)
The Intl.NumberFormat constructor formats numbers based on regional conventions (thousand separators, decimals, etc.).
2.1 Currencies and Percentages
By setting the style property, we can format financials or units without manually concatenating strings:
const amount = 123456.789;
// Spanish format (Spain) - uses commas for decimals
const formatES = new Intl.NumberFormat('es-ES');
console.log(formatES.format(amount)); // "123.456,789"
// Currency format (Euros)
const formatEuro = new Intl.NumberFormat('es-ES', {
style: 'currency',
currency: 'EUR'
});
console.log(formatEuro.format(amount)); // "123.456,79 €"
// US Accounting format (negative numbers in parentheses)
const formatAccounting = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencySign: 'accounting'
});
console.log(formatAccounting.format(-amount)); // "($123,456.79)"
2.2 Physical Units
We can natively format standardized physical units (kilometers, liters, bytes):
const speed = new Intl.NumberFormat('es-MX', {
style: 'unit',
unit: 'kilometer-per-hour',
unitDisplay: 'long'
});
console.log(speed.format(120)); // "120 kilómetros por hora"
3. Pluralization (Intl.PluralRules)
Different languages have highly complex grammatical pluralization rules. Intl.PluralRules doesn’t return a formatted string, but instead classifies a number into grammatical categories ("zero", "one", "two", "few", "many", "other"), which you can use as keys for your translation system.
const arabicRules = new Intl.PluralRules('ar-EG');
console.log(arabicRules.select(0)); // "zero"
console.log(arabicRules.select(1)); // "one"
console.log(arabicRules.select(2)); // "two"
console.log(arabicRules.select(6)); // "few"
// Using ordinals in English (1st, 2nd, 3rd)
const ordinalRulesEN = new Intl.PluralRules('en-US', { type: 'ordinal' });
console.log(ordinalRulesEN.select(1)); // "one" (resolving to "st")
console.log(ordinalRulesEN.select(2)); // "two" (resolving to "nd")
console.log(ordinalRulesEN.select(3)); // "few" (resolving to "rd")
console.log(ordinalRulesEN.select(4)); // "other" (resolving to "th")
4. Time Manipulation
Time is comprehensively managed across three pillars within the Intl API:
4.1 Absolute Formatters: Intl.DateTimeFormat
Converts dates into human-readable text according to regional time zones and calendars. It supports macros like dateStyle and timeStyle to simplify options parameters.
const date = new Date(Date.UTC(2026, 0, 10, 10, 0, 0));
const fullFormat = new Intl.DateTimeFormat('es-ES', {
dateStyle: 'full',
timeStyle: 'long',
timeZone: 'Europe/Madrid'
});
console.log(fullFormat.format(date));
// "sábado, 10 de enero de 2026, 11:00:00 CET"
// Supports ranges out of the box without temporal overlaps
const endDate = new Date(Date.UTC(2026, 0, 12, 10, 0, 0));
const rangeFormat = new Intl.DateTimeFormat('en-US', { dateStyle: 'long' });
console.log(rangeFormat.formatRange(date, endDate));
// "January 10 - 12, 2026"
4.2 Relative Time: Intl.RelativeTimeFormat
Perfect for user-friendly UI feedback (“5 minutes ago”, “yesterday”).
const relativeTime = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' });
console.log(relativeTime.format(-1, 'day')); // "yesterday"
console.log(relativeTime.format(2, 'day')); // "in 2 days"
console.log(relativeTime.format(-3, 'month')); // "3 months ago"
4.3 Durations: Intl.DurationFormat
Displays the duration of a time span detached from the context of an absolute date. This matches up cleanly with abstractions present in the newer Temporal specs.
const span = { hours: 2, minutes: 4, seconds: 35 };
const stopwatch = new Intl.DurationFormat('en-US', {
style: 'digital',
hoursDisplay: 'always',
minutes: '2-digit'
});
console.log(stopwatch.format(span)); // "2:04:35"
5. String and Data Manipulation
5.1 Natural Sorting: Intl.Collator
Resolves the limitations of the default array .sort(), correctly ordering numbers naturally and ignoring accent marks.
const docs = ['archivo10.txt', 'archivo2.txt', 'Álbum.txt', 'Zebra.txt'];
// Array.sort() will fail by sorting "10" prior to "2", and push "Á" to the end.
// Intl.Collator fixes this effortlessly:
const collator = new Intl.Collator('es-ES', { numeric: true, sensitivity: 'base' });
docs.sort(collator.compare);
console.log(docs);
// ["Álbum.txt", "archivo2.txt", "archivo10.txt", "Zebra.txt"]
5.2 List Formatting: Intl.ListFormat
Generates language-compliant conjunctions or disjunctions (“and”, “or”, or others relative to the locale).
const ingredients = ['Flour', 'Eggs', 'Sugar'];
const formatterES = new Intl.ListFormat('es-ES', { type: 'conjunction' });
console.log(formatterES.format(ingredients)); // "Flour, Eggs y Sugar"
const formatterEN = new Intl.ListFormat('en-US', { type: 'conjunction' });
console.log(formatterEN.format(ingredients)); // "Flour, Eggs, and Sugar"
5.3 Intelligent Segmentation: Intl.Segmenter
Identifying word limits via .split(' ') drastically fails with logographic languages (e.g., Japanese, Chinese) or joined multi-codepoint emojis.
const japaneseText = "吾輩は猫である。名前はたぬき。";
const segmenter = new Intl.Segmenter('ja-JP', { granularity: 'word' });
const segments = segmenter.segment(japaneseText);
for (const chunk of segments) {
if (chunk.isWordLike) {
console.log(chunk.segment);
// Emits "吾輩", "は", "猫", "で", "ある", "名前", "は", "たぬき"
}
}
5.4 Displaying Countries and Languages: Intl.DisplayNames
Bypass shipping massive JSON dictionaries simply translating natively the names of global languages, geographies, or currencies.
const regionTranslator = new Intl.DisplayNames(['es-ES'], { type: 'region' });
console.log(regionTranslator.of('US')); // "Estados Unidos"
console.log(regionTranslator.of('JP')); // "Japón"
const languageTranslator = new Intl.DisplayNames(['en-US'], { type: 'language' });
console.log(languageTranslator.of('es-419')); // "Latin American Spanish"
6. Performance & Caching
Bootstrapping constructors from the Intl namespace constitutes a relatively expensive operation performance-wise.
If your application applies rapid redundant formatting (such as iterating thousands of transactional arrays scaling out UI components), you should never instanciate the target formatter inside the iteration cycle.
Recommended caching pattern:
const prices = [10.5, 20.9, 100, 450.2];
// ✅ CORRECT: We instantiate the formatter object explicitly ONCE outside the loop boundary.
const formatMXN = new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN' });
const renderOutput = prices.map(price => formatMXN.format(price));
7. Conclusion
The ECMAScript Intl API represents the gold-standard pathway for web or Node.js application globalization. By migrating algorithmic overhead securely to host environments, Intl strips extraneous codebase deadweight, driving top-tier execution speed while staying impervious to volatile geopolitical regulations without friction.
Sources: MDN Web Docs, ECMA-402 Internationalization API Specification.