Understanding the Intl Javascript API
Personalizing the user experience is becoming more critical every day, even more so if your application or content is consumed by users in different parts of the world who most likely use other languages, date formats, currency, etc.
There are multiple solutions to make your content adapt to the location or language of your users. Still, many of these methods have become outdated, complex, or dependent on a particular framework.
Javascript also offers an internationalization solution, the Intl object.
What is Internationalization?
The concept of internationalization, or i18n, is the process of supporting different languages, and countries in your application.
i18n is commonly confused with Localization or even translation, but i18n refers to developing a product, a process focused on supporting different languages and formats based on locality — a common code base.
Providing internationalization support is critical for many products but often overlooked.
Localization refers to the generation of a specific product to target a market or region, including translating content and even modifying the interface and terminology.
Typically i18n implementation may include:
- Development of software that is independent of a specific language or cultural convention. (e.g. display of times and dates)
- Use of localization frameworks
- Removal of "hard-coded" text in the code
- Support for bi-directional languages
- Support for different number formats
The Intl object
According to MDN, "The Intl object is the namespace for the ECMAScript Internationalization API, which provides language sensitive string comparison, number formatting, and the date and time formatting. The Intl object provides access to several constructors and functionality common to the internationalization constructors and other language sensitive functions."
In other words, through the global Intl object, you can access a set of tools to work with content sensitive to the user's language.
Most browsers currently support these methods well based on information available at caniuse.com
TLDR; A quick example of what this API can achieve:
You can see a demo of this code in the following link
In this code, you can briefly see some methods and transformations that Intl can do.
- Gives you static access to different constructors, in this case,
DateTimeFormat
andNumberFormat
- Allows you to "transform" content from one format to another based on the value of
locale
It is essential to note that the locale
argument is received by all constructors exposed by Intl. This locale
is the value you will ideally capture dynamically so you can modify content based on its value.
Browsers offer a method of getting the value of locale
based on the user's preferences or location.
What is locale
, and how does it work?
When we refer to locale
we refer to a string that represents a group of user preferences like:
- Date and Time
- numbers and currency
- Time zones, languages, and countries
- Measurement units
This locale
string must follow a particular format to be used, this consists of:
- A subtag or language code
- (optional) a region or country subtag
- (optional) a subtag script
- (optional) one or more variation subtags
- (optional) one or more BCP-47 extension sequences
Each subtag or sequence used to create the locale
string is separated by a hyphen. Also, identifiers identify the case.
Some examples of locale
strings
- "es": Spanish (language)
- "es-CL": Spanish (language) as it is used in Chile (region)
- "zh-Hans-CN": Simplified Chinese (language) (script) as used in China (region)
When a locale
string is passed as an argument to one of Intl's provided constructors, it is compared to a list of available locales
for the best fit. This process is done using one of two possible algorithms: lookup
or best-fit
.
The lookup
algorithm checks if the runtime environment has the locale
used by searching from the most specific to the least detailed result. If the exact value of locale
is unavailable, it will return the closest one. For example, if you search for de-DE-u-co-phonebk
if not found, you can return de-DE
, and in case of not seeing de
reference will return a default value.
The best-fit
algorithm is an improvement of the previous algorithm where a default value is not returned if the search is not found. If not, the one that best "fits" is returned. IF es-CL
is searched for but not found, es-AR
will be returned instead of just es
.
You can learn more about this process by reviewing the documentation on MDN.
What does each subtag of the string mean?
As described above, the string locale is made up of different parts separated by a hyphen, the first section being the language identifier.
What are the rest used for?
Script code
This script code is used to identify in what "format" a particular language is written. For example, in Asian languages Hans
means that Simplified Chinese will be used vs. Hant
, which indicates that Traditional Chinese will be used.
Variant code
Variant codes represent the different dialect options for a specific language.
Extensions
Extensions include identifiers for different calendars, and numeric or ordering systems. In the example of the previous image, phonebk
identifies the variant indicating the ordering used for the letters, in this case phone book style.
Time to code
Formatting dates and time
Let's start by formatting dates and times based on different values of locale
.
For this, you need access to the DateTimeFormat
constructor.
Find a demo of this code in this playground
This is the basic way to format date and time, but each constructor provided by the Intl object accepts a second argument that allows you to modify the result.
For DateTimeFormat
the options are:
You have many options to define the best way to display a date and time in your application.
Let's review some examples.
Formatting Numbers
Another constructor offered by Intl is NumberFormat
. You can use this constructor to change the way numbers are represented on the screen.
The only difference between DateTimeFormat
and NumberFormat
constructors is the set of options that these contructors receives.
Using this extensive list of options, you can very likely format a number with every possible use case.
Check this quick example.
In this code snippet, you can see the following
- A function was created to hold the formatter.
- The formatter is always using the
en-US
locale. - Three options define what style of number formatting will use
style: currency
.
Even though we kept the same locale, the values returned by the formatting function are different.
What would happen if you also changed the locale for each currency conversion?
In case these options don't cover your use-case, you can use another method that is provided by the formatter: formatToParts
. This method will return an array of objects representing the number as a string in parts so you can use them to customize and solve your particular situation.
What if you want to show a number like the social networks? Like 1.2K
?
The NumberFormat
can solve that.
In this case, the solution involves the use of the notation
option.
You can find a demo for the number formatting in this playground.
Relative Time
Sometimes, you want to display the dates in a relative form such as: 2 weeks ago
. You can do this by doing some math and string concatenation with the dates, but Intl helps you with this.
Intl expose a constructor named RelativeTimeFormat that, same as before, accepts an argument to tell it what language you want to use and a set of options
For this constructor the format
method accepts a numeric value (NOT A DATE) like this
When would you use something like this?
Imagine a list of articles, each article have a publication date, but you don't want to show just that, you want to show how much relative time is between the publication date and today (the day the user is reading the list).
To accomplish this you'll need to do a little math to get the difference between the two dates and use that result as a relative time value.
Check the demo playground in this link
Pluralization
The Intl object also supports a way to define plural-sensistive content with the PluralRules constructor.
One use case for this feature is to show the number of items that exist in your collection, ideally this should respect the grammatical numbering in each language you choose to use.
You can always get around this requirement by writing your copy avoiding the need of pluralization, but what happen is you need it?
Let's check an example
Check the demo in the playground
Note: in a real-woprld sceneario, you wouldn't hardcode plurals in this snippet; they'd be part of your translation files
Maybe this example is too naive, since English and Spanish have just two pluralization rules; however, not every language follow this rule, some have only a single plural form, while other have multiple forms.
This example has been borrowed from v8 blog
This constructor just like the others exposed by Intl accepts a second argument to define options. One option is the type
that allows you to define the selection rule. By default it uses the cardinal
option. If you want to get the ordinal
indicator for a number (for example to create a list ) you can accomplish that as follows:
List Formatting
Displaying a list is one of the most used ways to showcase information in a web app. But since your users speaks different language you need a way to format the list based on that language convention.
To avoid the hassle of implementing this formatting rules by hand - that could be really hard - Intl offers the ListFormat API.
What this formatter does is basically joining an array of string with the correct conjunction or disjunction to create a meaningful phrase.
The default way to format is by using a conjunction unless you pass the type
options as disjuntion
As always, you can check the playground example in this link.
Segmentation
Segmentation refers to the requirement of splitting some text in segments, for example, split a paragraph in words. The Intl.Segmenter constructor offers a locale-sensitive solution to split the text returning meaningful items for the source string.
The most naive and most often used solution to split a string is just using String.prototype.split
, for example splitting a text by whitespaces .split(' ')
. But, what happen if the language does not use whitespace between words? The result of the split action will be wrong, to avoid this let's use the Segmenter constructor.
Example extracted directly from MDN documentation
String comparison
The last constructor to review is the Intl.Collator, this constructor enables string comparison with locale sensitivity.
This constructor is very useful to sort strings that contain extra letters like German or Swedish. Different languages have different sorting rules, let's check a quick example of this
Check the code in the playground
Conclusion
Internationalization is an important piece of an application or website that is meant to be used by a worldwide audience, but getting it right is a complex topic. Luckly there are many building blocks directly available from the Javascript engine that can help you implement a locale sensible UI.
That’s a wrap! If you have any questions or feedback, open an issue on my AMA repo or ping me on Twitter.