An introduction to the JavaScript Internationalization API (i18n)

English is the world’s most widely used language, yet only one in seven people speak it. It’s the first (native) language of 379 million people, but 917 million speak Mandarin Chinese, 460 million speak Spanish, and 341 million speak Hindi.

Many non-English speakers reside in emerging markets with exponential internet growth. If your web app can be globally translated, your potential target market could increase by 700%!

The JavaScript Internationalization API (also known as i18n) allows you to design web pages and applications in such a way that they can be easily adapted to support the needs of users that speak different languages.

In this article, we’ll look at the various methods the API offers and how you can implement them in your code to reach a wider, more international audience.

Internationalization (I18n) Can Be Tricky

Internationalization looks easy … until you try to do it.

Latin-based languages can be superficially similar. For example, a form requesting a name, email, and date translates like this:

  • Spanish: nombre, email, fecha
  • French: nom, e-mail, date
  • German: name, email, datum

The Gettext internationalization and localization system has been around for several decades, and libraries are available for most programming languages.

In simpler cases, you could use some form of tokenization. For example, take an HTML template containing the following:

<label for="name"> NAME </label>

This is dynamically replaced by ‘name’ when a user has English set as their primary language. Unfortunately, that’s where the problems start for your user interface:

  1. There can be different variations of the same language. The Spanish spoken in Spain is not identical to that spoken in South America.
  2. Words in one language can be considerably longer in others. For example, “email” translates to “электронное письмо” in Russian.
  3. Text isn’t always oriented from left to right. Some is written from right to left — such as Arabic, Hebrew, Kurdish, and Yiddish. Others can be written from top to bottom, such as Chinese, Korean, Japanese, and Taiwanese.

Many issues can be addressed by keeping text to a minimum and adopting CSS properties such as direction, writing-mode, and logical dimensions for layout.

Terminology Turmoil

Further confusion will arise when your application needs to display dates, times, numbers, currencies, or units.

Consider a date shown as “12/03/24”. It will be read as:

  • “3 December 2024” by US residents who use the MDY format
  • “12 March 2024” by European, South American, and Asian residents who use the DMY format, and
  • “24 March 2012” by Canadian, Chinese, Japanese, and Hungarian residents who opt for the considerably more practical YMD format.

(Be aware that date delimiter slashes are not common in all languages!)

The number “1,000” will be read as:

  • “one thousand” by those in the US, UK, Canada, China, and Japan, and
  • “one (point zero)” by those in Spain, France, Germany, and Russia where a number’s decimal fraction is separated by a comma.

The situation can even be complex in English alone. The term “1,000 meters” means:

  • 1 kilometer (or 0.62 of a mile) to US residents
  • a collection of one thousand measuring instruments to those in the UK, Canada, and Australia!

The JavaScript Intl API

The little-known JavaScript Intl object implements the ECMAScript Internationalization API in most modern browsers and runtimes. Support is generally good, and even IE11 has many of the more useful methods. For older browsers, there’s a polyfill, and the API can be detected like so:

if (window.Intl) 
  // Intl supported

The API is slightly unusual. It provides several object constructors for dates, times, numbers, and lists, which are passed a locale and an optional object containing configuration parameters. For example, here’s a DateTime object specifying US English:

const dateFormatter = new Intl.DateTimeFormat('en-US');

This object can be used any number of times to call various methods which are passed a Date() value (or an ES6 Temporal when available). The format method is usually the most practical option. For example:

const valentinesDay = dateFormatter.format( new Date('2022-02-14') );
// returns US format "2/14/2022"

const starwarsDay = dateFormatter.format( new Date('2022-05-04') );
// returns US format "5/4/2022"

Alternatively, you can create the Intl object and run a method in one line of code:

const starwarsDay = new Intl.DateTimeFormat('en-US').format( new Date('2022-05-04') );

As well as the format() method, some objects support these:

  • formatToParts(): returns an array of objects containing formatted strings, such as type: 'weekday', value: 'Monday'
  • resolvedOptions(): returns a new object with properties reflecting the locale and formatting options used, such as dateFormatter.resolvedOptions().locale.

Defining Locales

All Intl objects require a locale argument. This is a string which identifies:

  • a language subtag
  • a script subtag (optional)
  • a region (or country) subtag (optional)
  • one or more variant subtags (optional)
  • one or more BCP 47 extension sequences (optional)
  • a private-use extension sequence (optional)

The language and region is often enough. For example, "en-US", "fr-FR", and so on.

As well as using a string, an Intl.locale object can be used to construct locales, such as English US with 12-hour time format:

const us = new Intl.Locale('en', 
  region: 'US', hourCycle: 'h12', calendar: 'gregory'
);

This can be used in another Intl constructor. For example:

new Intl.DateTimeFormat(us,  timeStyle: 'medium' )
  .format( new Date('2022-05-04T13:00:00') );

// "1:00:00 PM"

If no locale is defined, the device’s current language and region settings are used. For example:

new Intl.DateTimeFormat().format( new Date('2022-05-04') );

This returns "5/4/2022" on a device with US settings and "04/05/2022" on a device with UK settings.

Continue reading
What is the JavaScript Internationalization (I18n) API?
on SitePoint.