Web internationalisation (i18n) lessons I’ve learned the hard way

Justus Romijn
Frontmen
Published in
4 min readNov 13, 2017
The famous Babelfish from Douglas Adams: The Hitchhikers Guide To The Galaxy.

With plenty of international web projects under my belt, I’ve encountered different implementations of “Internationalisation” (referred to as i18n). This can be a tedious part of your application if you don’t do it right. Missing translations, keys without context, or even English translations in the French translation file are just a couple of examples. There are some rules of thumb which I use that greatly help to reduce these mistakes and can give you a common ground with designers, translators and developers to get a smooth multilingual experience of your app.

Don’t use a language as a translation source. Use translation keys.

There are many libraries that offer you the option to use a language (often English) as the source language, and you can start translating from there. The advantage you get is that you don’t have to translate “to” English so it saves you time. However, now you have semantic keys. So if someone doesn’t know the application that well, they might (and will) translate it completely wrong based on the lack of context. Simple strings like Next or Continue can occur at many places in the application, but might have very different meaning depending on their position. The literal translation to French, Spanish or other languages might be very inappropriate.

Translation keys however are language-agnostic, and you can add a lot of context to them to help translators indicate what purpose the text serves. Instead of having to cope with strings like Continue they can translate form:step_1:primary_button which in turn might show up in a reference document where they can see the English version of it, in context of a page. You can of course also have some generic buttons or translations, for example generic:help or generic:continue for those single-word translations that are context independent. Still, in the latter case you might want to swap the English version of Continue to Next, so that is another example of how fast things can go wrong if you mix up keys with translations.

Use a naming convention for translations that include markup

Sometimes there are strings that need to be translated that also include (html) markup like boldness, italic, underlines, even links. Depending on your setup, you might support all of this. Make sure to create a convention so that translators and developers know when to expect markup instead of flat strings. A prefix or suffix is enough, for example a -html suffix would look like about:content-html

Support wildcard injection

It won’t be long before you need translations that contain a programmatic value within them, for example your cart contains 10 items where the value 10 is inserted dynamically. Make sure to use a proper wildcard placeholder that will not be accidentally created by the language itself. Also make sure the wildcard is named instead of counted, because the order of the wildcards might change depending on the language. So the cart example key would be something like your cart contains !{itemCount} items.

Take pluralisation into account

You already see at the example above that pluralisation becomes an issue.
your cart contains 1 items
That looks horrible! Also again a simple naming convention can help out here, although there are of course libraries that have nicely build-in support for this. But without all of that, you can still keep yourself sane by using a convention.
shopping:cart_count:singular: “your cart contains !{itemCount} item.”
shopping:cart_count:plural: “your cart contains !{itemCount} items.”

Example of a translation file

The organisation of the translation file into understandable chunks is application-dependent, but you can always fall back on a design/component based approach to split it up. This is an example:

button:forward: “Next”
button:backward: “Previous”
page:about:title: “About this app”
page:about:content-html: “<p>About blabla.</p>”
page:stats:viewcount-dynamic-singular: “Your site has !{viewCount} view”.
page:stats:viewcount-dynamic-plural: “Your site has !{viewCount} views”.
page:stats:viewcount-dynamic-none: “Your site has no views yet.”
page:stats:wallet-dynamic: “Current credit: !{credit}”
page:stats:primary-button: “Enable on my site”

The keys might be a bit long, you can choose to use symbols or single letters to indicate the usage (d for dynamic, s/p/n for pluralisation, m for markup) but I like to keep it written out so it is understandable for everyone who jumps into these files and I don’t have to document anything.

To sum it up:

  • Use language-agnostic keys to avoid losing context;
  • Use naming conventions in keys to indicate the support of markup, dynamic values and/or plurality;
  • Use named injections to avoid issues with the order;

Hopefully it will help you out in your next project that extends beyond the border of a single language.

Note: when you are really going truly global, beyond the limit of Latin characters, you will encounter deeper issues that have to do with character sets, encodings, reading direction, and much more! I am aware of that trouble, but cannot help you with it. Good luck!

--

--

Justus Romijn
Frontmen

I’m a frontend developer at Frontmen, a company that focuses on frontend only. Beside my work I also enjoy tennis & table tennis, music, movies and games!