main mozilla tech

JavaScript Internationalization in 2020

2020 is shaping up to be an amazing year for JavaScript Internationalization API.

After many years of careful design we’re seeing a lot of the work now coming close to completion with a number of high profile APIs on track for inclusion in ECMAScript 2020 standard!

What’s coming up?

Let’s cut to the chase. Here’s the list of APIs coming to the JavaScript environment of your choice:


// Create a relative time formatter in your locale
// with default values explicitly passed in.
const rtf = new Intl.RelativeTimeFormat("en", {
    localeMatcher: "best fit", // other values: "lookup"
    numeric: "always", // other values: "auto"
    style: "long", // other values: "short" or "narrow"

// Format relative time using negative value (-1).
rtf.format(-1, "day");
// > "1 day ago"

// Format relative time using positive  value (1).
rtf.format(1, "day");
// > "in 1 day"

Intl.RelativeTimeFormat has been the first of the new APIs finished by the ECMA402 Work Group in a number of years.

Many UIs want to show date and time in a relative format because humans tend to understand such relative terms much better than absolute values.

This UX provides a foundation for a much better user experience that works across languages and cultures without requiring websites to ship their own data.

Status: The original requests comes from September 2015 and the proposal has been granted Stage 4 approval at the TC39 meeting in December 2019 and is now ready for inclusion into the spec.


let loc = new Intl.Locale("pl-u-hc-h12", {
  calendar: "gregory"
console.log(loc.language); // "pl"
console.log(loc.hourCycle); // "h12"
console.log(loc.calendar); // "gregory"
console.log(loc.toString()); // "pl-u-ca-gregory-hc-h12"

Intl.Locale is a user-friendly implementation of the Unicode Locale Identifier part of UTS #35.

It brings ability to perform basic operations such as parsing, serializing, modifying and reading elements of locale identifiers.

This removes a common scenario where a developer is trying to dissect a language identifier string by slicing it manually.

It also allows users to construct or augment locales with options that then get passed to other Intl formatters:

let loc = new Intl.Locale("en-CA", {
  region: "US",
  hourCycle: "h24",
let dtf = new Intl.DateTimeFormat(loc, { hour: "numeric" });
console.log(dtf.resolvedOptions().hourCycle); // "h24"

Status: Intl.Locale, originally proposed in September 2016, has been approved for Stage 4 at the last month’s TC39 meeting and is ready for landing into the main ECMA402 spec.

Intl.NumberFormat rev. 2

(299792458).toLocaleString("en-US", {
    style: "unit",
    unit: "meter-per-second",
    unitDisplay: "short"
}); // "299,792,458 m/s"

Unified NumberFormat revision 2 is the very first case of a significant rewrite of an already existing JavaScript Intl API.

The motivation is to build on top of an excellent rewrite of the formatter that has been completed in ICU and enable much richer set of formatting operations on numbers.

It brings unit formatting, scientific notation, and control over sign display.

(987654321).toLocaleString("en-US", {
    notation: "scientific"
}); // 9.877E8

(987654321).toLocaleString("en-US", {
    notation: "engineering"
}); // 987.7E6

(987654321).toLocaleString("en-US", {
    notation: "compact",
    compactDisplay: "long"
}); // 987.7 million
(55).toLocaleString("en-US", {
    signDisplay: "always"
}); // +55

All those features were added while preserving backward compatibility of the API, which is nothing short of amazing.

The new revision of the API lays foundation for internationalization of much broader range of use cases, and thanks to support for the formatToParts API it can be easily styled for display.

Status: The API combines a number of requests, most prominent of which was the request for Unit Formatting from September 2015. The proposal got approved for Stage 4 at the last months TC39 meeting and is ready for landing into the main ECMA402 spec.


// Create a list formatter in your locale
// with default values explicitly passed in.
const lf = new Intl.ListFormat("en", {
    localeMatcher: "best fit", // other values: "lookup"
    type: "conjunction", // "conjunction", "disjunction" or "unit"
    style: "long", // other values: "short" or "narrow"

lf.format(['Motorcycle', 'Truck' , 'Car']);
// > "Motorcycle, Truck, and Car"

Intl.ListFormat provides basic capabilities of formatting a list of elements. In the most common scenario, it shows a conjunction list, but can also be used for disjunction lists and even unit lists.

const list = ['Motorcycle', 'Bus', 'Car'];

 console.log(new Intl.ListFormat('en-GB', { style: 'long', type: 'conjunction' }).format(list));
// > Motorcycle, Bus and Car

 console.log(new Intl.ListFormat('en-GB', { style: 'short', type: 'disjunction' }).format(list));
// > Motorcycle, Bus or Car

 console.log(new Intl.ListFormat('en-GB', { style: 'narrow', type: 'unit' }).format(list));
// > Motorcycle Bus Car

The unit option is particularly useful in conjunction with the Intl.NumberFormatter when working with lists of units and together can produce values such as 6m 10cm or 2h 35m.

Status: The API was originally requested in September 2015 and is currently in Stage 3. We intend to request TC39’s approval for Stage 4 over the next couple of months.

Intl.DateTimeFormat dateStyle/timeStyle

let o = new Intl.DateTimeFormat("en" , {
  timeStyle: "short"
console.log(o.format(; // "13:31"

let o = new Intl.DateTimeFormat("en" , {
  dateStyle: "short"
console.log(o.format(; // "21.03.2012"

let o = new Intl.DateTimeFormat("en" , {
  timeStyle: "medium",
  dateStyle: "short"
console.log(o.format(; // "21.03.2012, 13:31"

Original design of the Intl.DateTimeFormat required developers to specify which fields in which style they want to be displayed, like this:

let date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0, 200));

// request a weekday along with a long date
let options = {
  weekday: 'long',
  year: 'numeric',
  month: 'long',
  day: 'numeric'
console.log(new Intl.DateTimeFormat('de-DE', options).format(date));
// → "Donnerstag, 20. Dezember 2012"

While the flexibility this model provides, it is far from intuitive. It requires the developer to make specific decisions for each piece of the user interface increasing the odds of inconsistency.

The historical reasons for the formatter to require all fields to be listed are now gone and we can introduce a much simplified approach for most common scenarios:

let date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0, 200));

// request a weekday along with a long date
let options = {
  dateStyle: 'full',
console.log(new Intl.DateTimeFormat('de-DE', options).format(date));
// → "Donnerstag, 20. Dezember 2012"

What’s even more important, the most popular formats of displaying time and date may differ per locale, and yet this approach locks down which fields in which style will be displayed for all of them.

Let’s see what happens when we format the dateStyle: "medium" in several locales:

let date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0, 200));

let options = {
  dateStyle: 'medium',
console.log(date.toLocaleString("pl", {dateStyle: "medium"}));
// → "20 gru 2012"

console.log(date.toLocaleString("ja-JP", {dateStyle: "medium"}));
// → "2012/12/19"

console.log(date.toLocaleString("he", {dateStyle: "medium"}));
// → "19 בדצמ׳ 2012"

As you see, all three displayed medium length date, but styles of fields chosen were different. That would not be possible before and a developer would have to enforce their idea of what a medium style date is onto all locales. Now we can have more internationalized and easier to work with dates and times!

Status: The request from October 2016 is now in Stage 3 and we hope to bring it to the TC39 committee for approval for Stage 4 over the next couple months.


// Get display names of language in English
var languageNames = new Intl.DisplayNames(['en'], {type: 'language'});
console.log(languageNames.of('fr')); // "French"
console.log(languageNames.of('de')); // "German"
console.log(languageNames.of('fr-CA')); // "Canadian French"
console.log(languageNames.of('zh-Hant')); // "Traditional Chinese"
console.log(languageNames.of('en-US')); // "American English"
console.log(languageNames.of('zh-TW')); // "Chinese (Taiwan)"]

// Get display names of script in English
let scriptNames = new Intl.DisplayNames(['en'], {type: 'script'});
console.log(scriptNames.of('Latn')); // "Latin"
console.log(scriptNames.of('Arab')); // "Arabic"
console.log(scriptNames.of('Kana')); // "Katakana"

// Get display names of region in English
let regionNames = new Intl.DisplayNames(['en'], {type: 'region'});
console.log(regionNames.of('419')); // "Latin America"
console.log(regionNames.of('BZ')); // "Belize"
console.log(regionNames.of('US')); // "United States"
console.log(regionNames.of('BA')); // "Bosnia & Herzegovina"
console.log(regionNames.of('MM')); // "Myanmar (Burma)"

// Get display names of currency code in English
let currencyNames = new Intl.DisplayNames(['en'], {type: 'currency'});
console.log(currencyNames.of('USD')); // "US Dollar"
console.log(currencyNames.of('EUR')); // "Euro"
console.log(currencyNames.of('TWD')); // "New Taiwan Dollar"
console.log(currencyNames.of('CNY')); // "Chinese Yuan"

In order to format date, time and other elements, JavaScript engine has to carry a lot of data for a lot of locales.

One of the most common request from developers working on making their JavaScript powered websites and applications internationalized, is to present a language selector.

In other cases, they’d like to format names of months, weekdays or currencies.

Intl.DisplayNames is a new API intended to expose translations for basic units used in other formatters.

While the scope of the proposal has been reduced for the initial revision, the second revision should bring us much awaited date and time related terms:

// Display names in English
symbolNames = new Intl.DisplayNames(
  ['en'], {type: 'dateSymbol'});
symbolNames.of('saturday'); // => "Saturday" 
symbolNames.of('september');  // => "September"
symbolNames.of('q1'); // => "1st quarter"
symbolNames.of('pm'); // => "PM"

This API should end up being useful not just for language selectors, but also eventually for date/time pickers etc.

Status: The request originally made in September 2015, is now a proposal in Stage 3 and we hope to reach Stage 4 for the first revision this year.


let date1 = new Date(Date.UTC(2007, 0, 10, 10, 0, 0));
let date2 = new Date(Date.UTC(2007, 0, 10, 11, 0, 0));

let fmt1 = new Intl.DateTimeFormat("en", {
    year: '2-digit',
    month: 'numeric',
    day: 'numeric',
    hour: 'numeric',
    minute: 'numeric'
console.log(fmt1.formatRange(date1, date2));
// > '1/10/07, 10:00 AM'
// > '1/10/07, 10:00 – 11:00 AM'

The formatRange proposal extends Intl.DateTimeFormat with ability to format ranges of dates.

This feature is particularly useful for all date/time pickers, calendars, booking apps etc.

Status: The request from October 2017, is now in Stage 3 and we hope to finalize it this year.

What’s next?

We’re wrapping up and getting ready to tie all the loose ends and get the ECMAScript 2020 edition ready.

It’ll take a bit of time for the implementers to deploy and enable all of them, but with good specification polyfills, we should be able to start building on top of them already this year.

On top of that, we’re working on a number of proposals such as Intl.Segmenter, Intl.DurationFormat, and others including a major effort to design future localization system.

If you like what’s going on, and want to contribute to making JavaScript even better for writing multilingual software, check the issues in ecma402 repo, and consider joining our monthly meeting!


Such a major effort would not be possible without an excellent group of experts from all around the World.

The original ECMA402 1.0 was edited by Norbert Lindenberg based on the work by Nebojša Ćirić and Jungshik Shin and support from a large number of domain experts.

Many of the proposals that are getting stabilized today came to life when Caridy Patiño was the editor of ECMA402, and all of that ground work was only possible with the help from Eric Ferraiuolo.

Both of them enabled the large influx of proposals that came in 2015 and we were all lucky to see that matched by an influx of industry experts who came to aid us in the process – Allen Wirfs-Brock, Rick Waldron, André Bargull, Steven R. Loomis, Daniel Ehrenberg, Rafael Xavier, Shane Carr, Leo Balter, and Frank Tang.

On top of the core contributors, a large number of people provided input, feedback, criticism, participated in design discussions, implemented polyfills and provided implementers feedback.

It’s great to see that a large number of currently discussed issues and new proposals are driven by new members of the working group and I expect a high influx of ideas and proposals to come when all of the above mentioned APIs start being usable by the Web Platform users!