- PVSM.RU - https://www.pvsm.ru -

Локализация приложений Node.js. Часть 3: локализация в действии

Локализация приложений Node.js. Часть 3: локализация в действииОт переводчика: Это деcятая статья из цикла о Node.js [1] от команды Mozilla Identity, которая занимается проектом Persona [2].


Использование строк

Итак, сначала мы подключили к нашему приложению модуль i18n-abide и обернули строки в вызовы gettext. Затем наша команда переводчиков перевела эти строки и у нас есть готовый перевод для каждого языка.

Теперь давайте заставим наше приложение Node.js работать с локализованными строками. Переводы в виде po-файлов лежат в файловой системе в таком виде:

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

Во время выполнения нашего приложения нужно извлекать переведённые строки из этих файлов. Есть два способа сделать это:

  • на стороне сервера с помощью всё той же функции gettext из модуля i18n-abide;
  • на стороне клиента с помощью скрипта gettext.js, который также входит в состав i18n-abide.

Оба этих метода требуют, чтобы строки были в формате JSON. При переводе на стороне сервера они загружаются при старте приложения, а клиент загружает их с помощью HTTP-запросов (или можно включить их в минифицированный файл JavaScript в процессе сборки).

Так как наша система перевода совместима с GNU Gettext, есть ещё и третий путь — модуль node-gettext [13]. Он довольно эффективен при переводе на стороне сервера.

Дальше мы будем рассматривать первый способ, так как это наиболее привычный способ использования i18n-abide.

compile-json

Как мы извлекаем строки из po-файлов и переводим их в формат JSON? Наш скрипт для этой цели называется compile-json.

Предположим, что наши файлы переводов лежат в папке locale в корне нашего проекта, а файлы JSON должны лежать в static/i18n:

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

Мы получим такую файловую структуру:

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

compile-json проходит по всем po-файлам и вызывает для каждого из них скрипт po2json.js, который генерирует соответствтующие файлы .json и .js.

Возьмём к примеру испанский перевод:

# 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 VERSIONn"
"Report-Msgid-Bugs-To: n"
"POT-Creation-Date: 2012-06-24 09:50+0200n"
"PO-Revision-Date: 2013-04-24 16:42-0700n"
"Last-Translator: Austin King <ozten@nutria.localdomain>n"
"Language-Team: Spanishn"
"Language: esn"
"MIME-Version: 1.0n"
"Content-Type: text/plain; charset=UTF-8n"
"Content-Transfer-Encoding: 8bitn"
"Plural-Forms: nplurals=2; plural=(n != 1);n"
 
#: /home/ozten/abide-demo/views/homepage.ejs:3
msgid "Mozilla Persona"
msgstr "Mozilla Personidada"

Он примет такой вид:

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

Мы можем использовать этот файл как на стороне сервера, в приложении Node.js, так и на стороне клиента, подтянув его ajax-запросом.

Директория static открыта для доступа из интернета, так что для получения испанского перевода нужно запросить файл /i18n/es/messages.json.

Директория static — это общепринятое соглашение фреймворка express, вы можете использовать любую другую. Вы можете отдавать файлы переводов как с помощью Node.js, так и любого веб-сервера, например Nginx.

Сами po-файлы можно, но совершенно необязательно выкладывать в продакшн.

Конфигурация

i18n-abide нуждается в конфигурации, чтобы знать, какие языки доступны, и где лежат файлы переводов. Пример такой конфигурации приводился в первой части:

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

  • supported_languages здесь указывает приложению, что есть переводы на английский, немецкий, испанский и традиционный китайский.
  • translation_directory говорит, что json-файлы с переводами лежат в директории static/i18n.

Обратите внимание, что параметр translation_directory нужен только для gettext на стороне сервера.

i18n-abide постарается выбрать из языков, перечисленных в supported_languages, наиболее подходящий.

Заводим моторы

Итак, когда конфигурация задана и готов перевод хотя бы для одной локали, можно начинать. В браузере меняем предпочитаемый язык на тот, который хотим проверить в работающем приложении:

image

Заходим на одну из страниц приложения. Он должна отображаться в переводе. Вот, например, греческая версия Persona:

image

gobbledygook

Для того, чтобы протестировать многоязычное приложение ещё до того, как готовы переводы, мы создали специальный модуль, навеянный клипом Дэвида Боуи "Лабиринт [14]".

Чтобы ипсользовать его, достаточно просто добавить локаль, для которой перевод ещё не готов, например it-CH в параметры supported_languages и debug_lang:

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

Теперь, если переключить браузер на использование швейцарского итальянского, i18n-abide будет использовать gobbledygook для этого языка.

image

Это удобный способ убедиться в том, что дизайн сайта нормально поддерживает в том числе и языки с написанием справа налево, например иврит:

image

Идём глубже

Мы лишь коснулись поверхности интернационализации и локализации. Разрабатывая многоязычное приложение на Node.js вы столкнётесь со множеством интересных нюансов и неожиданных ловушек. Вот некоторые из них.

Интерполяция строк

В i18n-abide есть функция format, которую можно использовать как на стороне клиента, так и на стороне сервера. Она принимает форматированную строку и подставляет значения параметров во время выполнения. Её можно использовать в двух вариантах:

  • %s – format вызывается с форматированной строкой и массивом параметров, которые будут подставляться по порядку.
  • %(имя)s – параметры передаются в виде объекта, названия полей которого должны совпадать с именами параметров в строке.

format можно использовать чтобы меньше засорять po-файлы разметкой HTML. Возьмём три примера:

<%= 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>

В po-файле строки для каждого из них будут выглядеть так:

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!"

В первом примере строка включает в себя тег <p>. Ужас! Если вы когда-нибудь измените разметку, вам придётся обновлять каждый файл перевода. Плюс этот уродливый URL.

Использование format позволяет избежать случаев, когда переводчики, незнакомые с HTML, могут по незнанию испортить код, а так же упрощает поддержку приложения.

Именованные параметры хороши тем, что они самодокументированы. Переводчик будет видеть, что под значением имеется в виду именно URL. Интерполяция строк — общепринятый приём в локализации ПО.

Ещё один вариант применения интерполяции — включение в строки информации, доступной только во в время выполнения:

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

Избегайте негибкого дизайна

Мы должны учитывать необходимость локализации на самых ранних этапах разработки, ещё когда создаётся предварительный макет сайта.

Избегайте картинок с надписями. Лучше использовать CSS, чтобы позиционировать слова поверх изображений.

Тщательно отлаживайте CSS. Немецкое слово может оказаться в несколько раз длиннее английского и порвать ваш дизайн в клочья. Вот, попробуйте этот букмарклет [15].

Замораживание строк

При каждом релизе нам придётся координировать свои действия с командой переводчиков. Придется мириться либо с тем, что необходимо будет дожидаться 100%-й готовности всех переводов перед публикацией очередных изменений, либо с тем, что на некоторые языки приложение будет переведено лишь частично.

Команде переводчиков может понадобиться несколько недель, если число строк для перевода велико. Это нужно учитывать в планировании разработки.

Для переводчиков стоит сделать внутреннюю версию сайта с живым предпросмотром изменений, чтобы они могли контролировать свою работу.


Продолжение следует...

Автор: ilya42

Источник [16]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/46119

Ссылки в тексте:

[1] цикла о Node.js: https://hacks.mozilla.org/category/a-node-js-holiday-season/

[2] Persona: http://ru.wikipedia.org/wiki/Mozilla_Persona

[3] Охотимся за утечками памяти в Node.js: http://habrahabr.ru/company/nordavind/blog/195494/

[4] Нагружаем Node под завязку: http://habrahabr.ru/company/nordavind/blog/195686/

[5] Храним сессии на клиенте, чтобы упростить масштабирование приложения: http://habrahabr.ru/company/nordavind/blog/196018/

[6] Производительность фронтэнда. Часть 1 — конкатенация, компрессия, кэширование: http://habrahabr.ru/company/nordavind/blog/196358/

[7] Пишем сервер, который не падает под нагрузкой: http://habrahabr.ru/company/nordavind/blog/196518/

[8] Производительность фронтэнда. Часть 2 — кешируем динамический контент с помощью etagify: http://habrahabr.ru/company/nordavind/blog/196818/

[9] Приручаем конфигурации веб-приложений с помощью node-convict: http://habrahabr.ru/company/nordavind/blog/197166/

[10] Производительность фронтенда. Часть 3 — оптимизация шрифтов: http://habrahabr.ru/company/nordavind/blog/197370/

[11] Локализация приложений Node.js. Часть 1: http://habrahabr.ru/company/nordavind/blog/197566/

[12] Локализация приложений Node.js. Часть 2: инструментарий и процесс: http://habrahabr.ru/company/nordavind/blog/198154/

[13] node-gettext: https://github.com/andris9/node-gettext

[14] Лабиринт: https://www.youtube.com/watch?v=rJLnGjhPT1Q

[15] этот букмарклет: https://gist.github.com/ozten/5492240

[16] Источник: http://habrahabr.ru/post/198252/