diff --git a/.github/helper/sync_hotfix_translations.sh b/.github/helper/sync_hotfix_translations.sh index 36387b29ea5..777d215d933 100644 --- a/.github/helper/sync_hotfix_translations.sh +++ b/.github/helper/sync_hotfix_translations.sh @@ -1,11 +1,13 @@ #!/bin/bash -# Syncs Crowdin translations from develop to version-16-hotfix. +# Syncs Crowdin translations from develop to a hotfix branch. # Merge logic: see merge_po_files.py. -# Env: GH_TOKEN, PR_REVIEWER, GITHUB_WORKSPACE (all set by Actions). +# Env: GH_TOKEN, PR_REVIEWER, GITHUB_WORKSPACE, APP_NAME, GITHUB_REPOSITORY +# (all set by Actions). set -e -HOTFIX_BRANCH="version-16-hotfix" +HOTFIX_BRANCH="${HOTFIX_BRANCH:?HOTFIX_BRANCH env var is required}" +APP_NAME="${APP_NAME:?APP_NAME env var is required}" cd ~ || exit @@ -13,32 +15,32 @@ echo "=== Setting up bench ===" pip install frappe-bench bench -v init frappe-bench --skip-assets --skip-redis-config-generation --python "$(which python)" cd ./frappe-bench || exit -bench get-app --skip-assets erpnext "${GITHUB_WORKSPACE}" +bench get-app --skip-assets "${APP_NAME}" "${GITHUB_WORKSPACE}" -echo "=== Setting up translations_hotfix branch ===" -cd ./apps/erpnext || exit +echo "=== Setting up sync_translations_${HOTFIX_BRANCH} branch ===" +cd "./apps/${APP_NAME}" || exit git config user.email "developers@erpnext.com" git config user.name "frappe-pr-bot" -git remote set-url upstream https://github.com/frappe/erpnext.git +git remote set-url upstream "https://github.com/${GITHUB_REPOSITORY}.git" gh auth setup-git git fetch upstream "${HOTFIX_BRANCH}" -git fetch upstream translations_hotfix 2>/dev/null || true -if git rev-parse --verify "upstream/translations_hotfix" >/dev/null 2>&1; then - git checkout -b translations_hotfix "upstream/translations_hotfix" +if git ls-remote --exit-code --heads upstream sync_translations_${HOTFIX_BRANCH} >/dev/null 2>&1; then + git fetch upstream sync_translations_${HOTFIX_BRANCH} + git checkout -b sync_translations_${HOTFIX_BRANCH} "upstream/sync_translations_${HOTFIX_BRANCH}" git merge -X theirs "upstream/${HOTFIX_BRANCH}" --no-edit else - git checkout -b translations_hotfix "upstream/${HOTFIX_BRANCH}" + git checkout -b sync_translations_${HOTFIX_BRANCH} "upstream/${HOTFIX_BRANCH}" fi cd ../.. || exit echo "=== Fetching develop's .po files ===" mkdir -p /tmp/develop-po git -C "${GITHUB_WORKSPACE}" fetch origin develop -git -C "${GITHUB_WORKSPACE}" archive origin/develop erpnext/locale/ \ +git -C "${GITHUB_WORKSPACE}" archive origin/develop "${APP_NAME}/locale/" \ | tar -xf - -C /tmp/develop-po/ -po_count=$(find /tmp/develop-po/erpnext/locale -name "*.po" | wc -l) +po_count=$(find "/tmp/develop-po/${APP_NAME}/locale" -name "*.po" | wc -l) if [ "${po_count}" -eq 0 ]; then echo "ERROR: No .po files found in develop's archive. Aborting." >&2 exit 1 @@ -47,46 +49,50 @@ echo "Extracted ${po_count} .po file(s) from develop." echo "=== Merging and reconciling ===" env/bin/python "${GITHUB_WORKSPACE}/.github/helper/merge_po_files.py" -bench update-po-files --app erpnext +bench update-po-files --app "${APP_NAME}" -cd ./apps/erpnext || exit +cd "./apps/${APP_NAME}" || exit -if git diff --quiet erpnext/locale/ && [ -z "$(git ls-files --others --exclude-standard erpnext/locale/)" ]; then +if git diff --quiet "${APP_NAME}/locale/" && [ -z "$(git ls-files --others --exclude-standard "${APP_NAME}/locale/")" ]; then echo "Translations are already up to date. No PR needed." exit 0 fi echo "Changed files:" -git diff --name-only erpnext/locale/ -git ls-files --others --exclude-standard erpnext/locale/ +git diff --name-only "${APP_NAME}/locale/" +git ls-files --others --exclude-standard "${APP_NAME}/locale/" echo "=== Committing ===" while IFS= read -r file; do git add "${file}" lang=$(basename "${file}" .po) - git commit -m "fix: add ${lang} translation to ${HOTFIX_BRANCH}" -done < <(git ls-files --others --exclude-standard erpnext/locale/ | grep '\.po$' | sort) + git commit -m "chore: add ${lang} translation to ${HOTFIX_BRANCH}" +done < <(git ls-files --others --exclude-standard "${APP_NAME}/locale/" | grep '\.po$' | sort) while IFS= read -r file; do git add "${file}" if ! git diff --staged --quiet -- "${file}"; then lang=$(basename "${file}" .po) - git commit -m "fix: sync ${lang} translation to ${HOTFIX_BRANCH}" + git commit -m "chore: sync ${lang} translation to ${HOTFIX_BRANCH}" else git restore --staged -- "${file}" fi -done < <(git diff --name-only erpnext/locale/ | grep '\.po$' | sort) +done < <(git diff --name-only "${APP_NAME}/locale/" | grep '\.po$' | sort) -git push -u upstream translations_hotfix +if git ls-remote --exit-code --heads upstream sync_translations_${HOTFIX_BRANCH} >/dev/null 2>&1; then + git fetch upstream sync_translations_${HOTFIX_BRANCH} + git merge -X ours "upstream/sync_translations_${HOTFIX_BRANCH}" --no-edit +fi +git push -u upstream sync_translations_${HOTFIX_BRANCH} echo "=== Opening PR (if not already open) ===" existing_pr=$(gh pr list \ --base "${HOTFIX_BRANCH}" \ - --head "translations_hotfix" \ + --head "sync_translations_${HOTFIX_BRANCH}" \ --state open \ --json number \ --jq 'length' \ - -R frappe/erpnext) + -R "${GITHUB_REPOSITORY}") if [ "${existing_pr}" -gt 0 ]; then echo "PR already open — branch updated in place. No new PR needed." @@ -95,8 +101,8 @@ fi gh pr create \ --base "${HOTFIX_BRANCH}" \ - --head "translations_hotfix" \ - --title "fix: sync translations to ${HOTFIX_BRANCH}" \ + --head "sync_translations_${HOTFIX_BRANCH}" \ + --title "chore: sync translations to ${HOTFIX_BRANCH}" \ --body "Automated sync of Crowdin translations from \`develop\` to \`${HOTFIX_BRANCH}\`. A 3-way merge is performed per language, then \`bench update-po-files\` reconciles each \`.po\` against hotfix's \`main.pot\`: @@ -111,4 +117,4 @@ Generated by the \`sync-hotfix-translations\` workflow." \ --label "translation" \ --label "skip-release-notes" \ --reviewer "${PR_REVIEWER}" \ - -R frappe/erpnext + -R "${GITHUB_REPOSITORY}" diff --git a/.github/workflows/run-hotfix-translation-sync.yml b/.github/workflows/run-hotfix-translation-sync.yml new file mode 100644 index 00000000000..8d0d13fca4a --- /dev/null +++ b/.github/workflows/run-hotfix-translation-sync.yml @@ -0,0 +1,52 @@ +# Runner — maintain this file on each hotfix branch, not on develop. +# +# Fires when main.pot changes on this branch (i.e. after a POT update PR +# merges), or when dispatched by the orchestrator on develop (weekly schedule). +# +# Uses github.ref_name so the file is identical across all hotfix branches +# with no branch-specific edits required. + +name: Run hotfix translation sync + +on: + workflow_dispatch: + +# One run at a time per branch. cancel-in-progress: false to avoid leaving +# an orphaned remote branch from a mid-flight git push + gh pr create. +concurrency: + group: sync-hotfix-translations-${{ github.ref_name }} + cancel-in-progress: false + +jobs: + sync-translations: + name: Sync translations from develop into ${{ github.ref_name }} + runs-on: ubuntu-latest + permissions: + contents: write + env: + HOTFIX_BRANCH: ${{ github.ref_name }} + APP_NAME: ${{ github.event.repository.name }} + + steps: + - name: Checkout ${{ env.HOTFIX_BRANCH }} + uses: actions/checkout@v6 + with: + ref: ${{ env.HOTFIX_BRANCH }} + fetch-depth: 0 + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.14" + + - name: Setup Node + uses: actions/setup-node@v6 + with: + node-version: 24 + + - name: Run sync script + run: | + bash "${GITHUB_WORKSPACE}/.github/helper/sync_hotfix_translations.sh" + env: + GH_TOKEN: ${{ secrets.RELEASE_TOKEN }} + PR_REVIEWER: diptanilsaha diff --git a/.github/workflows/sync-hotfix-translations.yml b/.github/workflows/sync-hotfix-translations.yml index cc41f6cb698..5de1718413f 100644 --- a/.github/workflows/sync-hotfix-translations.yml +++ b/.github/workflows/sync-hotfix-translations.yml @@ -1,67 +1,36 @@ -# Syncs Crowdin translations from develop into version-16-hotfix, -# filtered to only the strings present in hotfix's main.pot. +# Orchestrator — lives on develop only. # -# Trigger: fires when version-16-hotfix's main.pot is updated — i.e., when -# the POT update PR from generate-pot-file.yml is merged. At that point -# hotfix's main.pot is authoritative, and this workflow fetches develop's -# latest .po files (Crowdin translations) and merges them against it. +# Triggers on the weekly schedule and dispatches the runner workflow on each +# hotfix branch listed in the matrix. To add or remove a branch, edit the +# matrix below. # -# The weekly schedule acts as a safety net to pick up any Crowdin translations -# that arrived on develop between POT update cycles. -# -# POT file generation remains in generate-pot-file.yml (unchanged). -# Maintain this file on develop only; it always acts on version-16-hotfix. +# POT-change triggers are handled by the runner on each hotfix branch +# (run-hotfix-translation-sync.yml), since GitHub only evaluates a workflow +# from the branch that receives the push. -name: Sync translations to version-16-hotfix +name: Sync translations to hotfix branches on: - push: - branches: - - version-16-hotfix - paths: - - "erpnext/locale/main.pot" # fires exactly when the POT update PR merges schedule: - # 10:00 UTC Monday — safety net for Crowdin translations that arrived - # on develop since the last POT update PR merged to version-16-hotfix + # 10:00 UTC Monday - cron: "0 10 * * 1" workflow_dispatch: -# Prevent concurrent runs. cancel-in-progress: false because a mid-flight -# `git push` + `gh pr create` cancellation can leave an orphaned remote branch. -concurrency: - group: sync-hotfix-translations - cancel-in-progress: false - jobs: - sync-translations: - name: Sync translations to version-16-hotfix + trigger-runners: + name: Trigger sync → ${{ matrix.hotfix_branch }} runs-on: ubuntu-latest - permissions: - contents: write + strategy: + matrix: + hotfix_branch: + - version-16-hotfix + fail-fast: false steps: - - name: Checkout version-16-hotfix - uses: actions/checkout@v6 - with: - ref: version-16-hotfix - # Full history so `git fetch origin develop` inside the helper works - fetch-depth: 0 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - # Match generate-pot-file.yml — bench runs under the same frappe - # stack and must use the same Python version. - python-version: "3.14" - - - name: Setup Node - uses: actions/setup-node@v6 - with: - node-version: 24 - - - name: Run sync script + - name: Dispatch runner on ${{ matrix.hotfix_branch }} run: | - bash ${GITHUB_WORKSPACE}/.github/helper/sync_hotfix_translations.sh + gh workflow run run-hotfix-translation-sync.yml \ + --repo "${{ github.repository }}" \ + --ref "${{ matrix.hotfix_branch }}" env: GH_TOKEN: ${{ secrets.RELEASE_TOKEN }} - PR_REVIEWER: barredterra # change to your GitHub username if you copied this file