#!/bin/bash # Syncs Crowdin translations from develop to a hotfix branch. # Merge logic: see merge_po_files.py. # Env: GH_TOKEN, PR_REVIEWER, GITHUB_WORKSPACE, APP_NAME, GITHUB_REPOSITORY # (all set by Actions). set -e HOTFIX_BRANCH="${HOTFIX_BRANCH:?HOTFIX_BRANCH env var is required}" APP_NAME="${APP_NAME:?APP_NAME env var is required}" cd ~ || exit 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 "${APP_NAME}" "${GITHUB_WORKSPACE}" 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/${GITHUB_REPOSITORY}.git" git config remote.upstream.fetch "+refs/heads/*:refs/remotes/upstream/*" gh auth setup-git git fetch upstream "${HOTFIX_BRANCH}" 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 "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 "${APP_NAME}/locale/" \ | tar -xf - -C /tmp/develop-po/ 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 fi 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 "${APP_NAME}" cd "./apps/${APP_NAME}" || exit 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 "${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 "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 "chore: sync ${lang} translation to ${HOTFIX_BRANCH}" else git restore --staged -- "${file}" fi done < <(git diff --name-only "${APP_NAME}/locale/" | grep '\.po$' | sort) 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 "sync_translations_${HOTFIX_BRANCH}" \ --state open \ --json number \ --jq 'length' \ -R "${GITHUB_REPOSITORY}") if [ "${existing_pr}" -gt 0 ]; then echo "PR already open — branch updated in place. No new PR needed." exit 0 fi gh pr create \ --base "${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\`: | Case | Condition | Result | |------|-----------|--------| | **a** | \`msgid\` in hotfix's \`main.pot\`, **not** in develop's \`.po\` | Hotfix's existing \`msgstr\` is **preserved** (string removed from develop but still needed in hotfix) | | **b** | \`msgid\` **not** in hotfix's \`main.pot\` | **Dropped** from hotfix's \`.po\` | | **c** | \`msgid\` in both hotfix's \`main.pot\` and develop's \`.po\` | Develop's \`msgstr\` is used (Crowdin translation wins) | Generated by the \`sync-hotfix-translations\` workflow." \ --label "translation" \ --label "skip-release-notes" \ --reviewer "${PR_REVIEWER}" \ -R "${GITHUB_REPOSITORY}"