10.2.3. 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:
Extracting strings to translate from sources to pot-file (
extract
)Creating or updating po-files based on pot-files (
init
andupdate
)Compiling 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 are not belongs to any component.
Translations of each component exists within its own domain
in terms of the
gettext library. In one component one 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
foo
package. In this case, the directory structure will look like this:
foo
├── foo
│ └── bar
│ └── locale
└── setup.py
Extract strings from source code files with default settings:
(env) $ nextgisweb-i18n --package foo extract bar
This will create a pot-file 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 foo init bar ru
As a result, the po-file 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 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.
foo
├── 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 foo extract bar
(env) $ nextgisweb-i18n --package foo update bar
(env) $ nano foo/bar/locale/ru.po
(env) $ nextgisweb-i18n --package foo compile bar
10.2.3.1. Server side¶
10.2.3.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 foo
package``.
bar
├── __init__.py
├── util.py
└── template
└── section.mako
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.
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:
@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.
10.2.3.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.
<% from 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:
<% from 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.
10.2.3.2. Client side¶
10.2.3.2.1. Javascript¶
When executing javascript code on the client side, user preferences are already known and there is no need for two-step processing. Translation and marking strings for translation can be combined in one function. To work with gettext on the client side, the jed library is used. Source jed-files for which are prepared on the server during compilation of po-files.
bar
└── amd
└── ngw-bar
├── mod-a.js
├── mod-b.js
└── template
└── html.hbs
define([
'ngw-pyramid/i18n!bar'
], 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.
10.2.3.2.2. 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.
define([
"ngw-pyramid/hbs-i18n",
"dojo/text!.template/html.hbs",
"ngw-pyramid/i18n!bar"
], function (hbsI18n, template, i18n) {
var translated = hbsI18n(template, i18n);
alert(translated);
});
<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",
"ngw-pyramid/hbs-i18n",
"dojo/text!./template/SomeWidget.hbs",
"ngw-pyramid/i18n!comp"
], function(declare, _WidgetBase, _TemplatedMixin, hbsI18n, template, i18n) {
return declare([_WidgetBase, _TemplatedMixin], {
templateString: hbsI18n(template, i18n)
});
});
Note
According to the settings, specified in the babel.cfg file, widget templates
should have the .hbs
extension and be located inside template
directory.
10.2.3.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