ci: sync translations from develop to version-16-hotfix (backport #55348) (#55353)

Co-authored-by: Diptanil Saha <diptanil@frappe.io>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
fix (#55348)
This commit is contained in:
mergify[bot]
2026-05-27 16:30:59 +00:00
committed by GitHub
parent 91dcb96307
commit 65b6da552e
3 changed files with 196 additions and 0 deletions

49
.github/helper/merge_po_files.py vendored Normal file
View File

@@ -0,0 +1,49 @@
#!/usr/bin/env python3
"""Overlay develop's .po translations onto hotfix's .po files.
Called by sync_hotfix_translations.sh before `bench update-po-files`.
Merge rules:
a. msgid absent from develop → keep hotfix's existing msgstr
b. language not yet in hotfix → copy file as-is (bench will filter to main.pot)
c. msgid present in both → use develop's msgstr
"""
import shutil
from pathlib import Path
from babel.messages.pofile import read_po, write_po
DEVELOP = Path("/tmp/develop-po/erpnext/locale/")
LOCALE = Path("./apps/erpnext/erpnext/locale/")
added = updated = 0
for src in sorted(DEVELOP.glob("*.po")):
dst = LOCALE / src.name
with src.open("rb") as f:
dev = read_po(f)
if not dst.exists():
shutil.copy(src, dst)
added += 1
print(f" [new] {src.name}")
continue
with dst.open("rb") as f:
hf = read_po(f)
changes = 0
for msg in hf:
if msg.id and msg.id in dev and dev[msg.id].string and dev[msg.id].string != msg.string:
msg.string = dev[msg.id].string
changes += 1
if changes:
with dst.open("wb") as f:
write_po(f, hf)
updated += 1
print(f" [updated] {src.name} ({changes} msgstr(s) from develop)")
else:
print(f" [no-op] {src.name}")
print(f"\n{added} new language(s), {updated} updated.")

View File

@@ -0,0 +1,85 @@
#!/bin/bash
# Syncs Crowdin translations from develop to version-16-hotfix.
# Merge logic: see merge_po_files.py.
# Env: GH_TOKEN, PR_REVIEWER, GITHUB_WORKSPACE (all set by Actions).
set -e
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 erpnext "${GITHUB_WORKSPACE}"
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/ \
| tar -xf - -C /tmp/develop-po/
po_count=$(find /tmp/develop-po/erpnext/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 erpnext
cd ./apps/erpnext || 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
if git diff --quiet erpnext/locale/; then
echo "Translations are already up to date. No PR needed."
exit 0
fi
echo "Changed files:"
git diff --name-only erpnext/locale/
echo "=== Committing and pushing ==="
git checkout -B translations_hotfix
git add erpnext/locale/
git commit -m "fix: sync translations to version-16-hotfix"
gh auth setup-git
git push -u upstream translations_hotfix --force
echo "=== Opening PR (if not already open) ==="
existing_pr=$(gh pr list \
--base "version-16-hotfix" \
--head "translations_hotfix" \
--state open \
--json number \
--jq 'length' \
-R frappe/erpnext)
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 "version-16-hotfix" \
--head "translations_hotfix" \
--title "fix: sync translations to version-16-hotfix" \
--body "Automated sync of Crowdin translations from \`develop\` to \`version-16-hotfix\`.
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 frappe/erpnext

View File

@@ -0,0 +1,62 @@
# Syncs Crowdin translations from develop into version-16-hotfix,
# filtered to only the strings present in hotfix's main.pot.
#
# 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.
#
# 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.
name: Sync translations to version-16-hotfix
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
- 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
runs-on: ubuntu-latest
permissions:
contents: write
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: Run sync script
run: |
bash ${GITHUB_WORKSPACE}/.github/helper/sync_hotfix_translations.sh
env:
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
PR_REVIEWER: barredterra # change to your GitHub username if you copied this file