Understanding JavaScript's Internationalization API
A concise guide to mastering JavaScript's Internationalization API for globalized web apps.
Introduction
Do you believe in this?
Everyone in the world should be able to use their own language on the Web.
If you don't, then please close your browser, and go read a book with your device or go out to view nature's beauty. But if you do believe, then come with me, and let's learn how we can make the World Wide Web worldwide.
The web apps we build today by default aren't accessible in all languages. Surprised? Let me explain. The HTML, CSS, and JavaScript code we're accustomed to writing works well in a language with a left-to-right text direction, mm/dd/yyyy date format, and 12,345,678.90 number format. A typical example of such a language is U.S. English. If your app gets used in a different language than the one I described (without adapting it), all hell will break loose: text direction will be chaotic, dates will be misread, and numbers will make no sense.
In this article, I'll explain what writing internationalized JavaScript apps means. Also, you'll learn why we even need to write internationalized apps in the first place. And by the end, you'll learn how we can use the JavaScript internationalization API to internationalize our apps. Let's get the ball rolling.
Prerequisite
This article assumes you have a basic understanding of HTML, CSS, and JavaScript. If not, I recommend familiarising yourself with these technologies before proceeding.
What is internationalization, localization, and globalization?
According to ECMAScript International, the official standard body that defines JavaScript:
Internationalization of software (often shortened to "i18n") means designing it such that it supports or can be easily adapted to support the needs of users speaking different languages and having different cultural expectations, and enables worldwide communication between them.
Localization of software (sometimes shortened to "l10n") is the process of adapting it to a specific language and culture.
Globalization of software is commonly understood to be the combination of internationalization and localization.
The internationalization phase is where you deal with adaptation barriers. The barriers that would make it difficult to adapt your app so that your users can read and relate to it. The localization phase is where you actually adapt for different users. You change the language via translation; you flick the text direction; you use a different date, time, and number format, and so on.
Why do we need an Internationalization API in JavaScript?
The ECMAScript Language Specification (ECMA-262) is the standard that defines the JavaScript programming language from which all web browsers build their JavaScript engines. In this standard, strings are based on Unicode (making text in all writing systems supported), and a few language-sensitive methods are provided:
String.prototype.localeCompare
String.toLocaleLowerCase
String.prototype.toLocaleUpperCase
Number.prototype.toLocaleString
Date.prototype.toLocaleString
Date.prototype.toLocaleDaxteString
Date.prototype.toLocaleTimeString
But, none of these language-sensitive methods allow apps to specify the language or control details of their behavior. This limitation creates a barrier if you want to build apps that many people around the world use.
The open-source community over the years has created libraries, such as Globalize.js, Numeral.js, Moment,js, date-fns, Day.js, etc, to fill in some of the gaps created by the Web's lack or immature support of i18n. These libraries provided i18n support, as well as date, time, and number formatting.
In 2012, the ECMAScript Internationalization API Specification (ECMA 402) i.e., the JavaScript i18n API, was approved as a standard. ECMA-402 is designed as an extension of ECMA-262, and it defines the API for JavaScript objects that support programs that need to adapt to the regional, linguistic, and cultural conventions used by different human languages and countries. As I write this, all major browsers have implemented the i18n API. This means developers can now internationalize their apps, without reaching for external tools.
"But still I'm not building for a global audience, so I don't think I need this API" you might object. Well, you need to properly support that one region and language you're building for, and the i18n API makes doing so effortless. So, let's keep the ball rolling.
What does the JavaScript Internationalization API provide?
ECMA-402 provides a set of language-sensitive functionality that are required in most apps as a complement to ECMA-262. But, unlike ECMA-262, ECMA-402 provides additional functionality and lets apps specify languages, assist with language negotiation, and control details of the behavior.
Listed below are the customizable language-sensitive constructors currently provided by the i18n API:
- String comparison (
Intl.Collator
) - Date and time formatting (
Intl.DateTimeFormat
) - Number formatting (
Intl.NumberFormat
) - Relative time formatting (
Intl.RelativeTimeFormat
) - Duration formatting (
Intl.DurationFormat
) - List formatting (
Intl.ListFormat
) - Text segmentation (
Intl.Segmenter
) - Pluralization rules (
Intl.PluralRules
) - Display names (
Intl.DisplayNames
)
Also, in the i18n API, the existing language-sensitive methods defined on String
, Number
, and Date
in ECMA-262 were respecified to accept locale and other parameters and interpret them in the same way as the new API introduced by the i18n API.
What is the Intl object?
The Intl
object is a standard built-in object in JavaScript used to package all functionality, from constructors to other language-sensitive functions, defined in the i18n API. It provides a namespace for the constructors of the i18n API, minimizing the risk of name collisions as more constructors are added to the API over time.
Unlike most objects in the global scope (e.g., Date
, Number
, Error
), Intl
has some different behavior that is worth paying attention to:
The
Intl
object does not have a construct internal method, so you cannot use it as a constructor with thenew
operator:The
Intl
object does not have a call internal method, so you cannot invoke it as a function:All properties and methods of the
Intl
object are static, just like theMath
object:
Note that all Intl
constructors cannot be invoked as a function and must be called with the new
operator:
The exceptions to this rule are Intl.Collator
, Intl.DateTimeFormat
, and Intl.NumberFormat
. Each of these constructs and returns a new object when called as a function. But, why? For backward compatibility with past editions of the i18n API. Remember, you don't break the Web.
The locales
and options
arguments
A distinctive feature of the language-sensitive functionality provided in the i18n API as well as the respecified language-sensitive methods of ECMA-262, is that it allows apps to specify languages, assist with language negotiation, and control details of the behavior. This is possible because they all accept locales
and options
arguments, which gives them the room to tailor the functionality to their needs.
The locales
argument, which can be a string or an array of strings, is used to specify the locale to be used in a given operation. The JavaScript implementation (e.g., the browser or Node.js) examines locales
and then computes a locale it understands that comes closest to satisfying the expressed preference.
The options
argument, which must be an object, is used to specify other parameters that let the app control the behavior of the constructed object. The properties that can be specified in the options
argument vary between constructors and functions, and if the options
argument is not provided or is undefined
, default values will be used for all properties.
What is a locale?
A locale is a set of parameters that defines the user's region, language, and country-based preferences for a user interface.
Among other things, a locale represents the following according to the rules in the given region:
- Language
- Currency
- Time zone
- Measurement unit
- Numbering system
- Collation i.e. sort-order
- Calendar
How does the JavaScript Internationalization API identify locales?
Except for currencies, the i18n API identifies language tags, time zones, measurement units, numbering systems, collation types, and calendar types using the IETF Best Current Practice (BCP) 47 standard as defined by Unicode Technical Standard 35. Currencies are identified using 3-letter currency codes as defined by ISO 4127.
The IANA Language Subtag Registry defines and maintains the list of language tags. A language tag consists of a language code, an optional script code, and an optional regional code, all separated by hyphens (-). For example, sr
for Serbian, sr-Cyrl
for Serbian in Cyrillic script, sr-Cyrl-BA
for Serbian in Cyrillic script as used in Bosnia-Herzegovina.
But, BCP 47 allows for extensions, and with the "u"
( Unicode) extension, you can specify additional parameters for currency, date and time formatting, number formatting, etc., as part of a language tag. For example, en-GB-u-nu-thai
, which means use British English with Thai digits (๐, ๑, ๒, ๓, ๔, ๕, ๖, ๗, ๘, ๙) in number formatting:
BCP 47 extension subtags (e.g., cf
for currency, nu
for numbering systems) are defined in the Unicode CLDR Project. And, you can use the Intl.supportedValuesOf()
static method to see the supported currency, time zone, measurement unit, numbering system, collation, or calendar values supported by an implementation:
What does the JavaScript Internationalization API accept as a valid locales
?
The string defining a locale is often referred to as a locale identifier. A locale identifier is a case-insensitive ASCII string. As we learned earlier, each section contained in the locale must be separated by a hyphen (-) and it must follow this format:
- A language subtag with 2-3 or 5-8 letters
- A script subtag with 4 letters (optional)
- A region subtag with either 2 letters or 3 digits (optional)
- One or more variant subtags (all of which must be unique), each with either 5-8 alphanumerals or a digit followed by 3 alphanumerals (optional)
- One or more BCP 47 extension sequences (optional)
- A private-use extension sequence (optional)
The locales
argument may be:
undefined
(or omitted): The implementation's default locale will be used:A locale: A locale identifier or an
Intl.Locale
object that wraps a locale identifier:A locales list: An array of locale identifiers, where the locale identifier recognized by the implementation is used:
A TypeError
is thrown if the locale identifier isn't a string or an array of strings:
A RangeError
is thrown if the locale identifier is a syntactically invalid string:
To be safe and sure, you can use the Intl.getCanonicalLocales
static method to validate locale identifiers if they are structurally valid language tags, before using them:
The system's locale is used, if an implementation doesn't recognize a well-formed locale identifier or any locale identifiers from the locales list:
How does the JavaScript Internationalization API resolve locales?
The process of resolving a locale is referred to as locale (or language) negotiation. Knowing the internals of how this process works isn't important for you to write correct code, inasmuch as you specify well-formed valid locales
. But, in some rare instances, it might help.
As we now know, the constructors from the i18n API, as well as the updated language-sensitive functions in String
, Number
, and Date
each take a locales
and options
argument. Together, these arguments form a request. The constructors then compare this request against the actual capabilities of their implementation to determine the actual locale to be used.
Two matching algorithms exist from which implementations can use to compare the request represented by the locales
and options
argument against the locales it has available to pick the best one: the "lookup"
matcher which follows the Lookup algorithm specified in BCP 47, and the "best fit"
matcher, which is the default algorithm, lets the implementation provide a locale.
You can specify a matching algorithm from the existing ones using the localeMatcher
property in the options
argument:
And, you can find out the result of the negotiation for a constructed object using the resolvedOptions
method. It returns an object with properties for all parameters except the matcher parameters:
How is the JavaScript Internationalization API used?
The JavaScript i18n API can be used in two ways: directly or indirectly.
Using the i18n API directly
JavaScript apps can use the i18n API directly by using the new language-sensitive constructors, specifying a list of preferred locales and options to configure its behavior.
Intl.Collator
This object enables language-sensitive string comparison.
For a list of the Unicode extension keys allowed in locales
, and the properties supported in options
, when using the Intl.Collator()
constructor, see Parameters for Intl.Collator() from MDN.
Intl.DateTimeFormat
This object enables language-sensitive date and time formatting.
For a list of the Unicode extension keys allowed in locales
, and the properties supported in options
, when using the Intl.DateTimeFormat()
constructor, see Parameters for Intl.DateTimeFormat() from MDN.
Intl.NumberFormat
This object enables language-sensitive number formatting.
For a list of the Unicode extension keys allowed in locales
, and the properties supported in options
, when using the Intl.NumberFormat()
constructor, see Parameters for Intl.NumberFormat() from MDN.
Intl.RelativeTimeFormat
This object enables language-sensitive relative time formatting.
For a list of the Unicode extension keys allowed in locales
, and the properties supported in options
, when using the Intl.RelativeTimeFormat()
constructor, see Parameters for Intl.RelativeTimeFormat() from MDN.
Intl.ListFormat
This object enables language-sensitive list formatting.
For a list of the Unicode extension keys allowed in locales
, and the properties supported in options
, when using the Intl.ListFormat()
constructor, see Parameters for Intl.ListFormat() from MDN.
Intl.Segmenter
This object enables language-sensitive text segmentation, enabling you to get meaningful items (graphemes, words, or sentences) from a string.
For a list of the Unicode extension keys allowed in locales
, and the properties supported in options
, when using the Intl.Segmenter()
constructor, see Parameters for Intl.Segmenter() from MDN.
Intl.PluralRules
This object enables plural-sensitive formatting and plural-related language rules.
For a list of the Unicode extension keys allowed in locales
, and the properties supported in options
, when using the Intl.PluralRules()
constructor, see Parameters for Intl.PluralRules() from MDN.
Intl.DisplayNames
This object enables the consistent translation of language, region, and script display names.
For a list of the Unicode extension keys allowed in locales
, and the properties supported in options
, when using the Intl.DisplayNames()
constructor, see Parameters for Intl.DisplayNames() from MDN.
Using the i18n API indirectly
JavaScript apps can also use the i18n API indirectly by using the respecified language-sensitive functions in String
, Number
, and Date
, with the new locales
and options
parameters. This enables the updated collation and formatting methods to produce the same result as the compare
and format
methods of the Intl.Collator
, Intl.NumberFormat
, and Intl.DateTimeFormat
constructors.
String
Number
Date
Conclusion
Yay! You did it. You've finally reached the end of the journey. You learned what i8n means in the context of the Web, why we developers need it, and how we can use it in our JavaScript apps. The JavaScript i18n API has grown to maturity and we (might) no longer have to reach out for external tools to write internationalized apps. This not only reduces our dependencies, strips down bundle size but also makes the Web a joy for everyone to use.
While the journey ends here for this article, I hope the i18n journey doesn't end here for you. Yes, you've learned about writing internationalized JavaScript apps, but there's still more. JavaScript isn't the ONLY core Web technology: HTML and CSS are on the list too. So, this isn't complete knowledge of internationalization for the Web. As you master and use the concepts discussed here, take your time to also learn how to write internationalized HTML and CSS code.
Now, the ball is in your court. Make the most of it and let's have a global web.