docs: update translations for changed English sources (#371) #84
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: Translate Docs | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - 'docs/*.mdx' | |
| - 'docs/**/*.mdx' | |
| - 'README.md' | |
| workflow_dispatch: | |
| inputs: | |
| force: | |
| description: 'Ignore cache and re-translate everything' | |
| type: boolean | |
| default: false | |
| languages: | |
| description: 'Comma-separated language codes (leave empty for all)' | |
| type: string | |
| default: '' | |
| concurrency: | |
| group: translate-docs | |
| jobs: | |
| prepare: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| languages: ${{ steps.langs.outputs.languages }} | |
| steps: | |
| - id: langs | |
| run: | | |
| if [ -n "${{ inputs.languages }}" ]; then | |
| echo "languages=$(echo '${{ inputs.languages }}' | jq -Rc 'split(",") | map(gsub("\\s"; ""))')" >> "$GITHUB_OUTPUT" | |
| else | |
| echo 'languages=["zh","ja","ko","es","pt-br","de","fr","ru","hi","tr","vi","it","ar","he"]' >> "$GITHUB_OUTPUT" | |
| fi | |
| translate: | |
| needs: prepare | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| # Cap matrix fan-out so the LiteLLM proxy at ANTHROPIC_BASE_URL | |
| # never sees the full 14-way burst. With 4 jobs x MAX_CONCURRENT=2 | |
| # = 8 simultaneous requests, well under the proxy's empirical | |
| # connection-drop knee point (see scripts/translate-docs/cli.ts). | |
| max-parallel: 4 | |
| matrix: | |
| lang: ${{ fromJson(needs.prepare.outputs.languages) }} | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_AUTH_TOKEN }} | |
| ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL }} | |
| FAILPROOFAI_TELEMETRY_DISABLED: "1" | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - uses: actions/cache@v5 | |
| with: | |
| path: ~/.bun/install/cache | |
| key: bun-${{ runner.os }}-${{ hashFiles('bun.lockb') }} | |
| restore-keys: bun-${{ runner.os }}- | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Restore translation cache | |
| uses: actions/cache/restore@v5 | |
| with: | |
| path: scripts/translate-docs/.translation-cache.json | |
| key: translation-cache-${{ hashFiles('scripts/translate-docs/.translation-cache.json') }} | |
| restore-keys: translation-cache- | |
| - name: Translate ${{ matrix.lang }} | |
| run: bun run translate --languages ${{ matrix.lang }} ${{ inputs.force == true && '--force' || '' }} | |
| - name: Upload translated files | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: translations-${{ matrix.lang }} | |
| path: | | |
| docs/${{ matrix.lang }}/ | |
| docs/i18n/README.${{ matrix.lang }}.md | |
| retention-days: 1 | |
| if-no-files-found: warn | |
| - name: Upload cache fragment | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: cache-${{ matrix.lang }} | |
| path: scripts/translate-docs/.translation-cache.json | |
| retention-days: 1 | |
| if-no-files-found: warn | |
| include-hidden-files: true | |
| consolidate: | |
| needs: [prepare, translate] | |
| if: always() && needs.translate.result != 'cancelled' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Download all translations | |
| continue-on-error: true | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: translations-* | |
| merge-multiple: true | |
| path: docs | |
| - name: Download cache fragments | |
| continue-on-error: true | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: cache-* | |
| path: cache-fragments/ | |
| - name: Merge translation caches | |
| run: | | |
| node -e " | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const dir = 'cache-fragments'; | |
| if (!fs.existsSync(dir)) { console.log('No cache fragments found, skipping merge'); process.exit(0); } | |
| const merged = { sourceHash: '', lastUpdated: '', translations: {} }; | |
| let baseLoaded = false; | |
| for (const sub of fs.readdirSync(dir).sort()) { | |
| const f = path.join(dir, sub, '.translation-cache.json'); | |
| if (!fs.existsSync(f)) continue; | |
| const data = JSON.parse(fs.readFileSync(f, 'utf-8')); | |
| // Load base entries from the first fragment (all fragments share the same base) | |
| if (!baseLoaded) { | |
| Object.assign(merged.translations, data.translations); | |
| baseLoaded = true; | |
| } | |
| // Each fragment is authoritative only for its own language. | |
| // Extract lang from directory name: 'cache-zh' -> 'zh' | |
| const lang = sub.replace('cache-', ''); | |
| const suffix = '::' + lang; | |
| for (const [key, value] of Object.entries(data.translations)) { | |
| if (key.endsWith(suffix)) { | |
| merged.translations[key] = value; | |
| } | |
| } | |
| if (data.lastUpdated > merged.lastUpdated) { | |
| merged.lastUpdated = data.lastUpdated; | |
| } | |
| } | |
| fs.mkdirSync('scripts/translate-docs', { recursive: true }); | |
| fs.writeFileSync( | |
| 'scripts/translate-docs/.translation-cache.json', | |
| JSON.stringify(merged, null, 2) | |
| ); | |
| console.log('Merged ' + Object.keys(merged.translations).length + ' cache entries'); | |
| " | |
| - name: Save translation cache | |
| uses: actions/cache/save@v5 | |
| with: | |
| path: scripts/translate-docs/.translation-cache.json | |
| key: translation-cache-${{ hashFiles('scripts/translate-docs/.translation-cache.json') }} | |
| - name: Check for changes | |
| id: changes | |
| run: | | |
| git add -A | |
| if git diff --cached --quiet; then | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "changed=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Create or update PR with translations | |
| if: steps.changes.outputs.changed == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # When an auto-translation PR is already open, push to its branch | |
| # instead of dropping these translations. Previously this step | |
| # exited early if any open PR matched, which combined with the | |
| # cache-save above meant lost translations were never re-attempted | |
| # (cache marked them done, file changes were never committed). | |
| EXISTING_PR=$(gh pr list --base main --search "[auto] update translations" --state open --json number,headRefName --limit 1) | |
| EXISTING_COUNT=$(echo "$EXISTING_PR" | jq length) | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| if [ "$EXISTING_COUNT" -gt 0 ]; then | |
| EXISTING_BRANCH=$(echo "$EXISTING_PR" | jq -r '.[0].headRefName') | |
| EXISTING_NUMBER=$(echo "$EXISTING_PR" | jq -r '.[0].number') | |
| echo "Updating existing PR #$EXISTING_NUMBER on branch $EXISTING_BRANCH" | |
| # Snapshot freshly-generated translations and the merged cache, | |
| # then switch branches and replay them on top so newer English | |
| # source wins over whatever the existing PR branch had. | |
| git reset HEAD | |
| tar -czf /tmp/translations.tar.gz docs/ scripts/translate-docs/.translation-cache.json | |
| git fetch origin "$EXISTING_BRANCH" | |
| git checkout -f -B "$EXISTING_BRANCH" "origin/$EXISTING_BRANCH" | |
| tar -xzf /tmp/translations.tar.gz | |
| rm /tmp/translations.tar.gz | |
| BRANCH="$EXISTING_BRANCH" | |
| else | |
| BRANCH="auto/translate-docs-$(date +%Y%m%d-%H%M)" | |
| git checkout -b "$BRANCH" | |
| fi | |
| git add -A | |
| if git diff --cached --quiet; then | |
| echo "No new translation changes to push. Done." | |
| exit 0 | |
| fi | |
| git commit -m "docs: update translations for changed English sources" | |
| git push origin "$BRANCH" | |
| if [ "$EXISTING_COUNT" -eq 0 ]; then | |
| PR_BODY="## Summary | |
| Automated translation update triggered by changes to English documentation sources. | |
| - Only changed pages were re-translated (content-hash cache) | |
| - All 14 languages across 3 tiers (parallel matrix)" | |
| gh pr create \ | |
| --title "[auto] update translations" \ | |
| --body "$PR_BODY" \ | |
| --base main \ | |
| --head "$BRANCH" | |
| fi |