Как и у многих команд разрабатывающих большие продукты, при разработке опенсорсного фреймворка для визуального создания веб-приложений iondv.framework возникла задача документирования возможностей системы. Сначала для себя и новых сотрудников. Потом для пользователей. А в настоящее время документация является и важным средством продвижения продукта. И хотя последнее время идёт смещение в сторону медиа материалов (видео уроков), документация важна.
Мы прошли путь от wiki в гитлаб, до структурированной и синхронизированной по двум языкам документации в виде markdown файлов публикуемых в репозитории github. И вот мы начали переход на связку github + Weblate + readTheDocs. Так как статей об этом на хабре мало – ниже о наших ошибках, сложностях и опыте настройки перевода open-source проекта.
Подходы к организации документации и её локализации
Для начала почему не годится wiki гитлаба? В ней невозможно нормально организовать совместную работу и сопровождение версионирования изменений кода и изменений документации. Из-за этого мы очень сильно упустили разработку документации от функциональных возможностей на определенном этапе. Так как появление новых функций — не сопровождалось появлением документации. Второе почти невозможно организовать параллельный перевод — так как отслеживание изменений достаточно трудоёмко.
Выходом на определенном этапе стал перенос документации в формате markdown в репозиторий ядра — просто скопировали папку wiki в репозиторий в папку docs в корней фреймворка и в ней разделили на две подпапки ru и en. Соответственно документация разделилась на две части. Ввели бизнес-процесс в jira — по всем задачам с новой функциональностью обязательный этап документирования, а за ним и перевода. По коммитам в документы легко отслеживать какие изменения были, был ли сделан перевод.
Но у этой схемы есть существенные недостатки:
- работа с самой документацией обычным пользователям не удобна;
- переводчик хоть и освоила коммиты, git и markdown — но в целом это очень неудобный способ организации перевода больших объёмов текстов;
- сложно поддерживать целостность ссылок и соответствие текстов, особенно при небольших правках — надо вручную следить за изменениями в документах;
- одинаковые разделы шаблонов на каждой странице нужно прописывать;
- невозможно организовать участие сообщества в переводе документации.
После изучения вариантов, нам понравилась возможность размещения документов на http://readthedocs.org/ — платформы размещения разработческой документации. Изначально все это выросло из Python (у нас фреймворк на node.js) и использование методов из "конкурирующего лагеря" казался странным. Но в целом это решение имеет очень важные преимущества: поддерживает версионирование документации и языки — все это базируется на основе sphinx-doc.org. То, что можно собирать документацию и безвозмездно хостить её прямо на readthedocs это больше бонус, хотя и приятный.
Из минусов, нам не удалось нормально настроить перевод документации из русской версии документации в markdown – так как списки, таблицы, ссылки переводятся как текст и при обратной сборке они становятся обычным текстом в документации. Поэтому пришлось мигрировать на reStructuredText.
Мы запустили эксперимент по переходу, результатами которого и хотим поделиться. Что в итоге сделали:
- выделили исходную документацию ведущуюся на русском языке в отдельный репозиторий https://github.com/iondv/docs-ru ;
- создали отдельный репозиторий для локализации документации созданной на русском на английский язык и в перспективе на другие языки https://github.com/iondv/docs-l10n — он формируется автоматизированно сборкой и коммитами из weblate ;
- настроили задачу для периодического обновления локализаций для переводов sphinx;
- подняли свой weblate сервер для организации переводов https://weblate.iondv.com ;
- настроили readthedocs для сборки документации https://iondv.readthedocs.io/ и её публикации с изначально английской версией (английская версия перенесена не вся — находимся в процессе миграции с репозитория).
- запустили всё это в обкатку, перед запланированной существенной доработкой документации.
Немного подсмотрели у Godot Engine:
- организация репозиториев https://github.com/godotengine/godot-docs;
- организация перевода через сервис weblate.
Ниже описана процедура развёртывания с нуля с примерами конфигурирования. Так как мы только запустили этот сценарий, то перед тем, как мы окончательно углубимся, будем благодарны за обратную связь и опыт, особенно критику и предложения.
Организация репозиториев
Мы создали репозиторий с документацией на русском языке в который сконвертировали документацию подготовленную в формате markdown из репозитория фреймворка в формат reStructuredText. Создали поначалу пустой репозиторий для локализаций – он будет головным для readthedocs.
В репозитории iondv/docs-l10n с переводимой документацией создаём ссылку на репозиторий источник git submodule add https://github.com/iondv/docs-ru
. Он его сразу и склонирует. Также мы переводы осуществляем в специальной ветке translation
, а потом её мерджим в master
ветку. Reathedocs собирает документацию из master
ветки.
В репозитории с русской документации формируем файл конфигурации для sphinx. Пример конфига доступен по ссылке. Обратите внимание на два параметра.
Первый параметры latex_elements
в нем задаётся, что источник для перевода в utf8, иначе автосборка на readthedocs ругается на каждый русский символ.
latex_elements = {
'preamble': '\\usepackage[utf8]{inputenc}',
'babel': '\\usepackage[russian]{babel}',
'cmappkg': '\\usepackage{cmap}',
'fontenc': '\\usepackage[T1,T2A]{fontenc}',
'utf8extra':'\\DeclareUnicodeCharacter{00A0}{\\nobreakspace}',
}
Второй параметр gettext_compact=True
, если он False – то локализации будут создаваться отдельно для каждого файла и тогда каждый из файлов нужно будет отдельно подключать в Weblate для перевода. Если параметра нет или он установлен в True – то тогда каждый из каталогов будет собран в один файл, ну и достаточно будет подключить один файл как компонент. Можно ещё скриптом собирать все файлы с локализацией в один, чтобы в Weblate был только один файл для перевода.
Не забудьте после коммита и пуша в docs-ru, сделать коммит и пуш в вышестоящем репозитории docs-l10n для того, чтобы обновилась ссылка на последний коммит в документации.
Пока все файлы локализаций у нас формируются по папкам в репозитории с локализацией в ветке translation.
Мы хотим создать один проект для документации и отдельные проекты для перевода фреймворка и модулей. Для каждого файла, соответствующего каталогу (тематике) в документации, создаётся в Weblate отдельный компонент.
Мы пробовали делать все файлы раздельными (gettext_compact=False), проект был отдельным каталогом, каждый файл компонентом. Но когда файлов много – их подключать достаточно утомительно. А во-вторых, становится много проектов – и может появиться путаница. Если есть опыт организации и компоновки файлов, поделитесь пожалуйста.
Развёртывание серверного окружения
Все будем разворачивать в докер контейнерах. Мы подняли под Ubuntu, но под CentOs разница не большая. Сервера все будут во внутренней сети docker service-net
, создадим её командой docker network create service-net
Во-первых, поднимем окружение для Weblate – нужен Redis и PostgreSql (подправьте данные учётки в переменных окружения докер контейнера)
docker run --name redis -v redis-data:/data --expose 6379 --net service-net --restart unless-stopped -d redis:4-alpine redis-server --appendonly yes
docker run --name postgres -v postgres-data:/var/lib/postgresql/data --expose 5432 --env "POSTGRES_PASSWORD=123" --env "POSTGRES_USER=iondv" --env "POSTGRES_DATABASE=weblate" --env "POSTGRES_DB=weblate" --env "POSTGRES_HOST=postgres" --env "POSTGRES_PORT=5432" --net service-net --restart unless-stopped -d postgres:11-alpine
Далее устанавливаем Weblate для запуска системы. Подправьте хосты, учётки, в том числе пользователя github в соответствующих полях и часовой пояс. Как пример приведён наш часовой пояс Владивостока для Хабаровска.
docker run --name weblate -v weblate-data:/app/data --expose 8080 --env "DEBUG=True" --env "POSTGRES_PASSWORD=123" --env "POSTGRES_USER=iondv" --env "POSTGRES_DATABASE=weblate" --env "POSTGRES_DB=weblate" --env "POSTGRES_HOST=postgres" --env "POSTGRES_PORT=5432" --env "REDIS_HOST=redis" --env "REDIS_PORT=6379" --env "WEBLATE_EMAIL_HOST=smtp.yandex.ru" --env "WEBLATE_EMAIL_PORT=465" --env "WEBLATE_EMAIL_USE_TLS=True" --env "WEBLATE_EMAIL_HOST_USER=weblate@YOUR_HOST.com" --env "WEBLATE_EMAIL_HOST_PASSWORD=123" --env "WEBLATE_SERVER_EMAIL=weblate@ YOUR_HOST.com" --env "WEBLATE_DEFAULT_FROM_EMAIL=weblate@ YOUR_HOST.com" --env "WEBLATE_ADMIN_PASSWORD=123" --env "WEBLATE_ADMIN_NAME=IONDV Weblate Admin" --env "WEBLATE_ADMIN_EMAIL=weblate@YOUR_HOST.com" --env "WEBLATE_DEBUG=false" --env "WEBLATE_LOGLEVEL=DEBUG" --env "WEBLATE_SITE_TITLE=IONDV.Doc translation" --env "WEBLATE_ALLOWED_HOSTS=weblate.YOUR_HOST.com" --env "WEBLATE_REGISTRATION_OPEN=1" --env "WEBLATE_REQUIRE_LOGIN=0" --env "WEBLATE_TIME_ZONE=Asia/Vladivostok" --env "TZ=Asia/Vladivostok" --net service-net --restart unless-stopped -d weblate/weblate:3.11.2-1
Обратите внимание, что в версии Weblate 3.10 и старше – после создания проекта, нужно указать тип языка источника – иначе тексты для переводов с русского языка не выводятся. Мы проверяли на версии 3.11.2-1.
Чтобы к Weblate был доступ по ssl и с любыми необходимыми ограничениями и настройками – будем использовать внешний nginx, но можно обойтись и внутренним.
Добавляем в конфиг /etc/conf.d/weblate.conf
server {
listen 80;
server_name weblate.YOURHOST.ru;
return 301 https://weblate.YOURHOST.ru $request_uri;
}
server {
listen 443;
server_name weblate.YOURHOST.ru;
ssl_stapling on;
resolver 8.8.8.8 valid=300s;
ssl_certificate /etc/nginx/cert/YOURHOST.crt;
ssl_certificate_key /etc/nginx/cert/YOURHOST.key;
ssl_dhparam /etc/nginx/cert/dhparam.pem;
ssl_session_timeout 10m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers kEECDH+AES128:kEECDH:kEDH:-3DES:kRSA+AES128:kEDH+3DES:DES-CBC3-SHA:!RC4:!aNULL:!eNULL:!MD5:!EXPORT:!LOW:!SEED:!CAMELLIA:!IDEA:!PSK:!SRP:!SSLv2;
ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security "max-age=31536000;";
add_header Content-Security-Policy-Report-Only "default-src https:; script-src https: 'unsafe-eval' 'unsafe-inline'; style-src https: 'unsafe-inline'; img-src https: data:; font-src https: data:; report-uri /csp-report";
location / {
proxy_pass http://weblate:8080;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
proxy_ssl_session_reuse off;
proxy_set_header Host $http_host;
proxy_redirect off;
}
access_log /var/log/nginx/weblate.access.log;
error_log /var/log/nginx/weblate.error.log error;
}
В репозитории на github, есть подробный пример конфигурации nginx.
Запуск nginx
docker run --name nginx_proxy -v /etc/nginx.conf:/etc/nginx/nginx.conf:ro -v /etc/conf.d:/etc/nginx/conf.d:ro -v /etc/nginx/cert:/etc/nginx/cert:ro -v /var/log/nginx:/var/log/nginx -p "80:80" -p "443:443" --net service-net --env "TZ=Asia/Vladivostok" --restart unless-stopped -d nginx
Настройка задачи запуска по расписанию для sphinx
Мы напишем отдельный скрипт для обновления локализаций, который нужно будет запускать по расписанию в cron, например раз в день. Но для начала сделаем докер образ для запуска sphinx и создадим конфиг для него.
Dockerfile
FROM python:3-alpine
RUN apk add --update make && pip install sphinx sphinx_rtd_theme sphinx-intl
WORKDIR /docs
VOLUME ["/docs"]
CMD []
Собираем репозиторий в директории где расположен Dockerfile командой docker image build -t sphinx:latest .
Далее будем использовать каталог /workspace/docs-l10n
— для размещения склонированного репозитория с переведённой документаций и внутри него docs-ru документацию с русским языком, а в директории workspace
будет находиться наш скрипт.
Для автоматизации работы с githib.com будем использовать авторизацию с использованием токена. Для этого идём в раздел разработчика пользователя под которым будем осуществлять коммиты и генерируем токен https://github.com/settings/tokens. Это же токен понадобиться при указании репозиториев для Weblate.
Для формирования проекта мы будем скачивать репозиторий (обновлять) и, если есть изменения, пересобирать файлы локализаций sphinx-ом. В переменные gitUser и gitToken нужно проставить пользователя и его токен соответственно.
#!/bin/bash
gitToken="111111111111111111111111111111111"
gitUser="user"
curDir=`pwd`
cd ${0%/*} && scriptDir=`pwd` && cd $curDir
l10nDir=$scriptDir/iondv-docs-l10n
docsDir=$l10nDir/docs-ru
if [ -d $l10nDir ]; then
cd $l10nDir
git branch | grep "^* translation"
if [ $? -ne 0 ]; then git checkout translation; git branch | grep "^* translation"; if [ $? -ne 0 ]; then echo "Не удалось переключить на translation ветку"; exit; fi
fi
resPull=`git pull`
resPullSatus=$?
cd $curDir
echo -n "Проверяем изменения l10n: " && echo $resPull | grep "Already up to date"
if [ $? -eq 0 ]; then echo "Нет изменений l10n"; fi
else
git clone https://$gitUser:$gitToken@github.com/iondv/docs-l10n -b translation $l10nDir
cd $l10nDir
git branch | grep "^* translation"
if [ $? -ne 0 ]; then echo "Не удалось переключить на translation ветку"; exit; fi
fi
if [ -d $docsDir/.git ]; then
cd $docsDir
resPull=`git pull`
resPullSatus=$?
cd $curDir
echo -n "Проверяем изменения docs-ru: " && echo $resPull | grep "Already up to date"
if [ $? -eq 0 ]; then echo "Нет изменений. Выходим"; exit; fi
else
git clone https://$gitUser:$gitToken@github.com/iondv/docs-ru $docsDir
fi
docker run -it --rm -v $l10nDir:/docs sphinx sphinx-build -b gettext /docs/docs-ru /docs/gettext
if [ $? -ne 0 ]; then echo "Ошибка создания gettex"; exit 1; fi
docker run -it --rm -v $l10nDir:/docs sphinx sphinx-intl update -p /docs/gettext -l en
if [ $? -ne 0 ]; then echo "Ошибка обновления локалей"; exit 1; fi
docker run -it --rm -v $l10nDir:/docs sphinx chmod -R ugo+r /docs/gettext /docs/locales
curDate=`date +"%Y%m%d-%H-%M"`
cd $l10nDir
gitStatus=`git status | grep "nothing to commit, working tree clean"`
if [ $? -eq 0 ]; then
echo "Нет изменений"
exit 0
else
echo "Есть изменения"
git add .
git commit -a -m "$curDate autoupdate sphinx locales"
git push
fi
Настройка Weblate
Создаём проект, в котором указываем базовые параметры. Мы создаём один проект для документации и компоненты для каждой тематики (каталога) – [пример] (https://weblate.iondv.com/projects/docs/) – содержит документацию по фреймворку.
После создания проекта не забудьте установить в свойствах проекта язык источника – русский, иначе он неправильно распознает переводы.
После этого мы создаём компонент перевода в проекте – у нас они для файлов в корне репозитория и для каждой подпапки объединены в один файл и находятся в папке gettex. Для начала указываем название, проект, репозиторий и ветку источника.
После этого мы выбираем файл для перевода. Обратите внимание, что выбрать нужно файл для нескольких переводов, а не одноязычный.
На следующем шаге нужно уточнить URL для отправки репозитория – здесь сразу нужно задать пользователя, под которым есть права пушить в репозиторий. Например https://USER:123@github.com/iondv/docs-l10n
, где USER
– логин, а 123
соответственно пароль.
Далее на этой же странице нужно выбрать «Шаблон для новых переводов». В этом поле нужно указать файл источник для компонента в папке gettex – т.е. в папке куда sphinx собирает файлы с расширением pot.
Собственно, можно переводить. Например на английский, перейдя по пути компонента перевода index
в проекте docs
.
Для новых компонентов из того же репозитория, не нужно указывать все параметры, достаточно указать уже использовать локальную адресацию – т.е. уже созданного. Для нашего примера это будет weblate://docs/index
.
Пример перевода.
После перевода необходимо перейти в свойства проекта (или компонента, если репозитории разные). Сделать коммит, а затем отправить перевод в удалённый репозиторий – т.е. в репозиторий https://github.com/iondv/docs-l10n который мы указали в компонентах. Также если были изменения в удалённом репозитории, то мы их можем получить, нажав кнопку «Извлечь».
Перейдя в репозиторий, мы можем увидеть сделанные изменения в ветке translation
. После проверки их сливаем с веткой master
. Обратите внимание на скриншот с гитхаба, для папки gettex
– коммит был выполнен скриптом после обновления sphinx-ом документации из репозитория inondv/docs-ru
. А для папки locales/en/LC_MESSAGES
веблейтом.
Настройка readthedocs.org
В readthedocs.io мы делаем импорт наших репозиториев.
Сначала импортируем iondv/docs-l10n. В названии указываем желаемое название в url NAME.reathedocs.io
. Ставим галочку «Edit advanced project options» — она нужна, чтобы указать язык проекта. Но так как мы хотим, чтобы основным переводом был английский – то язык менять не будем.
После этого можно перейти на вкладку Build – проект уже начал собираться. Он склонирует не только репозиторий iondv/docs-l10n, но и подмодуль iondv/docs-ru.
После сборки можно перейти по кнопке “View docs”, у нас она будет вести на последнюю английскую версию которую мы сформировали в веблейт, запушили в ветку translate
и смерджли в master
.
Теперь добавить русский язык в документацию. Для этого снова переходим в панель управления, указываем название проекта «iondv-ru» и импортируем проект iondv/docs-ru
. Также ставим галочку «Edit advanced project options» и задаём в дополнительных настройках язык «Russian». Проект также начнёт собираться сразу, но не дожидаясь этого, нужно вернуться в основной проект «iondv» с английской версией документации и перейти в настройки «Admin» и в пункте «Translations» добавить проект «iondv-ru»
Теперь в параметрах документации появится выбор не только версии, но и языка:
Что в планах:
- переделать структуру документации, донастроить автоматизированное формирование индексацию и выверить все ссылки в документации и подправитьшаблоны;
- перенести весь перевод из репозитория фреймворка в гитхабе в Weblate;
- разместить в описании фреймворка и на сайте ссылки на документацию iondv.framework на readthedocs, возможно под своим доменом;
- доработать вводную документацию о фреймворке и собрать все описания, в том числе демо и модулей в одном месте;
- перевести новые документы на английский, возможно с помощью сообщества;
- попробовать другие языки.
- подключить weblate для перевода сообщений системы на другие локали в репозитории ядра фреймворка (сейчас сквозной перевод скриптами).
Этот последний пункт ключевой. Сейчас в планируемой к выпуску версии полностью реализована многоязычность не только в ядре, но и на уровне модулей и шаблонов. Но мы не обкатывали её для переводов в weblate. Смущают трудности с нарушением синтаксиса языка при переводе – условно говоря было ERR_MSG='Сервер запущен'
, а стало ERR_ MSG="Server\'s up and running.'
. Возможно, имеет смысл переработать на использование модели GNU gettext, но с ней тоже не все очевидно.
Если есть опыт – поделитесь пожалуйста в комментариях.
Вы можете следить за проектом и этим опытом организации локализации также через социальные сети – facebook или LinkedIn.
maxim_ge
Интересно было бы услышать, почему не подходит решение хранить все на github/gitlabe а показывать в каком-то таком виде: docs.docker.com/get-started
Там и версии есть, и кнопочка Edit. Понятно, надо «смотрелку» для начала найти, но, по-моему, это не есть большая проблема.
akumidv Автор
Может быть не понял вопрос, но именно это и реализовано? Т.е. документация хранится вся на https://github.com/iondv/docs-ru исходная и переводы на https://github.com/iondv/docs-l18n. При этом к docs-l18n подключена веб-систем для переводов Weblate (это чтобы в обычных редакторах не редактировать и не пушить в репозиторий — хотя это тоже вполне возможно, т.е. веблейт он рядом, а не вместо).
При этом для смотрелки подключена readthedocs которая билдит из файлов rst с помощью sphinx html странички (но кстати и pdf-ки и epub тоже) — это можно и на сервере делать и выкладывать файлы под любой веб-сервер. На readthedocs на каждой страничке есть ссылка на редактирование оригинала на github.
Я к сожалению не знаю, что за системе у docker для документации используется.
maxim_ge
Да, теперь понял. Несколько сбила с толка фраза «То, что можно собирать документацию и безвозмездно хостить её прямо на readthedocs это больше бонус, хотя и приятный».
akumidv Автор
Если хостить Weblate самим, то собирать документацию и хостить статичные html-ки собранные sphinx конечно уже не сложно, больше было про это.