Pull translations from Transifex #27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Pull translations from Transifex | |
| on: | |
| workflow_dispatch: {} | |
| schedule: | |
| - cron: "0 0,12 * * *" # every day at 00:00 and 12:00 UTC | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| concurrency: | |
| group: pull-translations-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| pull-translations: | |
| runs-on: ubuntu-latest | |
| env: | |
| TX_TOKEN: ${{ secrets.TRANSIFEX_TOKEN }} | |
| GH_PAT: ${{ secrets.GH_PAT }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| # Use GH_PAT so pushes trigger downstream workflows (not GITHUB_TOKEN) | |
| token: ${{ secrets.GH_PAT }} | |
| - name: Ensure GH_PAT is provided (required to trigger downstream workflows) | |
| run: | | |
| if [ -z "${GH_PAT:-}" ]; then | |
| echo "GH_PAT secret is not set. This job requires a PAT with 'repo' and 'workflow' scopes so that pushes and PR creation trigger other workflows (like lint)." >&2 | |
| exit 1 | |
| fi | |
| - name: Install Transifex CLI | |
| run: | | |
| curl -sSfL https://raw.githubusercontent.com/transifex/cli/master/install.sh | bash | |
| mkdir -p "$HOME/bin" | |
| install -m 0755 ./tx "$HOME/bin/tx" | |
| echo "$HOME/bin" >> "$GITHUB_PATH" | |
| export PATH="$HOME/bin:$PATH" | |
| tx --version | |
| - name: Configure Transifex credentials | |
| run: | | |
| if [ -z "${TX_TOKEN:-}" ]; then | |
| echo "TRANSIFEX_TOKEN secret is not set" >&2 | |
| exit 1 | |
| fi | |
| mkdir -p "$HOME" | |
| printf "%s\n" \ | |
| "[https://app.transifex.com]" \ | |
| "hostname = https://app.transifex.com" \ | |
| "api_hostname = https://api.transifex.com" \ | |
| "token = ${TX_TOKEN}" > "$HOME/.transifexrc" | |
| chmod 600 "$HOME/.transifexrc" | |
| echo "Created ~/.transifexrc" | |
| - name: Configure Git user | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Prepare transifex branch | |
| id: prep | |
| run: | | |
| set -euo pipefail | |
| git fetch origin main transifex || true | |
| if git ls-remote --exit-code --heads origin transifex >/dev/null 2>&1; then | |
| git checkout -B transifex origin/transifex | |
| prev_sha=$(git rev-parse HEAD) | |
| { echo "previous_sha=${prev_sha}"; } >> "$GITHUB_OUTPUT" | |
| # Try to merge main; if conflicts, abort and hard reset to origin/main | |
| if git merge --no-ff --no-edit origin/main; then | |
| { | |
| echo "branch_state=existing" | |
| echo "merge_strategy=merged_clean" | |
| echo "did_merge=true" | |
| echo "did_reset=false" | |
| } >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Merge had conflicts; aborting and resetting to origin/main" | |
| git merge --abort || true | |
| git reset --hard origin/main | |
| { | |
| echo "branch_state=existing" | |
| echo "merge_strategy=reset_due_to_conflicts" | |
| echo "did_merge=false" | |
| echo "did_reset=true" | |
| } >> "$GITHUB_OUTPUT" | |
| fi | |
| else | |
| echo "Creating transifex branch from origin/main" | |
| { | |
| echo "previous_sha=none" | |
| echo "branch_state=created" | |
| echo "merge_strategy=created_from_main" | |
| echo "did_merge=false" | |
| echo "did_reset=false" | |
| } >> "$GITHUB_OUTPUT" | |
| git checkout -B transifex origin/main | |
| fi | |
| - name: Pull translations from Transifex | |
| run: | | |
| set -euo pipefail | |
| echo "Pulling XLF translation files from Transifex..." | |
| TX_CONFIG=.tx/config tx pull -a -r orcid-angular.messages-xlf --force --minimum-perc=0 | |
| echo "Translation pull complete" | |
| - name: Commit and push changes (if any) | |
| id: commit | |
| run: | | |
| set -euo pipefail | |
| if [ -n "$(git status --porcelain)" ]; then | |
| current_branch=$(git branch --show-current) | |
| if [ "$current_branch" != "transifex" ]; then | |
| git checkout transifex | |
| fi | |
| git add -A | |
| git commit -m "Transifex: pull XLF translations via CI" | |
| git push -f origin transifex | |
| echo "Changes pushed to transifex branch." | |
| echo "changes=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "No changes to commit." | |
| echo "changes=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Generate outputs report | |
| id: report | |
| run: | | |
| set -euo pipefail | |
| tx_version=$(tx --version 2>/dev/null || echo "unknown") | |
| { | |
| echo "tx_version=${tx_version}" | |
| echo "previous_sha=${{ steps.prep.outputs.previous_sha }}" | |
| echo "branch_state=${{ steps.prep.outputs.branch_state }}" | |
| } >> "$GITHUB_OUTPUT" | |
| if [ "${{ steps.commit.outputs.changes }}" = "true" ]; then | |
| commit_sha=$(git rev-parse HEAD) | |
| short_sha=${commit_sha:0:7} | |
| commit_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${commit_sha}" | |
| files_changed=$(git diff --name-only HEAD^ HEAD | wc -l | tr -d ' ') | |
| shortstat=$(git diff --shortstat HEAD^ HEAD | sed 's/^ *//') | |
| { | |
| echo "commit_sha=${commit_sha}" | |
| echo "files_changed=${files_changed}" | |
| echo "diffstat=${shortstat}" | |
| } >> "$GITHUB_OUTPUT" | |
| { | |
| echo "### Transifex run report" | |
| echo "- tx version: ${tx_version}" | |
| echo "- previous head: ${{ steps.prep.outputs.previous_sha }} (${{ steps.prep.outputs.branch_state }})" | |
| echo "- merge strategy: ${{ steps.prep.outputs.merge_strategy }}" | |
| echo "- did merge: ${{ steps.prep.outputs.did_merge }}" | |
| echo "- did reset: ${{ steps.prep.outputs.did_reset }}" | |
| echo "- new commit: [${short_sha}](${commit_url})" | |
| echo "- changes: ${files_changed} files (${shortstat})" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| else | |
| { | |
| echo "commit_sha=none" | |
| echo "files_changed=0" | |
| echo "diffstat=none" | |
| } >> "$GITHUB_OUTPUT" | |
| { | |
| echo "### Transifex run report" | |
| echo "- tx version: ${tx_version}" | |
| echo "- previous head: ${{ steps.prep.outputs.previous_sha }} (${{ steps.prep.outputs.branch_state }})" | |
| echo "- merge strategy: ${{ steps.prep.outputs.merge_strategy }}" | |
| echo "- did merge: ${{ steps.prep.outputs.did_merge }}" | |
| echo "- did reset: ${{ steps.prep.outputs.did_reset }}" | |
| echo "- changes: none" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| - name: Check differences between transifex and main | |
| id: diff | |
| run: | | |
| set -euo pipefail | |
| git fetch origin main transifex || true | |
| if git ls-remote --exit-code --heads origin transifex >/dev/null 2>&1; then | |
| if git diff --quiet origin/main..origin/transifex; then | |
| { echo "differs=false"; } >> "$GITHUB_OUTPUT" | |
| echo "No differences between origin/transifex and origin/main" | |
| else | |
| { echo "differs=true"; } >> "$GITHUB_OUTPUT" | |
| echo "Differences detected between origin/transifex and origin/main" | |
| fi | |
| else | |
| { echo "differs=false"; } >> "$GITHUB_OUTPUT" | |
| echo "origin/transifex does not exist; skipping PR creation" | |
| fi | |
| - name: Open PR to main if differences (PAT) | |
| if: steps.diff.outputs.differs == 'true' && env.GH_PAT != '' | |
| id: openpr | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ env.GH_PAT }} | |
| script: | | |
| const { owner, repo } = context.repo; | |
| const head = 'transifex'; | |
| const base = 'main'; | |
| const list = await github.rest.pulls.list({ owner, repo, state: 'open', base, head: `${owner}:${head}` }); | |
| if (list.data.length > 0) { | |
| core.info('An open PR from transifex to main already exists.'); | |
| const pr = list.data[0]; | |
| core.setOutput('pr_state', 'existed'); | |
| core.setOutput('pr_number', String(pr.number)); | |
| core.setOutput('pr_url', pr.html_url); | |
| } else { | |
| const prev = `${{ steps.prep.outputs.previous_sha }}`; | |
| const didReset = `${{ steps.prep.outputs.did_reset }}` === 'true'; | |
| const didMerge = `${{ steps.prep.outputs.did_merge }}` === 'true'; | |
| let prevLine; | |
| if (prev && prev !== 'none') { | |
| if (didReset) prevLine = `Previous transifex head (before reset): ${prev}`; | |
| else if (didMerge) prevLine = `Previous transifex head (before merge): ${prev}`; | |
| else prevLine = `Previous transifex head: ${prev}`; | |
| } else { | |
| prevLine = 'Branch was created from main (no previous head).'; | |
| } | |
| const body = [ | |
| 'Automated update of XLF translation files via CI.', | |
| '', | |
| prevLine | |
| ].join('\n'); | |
| const created = await github.rest.pulls.create({ owner, repo, head, base, title: 'Transifex: update translations', body }); | |
| core.info('Opened PR from transifex to main.'); | |
| core.setOutput('pr_state', 'created'); | |
| core.setOutput('pr_number', String(created.data.number)); | |
| core.setOutput('pr_url', created.data.html_url); | |
| } | |
| - name: Append PR status to summary | |
| if: always() | |
| run: | | |
| { | |
| echo "### PR status"; | |
| if [ "${{ steps.diff.outputs.differs }}" = "true" ]; then | |
| if [ "${{ steps.openpr.outputs.pr_state }}" = "created" ]; then | |
| echo "- PR created: ${{ steps.openpr.outputs.pr_url }}"; | |
| elif [ "${{ steps.openpr.outputs.pr_state }}" = "existed" ]; then | |
| echo "- PR already existed: ${{ steps.openpr.outputs.pr_url }}"; | |
| else | |
| if [ -z "${{ env.GH_PAT }}" ]; then | |
| echo "- Differences found, but GH_PAT not set; PR not created."; | |
| else | |
| echo "- Differences found, but PR was not created (step skipped or failed)."; | |
| fi | |
| fi | |
| else | |
| echo "- No differences between 'transifex' and 'main'; no PR needed."; | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" | |