- PVSM.RU - https://www.pvsm.ru -
От переводчика: Это де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
Во время выполнения нашего приложения нужно извлекать переведённые строки из этих файлов. Есть два способа сделать это:
Оба этих метода требуют, чтобы строки были в формате JSON. При переводе на стороне сервера они загружаются при старте приложения, а клиент загружает их с помощью HTTP-запросов (или можно включить их в минифицированный файл JavaScript в процессе сборки).
Так как наша система перевода совместима с GNU Gettext, есть ещё и третий путь — модуль node-gettext [13]. Он довольно эффективен при переводе на стороне сервера.
Дальше мы будем рассматривать первый способ, так как это наиболее привычный способ использования i18n-abide.
Как мы извлекаем строки из 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'
}));
Обратите внимание, что параметр translation_directory нужен только для gettext на стороне сервера.
i18n-abide постарается выбрать из языков, перечисленных в supported_languages, наиболее подходящий.
Итак, когда конфигурация задана и готов перевод хотя бы для одной локали, можно начинать. В браузере меняем предпочитаемый язык на тот, который хотим проверить в работающем приложении:
Заходим на одну из страниц приложения. Он должна отображаться в переводе. Вот, например, греческая версия Persona:
Для того, чтобы протестировать многоязычное приложение ещё до того, как готовы переводы, мы создали специальный модуль, навеянный клипом Дэвида Боуи "Лабиринт [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 для этого языка.
Это удобный способ убедиться в том, что дизайн сайта нормально поддерживает в том числе и языки с написанием справа налево, например иврит:
Мы лишь коснулись поверхности интернационализации и локализации. Разрабатывая многоязычное приложение на Node.js вы столкнётесь со множеством интересных нюансов и неожиданных ловушек. Вот некоторые из них.
В i18n-abide есть функция format, которую можно использовать как на стороне клиента, так и на стороне сервера. Она принимает форматированную строку и подставляет значения параметров во время выполнения. Её можно использовать в двух вариантах:
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/
Нажмите здесь для печати.