.github/workflows/sync_docs_update.yml #4
Workflow file for this run
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: Update Translation PR | ||
| on: | ||
| pull_request: | ||
| types: [synchronize] | ||
| paths: | ||
| - 'docs.json' | ||
| - 'en/**/*.md' | ||
| - 'en/**/*.mdx' | ||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
| actions: read | ||
| jobs: | ||
| update-translation: | ||
| runs-on: ubuntu-latest | ||
| # Only run if this is an English-only PR update | ||
| if: github.event.pull_request.draft == false | ||
| steps: | ||
| - name: Checkout PR | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
| - name: Set up Python | ||
| uses: actions/setup-python@v4 | ||
| with: | ||
| python-version: '3.9' | ||
| - name: Check if PR is English-only | ||
| id: check-pr-type | ||
| run: | | ||
| echo "Checking if this PR contains only English changes..." | ||
| BASE_SHA="${{ github.event.pull_request.base.sha }}" | ||
| HEAD_SHA="${{ github.event.pull_request.head.sha }}" | ||
| # Use PR analyzer to check PR type | ||
| cd tools/translate | ||
| python pr_analyzer.py "$BASE_SHA" "$HEAD_SHA" > /tmp/pr_analysis_output.txt 2>&1 | ||
| if [ $? -eq 0 ]; then | ||
| # Parse analyzer output | ||
| source /tmp/pr_analysis_output.txt | ||
| echo "PR Type: $pr_type" | ||
| echo "pr_type=$pr_type" >> $GITHUB_OUTPUT | ||
| if [ "$pr_type" = "english" ]; then | ||
| echo "✅ English-only PR detected - proceeding with translation update" | ||
| echo "should_update=true" >> $GITHUB_OUTPUT | ||
| else | ||
| echo "ℹ️ Not an English-only PR (type: $pr_type) - skipping translation update" | ||
| echo "should_update=false" >> $GITHUB_OUTPUT | ||
| fi | ||
| else | ||
| echo "❌ PR analysis failed - likely mixed content, skipping translation update" | ||
| echo "should_update=false" >> $GITHUB_OUTPUT | ||
| echo "pr_type=unknown" >> $GITHUB_OUTPUT | ||
| fi | ||
| - name: Find associated translation PR | ||
| if: steps.check-pr-type.outputs.should_update == 'true' | ||
| id: find-translation-pr | ||
| run: | | ||
| PR_NUMBER=${{ github.event.pull_request.number }} | ||
| echo "Looking for translation PR associated with PR #${PR_NUMBER}..." | ||
| # Search for translation PR by branch name pattern | ||
| TRANSLATION_PR_DATA=$(gh pr list \ | ||
| --search "head:docs-sync-pr-${PR_NUMBER}" \ | ||
| --json number,title,url,state \ | ||
| --jq '.[0] // empty' 2>/dev/null || echo "") | ||
| if [ -n "$TRANSLATION_PR_DATA" ] && [ "$TRANSLATION_PR_DATA" != "null" ]; then | ||
| TRANSLATION_PR_NUMBER=$(echo "$TRANSLATION_PR_DATA" | jq -r '.number') | ||
| TRANSLATION_PR_STATE=$(echo "$TRANSLATION_PR_DATA" | jq -r '.state') | ||
| TRANSLATION_PR_URL=$(echo "$TRANSLATION_PR_DATA" | jq -r '.url') | ||
| if [ "$TRANSLATION_PR_STATE" = "OPEN" ]; then | ||
| echo "✅ Found active translation PR #${TRANSLATION_PR_NUMBER}" | ||
| echo "translation_pr_number=$TRANSLATION_PR_NUMBER" >> $GITHUB_OUTPUT | ||
| echo "translation_pr_url=$TRANSLATION_PR_URL" >> $GITHUB_OUTPUT | ||
| echo "found_translation_pr=true" >> $GITHUB_OUTPUT | ||
| else | ||
| echo "ℹ️ Found translation PR #${TRANSLATION_PR_NUMBER} but it's ${TRANSLATION_PR_STATE} - skipping update" | ||
| echo "found_translation_pr=false" >> $GITHUB_OUTPUT | ||
| fi | ||
| else | ||
| echo "ℹ️ No translation PR found for PR #${PR_NUMBER} - this might be the first update" | ||
| echo "found_translation_pr=false" >> $GITHUB_OUTPUT | ||
| fi | ||
| - name: Install dependencies | ||
| if: steps.find-translation-pr.outputs.found_translation_pr == 'true' | ||
| run: | | ||
| cd tools/translate | ||
| pip install httpx aiofiles python-dotenv | ||
| - name: Update translations | ||
| if: steps.find-translation-pr.outputs.found_translation_pr == 'true' | ||
| id: update-translations | ||
| env: | ||
| DIFY_API_KEY: ${{ secrets.DIFY_API_KEY }} | ||
| run: | | ||
| echo "Updating translations for PR #${{ github.event.pull_request.number }}..." | ||
| PR_NUMBER=${{ github.event.pull_request.number }} | ||
| SYNC_BRANCH="docs-sync-pr-${PR_NUMBER}" | ||
| BASE_SHA="${{ github.event.pull_request.base.sha }}" | ||
| HEAD_SHA="${{ github.event.pull_request.head.sha }}" | ||
| # Switch to translation branch | ||
| git fetch origin "$SYNC_BRANCH:$SYNC_BRANCH" || { | ||
| echo "❌ Could not fetch translation branch $SYNC_BRANCH" | ||
| echo "update_successful=false" >> $GITHUB_OUTPUT | ||
| exit 0 | ||
| } | ||
| git checkout "$SYNC_BRANCH" | ||
| # Reset translation branch to latest main to get fresh translations | ||
| git reset --hard origin/main | ||
| # Re-run translation analysis and generation | ||
| cd tools/translate | ||
| # Create updated sync script | ||
| cat > update_translations.py <<'EOF' | ||
| import json | ||
| import sys | ||
| import os | ||
| import asyncio | ||
| from pathlib import Path | ||
| # Add parent directory to path | ||
| sys.path.append(os.path.dirname(__file__)) | ||
| from sync_and_translate import DocsSynchronizer | ||
| from pr_analyzer import PRAnalyzer | ||
| async def update_translations(): | ||
| base_sha = sys.argv[1] | ||
| head_sha = sys.argv[2] | ||
| # Analyze changes | ||
| analyzer = PRAnalyzer(base_sha, head_sha) | ||
| result = analyzer.categorize_pr() | ||
| if result['type'] != 'english': | ||
| print(f"PR type is {result['type']}, not english - skipping") | ||
| return False | ||
| # Initialize synchronizer | ||
| api_key = os.environ.get("DIFY_API_KEY") | ||
| if not api_key: | ||
| print("Error: DIFY_API_KEY not set") | ||
| return False | ||
| synchronizer = DocsSynchronizer(api_key) | ||
| # Get English files that need translation | ||
| file_categories = result['files'] | ||
| english_files = file_categories['english'] | ||
| results = { | ||
| "translated": [], | ||
| "failed": [], | ||
| "skipped": [], | ||
| "updated": True | ||
| } | ||
| # Translate English files | ||
| for file_path in english_files[:10]: # Limit to 10 files for safety | ||
| print(f"Updating translations for: {file_path}") | ||
| try: | ||
| for target_lang in ["zh-hans", "ja-jp"]: | ||
| target_path = file_path.replace("en/", f"{target_lang}/") | ||
| success = await synchronizer.translate_file_with_notice( | ||
| file_path, | ||
| target_path, | ||
| target_lang | ||
| ) | ||
| if success: | ||
| results["translated"].append(target_path) | ||
| else: | ||
| results["failed"].append(target_path) | ||
| except Exception as e: | ||
| print(f"Error processing {file_path}: {e}") | ||
| results["failed"].append(file_path) | ||
| # Handle docs.json structure sync if needed | ||
| docs_changes = result['docs_json_changes'] | ||
| if docs_changes['any_docs_json_changes']: | ||
| print("Updating docs.json structure...") | ||
| try: | ||
| sync_log = synchronizer.sync_docs_json_structure() | ||
| print("\n".join(sync_log)) | ||
| except Exception as e: | ||
| print(f"Error syncing docs.json structure: {e}") | ||
| # Save results | ||
| with open("/tmp/update_results.json", "w") as f: | ||
| json.dump(results, f, indent=2) | ||
| return len(results["failed"]) == 0 | ||
| if __name__ == "__main__": | ||
| success = asyncio.run(update_translations()) | ||
| sys.exit(0 if success else 1) | ||
| EOF | ||
| # Run the update | ||
| python update_translations.py "$BASE_SHA" "$HEAD_SHA" | ||
| UPDATE_EXIT_CODE=$? | ||
| echo "update_exit_code=$UPDATE_EXIT_CODE" >> $GITHUB_OUTPUT | ||
| # Check for changes | ||
| if [[ -n $(git status --porcelain) ]]; then | ||
| echo "has_changes=true" >> $GITHUB_OUTPUT | ||
| echo "✅ Translation updates detected" | ||
| else | ||
| echo "has_changes=false" >> $GITHUB_OUTPUT | ||
| echo "ℹ️ No translation updates needed" | ||
| fi | ||
| - name: Commit and push translation updates | ||
| if: steps.update-translations.outputs.has_changes == 'true' | ||
| id: commit-updates | ||
| run: | | ||
| PR_NUMBER=${{ github.event.pull_request.number }} | ||
| SYNC_BRANCH="docs-sync-pr-${PR_NUMBER}" | ||
| git config user.name 'github-actions[bot]' | ||
| git config user.email 'github-actions[bot]@users.noreply.github.com' | ||
| git add . | ||
| git commit -m "🔄 Update translations for PR #${PR_NUMBER} | ||
| Updated translations following changes in PR #${PR_NUMBER}. | ||
| Auto-updated by translation workflow. | ||
| 🤖 Generated with GitHub Actions" | ||
| # Push updates to translation branch | ||
| git push origin "$SYNC_BRANCH" --force | ||
| echo "commit_successful=true" >> $GITHUB_OUTPUT | ||
| echo "✅ Translation updates committed and pushed" | ||
| - name: Comment on original PR about update | ||
| if: steps.update-translations.outputs.has_changes == 'true' && steps.commit-updates.outputs.commit_successful == 'true' | ||
| uses: actions/github-script@v7 | ||
| continue-on-error: true | ||
| with: | ||
| script: | | ||
| const fs = require('fs'); | ||
| const prNumber = ${{ github.event.pull_request.number }}; | ||
| const translationPrNumber = '${{ steps.find-translation-pr.outputs.translation_pr_number }}'; | ||
| const translationPrUrl = '${{ steps.find-translation-pr.outputs.translation_pr_url }}'; | ||
| // Load update results | ||
| let results = { translated: [], failed: [], skipped: [] }; | ||
| try { | ||
| results = JSON.parse(fs.readFileSync('/tmp/update_results.json', 'utf8')); | ||
| } catch (e) { | ||
| console.log('Could not load update results'); | ||
| } | ||
| let comment = `## 🔄 Translation PR Updated | ||
| Your English documentation changes have been automatically translated and the translation PR has been updated. | ||
| ### 🔗 Updated Translation PR: [#${translationPrNumber}](${translationPrUrl}) | ||
| `; | ||
| if (results.translated && results.translated.length > 0) { | ||
| comment += `### ✅ Updated Translations (${results.translated.length} files):\n`; | ||
| results.translated.slice(0, 6).forEach(file => { | ||
| comment += `- \`${file}\`\n`; | ||
| }); | ||
| if (results.translated.length > 6) { | ||
| comment += `- ... and ${results.translated.length - 6} more files\n`; | ||
| } | ||
| comment += '\n'; | ||
| } | ||
| if (results.failed && results.failed.length > 0) { | ||
| comment += `### ⚠️ Update Issues (${results.failed.length}):\n`; | ||
| results.failed.slice(0, 3).forEach(file => { | ||
| comment += `- \`${file}\`\n`; | ||
| }); | ||
| comment += '\n'; | ||
| } | ||
| comment += `### 🔄 What's Updated: | ||
| - **Translation Files**: All corresponding zh-hans and ja-jp files | ||
| - **Navigation Structure**: Updated docs.json if needed | ||
| - **Automatic**: This update happened automatically when you updated your PR | ||
| --- | ||
| 🤖 _Automatic update from the translation workflow._`; | ||
| try { | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: prNumber, | ||
| body: comment | ||
| }); | ||
| } catch (error) { | ||
| console.log('Could not comment on original PR:', error.message); | ||
| } | ||
| - name: Comment on translation PR about update | ||
| if: steps.update-translations.outputs.has_changes == 'true' && steps.commit-updates.outputs.commit_successful == 'true' | ||
| uses: actions/github-script@v7 | ||
| continue-on-error: true | ||
| with: | ||
| script: | | ||
| const fs = require('fs'); | ||
| const prNumber = ${{ github.event.pull_request.number }}; | ||
| const translationPrNumber = '${{ steps.find-translation-pr.outputs.translation_pr_number }}'; | ||
| // Load update results | ||
| let results = { translated: [], failed: [], skipped: [] }; | ||
| try { | ||
| results = JSON.parse(fs.readFileSync('/tmp/update_results.json', 'utf8')); | ||
| } catch (e) { | ||
| console.log('Could not load update results'); | ||
| } | ||
| const updateComment = `## 🔄 Automatic Translation Update | ||
| This translation PR has been automatically updated following changes in the original PR #${prNumber}. | ||
| ### 📝 What Was Updated: | ||
| - **Source**: Changes from PR #${prNumber} | ||
| - **Updated Files**: ${results.translated ? results.translated.length : 0} translation files | ||
| - **Languages**: Chinese (zh-hans) and Japanese (ja-jp) | ||
| ### ✅ Translation Status: | ||
| ${results.translated && results.translated.length > 0 ? | ||
| `**Successfully Updated (${results.translated.length} files):**\n` + | ||
| results.translated.slice(0, 5).map(f => `- \`${f}\``).join('\n') + | ||
| (results.translated.length > 5 ? `\n- ... and ${results.translated.length - 5} more` : '') : | ||
| '- All translations are up to date'} | ||
| ${results.failed && results.failed.length > 0 ? | ||
| `\n### ⚠️ Update Issues:\n${results.failed.slice(0, 3).map(f => `- \`${f}\``).join('\n')}` : ''} | ||
| ### 🔄 Review Process: | ||
| 1. **Automatic Update**: This PR was updated automatically | ||
| 2. **Review Needed**: Please review the updated translations | ||
| 3. **Independent Merge**: This PR can still be merged independently | ||
| --- | ||
| 🤖 _This update was triggered automatically by changes to PR #${prNumber}._`; | ||
| try { | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: translationPrNumber, | ||
| body: updateComment | ||
| }); | ||
| } catch (error) { | ||
| console.log('Could not comment on translation PR:', error.message); | ||
| } | ||
| - name: Handle no updates needed | ||
| if: steps.find-translation-pr.outputs.found_translation_pr == 'true' && steps.update-translations.outputs.has_changes != 'true' | ||
| uses: actions/github-script@v7 | ||
| continue-on-error: true | ||
| with: | ||
| script: | | ||
| const prNumber = ${{ github.event.pull_request.number }}; | ||
| const translationPrNumber = '${{ steps.find-translation-pr.outputs.translation_pr_number }}'; | ||
| const comment = `## ✅ Translation PR Already Up to Date | ||
| Your changes to PR #${prNumber} did not require translation updates. | ||
| The translation PR [#${translationPrNumber}](https://github.com/${{ github.repository }}/pull/${translationPrNumber}) remains current. | ||
| 🤖 _Automatic check from the translation workflow._`; | ||
| try { | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: prNumber, | ||
| body: comment | ||
| }); | ||
| } catch (error) { | ||
| console.log('Could not comment on original PR:', error.message); | ||
| } | ||