13.2.5. 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:
Extract strings to translate from sources to
.pot
file (extract
)Create or update
.po
files from.pot
files (update
)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 --locale 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
13.2.5.1. Server sideο
13.2.5.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.lib.i18n.trstr.TrStr
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.
π bar
βββ π template
β βββ π section.mako
βββ π __init__.py
βββ π util.py
from nextgisweb.lib.i18n import trstr_factory
_ = trstr_factory('bar')
Function nextgisweb.lib.i18n.trstr.trstr_factory()
allows you to
simplify creation of strings TrStr
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.
13.2.5.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 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:
<% 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.
13.2.5.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.
13.2.5.2.1. Modern JavaScriptο
Consider the following directory structure of bar
component:
π bar
βββ π nodepkg
βββ π bar
βββ π some-module.js
βββ π package.json
And here is the simple example, where string extraction and translation work:
import i18n from "@nextgisweb/pyramid/i18n!";
const translated = i18n.gettext("Some message for translation");
console.log("Localized message: " + translated);
13.2.5.2.2. Old-style JavaScriptο
π bar
βββ π amd
βββ π ngw-bar
βββ π mod-a.js
βββ π mod-b.js
βββ π template
βββ π html.hbs
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.
13.2.5.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.
define([
"@nextgisweb/pyramid/i18n!",
"dojo/text!.template/html.hbs"
], function (i18n, template) {
var translated = i18n.renderTemplate(template);
alert(translated);
});
<strong>{{gettext "Another message for translation"}}</strong>
In case of a template-based widget, using handlebars for internationalization would look like the original example in the dijit documentation:
<div data-dojo-type="${baseClass}">
<input data-dojo-type="dijit/form/TextBox"
data-dojo-props="placeHolder: {{gettextString 'Placeholder'}}"/>
<button data-dojo-type="dijit/form/Button">{{gettext 'Button'}}</button>
</div>
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)
});
});
Warning
Pay attention to quotes escaping inside attribute values such as
data-dojo-props
and use gettextString
there instead of gettext
.
Itβll escape quotes keeping javascript code valid.
Note
According to the settings, specified in the babel.cfg file, widget templates
should have the .hbs
extension and be located inside template
directory.
13.2.5.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