From ac6c48e1a68e13f50a8a0baaa459c5357b5c47cc Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Tue, 6 Jan 2026 14:27:42 +1100 Subject: [PATCH] feat: optimise generation and add validation and notificaitons optimised string generation with new shared parsing steps and parallelised downloads added new failure notifications with discord webhooks added validation for string rules from session desktop --- .../workflows/check_for_crowdin_updates.yml | 107 ++- .github/workflows/notify_failure.yml | 34 + .../workflows/test_failure_notification.yml | 11 + .gitignore | 1 + README.md | 57 ++ crowdin/download_translations_from_crowdin.py | 238 ++++--- crowdin/generate_android_strings.py | 218 ++++--- crowdin/generate_desktop_strings.py | 261 ++++---- crowdin/generate_ios_strings.py | 291 ++++----- crowdin/generate_shared.py | 89 ++- crowdin/parse_xliff.py | 607 ++++++++++++++++++ 11 files changed, 1390 insertions(+), 524 deletions(-) create mode 100644 .github/workflows/notify_failure.yml create mode 100644 .github/workflows/test_failure_notification.yml create mode 100644 crowdin/parse_xliff.py diff --git a/.github/workflows/check_for_crowdin_updates.yml b/.github/workflows/check_for_crowdin_updates.yml index 2ee495b..42a6430 100644 --- a/.github/workflows/check_for_crowdin_updates.yml +++ b/.github/workflows/check_for_crowdin_updates.yml @@ -14,6 +14,11 @@ on: required: true type: boolean default: true + SKIP_VALIDATION_ERRORS: + description: 'Continue even if string validation fails' + required: false + type: boolean + default: false concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -77,10 +82,56 @@ jobs: if-no-files-found: warn retention-days: 7 + parse_translations: + name: Parse and validate translations + runs-on: ubuntu-latest + needs: [fetch_translations] + steps: + - name: Checkout Repo Content + uses: actions/checkout@v4 + with: + path: 'scripts' + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: 3.12 + cache: 'pip' + + - name: Install Dependencies + shell: bash + run: | + pip install -r ${{ github.workspace }}/scripts/crowdin/requirements.txt + + - name: Download raw translations + uses: actions/download-artifact@v4 + with: + name: session-download + path: "${{ github.workspace }}/raw_translations" + + - name: Parse and validate XLIFF files + run: | + python "${{ github.workspace }}/scripts/crowdin/parse_xliff.py" \ + "${{ github.workspace }}/raw_translations" \ + "${{ github.workspace }}/parsed_translations.json" \ + ${{ inputs.SKIP_VALIDATION_ERRORS != true && '--error-on-validation-failure' || '' }} \ + --validation-report "${{ github.workspace }}/validation_report.json" + + - name: Upload parsed translations + uses: actions/upload-artifact@v4 + with: + name: session-parsed + path: | + ${{ github.workspace }}/parsed_translations.json + ${{ github.workspace }}/validation_report.json + overwrite: true + if-no-files-found: error + retention-days: 7 + build_ios: name: Build iOS strings runs-on: ubuntu-latest - needs: [fetch_translations] + needs: [parse_translations] steps: - name: Checkout Repo Content uses: actions/checkout@v4 @@ -93,10 +144,16 @@ jobs: - name: Setup shared uses: ./scripts/actions/setup_shared + - name: Download parsed translations + uses: actions/download-artifact@v4 + with: + name: session-parsed + path: "${{ github.workspace }}" + - name: Prepare iOS Strings run: | python "${{ github.workspace }}/scripts/crowdin/generate_ios_strings.py" \ - "${{ github.workspace }}/raw_translations" \ + "${{ github.workspace }}/parsed_translations.json" \ "${{ github.workspace }}/ios/Session/Meta/Translations" \ "${{ github.workspace }}/ios/SessionUIKit/Style Guide/Constants.swift" - name: Upload iOS artefacts @@ -114,7 +171,7 @@ jobs: build_desktop: name: Build Desktop strings - needs: [fetch_translations] + needs: [parse_translations] runs-on: ubuntu-latest steps: - name: Checkout Repo Content @@ -128,11 +185,16 @@ jobs: - name: Checkout Desktop uses: ./scripts/actions/checkout_desktop + - name: Download parsed translations + uses: actions/download-artifact@v4 + with: + name: session-parsed + path: "${{ github.workspace }}" - name: Prepare Desktop Strings run: | python "${{ github.workspace }}/scripts/crowdin/generate_desktop_strings.py" \ - "${{ github.workspace }}/raw_translations" \ + "${{ github.workspace }}/parsed_translations.json" \ "${{ github.workspace }}/desktop/_locales" \ "${{ github.workspace }}/desktop/ts/localization/constants.ts" @@ -152,7 +214,7 @@ jobs: build_qa: name: Build QA strings - needs: [fetch_translations] + needs: [parse_translations] runs-on: ubuntu-latest steps: - name: Checkout Repo Content @@ -166,10 +228,16 @@ jobs: - name: Checkout Desktop uses: ./scripts/actions/checkout_desktop + - name: Download parsed translations + uses: actions/download-artifact@v4 + with: + name: session-parsed + path: "${{ github.workspace }}" + - name: Export QA Strings (json) run: | python "${{ github.workspace }}/scripts/crowdin/generate_desktop_strings.py" --qa_build \ - "${{ github.workspace }}/raw_translations" \ + "${{ github.workspace }}/parsed_translations.json" \ "${{ github.workspace }}/desktop/_locales" \ "${{ github.workspace }}/desktop/ts/localization/constants.ts" - name: Prepare QA strings (ts) @@ -191,7 +259,7 @@ jobs: build_android: name: Build Android strings runs-on: ubuntu-latest - needs: [fetch_translations] + needs: [parse_translations] steps: - name: Checkout Repo Content @@ -210,16 +278,24 @@ jobs: with: distribution: 'temurin' java-version: 17 - cache: gradle - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 + with: + gradle-version: wrapper + cache-read-only: false + + - name: Download parsed translations + uses: actions/download-artifact@v4 + with: + name: session-parsed + path: "${{ github.workspace }}" - name: Prepare Android Strings run: | rm -rf ${{ github.workspace }}/android/app/src/main/res/values*/strings.xml python "${{ github.workspace }}/scripts/crowdin/generate_android_strings.py" \ - "${{ github.workspace }}/raw_translations" \ + "${{ github.workspace }}/parsed_translations.json" \ "${{ github.workspace }}/android/app/src/main/res" \ "${{ github.workspace }}/android/app/src/main/java/org/session/libsession/utilities/NonTranslatableStringConstants.kt" - name: Upload Android artefacts @@ -234,7 +310,16 @@ jobs: retention-days: 7 - name: Validate strings for Android - run: cd ${{ github.workspace }}/android && ${{ github.workspace }}/android/gradlew app:mergePlayDebugResources + run: | + cd ${{ github.workspace }}/android + ./gradlew app:generatePlayDebugResValues \ + --parallel \ + --build-cache \ + --configuration-cache \ + -Dorg.gradle.jvmargs="-Xmx2g -XX:+UseParallelGC" \ + -Dorg.gradle.caching=true \ + -x lint \ + -x test jobs_sync: @@ -370,4 +455,4 @@ jobs: body: ${{ env.PR_DESCRIPTION }} branch: ${{ env.PR_TARGET_BRANCH }} commit-message: ${{ env.PR_TITLE }} - delete-branch: true \ No newline at end of file + delete-branch: true diff --git a/.github/workflows/notify_failure.yml b/.github/workflows/notify_failure.yml new file mode 100644 index 0000000..2757c16 --- /dev/null +++ b/.github/workflows/notify_failure.yml @@ -0,0 +1,34 @@ +name: Discord Failure Notification + +on: + workflow_run: + workflows: + - "Check for Crowdin Updates" + - "Test Failure Notification" + types: + - completed + +jobs: + notify: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'failure' }} + steps: + - name: Send Discord notification + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + DISCORD_ROLE_ID: ${{ secrets.DISCORD_ROLE_ID }} + run: | + curl -H "Content-Type: application/json" \ + -d '{ + "content": "<@&'"$DISCORD_ROLE_ID"'> ⚠️ GitHub Action failed!", + "embeds": [{ + "title": "Workflow Failed: ${{ github.event.workflow_run.name }}", + "url": "${{ github.event.workflow_run.html_url }}", + "color": 15158332, + "fields": [ + {"name": "Repository", "value": "${{ github.repository }}", "inline": true}, + {"name": "Branch", "value": "${{ github.event.workflow_run.head_branch }}", "inline": true} + ] + }] + }' \ + "$DISCORD_WEBHOOK_URL" diff --git a/.github/workflows/test_failure_notification.yml b/.github/workflows/test_failure_notification.yml new file mode 100644 index 0000000..d22440c --- /dev/null +++ b/.github/workflows/test_failure_notification.yml @@ -0,0 +1,11 @@ +name: Test Failure Notification + +on: + workflow_dispatch: + +jobs: + fail: + runs-on: ubuntu-latest + steps: + - name: Fail on purpose + run: exit 1 diff --git a/.gitignore b/.gitignore index e046d9c..3fb72ac 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ # Exclude venv folder venv +.env __pycache__ .vscode/ diff --git a/README.md b/README.md index 8b9e7f1..88cf0ee 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,60 @@ # Session Shared Scripts This repo houses scripts which are shared between the different platform repos for Session, it also contains a number of Actions used to automatically sync some shared elements across the repos. + +## Crowdin Translation Workflow + +Automated workflow that downloads translations from Crowdin, validates them, and creates PRs for iOS, Android, and Desktop platforms. + +### Required Secrets + +| Secret | Description | +| ------------------- | ----------------------------------------- | +| `CROWDIN_API_TOKEN` | Crowdin API token with project access | +| `CROWDIN_PR_TOKEN` | GitHub token with PR creation permissions | + +### Workflow Inputs + +| Input | Default | Description | +| ------------------------ | ------- | ---------------------------------------- | +| `UPDATE_PULL_REQUESTS` | `true` | Create/update PRs for all platforms | +| `SKIP_VALIDATION_ERRORS` | `false` | Continue even if string validation fails | + +### Schedule + +Runs automatically every Monday at 00:00 UTC. + +### Validation Rules + +#### All Strings (including plurals) + +- **Valid `{variable}` syntax** - No broken braces (`{`, `}`, `{}`, `{ space }`) +- **Allowed HTML tags only** - Only ``, `
`, `` +- **Valid tag syntax** - No malformed `<` (e.g., `