docs: update Pro 3 SE with first-day hands-on testing results #101
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: 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 |