W3docs

JavaScript Intl (Internationalization) API

Learn the built-in JavaScript Intl API to format numbers, currencies, dates, relative times, lists, and plurals for any locale.

Intl is a built-in JavaScript namespace for locale-aware formatting and comparison. There is no library to install and nothing to import — it ships with every modern browser and with Node.js. With it you can format numbers, currencies, dates, relative times, and lists exactly the way users in a given region expect, and you can sort text correctly for languages that English-only sorting gets wrong.

Almost every Intl constructor follows the same shape: you pass a locale (or an array of fallback locales) and an options object. A locale is a BCP 47 language tag such as 'en-US', 'de-DE', 'fr-FR', or 'ja-JP'. If you omit the locale entirely, Intl uses the runtime's default locale (the browser's language setting, or the system locale in Node).

// locale + options
new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' });

// no locale → uses the runtime default
new Intl.NumberFormat();

// an array provides fallbacks: try Welsh, then fall back to English
new Intl.NumberFormat(['cy', 'en']);

Intl.NumberFormat

Intl.NumberFormat formats numbers according to a locale's conventions. This matters because grouping and decimal separators differ from place to place: the number 1234.56 is written 1,234.56 in the United States but 1.234,56 in Germany.

const n = 1234.56;

console.log(new Intl.NumberFormat('en-US').format(n)); // "1,234.56"
console.log(new Intl.NumberFormat('de-DE').format(n)); // "1.234,56"
console.log(new Intl.NumberFormat('fr-FR').format(n)); // "1 234,56"

The style option selects what kind of value you are formatting: 'decimal' (the default), 'currency', 'percent', or 'unit'.

Currency

For money, set style: 'currency' and name the currency with the currency option (an ISO 4217 code such as 'USD' or 'EUR'). The locale decides the symbol's position and the separators.

const price = 1499.9;

console.log(
  new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(price)
); // "$1,499.90"

console.log(
  new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(price)
); // "1.499,90 €"

console.log(
  new Intl.NumberFormat('ja-JP', { style: 'currency', currency: 'JPY' }).format(price)
); // "¥1,500"   (yen has no minor unit, so it is rounded)

Percent, fraction digits, and compact notation

Use style: 'percent' to format a ratio as a percentage — the value is multiplied by 100. Control how many decimals appear with minimumFractionDigits and maximumFractionDigits. Set notation: 'compact' to produce short forms like 1.2K and 3.4M.

console.log(
  new Intl.NumberFormat('en-US', { style: 'percent' }).format(0.1875)
); // "19%"

console.log(
  new Intl.NumberFormat('en-US', {
    style: 'percent',
    minimumFractionDigits: 2,
  }).format(0.1875)
); // "18.75%"

console.log(
  new Intl.NumberFormat('en-US', { notation: 'compact' }).format(1200000)
); // "1.2M"

Units

With style: 'unit' you can format measurements. Name the unit with the unit option (for example 'kilometer-per-hour' or 'megabyte') and choose a unitDisplay of 'short', 'long', or 'narrow'.

console.log(
  new Intl.NumberFormat('en-US', {
    style: 'unit',
    unit: 'kilometer-per-hour',
  }).format(90)
); // "90 km/h"

console.log(
  new Intl.NumberFormat('en-US', {
    style: 'unit',
    unit: 'megabyte',
    unitDisplay: 'long',
  }).format(16)
); // "16 megabytes"

For deeper number work — rounding, precision, and arithmetic — see Numbers and JavaScript Math.

Intl.DateTimeFormat

Intl.DateTimeFormat formats Date objects (and timestamps) for a locale. The quickest approach is the dateStyle and timeStyle options, each of which accepts 'full', 'long', 'medium', or 'short'.

javascript— editable

For finer control, set individual components such as year, month, day, hour, minute, and second, and pin the output to a zone with timeZone.

const date = new Date('2026-06-19T14:30:00Z');

const fmt = new Intl.DateTimeFormat('en-GB', {
  year: 'numeric',
  month: 'short',
  day: '2-digit',
  hour: '2-digit',
  minute: '2-digit',
  timeZone: 'Europe/Berlin',
});

console.log(fmt.format(date)); // "19 Jun 2026, 16:30"

Two extra methods are worth knowing. .formatToParts() returns the output broken into labelled pieces ({ type: 'month', value: 'Jun' }, and so on), which lets you restyle individual parts. .formatRange(start, end) formats a date range compactly, collapsing the shared parts.

const fmt = new Intl.DateTimeFormat('en-US', { month: 'long', day: 'numeric' });

console.log(fmt.formatRange(new Date(2026, 5, 1), new Date(2026, 5, 5)));
// "June 1 – 5"

For everything about creating and manipulating dates, see JavaScript Date.

Intl.RelativeTimeFormat

Intl.RelativeTimeFormat produces human phrases like "2 days ago" or "in 3 hours". You call .format(value, unit), where a negative value is in the past and a positive value is in the future. The numeric: 'auto' option lets the formatter use words like "yesterday" and "tomorrow" instead of "1 day ago".

const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });

console.log(rtf.format(-1, 'day'));  // "yesterday"
console.log(rtf.format(3, 'hour'));  // "in 3 hours"
console.log(rtf.format(-2, 'day'));  // "2 days ago"
console.log(rtf.format(1, 'week'));  // "next week"

The same call in another locale produces native phrasing — new Intl.RelativeTimeFormat('fr').format(-1, 'day') gives "il y a 1 jour".

Intl.Collator

Sorting text is the place where naive code most often goes wrong. JavaScript's default Array.prototype.sort() compares strings by their UTF-16 code units, not by alphabetical rules. That means uppercase letters sort before lowercase ones, and accented or non-Latin letters land in surprising places.

const words = ['Zürich', 'apple', 'Banana', 'Älpler'];

console.log([...words].sort());
// ["Banana", "Zürich", "Älpler", "apple"]  ← not what a reader expects

Intl.Collator fixes this by comparing strings the way a given language does. Its .compare method has the exact signature sort() wants, so you can pass it straight in.

javascript— editable

For a one-off comparison of two strings you can also use String.prototype.localeCompare, which accepts the same locale and options arguments: 'ä'.localeCompare('z', 'de'). When you are sorting a whole array, prefer a reused Intl.Collator — it is faster than calling localeCompare on every pair. See Strings for more on working with text.

Intl.PluralRules

Different languages have different plural categories. English has just two ('one' and 'other'), but many languages have more. Intl.PluralRules tells you which category a number falls into so you can select the right wording in a translated message.

const pr = new Intl.PluralRules('en-US');

console.log(pr.select(0)); // "other"
console.log(pr.select(1)); // "one"
console.log(pr.select(5)); // "other"

function items(count) {
  const word = pr.select(count) === 'one' ? 'item' : 'items';
  return `${count} ${word}`;
}

console.log(items(1)); // "1 item"
console.log(items(3)); // "3 items"

Intl.ListFormat

Intl.ListFormat joins an array of strings into a natural-sounding list, inserting the right separators and conjunction for the locale — including the Oxford comma where the language uses one.

const items = ['apples', 'bananas', 'oranges'];

const en = new Intl.ListFormat('en-US', { style: 'long', type: 'conjunction' });
console.log(en.format(items)); // "apples, bananas, and oranges"

const enOr = new Intl.ListFormat('en-US', { type: 'disjunction' });
console.log(enOr.format(items)); // "apples, bananas, or oranges"

const de = new Intl.ListFormat('de-DE', { type: 'conjunction' });
console.log(de.format(items)); // "apples, bananas und oranges"

Reuse formatters for performance

Warning

Constructing an Intl formatter is comparatively expensive — it loads and resolves locale data. Create each formatter once and reuse it, especially inside loops or list rendering. Building a fresh new Intl.NumberFormat(...) for every row can be far slower than formatting itself.

// Slow: a new formatter is built on every iteration
prices.forEach((p) =>
  console.log(new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(p))
);

// Fast: build it once, reuse it
const money = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
prices.forEach((p) => console.log(money.format(p)));
Info

Each formatter also exposes .resolvedOptions(), which reports the locale and options actually chosen after negotiation. It is handy for debugging cases where the runtime falls back to a locale other than the one you requested.

Wrap-up

The Intl API covers the formatting and comparison tasks that used to require heavyweight third-party libraries: locale-aware numbers, currencies, dates, relative times, plurals, and lists, plus correct text sorting through Intl.Collator. Because it is built into every modern browser and into Node.js, reaching for it first keeps your bundle small and your output correct for users everywhere. Remember the one rule that pays off the most: build each formatter once and reuse it.

Test Your Knowledge

Practice
Which Intl constructor formats a number as a currency, and how?
Which Intl constructor formats a number as a currency, and how?
Practice
Why might [...words].sort() put accented or uppercase letters in the wrong order, and what fixes it?
Why might [...words].sort() put accented or uppercase letters in the wrong order, and what fixes it?
Practice
What is the recommended way to use an Intl formatter inside a loop?
What is the recommended way to use an Intl formatter inside a loop?
Was this page helpful?