20 проектов, 20 языков, срок вчера. Часть 2
Продолжаю серию статей, посвященных настройке непрерывных локализаций. Первую вы можете найти здесь. В этой статье я расскажу, как интегрировать связку Serge-Smartcat-GitLab и настроить конфигурационные файлы Serge на примере тестового проекта. Инструкция для Ubuntu.
Установка через vagrantfile
Результатом выполнения сборки Vagrant будет виртуальная машина с Ubuntu, на которую установлен Serge с плагином Smartcat. Все необходимые файлы будут скопированы, ssh-ключ — создан. Вы можете сразу переходить к настройке проекта и запуску приложения.
Если у вас под рукой нет сервера с Ubuntu, вы можете использовать Vagrant. Как это работает:
- Установите Vagrant и VirtualBox, следуя инструкции.
- Скачайте vagrantfile и bash-скрипт.
- Запустите vagrantfile, следуя инструкции.
Установка вручную
Создание пользователя
Создайте группу пользователей serge:
sudo groupadd serge
Создайте пользователя serge c домашним каталогом /usr/local/serge:
sudo useradd -g serge serge -m -d /usr/local/serge
Установка Serge
Полная документация Serge находится здесь.
Обновите метаданные локальной базы пакетов:
sudo apt-get -qq update
Установите инструментарий для сборки, а также необходимые пакеты. Перед началом установки убедитесь, что установлен Perl 5.10 или выше.
sudo apt-get -qq -y install build-essential libssl-dev libexpat-dev unzip wget
Перейдите в домашний каталог пользователя serge и установите Serge со всеми необходимыми зависимостями.
cd /usr/local/serge
sudo -u serge wget https://github.com/evernote/serge/archive/master.zip -O serge-master.zip
sudo -u serge unzip serge-master.zip
sudo -u serge unlink serge-master.zip
cd serge-master
sudo cpan App::cpanminus
sudo cpanm --installdeps .
sudo -u serge sudo ln -s /usr/local/serge/serge-master/bin/serge /usr/local/bin/serge
Установка плагина Smartcat
sudo cpanm HTTP::Daemon@6.01
sudo cpanm Serge::Sync::Plugin::TranslationService::Smartcat
sudo cpan install LWP::Protocol::https
Создание ключей
Создайте пользователю serge ssh-ключ, по которому пользователь serge будет ходить в GitLab:
sudo -u serge ssh-keygen -t rsa -N "" -f .id_rsa_serge
Интеграция с GitLab
Создайте пользователя serge в GitLab и добавьте его во все репозитории с правами developer.
Скопируйте ssh-ключ из файла id_rsa_serge.pub в настройки профиля пользователя serge в GitLab. Это необходимо, чтобы Serge мог получать и отправлять строки в GitLab.
Установка завершена!
Интеграция со Smartcat
Для начала работы необходимо:
- Зарегистрироваться в Smartcat.
- Создать проект, указав языки, на которые вы собираетесь переводить. При настройке плагина вам потребуются коды языков, используемые в Smartcat.
- Получить project_id, token и token_id.
- Написать на support@smartcat.ai и попросить включить поддержку алгоритма разбора файлов Serge
Настройка конфигурации Serge-Smartcat
Прежде чем перейти к настройке, опишу общий принцип работы. Стандартный цикл локализации включает в себя 5 шагов:
- pull — получение новых строк из репозитория.
- pull-ts — получение локализованных строк из Smartcat (включается плагин Smartcat).
- localize — все исходные форматы из репозитория парсятся и преобразуются в .po файлы, полученные переводы записываются в .po файлы. Все строки записываются в базу данных памяти переводов Serge.
- push-ts — новые строки, полученные на первом шаге, отправляются в Smartcat (используется плагин Smartcat).
- push — локализованные строки коммитятся в GitLab.
Вы можете использовать интеграцию с другой CAT-системой, например, есть плагин для Pootle.
Serge и git-репозитории
Как Serge узнает, какие строки надо отправить на перевод? Для определения новых строк используются стандартные инструменты git: берется разница между master-веткой и текущей. Diff отправляется на перевод.
Важно: Как правило, master-ветка является protected, поэтому для определения diff-ов используется дополнительная ветка переводов, мы ее называем base-translate. То есть, после мержа релиза в master, master необходимо дополнительно смержить в base-translate.
Здесь есть важный момент. Необходимо построить процессы таким образом, чтобы, с одной стороны, разработчики понимали, что и в какой момент уйдет на перевод, а с другой, чтобы техпис не бегал кругами, взявшись за голову, потому что у него diff составил 1000 строк. А вроде только заголовок поправили.
Принцип работы
Serge заходит в репозиторий, берет все ветки, и начинает сравнивать их с master-веткой. Для каждой ветки, в которой обнаружен diff, он создает в проекте Smartcat набор документов на перевод. Чтобы не погрязнуть в хаосе неактуальных веток и не стоять около каждого разработчика с вопросом: «а ты не забыл удалить ветку?», удобно использовать префиксы. Например, у нас Serge работает только с ветками, которые имеют префикс translate- .
Также важно, чтобы в ветку с префиксом translate- попадали уже вычитанные и утвержденные тексты, готовые к переводу. Никому ведь не хочется разбирать историю коммитов в тщетных попытках понять, что это за тексты и почему diff выглядит именно так?
Подготовка контента
Наш процесс подготовки контента выглядит следующим образом:
Постановка задачи. Когда задача находится в стадии постановки, иногда уже там появляются тексты, которые должны в неизменном виде попасть в приложение. Если они есть, копирайтер и технический писатель помогают их финализировать.
Разработка макетов. Когда дизайнер подготовил макеты, технический писатель и копирайтер вычитывают все тексты. Таким образом, в разработку попадают вычитанные макеты на английском языке. Разработчик не должен думать о текстах, он просто берет готовые строки с макетов.
Разработка. К сожалению, предыдущие шаги не могут покрыть все возможные типы текстов. Например, макеты не включают валидаторы, ошибки API, некоторые модальные окна. Если в процессе разработки понадобилось добавить строки, технический писатель и копирайтер предоставляют разработчику все необходимые тексты.
Таким образом, в какой-то момент времени мы имеем состояние, когда ветка содержит в себе все новые тексты, необходимые для той части кода, которая в ней разрабатывается. В этот момент от нее создается новая ветка с префиксом translate- и Serge может начинать процесс перевода. Когда переводы готовы, разработчик забирает их из translate-ветки, и далее действует по git flow, принятому в команде.
Данный процесс минимизирует вовлеченность разработчика в процесс локализации, а также сводит к абсолютному минимуму количество ручного труда: надо всего лишь создать ветку.
Дальнейшее описание процесса настройки во многом опирается на то, что в команде существуют схожие договоренности и процессы.
Настройка проекта
Войдите под пользователем serge:
sudo -Hu serge -i
Создайте директорию groups:
mkdir groups
В этой директории будут размещены проекты, соответствующие репозиториям в GitLab. Имеет смысл повторять структуру групп репозиториев и их расположения для облегчения навигации.
Создайте директорию для первого проекта:
cd groups
mkdir myproject
cd myproject
Скопируйте файлы конфигурации Serge. Можно установить midnight commander и управлять файлами при помощи файлового менеджера. Перед установкой MC необходимо выйти из-под учетной записи serge.
exit
sudo apt install mc
sudo -Hu serge -i
Файлы, которые необходимо скопировать из /usr/local/serge/serge-master/bin/tools/feature-branch-config-generator:
- конфигурационный файл Serge: myproject.cfg
- файл с шаблоном job: myproject.inc
- шаблон .serge: myproject.serge.tmpl
- файл для добавления ветки вручную: myproject_branches.txt
- скрипт для работы с GitLab: fbcgen.pl
cp /usr/local/serge/serge-master/bin/tools/feature-branch-config-generator/{myproject.cfg,myproject.inc,myproject.serge.tmpl,myproject_branches.txt} /usr/local/serge/serge-master/groups/myproject
cp /usr/local/serge/serge-master/bin/tools/feature-branch-config-generator/fbcgen.pl /usr/local/serge/serge-master/
Создайте файл для записи логов плагина:
mkdir log
cd log
touch smartcat.log
Настройка myproject.inc
- В поле name укажите используемый в проекте формат ресурсных файлов.
- Заполните id по маске %yourproject%.%yourformat%.%yourmasterbranchname%.
- В поле destination_languages укажите языки, на которые надо переводить.
- Заполните source по маске ./%yourproject%/%yourmasterbranch%/res/en.
- Заполните output_file_path по маске ./%yourproject%/%yourmasterbranch%/res/ %LANG%/%FILE%.
- Заполните source_match по маске .%yourformat%.
- В зависимости от формата ресурсных файлов укажите нужный плагин для парсинга. Плагины перечислены в документации serge. Заполните ts_file_path по маске ./po/%yourproject%/ %LOCALE%/%FILE% .po.
- Заполните master_job по маске %yourproject%.%yourformat%.%yourmasterbranch%.
Остальные параметры оставляем без изменений.
Настройка myproject.serge.tmpl
- project_id — id проекта в Smartcat
- token_id, token — данные из Smartcat
- remotepath — здесь надо верно указать и название параметра и его значение {%yourmasterbranch% %ssh_path_to_your_repo%#%yourmasterbranch%}
- $FBCGEN_DIR_PADDED — %ssh_path_to_your_repo%#FBCGEN_BRANCH
- id — job.yourmasterbranch
- name — yourproject
- source_language — en
- destination_languages — языки, на которые надо будет переводить
- source_dir — ./branches/yourmasterbranch/%path_to_resource_files%
- source_match — en.%yourformat%
- db_source — DBI: SQLite: dbname-./%yourproject%.db3
- db_namespace — %yourproject%
- ts_file_path — ./po/PROJECT_ID_IN_SMARTCAT/ %LANG%/%FILE% .po
- output_file_path — ./branches/%yourmasterbranch%/%path_to_resource_files%/ %LOCALE%. %yourformat%
- output_lang_rewrite{
zh-Hans zh-CN' 'zh-Hant-TW zh-TW
} — Данный параметр позволяет переопределять обозначения языков. Если обозначения языка в проекте не совпадает с обозначениями, используемыми в Smartcat, вы можете их переопределить. - master_job — job.%yourmasterbranch%
- @inherit — .#jobs/:%yourmasterbranch%
- source_dir (в FBCGEN_BRANCH_JOBS) — ./branches/$FBCGEN_DIR/%path_to_resource_files%/
- output_file_path (в FBCGEN_BRANCH_JOBS) — ./branches/$FBCGEN_DIR/%path_to_resource_files%/ %LOCALE%. %yourformat%
Важно! В source_path_prefix в конце должна стоять точка %FBCGEN_BRANCH.
Вызов плагина Smartcat необходимо добавить в соответствующий раздел myproject.serge.tmpl
sync {
ts
{
plugin Smartcat
data
{
project_id 12345678-1234-1234-1234-1234567890123
token_id 12345678-1234-1234-1234-1234567890123
token 1_qwertyuiopasdfghjklzxcvbn
push {
disassemble_algorithm_name Serge.io PO
}
pull {
complete_projects NO
complete_documents YES
}
log_file ./log/smartcat.log
}
}
Описание некоторых параметров:
- disassemble_algorithm_name Serge.io PO — использовать хеши ключа, переданные Serge. Параметр необходим для оптимизации времени выполнения команд pull-ts и push-ts.
- complete_projects NO — забирать строки из Smartcat, только если завершены все документы в проекте. Для нашей интеграции проект в Smartcat синонимичен репозиторию в GitLab, и включает в себя набор документов. Документ — это декартово произведение количества веток в репозитории и языков, на которые производится перевод. То есть, если в вашем репозитории 2 ветки, которые необходимо перевести на 6 языков, в проекте будет создано 12 документов.
- complete_documents YES — забирать строки из Smartcat, если документ в статусе done. Делается для минимизации числа коммитов в репозиторий.
Пример настроенного проекта
Ниже приведены конфигурационные файлы для проекта с 6 языками. Проект хранит исходные строки в js-формате. Используется 5-символьный формат именования файлов. Путь к ресурсным файлам: ./branches/base-translate/client/src/translations.
myproject.cfg
# This is a configuration file for fbcgen.pl
# Usage: fbcgen.pl myproject.cfg
# Root directory where the master branch checkout is located.
# (path is relative to the location of the configuration file itself).
# The local checkout should be initialized *before* fbcgen.pl is run.
# You can run `serge --initialize onboarding-frontend.serge.tmpl`
# to do an initial checkout of the project data.
$data_dir = './branches/base-translate';
$branch_list_file = 'myproject_branches.txt';
# Where to load Serge config template from.
# (path is relative to the location of the configuration file itself).
$template_file = "myproject.serge.tmpl";
# Where to save the localized Serge config file.
# (path is relative to the location of the configuration file itself).
$output_file = "myproject.local.serge";
our $skip_branch_mask = '^(master)$'; # skip these branches unconditionally
our $unmerged_branch_mask = '^(translate-)'; # process unmerged branches matching this mask
our $any_branch_mask = '^(translate-)'; # additionally, process these branches even if they were merged
# Filter out commits that match this mask when determining if branch is inactive.
$skip_commit_mask = '^l10n@example.com';
# This sub returns a hash map of additional parameters
# that can be referenced in template as `$FBCGEN_`.
# For example, `EXTRA_INCLUDE` parameter generated in the function below
# is referenced in `myproject.serge.tmpl` file as `$FBCGEN_EXTRA_INCLUDE`.
$calculate_params = sub {
my ($branch) = @_;
return {
# for branch names starting with `release/`, return an empty string;
# otherwise, return a string that will be used in the `@include` directive
EXTRA_INCLUDE => $branch =~ m!^release/! ? '' : "myproject.inc#skip-saving-localized-files\n"
}
}
myproject.inc
# Here we define a job template (common parameters) that will be reused
# across all jobs in the generated configuration file.
# Certain job parameters (like job ID and paths) will be overridden
# in each feature branch job.
job-template
{
name JS file processing ('master' branch)
id myproject.js.base-translate # master job id
db_namespace myproject
destination_languages ru ko de ja zh-Hans
source_dir ./myproject/base-translate/res/en
output_file_path ./myproject/base-translate/res/%LANG%/%FILE%
source_match \.js$
parser
{
plugin parse_js
}
ts_file_path ./po/myproject/%LOCALE%/%FILE%.po
callback_plugins
{
:feature_branch
{
plugin feature_branch
data
{
master_job myproject.js.base-translate # this must match your master job id
}
}
}
}
# This block will be included conditionally
# for all branches except the `release/` ones (see myproject.cfg).
# This allows one to skip saving localized files in non-release branches
# (but still gather from them strings for translation).
skip-saving-localized-files
{
callback_plugins
{
:skip-saving-localized-files
{
plugin process_if
phase can_generate_localized_file
data
{
if
{
lang_matches .
then
{
return NO
}
}
}
}
}
}
myproject.serge.tmpl
sync {
ts
{
plugin Smartcat
data
{
project_id %project_id%
token_id %token_id%
token %token%
push {
disassemble_algorithm_name Serge.io PO
}
pull {
complete_projects NO
complete_documents YES
}
}
}
vcs {
plugin git
data {
local_path ./branches
add_unversioned YES
name L10N Robot
email l10n-robot@example.com
remote_path {
base-translate git@gitlab.loc:common/myproject.git#base-translate
/* FBCGEN_BRANCH_REMOTES
$FBCGEN_DIR_PADDED git@gitlab.loc:common/myproject.git#$FBCGEN_BRANCH
*/
}
}
}
}
jobs {
:develop {
id job.base-translate
name myproject
source_language en
destination_languages ru zh-Hans ko de ja
optimizations NO
source_dir ./branches/base-translate/client/src/translations
source_match `en-US.js`
debug NO
parser {
plugin parse_js
}
leave_untranslated_blank YES
db_source DBI:SQLite:dbname=./myproject.db3
db_namespace myproject
ts_file_path ./po/1bd80338-a0b5-48b3-822c-e90affd2cdcc/%LANG%/%FILE%.po
output_file_path ./branches/base-translate/client/src/translations/%CULTURE%.%EXT%
output_bom NO
output_lang_rewrite {
zh-Hans zh
}
callback_plugins {
:feature_branch {
plugin feature_branch
data {
master_job job.base-translate
}
}
}
}
/* FBCGEN_BRANCH_JOBS
:$FBCGEN_DIR {
@inherit .#jobs/:develop
id job.$FBCGEN_DIR
$FBCGEN_EXTRA_INCLUDE
source_path_prefix $FBCGEN_BRANCH.
source_dir ./branches/$FBCGEN_DIR/client/src/translations/
output_file_path ./branches/$FBCGEN_DIR/client/src/translations/%CULTURE%.%EXT%
}
*/
}
Запуск приложения
Клонируем репозиторий
GIT_SSH_COMMAND="ssh -i /usr/local/serge/serge-master/.id_rsa_serge" git clone -b base-translate git@gitlab.loc:groups/myproject.git branches/base-translate/
Локализационный цикл
Формируется файл .serge:
GIT_SSH_COMMAND="ssh -i /usr/local/serge/serge-master/.id_rsa_serge" /usr/local/serge/serge-master/fbcgen.pl myproject.cfg
Забор изменений из всех веток репозитория:
GIT_SSH_COMMAND="ssh -i /usr/local/serge/serge-master/.id_rsa_serge" serge pull --initialize myproject.local.serge
Забор изменений переводов по всем веткам из Smartcat:
GIT_SSH_COMMAND="ssh -i /usr/local/serge/serge-master/.id_rsa_serge" serge pull-ts myproject.local.serge
Формирование БД и .po:
GIT_SSH_COMMAND="ssh -i /usr/local/serge/serge-master/.id_rsa_serge" serge localize myproject.local.serge
Отправка новых данных из репозитория в Smartcat:
GIT_SSH_COMMAND="ssh -i /usr/local/serge/serge-master/.id_rsa_serge" serge push-ts myproject.local.serge
Отправка полученных переводов в репозиторий в соответствующие ветки:
GIT_SSH_COMMAND="ssh -i /usr/local/serge/serge-master/.id_rsa_serge" serge push myproject.local.serge
Следующие статьи будут посвящены траблшутингу и описанию частных случаев интеграции.