mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-27 17:04:47 +00:00
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:
49
.github/helper/merge_po_files.py
vendored
Normal file
49
.github/helper/merge_po_files.py
vendored
Normal 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.")
|
||||
85
.github/helper/sync_hotfix_translations.sh
vendored
Normal file
85
.github/helper/sync_hotfix_translations.sh
vendored
Normal 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
|
||||
62
.github/workflows/sync-hotfix-translations.yml
vendored
Normal file
62
.github/workflows/sync-hotfix-translations.yml
vendored
Normal 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
|
||||
Reference in New Issue
Block a user