
Recently, I had to deal with the internationalization of a web application on Node.js + Express, which I am currently working on, and I think it turned out pretty good (foreign users are very satisfied, and I see a noticeable influx of traffic from non-English speaking countries). The internationalization strategy, which I will describe, is not too tightly tied to Node and can suit any web application.
I often had to use multilingual sites or visit English-speaking sites from around the world, so I was well aware of the requirements that internationalization should meet:
- Full equality between languages. The same content should be available to everyone.
- Different language versions must be contained in subdomains. Using separate top-level domains for different languages is too expensive, and not necessary. In addition, it is more convenient to switch between languages by editing the address bar.
- No automatic translation. There is nothing worse than to stumble upon an unreadable version of the site due to the fact that the server has substituted a machine translation curve, guided by your IP or computer language settings. The same URL should always point to the same language version.
- No automatic redirects to the national version of the site. Approximately for the same reasons as in the previous paragraph - if I go to foo.com, do not send me to es.foo.com just because the server decided that I am from Spain. Instead, it’s better to show the notification in (presumably) my native language and suggest manually switching to the national version.
In the end, you should get something like this:
- domain.com - the main (English) version;
- ja.domain.com - Japanese version;
- XX.domain.com - other languages.
Equality between languages and the use of subdomains make it possible to switch between the languages of any page simply by adding a national subdomain:
')
- domain.com/search?q=mountain
- ja.domain.com/search?q=mountain
Both pages should work and look the same, except that the second one will be in Japanese.
In the header of the page I can easily put a link to the same page in another language:

In addition, you can use the technique
rel = "alternate" hreflang = "x" to help Google search engines better understand the structure of my site. If I put this line in the page title, Google will show the localized version in the search results.
<link rel="alternate" hreflang="ja" href="http://ja.domain.com/" />
Server part
It is very important to help the user find the content he needs, especially if you do not want an automatic translation or redirect. Links to alternative languages in the header of the site are already good, but it is even better to display a notification at the top inviting the user to switch to his native language (and here the attempt to automatically guess the language is quite appropriate):
As it turned out, this is not so easy to implement. The easiest way is to look in the
Accepted Language field of the request header, but this only works if your page is completely dynamic and never cached.
If not, then you need to consider how and where the cache is connected.
In my case, I used Nginx in front of several Node / Express servers. So, everything that was issued by the application server on Node was cached, including notifications with the suggestion to switch the language.
As a result, you have to manage this notification on the client side. And here we are waiting for another obstacle: on the client using DOM / JavaScript, it is impossible to reliably determine the desired language.
Therefore, we need the server to additionally inform the application about the desired language.
For this, I used the Nginx
AcceptedLanguage module. I set a cookie indicating the desired language and passed it to the client. Here is what the Nginx configuration looked like:
set_from_accept_language $lang en ja; add_header Set-Cookie lang=$lang;
Now all I had to do in the client part was to read the cookie and display a notification if the desired language did not match the page language.
Thus, Nginx continues to aggressively cache all content, and the client correctly displays a message offering to visit the localized version of the page.
The logic of internationalization
I wrote my
Node.js module for internationalization. He uses the following strategy:
- All translations are stored in JSON files;
- These files are used for translation on the fly at the time of use;
- Translation is carried out in the standard way, through a call
__("Some string")
. If the translation exists, the string "Some string"
is replaced with the translation; if not, it is used directly.
Since a limited set of servers processes multiple requests at the same time, the internationalization logic cannot be shared, it should work separately for each request. I met solutions, for example
i18n-node , which assume that the server will simultaneously issue pages in only one language. In practice, this does not work, especially in the asynchronous world of Node.js.
If the incoming request sets the preferred language for a common internationalization object, this may result in the server issuing an incorrect language version in response to another request:
At a minimum, you need to make sure that the information about the preferred language is not lost during the execution of the current request (this is implemented in my module).
In practice, this means that it is necessary to add the
i18n
property to the
request
object, just as middleware is usually added to Express:
app.use(function(req, res, next) { req.i18n = new i18n(); next(); });
Work organization
The logic of internationalization behaves differently in development mode and production.
In development mode:
- Translation files are loaded at every request;
- Translation files are updated dynamically after any changes;
- Warning and debug messages are displayed.
In production:
- All translation files are cached;
- Files are never updated dynamically;
- Warnings and debug messages are not displayed.
Two key differences are dynamic loading and updating translations. During development, it is useful to update them with each change in order to see the result of your actions, and not to forget to translate a single line. In production, it’s worthwhile to consider them as static files - it’s stupid to jerk the disk on every request.
Code organization
Strings that need translation can be anywhere: in the application code, templates, and even (God forbid!) In styles.
In my application, I immediately made sure that there were no text lines in JavaScript and CSS. If I needed to dynamically generate some text in an application, I would do it through templates using something like
my micro template .
I consider it very important to avoid getting any strings to be translated into CSS or JavaScript files, since these files should be cashed or even placed on a CDN. Naturally, you can create several language versions of all the JavaScript and CSS files during project build, but I prefer not to complicate the build process.
The only exception to my application when the code contains translatable strings is the code of Express views, where I use the
i18n
object attached to the query:
module.exports = { index: function(req, res) { req.render("index", { title: req.i18n.__("My Site Title"), desc: req.i18n.__("My Site Description") }); } };
I use the
swig template engine, but the translation technique will be about the same for any template engine:
{% extends "page.swig" %} {% block content %} <h1>{{ __("Welcome to:") }} {{ title }}</h1> <p>{{ desc }}</p> {% endblock %}
The string wrapped in a call to
__(...)
is replaced with a translation.
Actually translation
So far, I myself have been translating my application, without resorting to the help of third-party translators. The application is still very small, there are only a few dozen lines for translation.
I can share a few tricks that can help you hold out longer without bringing in a professional translator:
- Use ready-made translations from open source projects. There are many open source projects and frameworks containing professional translations of interface elements. For example, the Drupal framework contains many such translations in a convenient format. I found several ready-made phrases there.
- Look for similar multilingual projects. If there are qualitative examples of multilingual sites that implement similar to your functionality, you can take a translation from there. My current application is a search engine, so many lines from Google pages exactly matched mine.
- Google translate. Yes, yes, I know what you are thinking now, but I am very impressed with the progress of Google Translate lately, especially when translating individual words and expressions. Now there even appeared different versions of the translation indicating their likelihood, which is very good.
findings
I have been working on the internationalization of the site just a couple of weeks, so for sure a lot will change when the site grows. Nevertheless, the translation has already yielded tangible results, the number of visits has increased noticeably, largely due to the fact that Google has indexed localized pages. My internationalization module is a good support for me, and I hope that I can facilitate the work on translation and others.