mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
Both entries in crowdin.yml are declared `type: android`, so Crowdin's Android serializer escapes apostrophes on the way down: `l'URL` comes back as `l\'URL`. That is right for amethyst/src/main/res/, which aapt un-escapes at build time, and wrong for commons/.../composeResources/, where Compose resolves only \uXXXX, \n and \t and leaves \' \" \? \@ alone -- so the backslash reaches the screen. Nothing prevented this, so every sync reopened the same regression and CI's compose_escaping_check.py failed on the bot's own PR. It happened three times (f9baab0e,1685d7c0,e223d505), most recently 2,888 occurrences across 40 locale files, each time repaired by hand after the fact. Convert on the way in instead, so the PR is born clean. Two steps, placed after the ownership fix (the Crowdin container writes as root, so the tree is not writable before it) and before the PR is opened: - Convert: runs the documented repair over the Compose catalog only, so the Android res tree keeps the escaping it needs. --no-unwrap-quotes is mandatory -- escape conversion is idempotent, quote-unwrapping is not, and a second unwrap would strip the real display quotes from values like import_follows_tips. - Verify: re-runs the check that guards main, so a case the converter cannot repair fails the sync loudly here instead of opening a red PR. Verified by replaying both steps against the real Crowdin output on l10n_crowdin_translations (df930817): the check reproduces the failure at 2,888 occurrences, the convert step fixes 1,996 entries across 40 files, the verify step then exits 0, amethyst/src/main/res/ is left untouched, and the resulting catalog is byte-identical to main -- so with this in place that sync would have carried no string changes at all. Known gap, documented inline on the verify step: fix_escapes.py only rewrites text inside <string>/<item> elements while the check scans the whole file, so an escape in an XML comment (comments do propagate into the locale files) would fail the gate without the converter being able to repair it. No such comment exists today; it has to be fixed at the source string by hand if one ever appears. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVrW3p8NGWHgLbN673Fet2
113 lines
5.6 KiB
YAML
113 lines
5.6 KiB
YAML
name: Crowdin Action
|
|
|
|
on:
|
|
push:
|
|
# paths: ["app/src/main/res/**/strings.xml"] // removes filter to allow downloads at any moment.
|
|
branches: [ main ]
|
|
|
|
permissions:
|
|
contents: write
|
|
pull-requests: write
|
|
|
|
jobs:
|
|
# Single job so the Crowdin translation sync and the translator-placeholder seed
|
|
# land in ONE pull request instead of two. The Crowdin action only downloads into
|
|
# the working tree (push_translations/create_pull_request disabled); the seed
|
|
# script then edits translators.json; finally one create-pull-request step opens a
|
|
# single PR with both sets of changes (and no-ops when there is no diff).
|
|
synchronize-with-crowdin:
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v7
|
|
with:
|
|
# Need tags so scripts/translators.sh can resolve the last v* release tag
|
|
# for the "since last tag" window.
|
|
fetch-depth: 0
|
|
|
|
- name: crowdin action
|
|
uses: crowdin/github-action@v2
|
|
with:
|
|
upload_sources: true
|
|
upload_translations: true
|
|
download_translations: true
|
|
# Let the downloaded translations stay in the working tree; the single
|
|
# create-pull-request step below opens the combined PR.
|
|
push_translations: false
|
|
create_pull_request: false
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
|
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
|
|
|
# crowdin/github-action runs in a Docker container as root, so any file or
|
|
# directory it downloads (especially a brand-new locale folder like
|
|
# values-en-rGB/) ends up owned by root. The unprivileged runner user in
|
|
# the create-pull-request step below then can't unlink those files, which
|
|
# aborts its branch checkout with "unable to unlink ... Permission denied".
|
|
# Reclaim ownership of the whole working tree before touching git.
|
|
- name: Fix ownership after Crowdin Docker action
|
|
run: sudo chown -R "$(id -u):$(id -g)" "$GITHUB_WORKSPACE"
|
|
|
|
# Both files in crowdin.yml are declared `type: android`, so Crowdin's Android
|
|
# serializer escapes apostrophes on the way down: `l'URL` comes back as `l\'URL`.
|
|
# That is correct for amethyst/src/main/res/, which aapt un-escapes at build time,
|
|
# and WRONG for commons/.../composeResources/, where Compose resolves only \uXXXX,
|
|
# \n and \t and leaves \' \" \? \@ alone -- so the backslash reaches the screen.
|
|
#
|
|
# Without this step every sync reopens the same regression and CI's
|
|
# compose_escaping_check.py fails on the bot's own PR. It happened three times
|
|
# (f9baab0e, 1685d7c0, e223d505 -- 2,888 occurrences across 40 locales the last
|
|
# time) before this step existed. Convert on the way in, so the PR is born clean.
|
|
#
|
|
# Only the Compose catalog is passed in; the Android res tree keeps its escaping.
|
|
# --no-unwrap-quotes is mandatory here: escape conversion is idempotent but
|
|
# quote-unwrapping is not, and a second unwrap would strip the real display quotes
|
|
# from values like import_follows_tips.
|
|
- name: Convert Android escaping to Compose escaping in the shared catalog
|
|
run: |
|
|
python3 tools/strings-migrate/fix_escapes.py --no-unwrap-quotes \
|
|
commons/src/commonMain/composeResources
|
|
|
|
# Assert the conversion actually satisfied the check that guards main, so a case
|
|
# the converter cannot repair fails the sync loudly here instead of opening a red
|
|
# PR. Known gap if this ever trips: fix_escapes.py only rewrites text inside
|
|
# <string>/<item> elements, while the check scans the whole file -- an escape in an
|
|
# XML comment (comments do propagate into the locale files) has to be fixed at the
|
|
# source string in commons/.../composeResources/values/strings.xml by hand.
|
|
- name: Verify the shared catalog is free of Android-only escaping
|
|
run: .claude/hooks/compose_escaping_check.py
|
|
|
|
# Keep docs/changelog/translators.json seeded with everyone who has translated
|
|
# recently, so the per-release `## Translations` credits (scripts/translators.sh)
|
|
# can resolve them to npubs. Only adds rows when a genuinely new contributor
|
|
# appears.
|
|
- name: Seed translator placeholders from Crowdin
|
|
run: bash scripts/translators.sh --seed
|
|
env:
|
|
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
|
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
|
|
|
- name: Open or update the combined Crowdin PR
|
|
# peter-evans/create-pull-request is MIT-licensed CI-only tooling (not
|
|
# linked into any shipped artifact). It no-ops when there is no diff.
|
|
uses: peter-evans/create-pull-request@v8
|
|
with:
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
base: main
|
|
branch: l10n_crowdin_translations
|
|
add-paths: |
|
|
amethyst/src/main/res/**/strings.xml
|
|
commons/src/commonMain/composeResources/**/strings.xml
|
|
docs/changelog/translators.json
|
|
commit-message: 'chore: sync Crowdin translations and seed translator npub placeholders'
|
|
title: 'New Crowdin Translations'
|
|
body: |
|
|
New Crowdin translations by [Crowdin GH Action](https://github.com/crowdin/github-action).
|
|
|
|
Any new Crowdin contributors were added to `docs/changelog/translators.json`
|
|
with blank npubs. Fill in the npubs you have so the next release's
|
|
`## Translations` credits generate automatically via
|
|
`scripts/translators.sh --from <prev-tag> --to <this-tag>`.
|