Skip to content

Push Translations to Transifex #9

Push Translations to Transifex

Push Translations to Transifex #9

name: Push Translations to Transifex
on:
push:
branches:
- "3.15"
paths:
- "**/*.po"
workflow_dispatch:
inputs:
push_translations:
description: "Push Punjabi translations to Transifex"
required: true
type: boolean
default: true
push_sources:
description: "Generate and push CPython source templates"
required: true
type: boolean
default: false
permissions:
contents: read
concurrency:
group: transifex-push-${{ github.ref }}
cancel-in-progress: false
env:
TX_CLI_VERSION: "1.6.16"
TX_PROJECT: python-newest
TX_LANGUAGE: pa
CPYTHON_VERSION: "3.15"
jobs:
push-translations:
name: Push Punjabi translations
if: github.actor != 'github-actions[bot]' && (github.event_name == 'push' || inputs.push_translations)
runs-on: ubuntu-latest
steps:
- name: Checkout translation repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install configuration dependencies
run: |
sudo apt-get update
sudo apt-get install -y gettext
python -m pip install requests cogapp polib transifex-python sphinx-intl blurb six
- name: Install Transifex CLI
shell: bash
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/txcli"
curl -fsSL \
"https://github.com/transifex/cli/releases/download/v${TX_CLI_VERSION}/tx-linux-amd64.tar.gz" \
-o "$RUNNER_TEMP/txcli/tx.tar.gz"
tar -xzf "$RUNNER_TEMP/txcli/tx.tar.gz" -C "$RUNNER_TEMP/txcli"
TX_BINARY="$(find "$RUNNER_TEMP/txcli" -type f -name tx -print -quit)"
if [ -z "$TX_BINARY" ]; then
echo "ERROR: Transifex CLI binary was not found in the archive."
find "$RUNNER_TEMP/txcli" -maxdepth 3 -type f -print
exit 1
fi
sudo install -m 0755 "$TX_BINARY" /usr/local/bin/tx
echo "/usr/local/bin" >> "$GITHUB_PATH"
/usr/local/bin/tx --version
- name: Generate Transifex configuration
run: |
curl -fsSLO https://raw.githubusercontent.com/python-docs-translations/transifex-automations/main/sample-workflows/transifex-util.py
chmod +x transifex-util.py
./transifex-util.py recreate_tx_config \
--language "$TX_LANGUAGE" \
--project-slug "$TX_PROJECT" \
--version "$CPYTHON_VERSION"
- name: Validate Transifex configuration
run: |
python - <<'PY'
import configparser
from pathlib import Path
path = Path(".tx/config")
config = configparser.ConfigParser(interpolation=None)
if not path.is_file() or not config.read(path):
raise SystemExit("ERROR: .tx/config was not generated")
expected = {
"builtins--constants",
"builtins--exceptions",
"builtins--functions",
"builtins--index",
"builtins--stdtypes",
"builtins--threadsafety",
"builtins--time-complexity",
}
resources = {
section.rsplit(":r:", 1)[-1]: section
for section in config.sections()
if ":r:" in section
}
missing = expected - resources.keys()
if missing:
raise SystemExit(
"ERROR: missing builtins resources: "
+ ", ".join(sorted(missing))
)
for resource in sorted(expected):
section = resources[resource]
source = config.get(
section, "source_file", fallback=""
).strip()
expected_source = (
f"gettext/{resource.replace('--', '/')}.pot"
)
if source != expected_source:
raise SystemExit(
f"ERROR: unexpected source_file for {resource}: "
f"{source}"
)
print(
"Transifex configuration and all seven builtins mappings validated."
)
PY
- name: Diagnose builtins index resource
run: |
python - <<'PY'
import ast
import configparser
from pathlib import Path
config = configparser.ConfigParser(interpolation=None)
if not config.read(".tx/config"):
raise SystemExit("ERROR: Could not read .tx/config")
section = "o:python-doc:p:python-newest:r:builtins--index"
if not config.has_section(section):
raise SystemExit(
f"ERROR: missing Transifex resource: {section}"
)
print("=== Generated Transifex mapping ===")
for key in (
"file_filter",
"source_file",
"type",
"resource_name",
):
print(
f"{key} = "
f"{config.get(section, key, fallback='<missing>')}"
)
po_path = Path("builtins/index.po")
if not po_path.is_file():
raise SystemExit(
f"ERROR: translation file not found: {po_path}"
)
print(f"\nTranslation file exists: {po_path}")
entries = []
msgid = None
msgstr = ""
field = None
for line in (
po_path.read_text(encoding="utf-8").splitlines() + [""]
):
if line.startswith("msgid "):
if msgid is not None:
entries.append((msgid, msgstr))
msgid = ast.literal_eval(line[6:])
msgstr = ""
field = "msgid"
elif line.startswith("msgstr "):
msgstr = ast.literal_eval(line[7:])
field = "msgstr"
elif line.startswith('"') and field:
value = ast.literal_eval(line)
if field == "msgid":
msgid += value
else:
msgstr += value
elif not line.strip():
if msgid is not None:
entries.append((msgid, msgstr))
msgid = None
msgstr = ""
field = None
translated = [
(source, target)
for source, target in entries
if source and target.strip()
]
print(f"Non-empty translated entries: {len(translated)}")
for source, target in translated:
print(
f" {source[:100]!r} -> {target[:100]!r}"
)
if len(translated) < 5:
raise SystemExit(
"ERROR: expected at least five translated entries "
"in builtins/index.po"
)
print("Builtins index diagnostic passed.")
PY
- name: Stage builtins index translation for Transifex
run: |
python - <<'PY'
import configparser
import shutil
from pathlib import Path
config_path = Path(".tx/config")
config = configparser.ConfigParser(interpolation=None)
if not config.read(config_path):
raise SystemExit("ERROR: Could not read .tx/config")
section = "o:python-doc:p:python-newest:r:builtins--index"
if not config.has_section(section):
raise SystemExit(
f"ERROR: Missing Transifex resource: {section}"
)
source = Path("builtins/index.po")
staged = Path("builtins/pa/index.po")
if not source.is_file():
raise SystemExit(
f"ERROR: Missing translation file: {source}"
)
staged.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, staged)
config.set(
section,
"file_filter",
"builtins/<lang>/index.po",
)
with config_path.open("w", encoding="utf-8") as file:
config.write(file)
print(f"Staged Punjabi translation: {staged}")
print(
"Updated file_filter:",
config.get(section, "file_filter"),
)
if not staged.is_file():
raise SystemExit("ERROR: Staged translation was not created")
if staged.read_bytes() != source.read_bytes():
raise SystemExit(
"ERROR: Staged translation differs from the original"
)
print("Staged translation validated successfully.")
PY
- name: Push Punjabi translations
env:
TX_TOKEN: ${{ secrets.TX_TOKEN }}
run: |
/usr/local/bin/tx push \
--translation \
--languages "$TX_LANGUAGE" \
--use-git-timestamps
- name: Report Transifex push status
if: always()
run: |
echo "Review the preceding Transifex CLI logs."
echo "Confirm that builtins--index was processed successfully."
echo "A successful command alone does not prove that remote translations changed."
push-sources:
name: Generate and push source templates
needs: push-translations
if: ${{ always() && github.event_name == 'workflow_dispatch' && inputs.push_sources && (needs.push-translations.result == 'success' || needs.push-translations.result == 'skipped') }}
runs-on: ubuntu-latest
steps:
- name: Checkout translation repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Checkout CPython source
uses: actions/checkout@v4
with:
repository: python/cpython
ref: "3.15"
path: cpython
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install documentation build dependencies
run: |
sudo apt-get update
sudo apt-get install -y gettext
python -m pip install -r cpython/Doc/requirements.txt
python -m pip install requests cogapp polib transifex-python sphinx-intl blurb six
- name: Install Transifex CLI
shell: bash
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/txcli"
curl -fsSL \
"https://github.com/transifex/cli/releases/download/v${TX_CLI_VERSION}/tx-linux-amd64.tar.gz" \
-o "$RUNNER_TEMP/txcli/tx.tar.gz"
tar -xzf "$RUNNER_TEMP/txcli/tx.tar.gz" -C "$RUNNER_TEMP/txcli"
TX_BINARY="$(find "$RUNNER_TEMP/txcli" -type f -name tx -print -quit)"
if [ -z "$TX_BINARY" ]; then
echo "ERROR: Transifex CLI binary was not found in the archive."
find "$RUNNER_TEMP/txcli" -maxdepth 3 -type f -print
exit 1
fi
sudo install -m 0755 "$TX_BINARY" /usr/local/bin/tx
echo "/usr/local/bin" >> "$GITHUB_PATH"
/usr/local/bin/tx --version
- name: Generate gettext source templates
run: |
make -C cpython/Doc gettext
test -d cpython/Doc/build/gettext
mkdir -p gettext
cp -a cpython/Doc/build/gettext/. gettext/
- name: Generate Transifex configuration
run: |
curl -fsSLO https://raw.githubusercontent.com/python-docs-translations/transifex-automations/main/sample-workflows/transifex-util.py
chmod +x transifex-util.py
./transifex-util.py recreate_tx_config \
--language "$TX_LANGUAGE" \
--project-slug "$TX_PROJECT" \
--version "$CPYTHON_VERSION"
- name: Validate configured source templates
run: |
python - <<'PY'
import configparser
from pathlib import Path
path = Path(".tx/config")
config = configparser.ConfigParser(interpolation=None)
if not path.is_file() or not config.read(path):
raise SystemExit("ERROR: .tx/config was not generated")
missing = []
for section in config.sections():
source = config.get(
section, "source_file", fallback=""
).strip()
if source and not Path(source).is_file():
missing.append(f"{section}: {source}")
if missing:
print("Missing source templates:")
print("\n".join(missing))
raise SystemExit(1)
print("All configured source templates exist.")
PY
- name: Push source templates
env:
TX_TOKEN: ${{ secrets.TX_TOKEN }}
run: |
/usr/local/bin/tx push --source --skip