Sync Translations #16
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: Sync Translations | |
| on: | |
| # Run nightly at 6 AM UTC | |
| schedule: | |
| - cron: '0 6 * * *' | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| jobs: | |
| sync: | |
| if: github.repository == 'fern-api/docs' | |
| name: Update stale translations | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: '3.12' | |
| - name: Install dependencies | |
| run: pip install anthropic | |
| - name: Find and re-translate stale translation files | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| # For each zh translation file, check if its EN source has a newer | |
| # commit than the zh file. If so, the translation is stale. | |
| export STALE_EN_FILES="" | |
| for zh_file in $(find fern/translations/zh/products -name '*.mdx' 2>/dev/null); do | |
| en_file="fern/${zh_file#fern/translations/zh/}" | |
| [ -f "$en_file" ] || continue | |
| en_date=$(git log -1 --format=%ct -- "$en_file" 2>/dev/null || echo 0) | |
| zh_date=$(git log -1 --format=%ct -- "$zh_file" 2>/dev/null || echo 0) | |
| if [ "$en_date" -gt "$zh_date" ]; then | |
| echo "Stale: $zh_file (EN updated $(date -d @"$en_date" -u +%Y-%m-%d), zh last updated $(date -d @"$zh_date" -u +%Y-%m-%d))" | |
| STALE_EN_FILES="$STALE_EN_FILES $en_file" | |
| fi | |
| done | |
| export STALE_EN_FILES | |
| if [ -z "$STALE_EN_FILES" ]; then | |
| echo "No stale translation files found" | |
| exit 0 | |
| fi | |
| python3 << 'PYEOF' | |
| import os, sys, time | |
| import anthropic | |
| SYSTEM_PROMPT = """You are translating Fern developer documentation from English to Simplified Chinese (zh). | |
| Rules: | |
| 1. Translate ALL prose, headings, frontmatter (title, description, sidebar-title, headline), callout text, and step titles to Chinese. | |
| 2. DO NOT translate: | |
| - Code blocks (content inside ``` fences) | |
| - Component tag names (<Steps>, <Accordion>, <Note>, <Frame>, <CodeBlock>, <Tabs>, <Tab>, etc.) | |
| - Component prop names and values (e.g. title="...", href="...", src="...", language="...") | |
| - URLs, file paths, import paths | |
| - Variable names, API endpoints, CLI commands, package names | |
| - YAML/JSON keys in code blocks | |
| - <Markdown src="..."/> includes | |
| - <llms-only> and <llms-ignore> tags (keep them as-is) | |
| - Content inside <llms-only> blocks (keep in English as it's for AI agents) | |
| 3. Translate component prop values ONLY when they contain human-readable display text: | |
| - Translate: title="Getting started" → title="开始使用" | |
| - Do NOT translate: href="/learn/docs/...", src="./image.png", language="python" | |
| 4. Keep the same MDX structure, whitespace, and formatting as the original. | |
| 5. Keep frontmatter YAML structure exactly the same (same keys, just translate values). | |
| 6. For technical terms commonly kept in English in Chinese tech docs, keep them in English or use the standard Chinese translation with the English term in parentheses on first use. Examples: | |
| - SDK, API, CLI, MDX, YAML, JSON, OpenAPI, gRPC → keep in English | |
| - endpoint → 端点 or keep as endpoint | |
| - middleware → 中间件 | |
| 7. Output ONLY the translated MDX content. No explanations, no markdown fences around the output.""" | |
| client = anthropic.Anthropic() | |
| stale = os.environ.get("STALE_EN_FILES", "").split() | |
| updated = 0 | |
| for en_file in stale: | |
| zh_file = "fern/translations/zh/" + en_file.removeprefix("fern/") | |
| with open(en_file) as f: | |
| en_content = f.read() | |
| print(f"Translating: {en_file} -> {zh_file}") | |
| try: | |
| resp = client.messages.create( | |
| model="claude-sonnet-4-20250514", | |
| max_tokens=16000, | |
| system=SYSTEM_PROMPT, | |
| messages=[{"role": "user", "content": f"Translate this MDX documentation page to Simplified Chinese:\n\n{en_content}"}], | |
| ) | |
| zh_content = resp.content[0].text | |
| os.makedirs(os.path.dirname(zh_file), exist_ok=True) | |
| with open(zh_file, "w") as f: | |
| f.write(zh_content) | |
| if not zh_content.endswith("\n"): | |
| f.write("\n") | |
| updated += 1 | |
| time.sleep(1) | |
| except Exception as e: | |
| print(f" ERROR translating {en_file}: {e}", file=sys.stderr) | |
| print(f"Updated {updated}/{len(stale)} translation files") | |
| if updated > 0: | |
| with open(os.environ["GITHUB_ENV"], "a") as f: | |
| f.write("stale_found=true\n") | |
| PYEOF | |
| - name: Commit and push | |
| if: env.stale_found == 'true' | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| git add fern/translations/zh/ | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git commit -m "chore: re-translate stale zh translations for updated EN pages | |
| Automatically re-translates zh translation files whose EN source was | |
| updated, keeping pre-computed translations in sync with the latest | |
| English content." | |
| git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git" | |
| git push |