πŸ“œ ⬆️ ⬇️

Localization of Node.js applications Part 3: localization in action

From the translator: This is the eleventh article from the Node.js series from the Mozilla Identity team that deals with the Persona project.





Use strings


So, first we connected the i18n-abide module to our application and wrapped the lines in gettext calls. Then our translation team translated these lines and we have a ready translation for each language.
')
Now let's make our Node.js application work with localized strings. Translations in the form of po-files are in the file system in the following form:

locale en LC_MESSAGES messages.po de LC_MESSAGES messages.po es LC_MESSAGES messages.po 

During the execution of our application, we need to extract the translated strings from these files. There are two ways to do this:


Both of these methods require strings to be in JSON format. When translated on the server side, they are loaded when the application starts, and the client loads them using HTTP requests (or you can include them in the minified JavaScript file during the build process).

Since our translation system is compatible with GNU Gettext, there is also a third way - the node-gettext module. It is quite effective when translating on the server side.

Then we will consider the first method, since this is the most familiar way of using i18n-abide.

compile-json


How do we extract strings from po-files and translate them into JSON format? Our script for this purpose is called compile-json.

Suppose that our translation files are in the locale folder in the root of our project, and the JSON files must be in static / i18n:

 $ mkdir -p static/i18n $ ./node_modules/.bin/compile-json locale static/i18n 

We get the following file structure:

 static i18n en messages.json messages.js de messages.json messages.js es messages.json messages.js 

compile-json goes through all po-files and calls for each of them a script po2json.js, which generates the corresponding .json and .js files.

Take for example the Spanish translation:

 # Spanish translations for PACKAGE package. # Copyright (C) 2013 THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Austin King <ozten@localhost>, 2013. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-06-24 09:50+0200\n" "PO-Revision-Date: 2013-04-24 16:42-0700\n" "Last-Translator: Austin King <ozten@nutria.localdomain>\n" "Language-Team: Spanish\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: /home/ozten/abide-demo/views/homepage.ejs:3 msgid "Mozilla Persona" msgstr "Mozilla Personidada" 

It will look like this:

 "messages": { "": { "Project-Id-Version": " PACKAGE VERSION\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2012-06-24 09:50+0200\nPO-Revision-Date: 2013-04-24 16:42-0700\nLast-Translator: Austin King <ozten@nutria.localdomain>\nLanguage-Team: German\nLanguage: de\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=(n != 1);\n" }, "Mozilla Persona": [ null, "Mozilla Personidada" ] } } 

We can use this file both on the server side, in the Node.js application, and on the client side, pulling it with an ajax request.

The static directory is open for access from the Internet, so to get a Spanish translation you need to request the file /i18n/es/messages.json.

The static directory is a common convention of the express framework, you can use any other. You can submit translation files using Node.js, as well as any web server, such as Nginx.

The po-files themselves can, but it is not necessary to lay out in production.

Configuration

i18n-abide needs configuration to know which languages ​​are available and where the translation files are located. An example of such a configuration was given in the first part:

 app.use(i18n.abide({ supported_languages: ['en-US', 'de', 'es', 'zh-TW'], default_lang: 'en-US', translation_directory: 'static/i18n' })); 


Note that the translation_directory parameter is needed only for gettext on the server side.

i18n-abide will try to choose from the languages ​​listed in supported_languages, the most appropriate.

We start motors


So, when the configuration is set and the translation is ready for at least one locale, you can begin. In the browser, we change the preferred language to the one we want to check in the running application:

image

Go to one of the pages of the application. It should be displayed in translation. For example, the Greek version of Persona:

image

gobbledygook


In order to test a multilingual application even before the translations are ready, we have created a special module inspired by the David Bowie video " Labyrinth ".

To use it, simply add a locale for which the translation is not yet ready, for example, it-CH in the supported_languages ​​and debug_lang parameters:

 app.use(i18n.abide({ supported_languages: ['en-US', 'de', 'es', 'zh-TW', 'it-CH'], debug_lang: 'it-CH', ... 

Now, if you switch the browser to use Swiss Italian, i18n-abide will use gobbledygook for this language.

image

This is a convenient way to make sure that the website design also supports languages ​​with right-to-left writing, for example, Hebrew:

image

Go deeper


We have only touched the surface of internationalization and localization. When developing a multilingual application on Node.js, you will encounter many interesting nuances and unexpected pitfalls. Here are some of them.

String interpolation

I18n-abide has a format function that can be used both on the client side and on the server side. It accepts a formatted string and substitutes the parameter values ​​at run time. It can be used in two versions:


The format can be used to less clutter po-files with HTML markup. Take three examples:

 <%= gettext('<p>Buy <a href="/buy?prod=blue&type=ticket">Blue Tickets</a> Now!</p>') %> <p><%= format(gettext('Buy <a href="%s">Blue Tickets</a> Now!'), ['/buy?prod=blue&type=ticket']) %></p> <p><%= format(gettext('Buy <a href="%(url)s">Blue Tickets</a> Now!'), {url: '/buy?prod=blue&type=ticket'}) %></p> 

In the po-file, the lines for each of them will look like this:

 msgid "<p>Buy <a href=\"/buy?prod=blue&type=ticket\">Blue Tickets</a> Now!</p>" msgid "Buy <a href="%s">Blue Tickets</a> Now!" msgid "Buy <a href="%(url)s">Blue Tickets</a> Now!" 

In the first example, the string includes the tag
 . !   -  ,      .    URL. 

format , , HTML, , .

, . , URL. - .

- , :

<p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>

, .

. CSS, .

CSS. . , .


. , 100%- , , .

, . .

, .


: " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "
. ! - , . URL.

format , , HTML, , .

, . , URL. - .

- , :

<p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


, .

. CSS, .

CSS. . , .


. , 100%- , , .

, . .

, .


: " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "
. ! - , . URL.

format , , HTML, , .

, . , URL. - .

- , :

<p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


, .

. CSS, .

CSS. . , .


. , 100%- , , .

, . .

, .


: " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "
. ! - , . URL.

format , , HTML, , .

, . , URL. - .

- , :

<p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


, .

. CSS, .

CSS. . , .


. , 100%- , , .

, . .

, .


: " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "
. ! - , . URL.

format , , HTML, , .

, . , URL. - .

- , :

<p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


, .

. CSS, .

CSS. . , .


. , 100%- , , .

, . .

, .


: " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "
. ! - , . URL.

format , , HTML, , .

, . , URL. - .

- , :

<p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


, .

. CSS, .

CSS. . , .


. , 100%- , , .

, . .

, .


: " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "

. ! - , . URL.

format , , HTML, , .

, . , URL. - .

- , :

<p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


, .

. CSS, .

CSS. . , .


. , 100%- , , .

, . .

, .


: " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "
. ! - , . URL.

format , , HTML, , .

, . , URL. - .

- , :

<p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


, .

. CSS, .

CSS. . , .


. , 100%- , , .

, . .

, .


: " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "
  1. . ! - , . URL.

    format , , HTML, , .

    , . , URL. - .

    - , :

    <p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


    , .

    . CSS, .

    CSS. . , .


    . , 100%- , , .

    , . .

    , .


    : " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "
  2. . ! - , . URL.

    format , , HTML, , .

    , . , URL. - .

    - , :

    <p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


    , .

    . CSS, .

    CSS. . , .


    . , 100%- , , .

    , . .

    , .


    : " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "
  3. . ! - , . URL.

    format , , HTML, , .

    , . , URL. - .

    - , :

    <p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


    , .

    . CSS, .

    CSS. . , .


    . , 100%- , , .

    , . .

    , .


    : " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "
  4. . ! - , . URL.

    format , , HTML, , .

    , . , URL. - .

    - , :

    <p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


    , .

    . CSS, .

    CSS. . , .


    . , 100%- , , .

    , . .

    , .


    : " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "
  5. . ! - , . URL.

    format , , HTML, , .

    , . , URL. - .

    - , :

    <p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


    , .

    . CSS, .

    CSS. . , .


    . , 100%- , , .

    , . .

    , .


    : " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "
  6. . ! - , . URL.

    format , , HTML, , .

    , . , URL. - .

    - , :

    <p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


    , .

    . CSS, .

    CSS. . , .


    . , 100%- , , .

    , . .

    , .


    : " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "
  7. . ! - , . URL.

    format , , HTML, , .

    , . , URL. - .

    - , :

    <p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


    , .

    . CSS, .

    CSS. . , .


    . , 100%- , , .

    , . .

    , .


    : " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "
  8. . ! - , . URL.

    format , , HTML, , .

    , . , URL. - .

    - , :

    <p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


    , .

    . CSS, .

    CSS. . , .


    . , 100%- , , .

    , . .

    , .


    : " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "
  9. . ! - , . URL.

    format , , HTML, , .

    , . , URL. - .

    - , :

    <p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


    , .

    . CSS, .

    CSS. . , .


    . , 100%- , , .

    , . .

    , .


    : " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "
  10. . ! - , . URL.

    format , , HTML, , .

    , . , URL. - .

    - , :

    <p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


    , .

    . CSS, .

    CSS. . , .


    . , 100%- , , .

    , . .

    , .


    : " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "
  11. . ! - , . URL.

    format , , HTML, , .

    , . , URL. - .

    - , :

    <p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


    , .

    . CSS, .

    CSS. . , .


    . , 100%- , , .

    , . .

    , .


    : " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "
  12. . ! - , . URL.

    format , , HTML, , .

    , . , URL. - .

    - , :

    <p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


    , .

    . CSS, .

    CSS. . , .


    . , 100%- , , .

    , . .

    , .


    : " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "
. ! - , . URL.

format , , HTML, , .

, . , URL. - .

- , :

<p><%= format(gettext('Welcome back, %(user_name)s'), {user_name: user.name}) %></p>


, .

. CSS, .

CSS. . , .


. , 100%- , , .

, . .

, .


: " Node.js " " Node " " , " " . 1 β€” , , " " , " " . 2 β€” etagify " " - node-convict " " . 3 β€” " " Node.js. 1 " " Node.js. 2: " " Node.js. 3: " " Awsbox - PaaS- Node.js Amazon "

Source: https://habr.com/ru/post/198252/


All Articles