How to get started with internationalization in JavaScript
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.
JavaScript implements Internationalization API specification and defines the built-in Intl object.
And what makes it so useful is that it has great cross-browser compatibility and Node.js support:
Let’s get started!
The 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!
Diving deeper
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 😅
What is Locale?
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.
Script codes
These are used with language tags to indicate which script a language is written in. For instance:
Variant codes
Variants represent a language dialect.
Extensions
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.
Locale negotiation
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);
The 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!
Examples
The code for the examples can be found on GitHub.
Date/Time Formatting
Locales are not the only thing which is great about the Intl API. You can modify the result in a desirable way using an options
argument.
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.
Number Formatting
Knowing how to deal with dates, you know how to deal with numbers. The only difference is the list of options:
Currency Formatting
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.
Plural Rules Formatting
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
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:
Conclusion
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.
That’s it!
If you have any questions or feedback, let me know in the comments down below or ping me on Twitter.
If this was useful, please click the clap 👏 button down below a few times to show your support! ⬇⬇ 🙏🏼
Here are more articles I’ve written:
Thanks for reading ❤️