By adapting our apps for different languages and countries, we provide a better user experience. It’s simpler for users to deal with known notations for dates, currencies, and numbers.
Internationalization (i18n) involves adding support for different languages and countries in your app. The number 18 stands for the number of letters between the first ‘i’ and the last ‘n’.
Examples of internationalization could be Unicode support, user interface customization for different alphabets, or array sorting of non-English strings.
And what makes it so useful is that it has great cross-browser compatibility and Node.js support:
Intl object provides access to several constructors, like:
- Intl.DateTimeFormat — language-sensitive date and time formatting.
- Intl.NumberFormat — language-sensitive number formatting.
- Intl.PluralRules — plural sensitive formatting and plural language rules.
- Intl.Collator — language-sensitive string comparison.
Creating any of these objects follows a simple pattern:
const formatter = new Intl.ctor(locales, options);
For instance, the “de-AT” locale: German language as it’s used in Austria:
const dateFormatterAT = new Intl.DateTimeFormat("de-AT");
Then we call the format() method with a provided Date object:
const date = new Date("2018-11-25");
const format = dateFormatterAT.format(date); // "25.11.2018"
It contains only language and country codes. Soon, we will see more comprehensive examples. Here you can find more locale examples.
We can use navigator.language — the preferred language for the user, which we use as a locale:
Here instead of calling a format method directly, we can assign it as a function. It’s great because once we have created a specialized format function, we can use it multiple times.
Just a few lines of code and you have a localized date!
So, next, we are going to dive deeper and learn more about locales. If you are not ready for it and only want to see cool demos like this one in the picture below — go to the examples section below!
Well, this is enough to get an idea of how it works, but the real use cases could get way more complicated. What if we wanted to:
- display our date using the Japanese or Persian calendar
- use Thai or Arabic-Indic digits for both dates and numbers
- use simplified Chinese
- Any combination of the above 😅
In order to work with this API, we have to learn more about locales. First of all, let’s give a definition.
A locale is an identifier that refers to a set of user preferences such as:
- dates and times
- numbers and currencies
- translated names for time zones, languages, and countries
- measurement units
- sort-order (collation)
A locale is not case sensitive. It’s only a convention.
The locale must be a string holding a BCP 47 language tag, and all part are separated by hyphens.
Let’s take a look at the next example:
Again, only four lines of code 😉 Let’s take a look at the diagram below and examine each part of our locale:
From this picture, you can see that only the first part — language code — is required. It’s unlikely you need a locale like this. But, this is a good example of taking a look at every possible locale part and getting an idea of what a locale is.
Our locale contains all possible parts:
- zh (language code) — Chinese language
- Hans (script code) — written in simplified characters
- CN (country-code) — as used in China.
- bauddha (variant) — using a Buddhist Hybrid Sanskrit dialect
- u-nu-hanidec (extension) — using Han decimal numbers
Below you can find more examples for scripts, variants, and extensions.
These are used with language tags to indicate which script a language is written in. For instance:
Variants represent a language dialect.
It includes different calendars and numeric systems.
Calendars have “u-ca-” prefix, possible values (not all included):
Numeric systems have “u-nu” prefix, possible values (not all included):
The Iana organization is responsible for keeping this list up to date.
The last thing we have to learn about locales is how they are resolved. We saw this example before:
const formatter = new Intl.ctor(locales, options);
locales argument is specifying a single locale or an array of locales. The environment (browser or Node.js) compares it against the locales it has available and picks the best one.
There are two matching algorithms:
- lookup — checks from more specific to less: if zh-Hans-SG is not available, get zh-Hans, if not — zh, else — a default locale.
- best fit (default) — Improved algorithm. If “es-GT” — Spanish for Guatemala is requested, but not found, then instead of providing a fallback as “es”, the “es-MX” — Spanish in Mexico will be chosen.
If we provide an array of locales, then the first match wins.
So, enough theory — now is the time to practice!
The code for the examples can be found on GitHub.
Locales are not the only thing which is great about the Intl API. You can modify the result in a desirable way using an
This is a massive update compared to the Date object!
Unlike moment.js you cannot manually swap any part of the date like year and month. You have to use the proper locale instead. This may sound like a limitation, but it makes it more familiar for our users.
Knowing how to deal with dates, you know how to deal with numbers. The only difference is the list of options:
For the currencies we use
Intl.NumberFormat constructor, but provide a different list of options:
Note, we don’t convert money here. All we do is format the number 172630 using appropriate currency rules. Here you can find the list of currency codes.
This tells you which form applies based on a given number for a specific locale:
It can be very handy for formatting ordinal numbers:
Sorting strings which contain extra letters like ä in German or Swedish is not what you want to do manually, just because of the order depends on the language. Luckily for us, we have
Intl.Collator. And again all we have to do is to provide a required locale:
Internationalization is a great and complex topic. But if you know what a locale is and how to construct it, the rest is super easy to use.
If you have any questions or feedback, let me know in the comments down below or ping me on Twitter.