Заключительная статья об интеграции Serge+Smartcat. В этой статье я расскажу, как мы масштабируем Serge на всю компанию, рассмотрю 4 нестандартных интеграции и, в качестве бонуса, расскажу о 2 фичах которые могут упростить вам жизнь.
Предыдущие статьи:
20 проектов, 20 языков, срок вчера
20 проектов, 20 языков, срок вчера. Часть 2
В прошлой статье я рассказывала, как настроить Serge для одного репозитория. В нашей компании мы имеем несколько десятков репозиториев, которые нуждаются в переводах, поэтому был выделен отдельный сервер для локализаций. Файловая структура и окружение на нем полностью идентичны тому, что описано в предыдущей статье. Для каждого репозитория используется свой инстанс Serge. Чтобы не выполнять команды вручную, каждому инстансу соответствует крон, который последовательно запускает команды Serge: получение новых строк из репозитория, получение новых переводов, парсинг, отправка новых строк в Smartcat и отправка новых переводов в Gitlab.
Начнем с самого простого случая. Представьте, что в вашем репозитории есть несколько наборов ресурсных файлов. Например, строки для клиента и API приложения хранятся в одном репозитории, но в разных директориях. Клиент переводится на 20 языков, API — на 6.
Задача: организовать независимую поставку переводов в каждую из директорий.
Решение:
Обратите внимание, префиксы должны быть уникальными среди всех проектов, настроенных для одного репозитория.
Итого, мы имеем 2 проекта в Smartcat и 2 соответствующих им проекта на сервере локализаций. Оба проекта смотрят на один репозиторий в Gitlab, но в разные директории. Serge по префиксу ветки понимает, какие именно строки ему надо отправить на перевод. Для вычисления diff-а используется одна и та же ветка base-translate.
В нашей компании все продукты, включая документацию, локализованы. Сейчас мы внедряем автогенерацию документации из swagger, и перед нами встала необходимость его локализовать.
Задача: локализовать swagger с минимальными усилиями.
Решение: В файле myproject.tmpl.serge в объект parser добавляем объект data и перечисляем в нем те поля, значение которых надо извлечь и отправить на перевод:
Аналогичная задача: надо переводить тексты из файла, но не все, а только юридические. Прочие тексты поставляет команда маркетологов. Чтобы не усложнять структуру, и не создавать дополнительный файл для юридических текстов, ключи всех юридических строк получили префикс “legal”:
Еще один интересный кейс. У нас есть юридический документ, условия которого разнятся в зависимости от страны. Но, тем не менее, это одно приложение и ресурсные файлы лежат в одной директории.
Задача: в рамках одного проекта перевести несколько документов, причем каждый документ должен быть переведен на один конкретный язык.
Что было сделано:
В нашей системе есть часть кода, которая хранит переводы в БД, и по ряду причин переехать на ресурсные файлы в репозитории не может. Тем не менее, нам необходимо иметь возможность быстро и в автоматическом режиме поставлять переводы.
Задача: Организовать процесс непрерывных локализаций, если строки хранятся не в репозитории, а в БД.
Решение:
Базовые оповещения Smartcat нам не подходили, так как каждая команда хочет получать уведомления только о своих ветках и только о полной готовности переводов во всех ресурсных файлах продукта.
Было принято решение отталкиваться от наличия всех переводов в репозитории и, если они полностью готовы, отправлять оповещения в корпоративный мессенджер, в нашем случае это Google Chat.
Задача: организовать оповещения в репозитории, куда могут коммитить 8 команд, дублировать все оповещения в канал отдела технического документирования.
Решение:
Так выглядит наш менеджер по локализации, когда настает время назначить все ветки на перевод.
В среднем, у нас в работе каждый день находится более 10 веток. В Smartcat каждая языковая пара – это отдельный документ, и на каждый такой документ необходимо назначить переводчиков. Вручную. Представьте: 40-60 назначений каждый день. Для упрощения этого процесса мы сделали назначение через API, и тоже положили в pipeline. Запуск этого job-а производится по кнопке. Резонный вопрос: почему бы не сделать назначения автоматическими, при отправке переводов, и не разместить вызов методов в плагине Smartcat, а не в pipeline?
Причин такого решения несколько:
Решение: когда менеджер по локализации считает, что строки в данной ветке готовы к переводу, она нажимает на кнопку в Gitlab. На данную ветку назначается вся команда переводчиков. Задачу забирает тот переводчик, кто откликнулся первым.
На этом я завершаю цикл статей об интеграции и настройке непрерывных локализаций. Буду рада ответить на любые ваши вопросы.
Предыдущие статьи:
20 проектов, 20 языков, срок вчера
20 проектов, 20 языков, срок вчера. Часть 2
Масштабируемость
В прошлой статье я рассказывала, как настроить Serge для одного репозитория. В нашей компании мы имеем несколько десятков репозиториев, которые нуждаются в переводах, поэтому был выделен отдельный сервер для локализаций. Файловая структура и окружение на нем полностью идентичны тому, что описано в предыдущей статье. Для каждого репозитория используется свой инстанс Serge. Чтобы не выполнять команды вручную, каждому инстансу соответствует крон, который последовательно запускает команды Serge: получение новых строк из репозитория, получение новых переводов, парсинг, отправка новых строк в Smartcat и отправка новых переводов в Gitlab.
Варианты интеграций
Два набора языков в одном репозитории
Начнем с самого простого случая. Представьте, что в вашем репозитории есть несколько наборов ресурсных файлов. Например, строки для клиента и API приложения хранятся в одном репозитории, но в разных директориях. Клиент переводится на 20 языков, API — на 6.
Задача: организовать независимую поставку переводов в каждую из директорий.
Решение:
- Настроить 2 проекта в Smartcat: на 6 языков и на 20.
- Настроить 2 проекта на сервере локализаций.
- В первом проекте в файле project1.cfg добавить строку our $unmerged_branch_mask = '^(translateAPI-)'; # process unmerged branches matching this mask, где “translateAPI-” это префикс названия ветки. Префикс будет указывать Serge, что в данной ветке нужны переводы в директории API.
- В файле project1.serge.tmpl в параметре source_dir указать путь до ресурсных файлов в директории API.
- Аналогично, для второго проекта в файле project2.cfg добавить строку our $unmerged_branch_mask = '^(translateCLIENT-)'; # process unmerged branches matching this mask, где “translateCLIENT” это префикс для веток этого проекта. Префикс будет указывать Serge, что в данной ветке нужны переводы в директории Client.
- В файле project2.serge.tmpl в параметре source_dir указать путь до ресурсных файлов в директории CLIENT.
Обратите внимание, префиксы должны быть уникальными среди всех проектов, настроенных для одного репозитория.
Итого, мы имеем 2 проекта в Smartcat и 2 соответствующих им проекта на сервере локализаций. Оба проекта смотрят на один репозиторий в Gitlab, но в разные директории. Serge по префиксу ветки понимает, какие именно строки ему надо отправить на перевод. Для вычисления diff-а используется одна и та же ветка base-translate.
Локализация Swagger
В нашей компании все продукты, включая документацию, локализованы. Сейчас мы внедряем автогенерацию документации из swagger, и перед нами встала необходимость его локализовать.
Задача: локализовать swagger с минимальными усилиями.
Решение: В файле myproject.tmpl.serge в объект parser добавляем объект data и перечисляем в нем те поля, значение которых надо извлечь и отправить на перевод:
parser {
plugin parse_json
data
{
path_matches \/(summary|description)$
}
}
Аналогичная задача: надо переводить тексты из файла, но не все, а только юридические. Прочие тексты поставляет команда маркетологов. Чтобы не усложнять структуру, и не создавать дополнительный файл для юридических текстов, ключи всех юридических строк получили префикс “legal”:
parser {
plugin parse_json
data
{
path_matches ^\/legal\..*
}
}
Тонкости юридических переводов
Еще один интересный кейс. У нас есть юридический документ, условия которого разнятся в зависимости от страны. Но, тем не менее, это одно приложение и ресурсные файлы лежат в одной директории.
Задача: в рамках одного проекта перевести несколько документов, причем каждый документ должен быть переведен на один конкретный язык.
Что было сделано:
- Для каждой страны была создана соответствующая директория, внутри которой лежал релевантный этой стране исходный файл на английском языке.
- Путь для переменной source_dir указан до общей директории с ресурсными файлами.
- Включаем поиск ресурсных файлов во всех вложенных директориях: source_process_subdirs YES
- Добавляем в список вызываемых плагинов новый плагин, который позволяет отправлять каждый конкретный ресурсный файл на нужный язык. В качестве ориентира используем название директории, где он лежит:
callback_plugins {
:feature_branch {
plugin feature_branch
data {
master_job job.base-translate
}
}
:limit_languages
{
plugin limit_languages
data
{
# all rules are processed top to bottom; each rule can add or remove languages
# so the most priority rules are placed at the bottom
if
{
# by default, don't localize
file_matches .
then
{
exclude_all_languages YES
}
}
if
{
file_matches de-au\/
then
{
include_languages de-AT
}
}
if
{
file_matches li-LI\/
then
{
include_languages li
}
}
if
{
file_matches pt\/
then
{
include_languages pt-BR
}
}
if
{
file_matches zh-Hans\/
then
{
include_languages zh-Hans
}
}
# and so on..
}
}
Локализация при хранении строк в БД
В нашей системе есть часть кода, которая хранит переводы в БД, и по ряду причин переехать на ресурсные файлы в репозитории не может. Тем не менее, нам необходимо иметь возможность быстро и в автоматическом режиме поставлять переводы.
Задача: Организовать процесс непрерывных локализаций, если строки хранятся не в репозитории, а в БД.
Решение:
- Создать репозиторий, в нем собрать и сгруппировать по удобному нам принципу (по количеству языков перевода или по продуктам) все строки из БД.
- Создать проект в Smartcat.
- Запустить стандартный цикл непрерывных локализаций.
- Ветки переводов мержить в ветку base-translate.
- По крону проверять значение хеша последнего коммита в base-translate. Если хеш изменился, то есть, были смержены новые переводы, парсить diff между старым и текущим хешом, и отправлять новые/измененные строки в БД.
Бонусные фичи
Оповещения
Базовые оповещения Smartcat нам не подходили, так как каждая команда хочет получать уведомления только о своих ветках и только о полной готовности переводов во всех ресурсных файлах продукта.
Было принято решение отталкиваться от наличия всех переводов в репозитории и, если они полностью готовы, отправлять оповещения в корпоративный мессенджер, в нашем случае это Google Chat.
Задача: организовать оповещения в репозитории, куда могут коммитить 8 команд, дублировать все оповещения в канал отдела технического документирования.
Решение:
- Договориться с каждой из команд, что название веток должно содержать название команды. По-прежнему использовать префикс translate- для обозначения веток, нуждающихся в переводе.
- Создать pipeline, который запускается только для веток с префиксом translate-.
- В pipeline определять какой команде принадлежит ветка, проверять наличие строк с пустым value, и, если их нет, отправлять уведомления о готовности в соответствующий канал. Так как код достаточно объемный, то я его вынесла в скрипт.
CI
check-translations:
stage: check-translations
image: node:8.14.0
tags:
- devops
script:
- chmod +x ./notification.sh
- ./notification.sh
only:
- base-translate
- /^translate.*$/
when: always
Скрипт оповещений
#!/bin/bash
hangouts(){
curl -X POST --max-time 180 -H "Content-Type: application/json; charset=UTF-8" --data "{
\"cards\": [{\"header\": {\"title\": \"LOCALIZATION IS READY\",\"subtitle\": \"REPOSITORY NAME\",\"imageUrl\": \"https://avatanplus.com/files/resources/mid/5775880ee27f8155a31b7a50.png\"},\"sections\": [{\"widgets\": [{\"keyValue\": {\"topLabel\": \"Translation is finished in the branch\",\"content\": \"$1\"}}]},{\"widgets\": [{\"buttons\": [{\"textButton\": {\"text\": \"SEE COMMIT\",\"onClick\": {\"openLink\": {\"url\": \"https://gitlab.loc/common/publisher-client/commit/$2\"}}}}]}]}]}]}" "$3" || true
}
cd app/translations
if echo "$CI_COMMIT_REF_NAME" | grep "commandname1";
then
grep -rl '\:\s\"\"' *.json >> result.file
if [ -s network.file ];
then
echo "Translations are not ready";
cat result.file
else
hangouts $CI_COMMIT_REF_NAME $CI_COMMIT_SHA $HANGOUTS_NOTIFICATIONS_COMMAND_NAME_1
hangouts $CI_COMMIT_REF_NAME $CI_COMMIT_SHA $HANGOUTS_NOTIFICATIONS_DOC
fi
fi
if echo "$CI_COMMIT_REF_NAME" | grep "commandname2";
then
grep -rl '\:\s\"\"' *.json >> result.file
if [ -s result.file ];
then
echo "Translations are not ready";
cat result.file
else
hangouts $CI_COMMIT_REF_NAME $CI_COMMIT_SHA $HANGOUTS_NOTIFICATIONS_COMMAND_NAME_2
hangouts $CI_COMMIT_REF_NAME $CI_COMMIT_SHA $HANGOUTS_NOTIFICATIONS_DOC
fi
fi
...
if echo "$CI_COMMIT_REF_NAME" | grep "commandname8";
then
grep -rl '\:\s\"\"' *.json >> result.file
if [ -s result.file ];
then
echo "Translations are not ready";
cat result.file
else
hangouts $CI_COMMIT_REF_NAME $CI_COMMIT_SHA $HANGOUTS_NOTIFICATIONS_COMMAND_NAME_8
hangouts $CI_COMMIT_REF_NAME $CI_COMMIT_SHA $HANGOUTS_NOTIFICATIONS_DOC
fi
fi
Назначения переводчиков через Smartcat API
Так выглядит наш менеджер по локализации, когда настает время назначить все ветки на перевод.
В среднем, у нас в работе каждый день находится более 10 веток. В Smartcat каждая языковая пара – это отдельный документ, и на каждый такой документ необходимо назначить переводчиков. Вручную. Представьте: 40-60 назначений каждый день. Для упрощения этого процесса мы сделали назначение через API, и тоже положили в pipeline. Запуск этого job-а производится по кнопке. Резонный вопрос: почему бы не сделать назначения автоматическими, при отправке переводов, и не разместить вызов методов в плагине Smartcat, а не в pipeline?
Причин такого решения несколько:
- Человеческий фактор. Несмотря на то, что мы выстраиваем процессы и стараемся их придерживаться, невычитанные строки или строки без контекста регулярно попадают в Smartcat. Автоматическое назначение в данном случае означало бы для нас дополнительные траты, так как некоторые строки были бы отправлены на перевод дважды: до и после редактуры.
- Распределение ролей. Настройкой и управлением проектами на уровне сервера локализаций занимается инженер по локализации или технический писатель проекта. Назначениями и общением с переводчиками занимается менеджер по локализации. Таким образом, назначения должны быть управляемы, прозрачны и доступны через GUI.
Решение: когда менеджер по локализации считает, что строки в данной ветке готовы к переводу, она нажимает на кнопку в Gitlab. На данную ветку назначается вся команда переводчиков. Задачу забирает тот переводчик, кто откликнулся первым.
CI
assignee:
stage: assignee
image: node:8.14.0
tags:
- devops
script:
- chmod +x ./assignee.sh
- ./assignee.sh
only:
- base-translate
- /^translate.*$/
- assignee
when: manual
Скрипт назначений
#!/bin/bash
if echo "$CI_COMMIT_REF_NAME" | grep "translate-";
then
node -pe "JSON.parse(process.argv[1]).documents.forEach(function(elem){ if(elem.name.indexOf(\"$CI_COMMIT_REF_NAME\") !== -1) { console.log(elem.id) } });" "$(curl -XGET -H "Authorization: Basic $SMARTCAT_API_KEY" -H "Content-type: application/json" "https://smartcat.ai/api/integration/v1/project/$SMARTCAT_PROJECT_ID")" >> documents
fi
sed '$d' documents > documents.list
while read LINE; do bash -c "curl -XPOST -H 'Authorization: Basic $SMARTCAT_API_KEY' -H "Content-type:application/json" -d '{"documentIds":[\""$LINE"\"],"stageNumber": 1}' 'https://smartcat.ai/api/integration/v1/document/assignFromMyTeam'";done < documents.list
На этом я завершаю цикл статей об интеграции и настройке непрерывных локализаций. Буду рада ответить на любые ваши вопросы.
afan
> невычитанные строки или строки без контекста регулярно попадают в Smartcat. Автоматическое назначение в данном случае означало бы для нас дополнительные траты, так как некоторые строки были бы отправлены на перевод дважды: до и после редактуры.
В моей голове бытует мнение, что стоимость «лишнего» перевода может оказаться дешевле стоимости работы менеджера по локализации; а внедрение в процесс лишней кнопки может приводить к задержкам в работе (переводчики теряют день или больше, ожидая проверки строк) и накладкам (менеджер по локализации забыл что-то оправить в работу и потерял на этом еще день-два). Посему вопрос: не рассматривали ли вы возможность, хотя бы в порядке эксперимента, автоматической отправки на перевод всего и вся с последующими замерами (насколько быстрее проходит перевод, сколько вы реально переплачиваете за повторный перевод с учетом всех совпадений TM, и сколько вы экономите времени менеджера по локализации)?
AnastasiaMyasnikova
Добрый день! Я тот самый менеджер по локализации.
Тут дело даже не столько в том, что мы не хотим переплачивать и боимся, что в перевод уйдут невычитанные тексты. Помимо назначения переводчиков в Smartcat, я также передаю им описание задачи и контекст (в виде текста-пояснения или скриншотов) в Skype. Поскольку мы по большей части переводим интерфейсные тексты, скриншоты и подробности необходимы, иначе перевод будет некорректным.
Есть способ передавать ссылки на скриншоты в комментариях к ресурсному файлу, но не все наши ресурсные файлы поддерживают возможность комментирования. В таком случае, автоматическое назначение означало бы, что переводчики получают задачу на перевод сразу, как только соответствующая ветка попадает в Smartcat, а значит, без моего сообщения и без необходимого контекста. Тогда некоторые из них начнут писать мне в личку и требовать скриншоты, а некоторые переведут «как поняли», и хотя мы этого и не заметим, качество переводов заметно снизится.
afan
Ну у нас все то же самое — нужно обеспечивать контекстом переводчиков и все такое, но мы пошли от обратного: переводы выгружаются как можно раньше (какие-то с комментариями, какие-то без, тоже в зависимости от формата файлов и от проекта), и переводчики начинают переводить строки с тем контекстом, что есть. А вот когда у них появляются вопросы, мы уже им отвечаем, выборочно добавляем комментарии и скриншоты. Автоматическая выгрузка строк на перевод позволяет еще и как можно раньше обнаруживать проблемы с исходными строками, причем не только силами менеджера по локализации, но и переводчиками, а также позволяет интегрировать переводы раньше и делать QA в CI-сборках продукта (а QA в финальном продукте и подправка переводов, как мне кажется, намного более продуктивен, чем просто перевод, пусть даже и с дополнительными комментариями).
Поэтому я и подвожу вас размышлению: может быть выгрузка «сразу и как есть» с последующим выборочным добавлением контекста — это не только минус, но и плюс? И если у вас есть возможность реально оценить качество переводов, то это был бы отличный эксперимент (хотя бы на одном проекте каком-то). Вполне может оказаться, что плюсов от такого подхода больше, чем ожидаемых минусов.