Локализация расширений Google Chrome — необходимо и просто
Разнообразие и легкая доступность расширений сыграли значительную роль в популярности браузера Google Chrome. Вероятно, многие из вас имеют опыт их написания. Возможно, еще не опыт, но уже желание попробовать или даже конкретную идею. Осталось лишь начать.
Уже на этапе проектирования я предлагаю вам задуматься о мультиязычности. Большинству людей гораздо комфортней пользоваться продуктом на своем родном языке, даже если они знают английский, и особенно, если не знают.
Немалая польза и для вас: значительно вырастет потенциальная аудитория, увеличится число установок. Вы будете получать интригующий фидбэк:
В этом посте я расскажу о том, как перевел свое расширение «Изображение по центру» на 11 языков, как организовал процесс и какие вспомогательные инструменты использовал для автоматизации скучной рутины.
Документация
В документации для разработчиков Google Chrome подробно рассказывается о том, что именно нужно сделать, чтобы перевести интерфейс вашего расширения или приложения на более чем 50 языков. Не пугайтесь, на все 50 переводить не обязательно, вьетнамский можно пропустить, а то и вовсе оставить пару-тройку популярных, включая русский.
Отвлекся. Если вкратце, то:
- Все переводы лежат внутри директории _locales, расположенной в корне проекта.
- Для каждого языка внутри _locales создается папка, соответствующая идентификатору языка. Для русского ru, для английского en и так далее. Их полный список есть в доках.
- Внутри директории с именем-локалью лежит файл messages.json. Перевод в нем.
Файл устроен нехитро — это список ключей, каждый из которых соответствует определенному слову, фразе, предложению или целому рассказу. По этим ключам кусочки текста будут браться из messages.json и добавляться в интерфейс. Каждый из них несет в себе три части:
- message — тот самый текст для вывода на экран, ради которого все затевалось.
- description — вспомогательный текст, позволяющий переводчику понять контекст использования message. Ведь одно и то же слово можно в зависимости от ситуации перевести по-разному. Так «коса (идущая от берега узкая полоса земли)» превратится в «foreland», а «коса (сельскохозяйственный инструмент)» в «scythe». Текст в description необязательный, его можно пропустить.
- placeholders — опциональное свойство, позволяющее передавать для фразы произвольный контекст. Например, для Паши показать заголовок «Привет, Паша!», а для Маши «Привет, Маша!». Подробно об использовании placeholder-ов можно почитать в соответствующем разделе документации.
К сожалению, группа разработки движка Chromium, лежащего в основе Google Chrome, не стала реализовывать «плюрализацию» (склонение слов в зависимости от числительного) на уровне структуры файлов локалей. Их можно понять. Из-за многообразия языков, из-за обилия специфичных правил сделать это было бы очень трудно. Взамен они предлагают использовать обтекающие формулировки, например, «Число доступных языков: 11» вместо «Переведено на 11 языков».
Когда вы решите добавить перевод на очередной язык, вам нужно будет создать еще одну директорию, поместить туда файл и аккуратно заполнить ключи, как в других полях, фраза за фразой.
А что, если в интерфейсе появилась еще одна кнопка? Не беда — последовательно открываем каждую директорию, потом каждый файл, добавляем туда новый ключ с названием кнопки, пишем перевод, сохраняем. Так, стойте, а в директорию fr добавили? Открываем файл, проверяем, сохраняем… А на испанский перевели? Открываем, проверяем, сохраняем…
А что, если в интерфейсе появилось пять новых элементов? А что, если десять? Вероятность возникновения ошибки очень высока.
Google дал разработчикам неплохие инструменты для интернационализации, но на практике ежедневная работа с вязанкой файлов неудобна и скучна. Даже если вы готовы смириться со скукой, человеческий фактор оставляет лазейку для ошибок. Их вероятность необходимо снижать при любой возможности.
Как же улучшить существующую схему?
Оптимизация
Решив, что лучше день потерять, а потом за пять минут долететь, я начал поиск более удобного решения. Врать не буду, искал недолго. Как любой другой разработчик, быстро сдался и взялся за свой велосипед.
Первое, что приходит на ум — гораздо удобнее хранить и редактировать фразы в одном месте, в одном файле. Так, чтобы всё и сразу было перед глазами.
Идеальный формат — таблица. Столбцы соответствуют языкам, строки — ключам с фразами. Можно использовать любой удобный редактор: Microsoft Excel, Numbers от Apple, что-то еще. Я остановился на таблицах из Google Docs. К примеру, вот так теперь выглядит фрагмент моего единственного файла:
С такой таблицей легко добавлять новые языки — копируем файл и отправляем переводчику. Он заполняет свою колонку и присылает обратно.
С такой таблицей легко добавлять новые фразы — создаем новую строку для очередного ключа и последовательно заполняем все колонки. Можно настроить в редакторе подсветку пустых ячеек тревожно-оранжевым цветом — теперь точно ничего не пропустим.
Плейсхолдеры добавляются тоже просто, группу оборачиваете в скобки, ключ и значение разделяете двоеточием (см. пример).
Создание структуры, переписка с переводчиками, что-то своими силами — таблица готова. Что дальше?
Автоматизация
Для генерации всех необходимых директорий и файлов я написал npm-модуль csv-locales. Его ядром является другая, сторонняя утилита csv-parse. Название не врет, она парсит CSV-файл, преобразует его в json и передает результат мне. Сам CSV-файл мы получаем экспортом из нашей таблицы. Дальше документ анализируется и разбивается на фрагменты поменьше, на отдельные языки. Они в виде messages.json размещаются по целевым директориям. Всё происходит один в один, как я рассказывал чуть выше, но почти моментально и без ошибок **смех из зала**.
Снизим наше участие до минимума, будем запускать процесс одной командой из консоли. Я обернул модуль в плагины для двух популярных систем сборки:
- grunt-csv-locales
- gulp-csv-locales
Всё, что требуется — это экспортировать ваш файл в формат CSV и натравить на него скрипт. В качестве основы для таблицы используем этот образец.
Пример настройки для grunt-а:
module.exports = function (grunt) {
grunt.initConfig({
csvLocales: {
all: 'source/locales.csv',
options: {
dirPath: '_locales'
}
}
});
grunt.loadNpmTasks('grunt-csv-locales');
};
Результат:
Ура! Добавление новых языков, новых текстовых элементов в интерфейс больше не вызывает депрессию. Автоматизация рутины позволяет сосредоточиться на действительно важных моментах, на более интересных задачах, без скучного «открыл / отредактировал / закрыл / повторить».
Следует упомянуть, что команда Google предлагает заказать перевод вашего расширения прямо из панели инструментов разработчика. Вам надо лишь загрузить исходный message.json, выбрать языки и оплатить работу переводчиков. Я не пробовал. Эта услуга появилась относительно недавно, когда я в ней уже не нуждался. Плюс, по деньгам выйдет значительно дороже переводчиков-фрилансеров. Если у вас есть опыт подобного перевода, пожалуйста, расскажите о нем в комментарии.
Стандартным решением для локализации ПО является библиотека gettext. Для расширений Google Chrome она не используется, однако, gettext-parser в npm уже существует, а значит, при желании, реализовать поддержку PO-файлов мы сможем. Чем не планы на будущее?
Для тех, кто дочитал до конца, полезные ссылки:
Буду рад ответить на ваши вопросы, услышать фидбэк. Расскажите о вашем опыте. Что используете? Что посоветуете мне?