diff --git a/.github/workflows/native-platforms.yml b/.github/workflows/native-platforms.yml index 8a8f0cfe..df4849b0 100644 --- a/.github/workflows/native-platforms.yml +++ b/.github/workflows/native-platforms.yml @@ -80,7 +80,7 @@ jobs: - name: Install preflight tools run: | sudo apt-get update - sudo apt-get install -y appstream desktop-file-utils dpkg-dev + sudo apt-get install -y appstream desktop-file-utils dpkg-dev gettext - name: Check release metadata and packaging id: version env: @@ -91,6 +91,9 @@ jobs: else bash scripts/check-release.sh fi + - name: Require complete Spanish and Russian translations + if: github.event_name == 'push' && github.ref_type == 'tag' + run: bash scripts/check-translation-completeness.sh es ru linux: needs: metadata diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 33b60b23..3753d80e 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -58,6 +58,25 @@ jobs: key: quality-default - name: Check formatting run: cargo fmt --all -- --check + - name: Leave translation catalogs to Weblate + # Weblate's msgmerge add-on owns lang/*/LC_MESSAGES; edits from other + # branches conflict with its pending commits and lock the component. + if: >- + github.event_name == 'pull_request' && + !(github.head_ref == 'weblate-flectar-mail-mail' && + github.event.pull_request.head.repo.full_name == github.repository) + shell: bash + run: | + set -euo pipefail + # The checkout is the pull request's merge commit; its first parent + # is the base branch. + git fetch --quiet --deepen=1 origin "$GITHUB_SHA" + changed=$(git diff --name-only HEAD^1 HEAD -- 'lang/*/LC_MESSAGES/*.po') + if [[ -n $changed ]]; then + printf '%s\n' "$changed" + echo "::error::Only Weblate edits lang/*/LC_MESSAGES. Commit lang/flectar-mail.pot alone (scripts/update-translation-template.sh) and translate on https://translate.flectar.com/." + exit 1 + fi - name: Validate repository support files shell: bash run: | @@ -123,14 +142,6 @@ jobs: run: | cargo install slint-tr-extractor --version 1.18.1 --locked bash scripts/check-translations.sh - for language in es ru; do - catalog="lang/$language/LC_MESSAGES/flectar-mail.po" - msgfmt --check --check-header --check-format \ - -o "/tmp/flectar-mail-$language.mo" "$catalog" - test -z "$(msgattrib --untranslated --no-obsolete "$catalog")" - test -z "$(msgattrib --only-fuzzy --no-obsolete "$catalog")" - test -z "$(msgattrib --only-obsolete "$catalog")" - done - name: Run the default test suite run: cargo test --workspace --all-targets --locked - name: Test the vendored Blitz patches diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f6a7ef9b..cf1a8aa9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,6 +14,15 @@ and safe for a mail client that handles private user data. ## Translations Translate the application through [Weblate](https://translate.flectar.com/). +Weblate owns the catalogs in `lang/*/LC_MESSAGES/` and sends them back as pull +requests, so other pull requests must not edit them. When a change adds, edits +or moves translatable strings in `.slint` files, run +`scripts/update-translation-template.sh` (it needs `slint-tr-extractor` 1.18.1) +and commit only `lang/flectar-mail.pot`. Weblate merges the template into every +language after the change reaches `main`. Releases require complete Spanish and +Russian translations, which `scripts/check-translation-completeness.sh es ru` +checks. + README translations are maintained directly in this repository: - Use [README.md](README.md) as the source and add diff --git a/scripts/check-translation-completeness.sh b/scripts/check-translation-completeness.sh new file mode 100755 index 00000000..395653c1 --- /dev/null +++ b/scripts/check-translation-completeness.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Fails unless each given language translates every string in the current +# template, with no fuzzy entries. Releases require this for Spanish and +# Russian; finish missing strings on Weblate, then merge its pull request. +set -euo pipefail + +repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd) +command -v msgmerge >/dev/null || { + echo "GNU gettext is required" >&2 + exit 1 +} +if [[ $# -eq 0 ]]; then + echo "usage: $0 LANGUAGE..." >&2 + exit 2 +fi + +work_dir=$(mktemp -d) +trap 'rm -rf -- "$work_dir"' EXIT +cd "$repo_root" + +status=0 +for language in "$@"; do + merged="$work_dir/$language.po" + msgmerge --quiet --no-fuzzy-matching --output-file "$merged" \ + "lang/$language/LC_MESSAGES/flectar-mail.po" lang/flectar-mail.pot + untranslated=$(msgattrib --untranslated --no-obsolete "$merged") + fuzzy=$(msgattrib --only-fuzzy --no-obsolete "$merged") + if [[ -n $untranslated || -n $fuzzy ]]; then + printf '%s: ' "$language" >&2 + LC_ALL=C msgfmt --statistics -o /dev/null "$merged" + status=1 + fi +done +exit "$status" diff --git a/scripts/check-translations.sh b/scripts/check-translations.sh index 434adc5d..546019cd 100644 --- a/scripts/check-translations.sh +++ b/scripts/check-translations.sh @@ -2,11 +2,7 @@ set -euo pipefail repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd) -command -v slint-tr-extractor >/dev/null || { - echo "slint-tr-extractor 1.18.1 is required" >&2 - exit 1 -} -command -v msgmerge >/dev/null || { +command -v msgfmt >/dev/null || { echo "GNU gettext is required" >&2 exit 1 } @@ -15,26 +11,17 @@ work_dir=$(mktemp -d) trap 'rm -rf -- "$work_dir"' EXIT cd "$repo_root" -# Preview and screenshot scripts use temporary hidden .slint files. -find ui -type f -name '*.slint' ! -name '.*' -print0 \ - | sort -z \ - | xargs -0 slint-tr-extractor --no-default-translation-context \ - --package-name flectar-mail --package-version 0.1.0 \ - -o "$work_dir/flectar-mail.pot" +bash scripts/update-translation-template.sh "$work_dir/flectar-mail.pot" +if ! diff -u lang/flectar-mail.pot "$work_dir/flectar-mail.pot"; then + echo "lang/flectar-mail.pot is out of date; run scripts/update-translation-template.sh." >&2 + exit 1 +fi -# Extraction timestamps are intentionally volatile; compare every semantic -# catalog line while ignoring only that generated header field. -diff -u \ - <(sed '/^"POT-Creation-Date:/d' lang/flectar-mail.pot) \ - <(sed '/^"POT-Creation-Date:/d' "$work_dir/flectar-mail.pot") - -for language in da de es fr it ja nl ru tr zh_Hans; do - msgmerge --quiet --no-fuzzy-matching \ - --output-file "$work_dir/flectar-mail-$language.po" \ - "lang/$language/LC_MESSAGES/flectar-mail.po" "$work_dir/flectar-mail.pot" - diff -u \ - <(sed '/^"POT-Creation-Date:/d' "lang/$language/LC_MESSAGES/flectar-mail.po") \ - <(sed '/^"POT-Creation-Date:/d' "$work_dir/flectar-mail-$language.po") +# Weblate writes the catalogs and merges new template strings into them, so +# they may lag the template and keep Weblate's line wrapping. Check only that +# each one compiles. +for catalog in lang/*/LC_MESSAGES/flectar-mail.po; do + msgfmt --check --check-header --check-format -o /dev/null "$catalog" done bash scripts/check-ui-message-catalog.sh diff --git a/scripts/update-translation-template.sh b/scripts/update-translation-template.sh new file mode 100755 index 00000000..994c5100 --- /dev/null +++ b/scripts/update-translation-template.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# Regenerates the translation template from the .slint sources. Only the +# template lives in this repository's pull requests; Weblate merges it into +# lang/*/LC_MESSAGES/flectar-mail.po with its msgmerge add-on. +set -euo pipefail + +repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd) +template="$repo_root/lang/flectar-mail.pot" +output=${1:-$template} +command -v slint-tr-extractor >/dev/null || { + echo "slint-tr-extractor 1.18.1 is required" >&2 + exit 1 +} + +work_dir=$(mktemp -d) +trap 'rm -rf -- "$work_dir"' EXIT +cd "$repo_root" + +# Preview and screenshot scripts use temporary hidden .slint files. +find ui -type f -name '*.slint' ! -name '.*' -print0 \ + | sort -z \ + | xargs -0 slint-tr-extractor --no-default-translation-context \ + --package-name flectar-mail --package-version 0.1.0 \ + -o "$work_dir/flectar-mail.pot" + +# Keep the committed creation date so that unchanged strings give an +# unchanged template; Weblate copies this header into every catalog. +creation_date=$(grep -m1 '^"POT-Creation-Date:' "$template" || true) +if [[ -n $creation_date ]]; then + CREATION_DATE=$creation_date awk ' + !done && /^"POT-Creation-Date:/ { print ENVIRON["CREATION_DATE"]; done = 1; next } + { print } + ' "$work_dir/flectar-mail.pot" >"$work_dir/dated.pot" + mv "$work_dir/dated.pot" "$work_dir/flectar-mail.pot" +fi +mv "$work_dir/flectar-mail.pot" "$output"