От переводчика: Это деcятая статья из цикла о Node.js от команды Mozilla Identity, которая занимается проектом Persona.
- "Охотимся за утечками памяти в Node.js"
- "Нагружаем Node под завязку"
- "Храним сессии на клиенте, чтобы упростить масштабирование приложения"
- "Производительность фронтэнда. Часть 1 — конкатенация, компрессия, кэширование"
- "Пишем сервер, который не падает под нагрузкой"
- "Производительность фронтэнда. Часть 2 — кешируем динамический контент с помощью etagify"
- "Приручаем конфигурации веб-приложений с помощью node-convict"
- "Производительность фронтенда. Часть 3 — оптимизация шрифтов"
- "Локализация приложений Node.js. Часть 1"
В прошлой статье о локализации приложений Node.js мы узнали, как использовать модуль i18n-abide в нашем коде. Наша работа, как программистов, фактически закончилась на том, что мы обернули строки в шаблонах и коде приложения в вызовы
gettext()
. Но работа по локализации и переводу приложения только начинается.
Инструментарий
Инструментарий локализации команды Mozilla Persona совместим с теми инструментами, которые используются в остальном сообществе Mozilla, и при этом сохраняет преимущества в дружественности и гибкости, присущие Node.
Проекту Mozilla уже почти 15 лет, и наша команда локализаторов и переводчиков одна из самых больших (и клёвых) в мире Open Source. Поэтому у нас широко используются давно привычные, можно даже сказать старинные и причудливые инструменты.
Gettext
GNU Gettext — это набор инструментов, предназначенный для локализации настольных и веб-приложений. Когда вы пишете код и шаблоны для Node, вы используете везде английские фразы, но оборачиваете каждую из них в вызов gettext()
.
gettext делает две вещи:
- во время сборки составляет каталог всех строк, встреченных в приложении;
- во время выполнения заменяет их на локализованные варианты.
Все извлечённые строки хранятся в текстовых файлах с расширением .po
В дальнейшем мы будем называть их po-файлы.
Po-файлы
Po-файлы — это текстовые файлы определённого формата, которые gettext может читать, писать и объединять.
Вот пример содержимого po-файла zhTW/LCMESSAGES/messages.po:
#: resources/views/about.ejs:46
msgid "Persona preserves your privacy"
msgstr "Persona 保護您的隱私"
Подробнее мы его рассмотрим позже, а сейчас нам важно понимать, что msgid
— это английская строка, а msgstr
— её перевод на китайский. Всё, что начинается с #
— комментарий. Комментарий в этом примере указывает на расположение этой строки в коде.
Gettext предоставляет множество других инструментов для работы со строками и po-файлами. Мы коснёмся и их.
Почему именно этот инструментарий?
Прежде чем мы погрузимся в более подробное изучение модулей Node.js для работы с gettext, мы должны спросить себя, почему мы выбрали именно этот набор инструментов?
Год назад я подробно исследовал существующие модули Node.js для интернационализации и локализации. Большинство из них изобретали собственные велосипеды и основанные на JSON форматы для хранения строк.
С другой стороны, в Mozilla давно и успешно используются такие инструменты, как POEdit, Verbatim, Translate Toolkit и Pootle. Вместо того, чтобы заставлять людей переучиваться, мы решили разработать для них инструменты, совместимые с привычными стандартами и процессами.
Po-файлы — общепринятый формат обмена и сотрудничества наших переводчиков. Именно в этом формате они должны получать от нас строки для перевода, и отдавать нам готовый текст.
Имея большой опыт разработки в Mozilla ещё на PHP и Python, я нахожу Gettext очень удобным. По мере того, как веб-приложение растёт и содержит всё больше текста, проявляется всё больше нюансов, которые требуют применения хорошо проверенных инструментов и API Gettext.
Создаём po-файлы для переводчиков
Итак, мы разметили наш код вызовами gettext. Что дальше? В дело вступает тот, кого мы называем «строководом». Это можете быть вы сами, переводчик или администратор. Что делает строковод?
- Извлекает строки, впервые появившиеся в приложении.
- Находит новые, изменившиеся строки или помечает удалённые в последующих релизах.
- Готовит po-файлы для каждой команды переводчиков.
- Разрешает конфликты и помечает изменённые или удалённые строки переводов.
Это может звучать несколько запутанно, но, к счастью, большинство этих задач хорошо автоматизируется. Строководу приходится вмешиваться только, когда возникают проблемы.
msginit, xgettext, msgfmt и другие инструменты GNU Gettext — это мощный набор для работы с каталогами строк. С этими инструментами работает только строковод. Большинство разработчиков могут оставаться в блаженном неведении относительно них.
Создание дерева файлов для локали:
$ mkdir -p locale/templates/LC_MESSAGES
В этой директории хранятся шаблоны po-файлов — файлы .pot
. Они будут использованы gettext в дальнейшем.
Извлечение строк
В прошлой статье мы установили i18n-abide:
$ npm install i18n-abide
Среди других инструментов командной строки, abide предоставляет extract-pot. Эта команда используется для извлечения строк в директорию локали:
mkdir -p locale/templates/LC_MESSAGES
$ ./node_modules/.bin/extract-pot --locale locale
Скрипт пройдёт по всему исходному коду приложения, находит строки и записывает их в файл шаблона po.
Для создания pot-файлов можно было бы воспользоваться и традиционными утилитами gettext, но мы написали специальный модуль jsxgettext, удобный и кроссплатформенный. Под капотом extract-pot использует именно его.
Jsxgettext ищет в коде вызовы gettext()
и извлекает из них строковый аргумент, затем он форматирует строки в формат, совместимый с инструментарием gettext. Вот отрывок такого pot-файла:
#: resources/views/about.ejs:46
msgid "Persona preserves your privacy"
msgstr ""
#: resources/views/about.ejs:47
msgid ""
"Persona does not track your activity around the Web. It creates a wall "
"between signing you in and what you do once you're there. The history of "
"what sites you visit is stored only on your own computer."
msgstr ""
""
#: resources/views/about.ejs:51
msgid "Persona for developers"
msgstr ""
Позже на основе этого шаблона будут созданы po-файлы с переводом. Они будут выглядеть так:
#: resources/views/about.ejs:46
msgid "Persona preserves your privacy"
msgstr "Persona 保護您的隱私"
#: resources/views/about.ejs:47
msgid ""
"Persona does not track your activity around the Web. It creates a wall "
"between signing you in and what you do once you're there. The history of "
"what sites you visit is stored only on your own computer."
msgstr ""
"Persona 只是連結您登入過程的一座橋樑,不會追蹤您在網路上的行為。您的網頁瀏覽"
"紀錄只會留在您自己的電腦當中。"
#: resources/views/about.ejs:51
msgid "Persona for developers"
msgstr "Persona 的開發人員資訊"
Чтобы лучше почувствовать тему, вы можете взглянуть на полный вариант po-файла для китайского языка.
Создание локали
Команда msginit из набора Gettext используется для создания po-файла для конкретной локали, основанного на файле шаблона:
$ for l in en_US de es; do
mkdir -p locale/${l}/LC_MESSAGES/
msginit --input=./locale/templates/LC_MESSAGES/messages.pot
--output-file=./locale/${l}/LC_MESSAGES/messages.po
-l ${l}
done
Мы только что создали po-файлы для американского английского, немецкого и испанского языков.
Po-файлы
Итак, мы извлекли строки и создали папки локалей. Вот так выглядит наше дерево файлов:
locale/
el/
LC_MESSAGES/
messages.po
en_US
LC_MESSAGES/
messages.po
es
LC_MESSAGES/
messages.po
templates
LC_MESSAGES/
messages.pot
К этим частям вашего приложения можно дать доступ переводчикам. К примеру, испанская команда будет иметь доступ к locale/es/LC_MESSAGES/messages.po
. Если у вас очень большой проект, могут быть даже две отдельные локали для испанского и аргентинского вариантов испанского языка: es-ES и es-AR.
Со временем могут добавляться новые локали.
Слияние изменений строк
Релиз за релизом вы будете добавлять новые, менять и удалять старые строки. Вам будет необходимо обновлять все po-файлы в соответствии с этими изменениями. В gettext есть мощные инструменты для этого. Для себя мы сделали скрипт-обертку merge-po.sh, который использует команду msgmerge из пакета GNU Gettext.
Добавим инструменты i18n-abide в системные пути:
$ export PATH=$PATH:node_modules/i18n-abide/bin
и запустим процесс слияния строк:
$ ./node_modules/.bin/extract-pot --locale locale .
$ merge_po.sh ./locale
Как и в первый раз, extract-pot собирает все строки и создаёт шаблон. Затем merge-po.sh обновляет все po-файлы, приводя их в соответствие с текущей версией приложения. После этого команды переводчиков могут снова браться за работу.
Gettext против синдрома «изобретено не здесь»
Нет ничего сложного в том, чтобы изобрести свой велосипед на основе JSON вместо gettext. Большинство авторов модулей Node пошли именно этим путём. Но по мере роста приложения и добавления новых и новых языков мелкие неприятности будут нарастать как снежный ком. Например, без merge-po.sh вам рано или поздно придётся писать и отлаживать собственные инструменты для слияния. Вручную обновить 30 файлов для 30 локалей, ничего при этом не потеряв и не перепутав — та ещё морока.
А в gettext всё необходимое уже есть и это экономит нам кучу времени и нервов.
Заключение
Теперь, когда мы окончательно разобрались, как создавать и обновлять po-файлы, можно перепоручить их заботам переводчиков. Вообще, всегда лучше заранее пообщаться с ними и обсудить, когда можно будет начать перевод, какой предполагается объём и когда желательно закончить. Также будет нелишним изучить документацию gettext.
Итак, строки переведены, и в следующей статье мы узнаем, как работает локализация во время выполнения приложения.
Продолжение следует...
Автор: ilya42