i18n with tagged template literals in ECMAScript 2015
One of the new features of ECMAScript 2015 are template literals.
The simplest use cases for template literals are creating multiline strings, and doing string interpolation.
Multiline string:
String interpolation:
You can also tag template literals by adding an expression before it.
When template literals are tagged, the strings and place holder substitutions are passed to
the tag function, and whatever returns from the function is the resulting value.
This feature opens up some interesting syntactic sugar for producing and manipulating
content. One such example is Internationalization or i18n.
In this post we’ll look at:
Implementing a basic i18n tag function for translating messages.
Localization (l10n) of values using the Intl native object.
This post requires some knowledge of new ES6 features such as array comprehensions,
arrow functions, spreads, etc. If you are new to ES6, I suggest brushing up on the
basics. I found Ariya’s posts on ES6 to be useful.
The i18n tag
Let’s start with an example of how we want to use the i18n tag.
Here, we are expecting to translate the literals into the language of our choosing.
We are also adding optional annotations to the place holders. In this case, the
amount place holder has the :c(CAD) annotation, which marks it as a currency type
that is in Canadian Dollars (CAD).
If we translated this string to German, for example, we expect the result to be
'Hallo Bob, Sie haben 1.234,56 $CA auf Ihrem Bankkonto.' Note that the currency
amount has thousands and decimal separator as per German locale.
Implementation
We’ll allow configuration of our i18n tag through I18n.use.
Let’s start with implementing the I18n.use method, which should return the
tag function.
The translationString should match the keys in our message bundle. In our previous
German translation example, this would be 'Hello {0}, you have {1} in your bank account.'.
The localizedValues array is a map of values in their localized
forms. For example, in German 1000.12 should show as '1.000,12'.
Now, I will show a full implementation of the library.
One important thing to note here is that the _localizers are using the
native Intl
object to localize values.
ES Internationalization API
The Internationalization API
adds new methods to native types, such as Number.prototype.toLocaleString() and Date.prototype.toLocaleDateString().
These methods delegate to three objects: Intl.Collator, Intl.NumberFormat, and Intl.DateTimeFormat. The
collator object enables string comparisons to be language sensitive, while the other two are pretty self-explanatory.
An option of the NumberFormat we took advantage of in our implementation is the style. The style value can be
decimal (default), currency, or percent. In our case, we used both the currency and decimal support for the :c and
:n annotations respectively. For number localization, we also support rounding of fractional digits through the use of
minimumFractionDigits and maximumFractionDigits options.
Example usages
Now, we can translate our original string into different languages by configuring
the I18n object using corresponding locales and message bundle.
The tagged template literals can be used to provide powerful DSL to JavaScript libraries.
I chose to try my hand at adding simple i18n support. There are still a lot of pieces
missing to provide a comprehensive i18n solution, but I hope to have shown how one
may go about building out this framework.
Pluralization can be a very tricky thing to tackle, and I am not qualified as of this
writing to understand all the nuances of implementing this feature. Perhaps with
a little more thinking, there may be some hope to add it in as well.
In any case, I do see a lot of potential in tagged template literals to add clean, clear
syntax for library functionalities.
Edit (2014/03/21): Fixed German translation and added a conclusion section.
Edit (2014/10/01): Updated examples to not use array comprehension since they were removed in ES6