Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mechanizes continuous-localization workflow #1282

Merged
merged 9 commits into from
Sep 22, 2021
30 changes: 30 additions & 0 deletions .github/workflows/l10n.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: l10n
on:
# Run nightly at midnight UTC.
schedule:
- cron: "0 0 * * *"
# Also allow manual invocation.
workflow_dispatch:

jobs:
update-translation-catalogs:
runs-on: ubuntu-20.04
steps:
- name: Check out securedrop-client
uses: actions/checkout@v2
- name: Set Git identity
run: |
git config user.name github-actions
git config user.email [email protected]
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.7
- name: Install dependencies
run: pip install --require-hashes -r requirements/dev-requirements.txt
- name: Update translation catalogs
run: make update-translation-catalogs
- name: Update list of supported languages
run: make supported-languages
- name: Push
run: git push
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ coverage.xml

# Translations
*.mo
*.pot

# Django stuff:
*.log
Expand Down
89 changes: 89 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,92 @@ help: ## Print this message and exit.
@awk 'BEGIN {FS = ":.*?## "} /^[0-9a-zA-Z_-]+:.*?## / {printf "\033[36m%s\033[0m : %s\n", $$1, $$2}' $(MAKEFILE_LIST) \
| sort \
| column -s ':' -t

.PHONY: version
version:
@python -c "import securedrop_client; print(securedrop_client.__version__)"

##############
#
# Localization
#
##############

LOCALE_DIR=securedrop_client/locale
LOCALES=$(shell find ${LOCALE_DIR} -name "*.po")
POT=${LOCALE_DIR}/messages.pot
SUPPORTED_LOCALES_LIST=l10n.txt
VERSION=$(shell python -c "import securedrop_client; print(securedrop_client.__version__)")
WEBLATE_API=https://weblate.securedrop.org/api/
WEBLATE_COMPONENT=securedrop # FIXME: securedrop-client (once unmarked "restricted" in Weblate)

# Update POTs from translated strings in source code and merge into
# per-locale POs.
.PHONY: update-translation-catalogs
update-translation-catalogs:
@make --always-make ${POT}
@git add --verbose ${POT}
@for catalog in $$(find ${LOCALE_DIR} -name "*.po"); do make $${catalog}; git add --verbose $${catalog}; done
-git commit --message "l10n: update translation catalogs"

# Compile loadable/packageable MOs.
.PHONY: compile-translation-catalogs
compile-translation-catalogs: ${LOCALE_DIR}/*/LC_MESSAGES/messages.mo
@for locale in $^; do make $${locale}; done

# Derive POT from sources.
$(POT): securedrop_client
@echo "updating catalog template: $@"
@mkdir -p ${LOCALE_DIR}
@pybabel extract \
--charset=utf-8 \
--output=${POT} \
--project="SecureDrop Client" \
--version=${VERSION} \
[email protected] \
--copyright-holder="Freedom of the Press Foundation" \
--add-comments="Translators:" \
--strip-comments \
--add-location=never \
--no-wrap \
$^
@sed -i -e '/^"POT-Creation-Date/d' ${POT}

# Merge current POT with a locale's PO.
#
# NB. freedomofpress/securedrop/securedrop/i18n_tool.py updates via
# msgmerge even though pybabel.update() is available. Here we use
# "pybabel update" for consistency with "pybabel extract".
${LOCALE_DIR}/%/LC_MESSAGES/messages.po: ${POT}
ifeq ($(strip $(LOCALES)),)
@echo "no translation catalogs to update"
else
@pybabel update \
--locale $$(echo $@ | grep -Eio "[a-zA-Z_]+/LC_MESSAGES/messages.po" | sed 's/\/LC_MESSAGES\/messages.po//') \
--input-file ${POT} \
--output-file $@ \
--no-wrap \
--previous
@sed -i -e '/^"POT-Creation-Date/d' $@
endif

# Compile a locale's PO to MO for (a) development runtime or (b) packaging.
${LOCALE_DIR}/%/LC_MESSAGES/messages.mo: ${LOCALE_DIR}/%/LC_MESSAGES/messages.po
ifeq ($(strip $(LOCALES)),)
@echo "no translation catalogs to compile"
else
@pybabel compile \
--directory ${LOCALE_DIR} \
--statistics
endif

# List languages 100% translated in Weblate.
.PHONY: supported-languages
supported-languages:
@wlc \
--format json \
--url ${WEBLATE_API} \
list-translations \
| jq -r 'map(select(.component.slug == "${WEBLATE_COMPONENT}", .translated_percent == 100)) | map("* \(.language.name|tostring) (``\(.language.code|tostring)``)") | join("\n")' \
> ${SUPPORTED_LOCALES_LIST}
@git add ${SUPPORTED_LOCALES_LIST}
2 changes: 2 additions & 0 deletions requirements/dev-requirements.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
atomicwrites==1.2.1
attrs==19.3.0
babel==2.9.1
black==19.10b0
Click==7.0
coverage==4.5.1
Expand Down Expand Up @@ -34,3 +35,4 @@ semgrep==0.42.0
sip==4.19.8
typed-ast==1.4.1
vcrpy==4.0.2
wlc==1.12
23 changes: 23 additions & 0 deletions requirements/dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ appdirs==1.4.4 \
--hash=sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41 \
--hash=sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128
# via black
argcomplete==1.12.3 \
--hash=sha256:291f0beca7fd49ce285d2f10e4c1c77e9460cf823eef2de54df0c0fec88b0d81 \
--hash=sha256:2c7dbffd8c045ea534921e63b0be6fe65e88599990d8dc408ac8c542b72a5445
# via wlc
arrow==0.12.1 \
--hash=sha256:a558d3b7b6ce7ffc74206a86c147052de23d3d4ef0e17c210dd478c53575c4cd
# via -r requirements/requirements.in
Expand All @@ -29,6 +33,10 @@ attrs==19.3.0 \
# jsonschema
# pytest
# semgrep
babel==2.9.1 \
--hash=sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9 \
--hash=sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0
# via -r requirements/dev-requirements.in
black==19.10b0 \
--hash=sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b \
--hash=sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539
Expand Down Expand Up @@ -116,6 +124,7 @@ importlib-metadata==4.6.3 \
--hash=sha256:0645585859e9a6689c523927a5032f2ba5919f1f7d0e84bd4533312320de1ff9 \
--hash=sha256:51c6635429c77cf1ae634c997ff9e53ca3438b495f10a55ba28594dd69764a8b
# via
# argcomplete
# jsonschema
# pluggy
# pytest
Expand Down Expand Up @@ -454,6 +463,7 @@ python-dateutil==2.7.5 \
# -r requirements/requirements.in
# alembic
# arrow
# wlc
python-editor==1.0.3 \
--hash=sha256:a3c066acee22a1c94f63938341d4fb374e3fdd69366ed6603d7b24bed1efc565
# via
Expand All @@ -467,6 +477,14 @@ python3-xlib==0.15 \
pytweening==1.0.3 \
--hash=sha256:4b608a570f4dccf2201e898f643c2a12372eb1d71a3dbc7e778771b603ca248b
# via pyautogui
pytz==2021.1 \
--hash=sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da \
--hash=sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798
# via babel
pyxdg==0.27 \
--hash=sha256:2d6701ab7c74bbab8caa6a95e0a0a129b1643cf6c298bf7c569adec06d0709a0 \
--hash=sha256:80bd93aae5ed82435f20462ea0208fb198d8eec262e831ee06ce9ddb6b91c5a5
# via wlc
pyyaml==5.4.1 \
--hash=sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf \
--hash=sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696 \
Expand Down Expand Up @@ -542,6 +560,7 @@ requests==2.22.0 \
# -r requirements/requirements.in
# securedrop-sdk
# semgrep
# wlc
ruamel.yaml.clib==0.2.6 \
--hash=sha256:0847201b767447fc33b9c235780d3aa90357d20dd6108b92be544427bea197dd \
--hash=sha256:1866cf2c284a03b9524a5cc00daca56d80057c5ce3cdc86a52020f4c720856f0 \
Expand Down Expand Up @@ -676,6 +695,10 @@ wcwidth==0.2.5 \
--hash=sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784 \
--hash=sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83
# via pytest
wlc==1.12 \
--hash=sha256:3912b4c3c6b71fe920ec633dff68cd40a95678241aaffb32b277f76670c78105 \
--hash=sha256:e0a046aec8c1bb199bec20bd73e5b9ea1668d19e72fb6d0349bf7b80032af958
# via -r requirements/dev-requirements.in
wrapt==1.12.1 \
--hash=sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7
# via vcrpy
Expand Down
1 change: 1 addition & 0 deletions run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,5 @@ fi

wait

make compile-translation-catalogs
python -m securedrop_client --sdc-home "$SDC_HOME" --no-proxy "$qubes_flag" "$@"
7 changes: 2 additions & 5 deletions securedrop_client/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,8 @@ def configure_locale_and_language() -> str:
language_code = current_locale[:2]
except ValueError: # pragma: no cover
language_code = "en" # pragma: no cover
# DEBUG/TRANSLATE: override the language code here (e.g. to Chinese).
# language_code = 'zh'
gettext.translation(
"securedrop_client", localedir=localedir, languages=[language_code], fallback=True
).install()
gettext.bindtextdomain("messages", localedir=localedir)
gettext.textdomain("messages")
return language_code


Expand Down
1 change: 1 addition & 0 deletions tests/gui/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import math
import random
from datetime import datetime
from gettext import gettext as _
from unittest.mock import Mock, PropertyMock, patch

import arrow
Expand Down
1 change: 1 addition & 0 deletions tests/test_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import datetime
import logging
import os
from gettext import gettext as _
from typing import Type
from unittest.mock import Mock, call

Expand Down