20 проектов, 20 языков, срок вчера. Часть 2

Продолжаю серию статей, посвященных настройке непрерывных локализаций. Первую вы можете найти здесь. В этой статье я расскажу, как интегрировать связку Serge-Smartcat-GitLab и настроить конфигурационные файлы Serge на примере тестового проекта. Инструкция для Ubuntu.


Установка через vagrantfile

Результатом выполнения сборки Vagrant будет виртуальная машина с Ubuntu, на которую установлен Serge с плагином Smartcat. Все необходимые файлы будут скопированы, ssh-ключ — создан. Вы можете сразу переходить к настройке проекта и запуску приложения.

Если у вас под рукой нет сервера с Ubuntu, вы можете использовать Vagrant. Как это работает:


  1. Установите Vagrant и VirtualBox, следуя инструкции.
  2. Скачайте vagrantfile и bash-скрипт.
  3. Запустите 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

Для начала работы необходимо:


  1. Зарегистрироваться в Smartcat.
  2. Создать проект, указав языки, на которые вы собираетесь переводить. При настройке плагина вам потребуются коды языков, используемые в Smartcat.
  3. Получить project_id, token и token_id.
  4. Написать на support@smartcat.ai и попросить включить поддержку алгоритма разбора файлов Serge


Настройка конфигурации Serge-Smartcat

Прежде чем перейти к настройке, опишу общий принцип работы. Стандартный цикл локализации включает в себя 5 шагов:
image


  • pull — получение новых строк из репозитория.
  • pull-ts — получение локализованных строк из Smartcat (включается плагин Smartcat).
  • localize — все исходные форматы из репозитория парсятся и преобразуются в .po файлы, полученные переводы записываются в .po файлы. Все строки записываются в базу данных памяти переводов Serge.
  • push-ts — новые строки, полученные на первом шаге, отправляются в Smartcat (используется плагин Smartcat).
  • push — локализованные строки коммитятся в GitLab.
    Вы можете использовать интеграцию с другой CAT-системой, например, есть плагин для Pootle.


Serge и git-репозитории

Как Serge узнает, какие строки надо отправить на перевод? Для определения новых строк используются стандартные инструменты git: берется разница между master-веткой и текущей. Diff отправляется на перевод.

image

Важно: Как правило, master-ветка является protected, поэтому для определения diff-ов используется дополнительная ветка переводов, мы ее называем base-translate. То есть, после мержа релиза в master, master необходимо дополнительно смержить в base-translate.

Здесь есть важный момент. Необходимо построить процессы таким образом, чтобы, с одной стороны, разработчики понимали, что и в какой момент уйдет на перевод, а с другой, чтобы техпис не бегал кругами, взявшись за голову, потому что у него diff составил 1000 строк. А вроде только заголовок поправили.


Принцип работы

Serge заходит в репозиторий, берет все ветки, и начинает сравнивать их с master-веткой. Для каждой ветки, в которой обнаружен diff, он создает в проекте Smartcat набор документов на перевод. Чтобы не погрязнуть в хаосе неактуальных веток и не стоять около каждого разработчика с вопросом: «а ты не забыл удалить ветку?», удобно использовать префиксы. Например, у нас Serge работает только с ветками, которые имеют префикс translate- .

Также важно, чтобы в ветку с префиксом translate- попадали уже вычитанные и утвержденные тексты, готовые к переводу. Никому ведь не хочется разбирать историю коммитов в тщетных попытках понять, что это за тексты и почему diff выглядит именно так?


Подготовка контента

Наш процесс подготовки контента выглядит следующим образом:

image

Постановка задачи. Когда задача находится в стадии постановки, иногда уже там появляются тексты, которые должны в неизменном виде попасть в приложение. Если они есть, копирайтер и технический писатель помогают их финализировать.

Разработка макетов. Когда дизайнер подготовил макеты, технический писатель и копирайтер вычитывают все тексты. Таким образом, в разработку попадают вычитанные макеты на английском языке. Разработчик не должен думать о текстах, он просто берет готовые строки с макетов.

Разработка. К сожалению, предыдущие шаги не могут покрыть все возможные типы текстов. Например, макеты не включают валидаторы, ошибки 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

Следующие статьи будут посвящены траблшутингу и описанию частных случаев интеграции.

© Habrahabr.ru