기존 Zanata 방식
project repo에 변화가 발생하면 우선적으로 project-config 레포지토리의 zuul.d/projects.yaml 로직이 수행되게 됩니다.
- project:
name: openstack/nova
queue: integrated
templates:
- official-openstack-repo-jobs
- periodic-jobs-with-oslo-master
- publish-to-pypi
- translation-jobs-master-only <- 이 job 이 i18n 작업
- api-guide-jobs
- api-ref-jobs
위는 그 중 openstack의 nova 프로젝트에 정의된 내용들이며 template에 정의된 각각의 작업들이 수행된다고 보면됩니다. 그중 translation-jobs-master-only 작업이 i18n 작업에 해당합니다. 그럼 translation-jobs-master-only이 정의된 부분을 확인해보면
해당 로직은 openstack-zuul-jobs 레포지토리에 정의되어 있으며 그 중 zuul.d/project-templates.yaml 에 정의되어 있습니다.
- project-template:
name: translation-jobs-master-only
description: |
Sync translations to translation server and back again for
**master** only.
This is intended for official OpenStack projects that have
translations set up.
post:
jobs:
- upstream-translation-update:
branches: master
periodic:
jobs:
- propose-translation-update:
branches: master
위의 정의를 보게되면 우리가 우선적으로 봐야할 것은 post, periodic job입니다. upstream-translateion-update는 프로젝트에서 코드가 머지되면 수행되는 작업이며, propose-translation-update는 매일 UTC 2시에 실행된다고 합니다.
자 우리는 Zuul에 merge 시에 어떤 Job을 수행할지 정의되어 있는지 확인하였습니다.해당 Job이 실제로 수행하는 작업은 Ansible에 정의되어 있습니다. 해당 작업은 project-config/playbooks/translation/upstream-translation-update.yaml 에 정의되어 있습니다. 여기서 Zuul과 Ansible이 헷갈릴 수 있는데 Zuul은 프로젝트 merge 이후 실행할 Job을 관리하고, 해당 Job이 실제로 수행하는 작업(예: 번역 파일 업데이트)은 Ansible Playbook에 정의한다고 보면 됩니다. 즉, Zuul이 Job을 실행할 때 내부적으로 Ansible을 호출해서 Playbook을 실행합니다.
- name: Upstream Translation Updates
hosts: all
roles:
- prepare-zanata-client
- legacy-zuul-git-prep-upper-constraints
tasks:
- name: Run upstream_translation_update.sh script
command: "{{ ansible_user_dir }}/scripts/upstream_translation_update.sh {{ zuul.project.short_name }} {{ zuul.job }} {{ zuul.branch }} {{ ansible_user_dir }}/{{ zuul.projects['opendev.org/openstack/horizon'].src_dir }}"
args:
chdir: "{{ zuul.project.src_dir }}"
- name: Fetch Translation Output
hosts: all
roles:
- fetch-translation-output
위의 Ansible을 확인하며 Run upstream_translation_update.sh script 를 수행하게 됩니다. 즉, upstream_translation_update.sh 파일이 merge시에 실제로 수행하게 될 작업이 된다는 의미입니다.
해당 로직은 openstack-zuull-jobs 에 정의되어 있습니다.
# Project setup and updating POT files.
case "$PROJECT" in
api-site|openstack-manuals|security-doc)
init_manuals "$PROJECT"
# POT file extraction is done in setup_manuals.
setup_manuals "$PROJECT" "$ZANATA_VERSION"
case "$PROJECT" in
api-site)
ALL_MODULES="api-quick-start firstapp"
;;
security-doc)
ALL_MODULES="security-guide"
;;
*)
ALL_MODULES="doc"
;;
esac
if [[ "$ZANATA_VERSION" == "master" && -f releasenotes/source/conf.py ]]; then
extract_messages_releasenotes
ALL_MODULES="releasenotes $ALL_MODULES"
fi
;;
training-guides)
setup_training_guides "$ZANATA_VERSION"
ALL_MODULES="doc"
;;
i18n)
setup_i18n "$ZANATA_VERSION"
ALL_MODULES="doc"
;;
tripleo-ui)
setup_reactjs_project "$PROJECT" "$ZANATA_VERSION"
# The pot file is generated in the ./i18n directory
ALL_MODULES="i18n"
;;
*)
# Common setup for python and django repositories
setup_project "$PROJECT" "$ZANATA_VERSION"
# ---- Python projects ----
module_names=$(get_modulename $PROJECT python)
if [ -n "$module_names" ]; then
if [[ "$ZANATA_VERSION" == "master" && -f releasenotes/source/conf.py ]]; then
extract_messages_releasenotes
ALL_MODULES="releasenotes $ALL_MODULES"
fi
for modulename in $module_names; do
extract_messages_python "$modulename"
ALL_MODULES="$modulename $ALL_MODULES"
done
fi
# ---- Django projects ----
module_names=$(get_modulename $PROJECT django)
if [ -n "$module_names" ]; then
install_horizon
if [[ "$ZANATA_VERSION" == "master" && -f releasenotes/source/conf.py ]]; then
extract_messages_releasenotes
ALL_MODULES="releasenotes $ALL_MODULES"
fi
for modulename in $module_names; do
extract_messages_django "$modulename"
ALL_MODULES="$modulename $ALL_MODULES"
done
fi
# ---- Documentation ----
if [[ -f doc/source/conf.py ]]; then
# Let's test this with some repos :)
if [[ ${DOC_TARGETS[*]} =~ "$PROJECT" ]]; then
extract_messages_doc
ALL_MODULES="doc $ALL_MODULES"
fi
fi
;;
esac
# The Zanata client works out what to send based on the zanata.xml file.
# Do not copy translations from other files for this change.
zanata-cli -B -e push --copy-trans False
# Move pot files to translation-source directory for publishing
copy_pot "$ALL_MODULES"
위는 해당 로직의 일부이며 간단히 요약하면 각 프로젝트(ex) django, document…)마다 pot 파일을 생성하여 합치고 그것을 zanata에 push하는 것입니다. 이런 방식으로 기존 Zanata에서 작업은 이루어지고 있었습니다. 그렇다면 이 작업을 똑같이 Weblate에서도 수행할 수 있게 변경해야합니다.
Weblate로 마이그레이션
우선적으로 작업해야할 것은 upstream_translation_update.sh파일을 어떻게 처리할 것인가 입니다. 해당 쉘 파일에 코드를 추가하여 작업하는 것은 무리가 있습니다. 일단 현재 팀에서의 방향은 zanata 로직은 살려두면서 zanata는 zanata대로 수행하고 weblate job을 추가하여 weblate는 weblate대로 수행하게 하는 것입니다. 그리고 완전히 마이그레이션이 이루어지면 zanata job을 삭제하는 것입니다. 그렇기에 upstream_translation_update_weblate.sh라는 쉘 파일을 추가로 만들어 작업하였습니다.
그렇다면 기존에 zanata에서는 zanata cli로 push 명령어로 해결했던 업데이트 문제를 어떻게 weblate에서 수행할까? 이 문제를 해결하기 위해 weblate API 문서를 찾아보았습니다.
처음 수행하였던 방식은 POST /api/translations API를 사용하는 방식입니다.
curl -X POST -H "Authorization: Token "
-F "file=@/home/ubuntu/test/locale/messages.pot"
-F "method=source" "<https://kgi.printf.kr/api/translations/openstack_zun_ui/django/en/file/>"
{"type":"validation_error","errors":[{"code":"invalid",
"detail":"type object 'PoMonoFormat' has no attribute 'get_msgmerge_args'"
,"attr":"file"}]
그러나 해당 방식을 사용하니 위와 같은 오류가 발생하였는데 해당 문제는 기존에는 po파일로 올라가 있었는데 지금 pot로 올려서 발생한 문제였습니다. 그렇기에 이걸 해결할려면 업데이트시에 po로 올려야하는데 사실 번역해야할 신규 string들을 올려야하기에 pot를 올리는 것이 적절합니다. 그렇기에 해당 api는 일단 접어 두었습니다.
두 번째로 찾아본 것은 아래의 api입니다.
PUT /api/componets api를 사용하는 것인데 해당 api를 테스트하면서 찾은 것은 해당 api는 현재 components의 설정을 바꾸는 것입니다. 즉, pot 파일을 새로 올리는게 아니라 pot가 올라가 있는 base를 수정하는 것입니다. 즉, 만약 weblate의 기반이 되는 repo가 있고 거기에 신규 pot를 올렸으면 그 주소로 수정하는 것입니다. 하지만 현재 우리의 weblate 구조는 원격 repo를 사용하는 것이 아니라 local로 지정하여 사용하는 것이기에 해당 로직은 맞지 않습니다.
그래서 돌고 돌아 여러 방식을 찾다보니 해결책을 찾은 것은 다음과 같습니다.
curl -X POST -H "Authorization: Token "
-F "file=@/home/ubuntu/test/locale/messages.pot"
-F "method=replace" "<https://kgi.printf.kr/api/translations/openstack_zun_ui/django/en/file/>"
처음 사용했던 api를 사용하지만 method를 replace로 지정하는 것입니다. source의 경우 update이기에 기존 파일 형식이 같아야하지만 replace는 새로 덮어쓰는 것이기에 상관이 없습니다. 즉, 덮어쓰는 방식을 채택한 것입니다. 실제로 테스트를 진행했을 때 문제가 없는 것을 확인하였습니다.
...
# Weblate API upload
copy_pot "$ALL_MODULES"
mkdir -p translation-source
mv .translation-source translation-source
# POT upload
for pot in translation-source/*.pot; do
[ -f "$pot" ] || continue
msgen "$pot" -o "$pot"
curl -X POST \\
--config ~/.curlrc \\
-H "Accept: application/json" \\
-F "file=@${pot}" \\
-F "method=replace" \\
"${WEBLATE_URL%/}/api/translations/${WEBLATE_PROJECT}/${WEBLATE_COMPONENT}/en_US/file/" >/dev/null
done
# Tell finish function that everything is fine.
ERROR_ABORT=0
그렇기에 위와 같이 shell 로직을 구성하여 upstream_translation_update_weblate.sh를 완성하였습니다.
https://review.opendev.org/c/openstack/openstack-zuul-jobs/+/921878/19
현재 로직에 대한 패치셋 또한 올린 상태입니다.
이제 추가로 작업해야하는 것은 Ansible Playbook에 등록, 그리고 Zuul에 job 등록이 있습니다.
참고자료
https://openstack.dooray.com/share/pages/9WW46xgzTaizL8DNSfzcSQ

