11.2.4. Internationalization and localizationΒΆ

Internationalization and localization of NextGIS Web is powered by gettext and babel libraries. The procedure for working with strings is standard for projects based on gettext:

  1. Extract strings to translate from sources to .pot file (extract)

  2. Create or update .po files from .pot files (update)

  3. Compile .po files into .mo files (compile)

All lines that are shown to the user must be written in sources in English and only then translated into other languages.

Translation is performed for each component of NextGIS Web separately. As a result, there is no way to internationalize strings that do not belong to any component.

Translations of each component exist within its own domain in terms of the gettext library. For a component a certain line can be translated in one way, and in another - in another, even without the use of contexts.

All actions on translations are performed using nextgisweb-i18n command line utility from the nextgisweb package. This utility is an add-on to the pybabel utility from babel package with default settings preset.

Let’s consider localization to Russian on the example of bar component from nextgisweb_foo package. In this case, the directory structure will look like this:

πŸ— nextgisweb_foo
β”œβ”€β”€ πŸ— nextgisweb_foo
β”‚   └── πŸ— bar
β”‚       └── πŸ—€ locale
└── πŸ—Ž setup.py

Extract strings from source code files with default settings:

(env) $ nextgisweb-i18n --package nextgisweb_foo extract bar

This will create a .pot file nextgisweb_foo/bar/locale/.pot. Since this file can be generated at any time, there is no need to put it inside the source control system.

Create a .po file for the Russian language based on the .pot file:

(env) $ nextgisweb-i18n --package nextgisweb_foo update bar ru

As a result, the .po file nextgisweb_foo/bar/locale/ru.po will be created. This file must be filled in accordance with the translation of the strings in it using a text editor or a specialized .po file editor.

After this, we compile .po files into .mo files, which also do not need to be placed in the version control system:

(env) $ nextgisweb-i18n --package nextgisweb_foo compile bar

The resulting directory structure is given bellow. The files bar.jed are analogous to the mo file for the javascript library jed, which is used for client-side internationalization. These files are also created at the compilation stage.

πŸ— nextgisweb_foo
β”œβ”€β”€ πŸ— nextgisweb_foo
β”‚   └── πŸ— bar
β”‚       └── πŸ— locale
β”‚           β”œβ”€β”€ πŸ—Ž .pot
β”‚           β”œβ”€β”€ πŸ—Ž ru.mo
β”‚           └── πŸ—Ž ru.po
└── πŸ—Ž setup.py

This completes the initial internationalization, after some time new lines may have be added to the package, in this case you need to re-extract the lines, automatically update the .po file, edit it and compile again:

(env) $ nextgisweb-i18n --package nextgisweb_foo extract bar
(env) $ nextgisweb-i18n --package nextgisweb_foo update bar
(env) $ nano nextgisweb_foo/bar/locale/ru.po
(env) $ nextgisweb-i18n --package nextgisweb_foo compile bar

11.2.4.1. Server sideΒΆ

11.2.4.1.1. Python codeΒΆ

To be able to extract strings for translation, they must be marked up appropriately. Below is described how to do this, as well as how to ensure the display of already translated strings to the user.

Since python code is executed on a server, the same application instance must be able to serve users with different locales, it is necessary to use a two-step work with strings: first, the string is marked as requiring translation, then before displaying it to the user, it’s translated according to the user’s preferences.

The class nextgisweb.i18n.trstring.TrString solves this problem, which is similar to the class translationstring.TranslationString, but with some additional convenience in terms of string interpolation. Let’s look at the example of the bar component from the nextgisweb_foo package.

Listing 11.1. Sample directory structureΒΆ
πŸ— bar
β”œβ”€β”€ πŸ— template
β”‚   └── πŸ—Ž section.mako
β”œβ”€β”€ πŸ—Ž __init__.py
└── πŸ—Ž util.py
Listing 11.2. File util.pyΒΆ
from nextgisweb.i18n import trstring_factory
_ = trstring_factory('bar')

Function nextgisweb.i18n.trstring.trstring_factory() allows you to simplify creation of strings TrString with a predefined domain, which is specified in the function parameters. For convenience, both the function and the class are also available for import from the module nextgisweb.i18n, as shown in the examples.

Listing 11.3. File __init__.py #1ΒΆ
from .util import _
def something():
    return _('Some message for translation')

Usage of the underscore character is necessary for extraction of translation strings, so you can’t import it with a different name from .util import _ as blah, it will break extraction process.

For string output in accordance with the user’s preferences (one user may want English, the other Russian), you need to translate the string using the request.localizer.translate(trstring) method:

Listing 11.4. File __init__.py #2ΒΆ
@view_config(renderer='string')
def view(request):
    return request.localizer.translate(something())

Note

Since request only makes sense in the web application, this means that currently it isn’t possible to use localization in the nextgisweb command line utilities.

11.2.4.1.2. Mako templatesΒΆ

Some of the strings that require translation are also contained in the mako-templates. In fact, the work of mako templates is not much different from the python code: first, we mark the string for translation with a special function, then we need to translate through request, taking into account the user’s preferences.

Listing 11.5. File template/section.mako #1ΒΆ
<% from nextgisweb_foo.bar.util import _ %>
<div>${request.localizer.translate(_("Another message for translation"))}</div>

To shorten this long notation a bit, a tr() function has been added to the mako-template’s context, which does the same. The example below is completely equivalent to the previous one:

Listing 11.6. File template/section.mako #2ΒΆ
<% from nextgisweb_foo.bar.util import _ %>
<div>${tr(_("Another message for translation"))}</div>

Note

Unfortunately, it isn’t possible use this function as a modifier ${expression | tr}. In this case, the result of the standard modifier n, that is markupsafe.Markup gets into the function.

In order to track that all strings requiring translation were translated when outputting in the template in debug mode (setting debug of the component core) a special modifier is added to the standard modifier n, which checks whether the translation was performed using request.localizer and if not, then the corresponding warning is displayed in the log.

11.2.4.2. Client sideΒΆ

When executing client-side code, user preferences are already known and there is no need for two-step processing. Translation and marking strings for translation can be combined into one function. The jed library is used as gettext library implementation with .jed files precompiled from .po files on the server side.

11.2.4.2.1. Modern JavaScriptΒΆ

Consider the following directory structure of bar component:

Listing 11.7. Directory structureΒΆ
πŸ— bar
└── πŸ— nodepkg
    └── πŸ— bar
        β”œβ”€β”€ πŸ—Ž some-module.js
        └── πŸ—Ž package.json

And here is the simple example, where string extraction and translation work:

Listing 11.8. File bar/nodepkg/bar/some-module.jsΒΆ
import i18n from "@nextgisweb/pyramid/i18n!";

const translated = i18n.gettext("Some message for translation");
console.log("Localized message: " + translated);

11.2.4.2.2. Old-style JavaScriptΒΆ

Listing 11.9. Directory structureΒΆ
πŸ— bar
└── πŸ— amd
    └── πŸ— ngw-bar
        β”œβ”€β”€ πŸ—Ž mod-a.js
        β”œβ”€β”€ πŸ—Ž mod-b.js
        └── πŸ— template
            └── πŸ—Ž html.hbs
Listing 11.10. File amd/ngw-bar/mod-a.jsΒΆ
define([
    "@nextgisweb/pyramid/i18n!"
], function (i18n) {
    var translated = i18n.gettext("Some message for translation");
    alert(translated);
});

As a result of loading this module, a message will be displayed, translated in exactly the same way as on the server. In this case client and server use the same set of strings.

11.2.4.2.3. HandlebarsΒΆ

Dijit-widgets often use template-based construction, which may also require internationalization. To do this, it is possible to first pass the template through the template engine handlebars.

Listing 11.11. File amd/ngw-bar/mod-b.jsΒΆ
define([
    "@nextgisweb/pyramid/i18n!",
    "dojo/text!.template/html.hbs"
], function (i18n, template) {
    var translated = i18n.renderTemplate(template);
    alert(translated);
});
Listing 11.12. amd/ngw-bar/html.hbsΒΆ
<strong>{{gettext "Another message for translation"}}</strong>

Note

To extract strings from handlebars templates, you need to have nodejs installed. This allows you to use the original handlebars javascript parser to handle templates.

In case of a template-based widget, using handlebars for internationalization would look like the original example in the dijit documentation:

define([
    "dojo/_base/declare",
    "dijit/_WidgetBase",
    "dijit/_TemplatedMixin",
    "@nextgisweb/pyramid/i18n!",
    "dojo/text!./template/SomeWidget.hbs"
], function(declare, _WidgetBase, _TemplatedMixin, i18n, template) {
    return declare([_WidgetBase, _TemplatedMixin], {
        templateString: i18n.renderTemplate(template)
    });
});

Note

According to the settings, specified in the babel.cfg file, widget templates should have the .hbs extension and be located inside template directory.

11.2.4.3. Configuration optionsΒΆ

The default language is determined by the locale.default setting of the core component. English is used by default. Thus, in order for all messages to be displayed in Russian in the config.ini, you need to specify:

[core]
locale.default = ru