Skip to content

docs: update Pro 3 SE with first-day hands-on testing results #101

docs: update Pro 3 SE with first-day hands-on testing results

docs: update Pro 3 SE with first-day hands-on testing results #101

name: Translation Sync
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
on:
workflow_dispatch: # Allow manual re-runs from the Actions UI
push:
branches: [main]
paths:
- 'docs/*.md'
- 'docs/introduction/**/*.md'
- 'docs/version-info/**/*.md'
- 'docs/ordering/**/*.md'
- 'docs/links/**/*.md'
- 'docs/troubleshooting/**/*.md'
concurrency:
group: translation-sync
cancel-in-progress: true
permissions:
contents: write
pull-requests: write
issues: read
actions: write # to dispatch deploy.yml after the auto-merge (GITHUB_TOKEN merges don't trigger push workflows)
jobs:
sync-translations:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
ref: main # Always checkout latest main so a surviving queued run sees all pending changes
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11 # keep in sync with deploy.yml so the validate build matches the real deploy
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Get changed English files
id: changed
run: |
# Compare commit timestamps of each English doc against its locale counterparts.
# An English file is "stale" for a locale if it was committed more recently
# than the locale file (or the locale file doesn't exist yet).
#
# This is more reliable than diffing a commit range because:
# - It catches files missed by queue-cancelled runs (rapid pushes can replace
# a queued run, dropping intermediate translations with cancel-in-progress: false)
# - It doesn't depend on sync commit history or before/after SHAs
# - It works correctly after manual locale edits
LOCALES="es pt da fr pl ru de tr"
FILES=""
PROTECTED_LOCALES=""
while IFS= read -r file; do
EN_TIME=$(git log -1 --format="%ct" -- "$file" 2>/dev/null)
if [ -z "$EN_TIME" ] || [ "$EN_TIME" = "0" ]; then
continue
fi
relative="${file#docs/}"
FILE_NEEDS_TRANSLATION=false
for locale in $LOCALES; do
locale_file="docs/$locale/$relative"
LOC_TIME=$(git log -1 --format="%ct" -- "$locale_file" 2>/dev/null)
LOC_TIME="${LOC_TIME:-0}"
if [ "$EN_TIME" -gt "$LOC_TIME" ]; then
FILE_NEEDS_TRANSLATION=true
else
# Locale file is at least as new as English — already up to date.
# Record it so the translate script skips it and preserves any
# hand-curated wording.
PROTECTED_LOCALES="${PROTECTED_LOCALES}${locale_file}"$'\n'
fi
done
if [ "$FILE_NEEDS_TRANSLATION" = "true" ]; then
FILES="${FILES}${file}"$'\n'
fi
done < <(find docs -maxdepth 4 -name "*.md" \
-not -path "docs/es/*" -not -path "docs/pt/*" \
-not -path "docs/da/*" -not -path "docs/fr/*" \
-not -path "docs/pl/*" -not -path "docs/ru/*" \
-not -path "docs/de/*" -not -path "docs/tr/*" \
-not -path "*/.vitepress/*" \
| sort)
FILES=$(printf '%s' "$FILES" | grep -v '^$' | sort -u || true)
# grep -c prints 0 AND exits 1 on no matches — `|| echo 0` would emit a second
# line ("0\n0") and corrupt $GITHUB_OUTPUT, failing the run when nothing changed.
COUNT=$(printf '%s\n' "$FILES" | grep -c . || true)
PROTECTED_LOCALES=$(printf '%s' "$PROTECTED_LOCALES" | grep -v '^$' | sort -u || true)
echo "files<<EOF" >> $GITHUB_OUTPUT
echo "$FILES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "count=$COUNT" >> $GITHUB_OUTPUT
echo "all_files<<EOF" >> $GITHUB_OUTPUT
echo "$PROTECTED_LOCALES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "English files needing translation ($COUNT):"
echo "$FILES"
- name: Translate changed files
if: steps.changed.outputs.count != '0'
env:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
TRANSLATION_MODEL: ${{ vars.TRANSLATION_MODEL }}
LLM_BASE_URL: ${{ vars.LLM_BASE_URL }}
OPENROUTER_HTTP_REFERER: ${{ vars.OPENROUTER_HTTP_REFERER }}
OPENROUTER_APP_TITLE: ${{ vars.OPENROUTER_APP_TITLE }}
TRANSLATION_LOCALES: ${{ vars.TRANSLATION_LOCALES }}
ALL_CHANGED_FILES: ${{ steps.changed.outputs.all_files }}
CHANGED_FILES: ${{ steps.changed.outputs.files }}
run: bun .github/scripts/translate-docs.mjs
- name: Detect locale file changes
if: steps.changed.outputs.count != '0'
id: locale_changes
run: |
COUNT=$(git status --porcelain -- docs/es docs/pt docs/da docs/fr docs/pl docs/ru docs/de docs/tr | grep -c . || true)
echo "count=$COUNT" >> $GITHUB_OUTPUT
# Build the site with the freshly-written translations BEFORE publishing.
# VitePress fails the build on dead links, so a mangled internal link in a
# translation is caught here and blocks the PR — instead of silently
# merging and breaking the GitHub Pages deploy afterwards.
- name: Validate translated build
if: steps.changed.outputs.count != '0' && steps.locale_changes.outputs.count != '0'
run: bunx vitepress build docs
- name: Push translations and open PR
if: success() && steps.changed.outputs.count != '0' && steps.locale_changes.outputs.count != '0'
env:
GH_TOKEN: ${{ github.token }}
run: |
set -e
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
BRANCH="auto/translation-sync"
git checkout -B "$BRANCH"
git add docs/es docs/pt docs/da docs/fr docs/pl docs/ru docs/de docs/tr
git commit -m "chore: sync translated docs"
git push origin "$BRANCH" --force-with-lease
# Reuse existing open PR or create a new one
PR=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number' 2>/dev/null || true)
if [ -z "$PR" ]; then
PR_URL=$(gh pr create --base main --head "$BRANCH" \
--title "chore: sync translated docs" \
--body "Automated translation sync from GitHub Actions.")
PR=$(gh pr view "$PR_URL" --json number --jq '.number')
fi
# Merge immediately (squash), or queue auto-merge if checks are required.
# Merges made with GITHUB_TOKEN don't fire push-triggered workflows, so
# explicitly dispatch the Pages deploy after a successful merge — without
# this the translations land on main but never ship.
if gh pr merge "$PR" --squash --delete-branch; then
gh workflow run deploy.yml --ref main
else
gh pr merge "$PR" --squash --auto --delete-branch
echo "::warning::Auto-merge queued; run deploy.yml manually once the PR merges (GITHUB_TOKEN merges don't trigger it)."
fi