solovyov.net

Переводы браузерных приложений

4 min read · i18n, javascript

Рано или поздно, если приложение ориентировано не на англоязычный рынок, всем приходится сталкиваться с локализацией. Пару недель назад у нас подошëл момент, когда дальше уже нельзя и пора что-то делать. :)

И я пошëл искать библиотеку, которую можно было бы использовать для переводов. Я нашëл какое-то их количество, некоторые из них были с зависимостью от jQuery (нынче уж и 2 + 3 не посчитаешь без неë), некоторые не обновлялись уже много лет и их работоспособность сомнительна, были еще какие-то проблемы. Самая похожая на победителя библиотека называется Jed.

В целом она вроде ничего, меня только напряг еë размер в тысячу строк, непонятные отношения с jQuery, и отсутствие инфраструктурной обвязки.

Очевидно, с такими проблемами мириться было нельзя и была рождена новая библиотека, которая получила говорящее имя puttext, и API, который на gettext походит только издалека. Не думаю, что кого-либо этот факт взволнует.

Маленькое отступление. Из фич, которые предоставляет геттекст, я забил на поддержку доменов (это легко решить отдельными файлами, имхо), и на контексты (тут я еще думаю). Если вы не знаете, что это, то не парьтесь, в целом всë ок. :)

Что это

Это маленькая (версия 1.0.1 - 1713 байт после UglifyJS), но при этом очень полезная (природная скромность иначе сказать не позволяет) библиотека занимается переводами и связанными с ними вопросами.

Основные вопросы: какую строку переводить, как быть с перестановками переменных внутри этой строки и что делать с множественным числом.

Какую строку переводить

Всë просто:

__('String to translate')

Если у нас есть перевод, то мы получим его вместо этой строки. Если перевода нет, то мы получим оригинал строки.

Что там с переменными в строках

Опять же, ничего сложного, просто небольшой синтаксис для подстановки переменных:

__('{name}, how are you?', {name: document.body.tagName})

Тут есть небольшая опасность: что делать, если надо честно вывести фигурную скобку? Всë просто, нужно еë экранировать: __('\\{ - works!') (два обратных слеша для того, чтоб в функцию пришли два символа отдельно - обратный слеш и фигурная скобка).

Что делать с множественным числом

__('one item left', '{num} items left', n, {num: n})

Тут может быть не совсем очевидно, что к чему, но всë просто: нужны две строки на английском (для единицы и остальных случаев), количество, и контекст для форматирования строки. Контекст не обязателен, конечно, если хватит просто строк “один/много”. :)

Тут вылезает одна особенность: у разных языков разное количество вариантов, и для их определения служит специальная такая строка Plural-Forms в файле с переводами. Чтоб не выдумывать еë каждый раз, она есть в документации к GNU gettext.

Инфраструктура

Добыча строк для перевода

Клиентская библиотека это хорошо, конечно, но для неë нужно подготовить переводы. Для добывания помеченных строк из исходного текста есть xgettext.js. По умолчанию он ищет все строки, которые обëрнуты в вызов функции __ (например, __('string')). При необходимости можно указать несколько вариантов:

$ ./xgettext.js -m __ -m env.__ path/to/source

xgettext.js нужен потому, что xgettext из состава GNU gettext не поддерживает синтаксис JavaScript’а, а самый близкий к нему - как ни странно, Python - довольно много ошибается.

Создание исходного файла

xgettext.js специально написан только для извлечения строк, чтобы как можно меньше копировать уже существующей функциональности. Для того, чтобы полноценно использовать gettext, нужно сначала создать файл с заголовком:

$ echo '' | xgettext -C --force - -o -

Это даст заготовку для нашего .po-файла, в которой нужно поправить charset (на UTF-8) и Plural-Forms на то, что нужно для языка.

Moving forward

Кроме того, есть скрипт i18n-collect на шелле, который занимается автоматизацией извлечения и объединением новых и уже переведëнных строк. Он очень простой и его можно скопировать себе в код и изменить, если, например, нужны дополнительные опции для xgettext.js.

Can I haz JSON

И в конце нужно скомпилировать наш .po-файлик и получить результирующий JSON, чтоб грузить его в приложении. В комплекте идëт po2json.js, так что никаких проблем:

$ ./po2json.js [path/to/lang.po] > [path/to/lang.json]

Зачем всë это

Зачем возиться с .po-файлами и всей этой инфраструктурой, когда можно было бы извлечь строки из JS и сразу сгенерировать JSON, в который переводчики писали бы текст? Тогда пришлось бы писать логику объединения с новыми строками, и не было бы PoEdit и прочих редакторов (po-mode в Емаксе, например). Быть совместимым с де-факто стандартом (у нормальных людей - я смотрю на тебя, .NET) - удобно. Даже если приходится потратить немного времени на настройку.

Что дальше

Дальше вы берëте все нужные файлики из репозитория или устанавливаете пакет puttext в NPM, добавляете себе в загружающийся джаваскрипт puttext.js, помечаете строки, генерируете исходный <lang>.po, шлëте его переводчикам (у меня есть подозрение, что как минимум на 1 язык может и программист перевести, для проверки, что все строки помечены), и файл с переводами компилируете в .json, который грузите в зависимости от настроек пользователя.

У проекта еще и README есть, тоже может помочь - но если и его не хватает, пишите вопросы здесь, или на гитхабе, или мне в мыло. Enjoy!

Еще раз, ссылка

И на всякий случай, еще раз, если вдруг вы всë поняли сразу и проскроллили текст без чтения, ссылка на репозиторий: puttext.

If you like what you read — subscribe to my Twitter, I always post links to new posts there. Or, in case you're an old school person longing for an ancient technology, put a link to my RSS feed in your feed reader (it's actually Atom feed, but who cares).

Other recent posts

Server-Sent Events, but with POST
ngrok for the wicked, or expose your ports comfortably
PostgreSQL collation
History Snapshotting in TwinSpark
Code streaming: hundred ounces of nuances