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); }