Files
dify-docs/.github/workflows/sync_docs_update.yml
Gu d4be02b3c3 fix: resolve YAML syntax errors in workflow files
Replace heredoc syntax with bash variable assignments using $'\n' for newlines.
Fix indentation of JavaScript template literals in script blocks.

Root cause: Multi-line strings in bash and JavaScript template literals
must be properly indented within YAML literal block scalars to prevent
parser errors.

\ud83e\udd16 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 11:19:17 -08:00

552 lines
24 KiB
YAML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Workflow for updating translation PRs on sync events
name: Update Translation PR
on:
pull_request:
types: [synchronize]
permissions:
contents: write
pull-requests: write
actions: read
jobs:
check-event-type:
runs-on: ubuntu-latest
outputs:
should_run: ${{ steps.check-paths.outputs.should_run }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check if relevant files changed
id: check-paths
run: |
echo "Checking if relevant files changed..."
BASE_SHA="${{ github.event.pull_request.base.sha }}"
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
# Check if docs.json or en/ files changed
CHANGED_FILES=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA")
RELEVANT_FILES=$(echo "$CHANGED_FILES" | grep -E '^(docs\.json|en/.*\.(md|mdx))$' || true)
if [ -n "$RELEVANT_FILES" ]; then
echo "should_run=true" >> $GITHUB_OUTPUT
echo "✅ Relevant files changed - proceeding with translation update"
echo "Changed files:"
echo "$RELEVANT_FILES"
else
echo "should_run=false" >> $GITHUB_OUTPUT
echo " No relevant files changed (docs.json or en/**/*.md|mdx)"
echo " Skipping translation update"
fi
update-translation:
needs: check-event-type
runs-on: ubuntu-latest
# dummy job to avoid getting error when no job is executed
if: |
needs.check-event-type.outputs.should_run == 'true' &&
github.event.pull_request.draft == false &&
!startsWith(github.event.pull_request.head.ref, 'docs-sync-pr-')
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
env:
GH_TOKEN: ${{ github.token }}
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: Determine update range
if: steps.find-translation-pr.outputs.found_translation_pr == 'true'
id: update-range
env:
GH_TOKEN: ${{ github.token }}
run: |
PR_NUMBER=${{ github.event.pull_request.number }}
TRANSLATION_PR_NUMBER="${{ steps.find-translation-pr.outputs.translation_pr_number }}"
PR_BASE="${{ github.event.pull_request.base.sha }}"
PR_HEAD="${{ github.event.pull_request.head.sha }}"
echo "Determining incremental update range..."
# Get last processed commit from translation PR comments
LAST_PROCESSED=$(gh pr view "$TRANSLATION_PR_NUMBER" \
--json comments \
--jq '.comments | reverse | .[] | .body' 2>/dev/null \
| grep -oP 'Last-Processed-Commit: \K[a-f0-9]+' \
| head -1 || echo "")
if [ -n "$LAST_PROCESSED" ]; then
echo "✅ Found last processed commit: $LAST_PROCESSED"
COMPARE_BASE="$LAST_PROCESSED"
else
echo "⚠️ No last processed commit found, using PR base"
COMPARE_BASE="$PR_BASE"
fi
COMPARE_HEAD="$PR_HEAD"
echo "compare_base=$COMPARE_BASE" >> $GITHUB_OUTPUT
echo "compare_head=$COMPARE_HEAD" >> $GITHUB_OUTPUT
echo "📊 Incremental update range: $COMPARE_BASE...$COMPARE_HEAD"
- 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}"
PR_BRANCH="${{ github.event.pull_request.head.ref }}"
BASE_SHA="${{ steps.update-range.outputs.compare_base }}"
HEAD_SHA="${{ steps.update-range.outputs.compare_head }}"
echo "Using incremental comparison: $BASE_SHA...$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"
# For incremental updates, checkout English files from PR HEAD
git fetch origin "$PR_BRANCH:refs/remotes/origin/$PR_BRANCH"
git checkout "origin/$PR_BRANCH" -- en/ || echo "No English files to checkout"
# 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
import subprocess
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 ["cn", "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 (INCREMENTAL MODE)
docs_changes = result['docs_json_changes']
if docs_changes['any_docs_json_changes']:
print("Updating docs.json structure (incremental mode)...")
try:
# Get added files
added_files = english_files
# Get deleted files from git diff
deleted_files = []
try:
print(f"Detecting deleted files between {base_sha[:8]} and {head_sha[:8]}...")
result_git = subprocess.run([
"git", "diff", "--name-status", "--diff-filter=D",
base_sha, head_sha
], capture_output=True, text=True, cwd="../../")
if result_git.returncode != 0:
print(f"Git diff failed: {result_git.stderr}")
else:
print(f"Git diff output:\n{result_git.stdout}")
for line in result_git.stdout.strip().split('\n'):
if line and line.startswith('D\t'):
file_path = line.split('\t')[1]
if file_path.startswith("en/"):
deleted_files.append(file_path)
print(f" Found deleted file: {file_path}")
except Exception as e:
print(f"Warning: Could not get deleted files: {e}")
import traceback
traceback.print_exc()
print(f"Added files: {added_files}")
print(f"Deleted files: {deleted_files}")
# Delete corresponding translation files
if deleted_files:
print(f"\nDeleting corresponding translation files for {len(deleted_files)} deleted English files...")
for en_file in deleted_files:
for target_lang in ["cn", "jp"]:
target_file = en_file.replace("en/", f"{target_lang}/")
target_path = Path(f"../../{target_file}")
if target_path.exists():
target_path.unlink()
print(f"✓ Deleted {target_file}")
results["translated"].append(f"deleted:{target_file}")
# Remove empty parent directories
parent = target_path.parent
repo_root = Path("../../").resolve()
while parent.resolve() != repo_root:
try:
# Only remove if directory is empty
if not any(parent.iterdir()):
parent.rmdir()
print(f"✓ Removed empty directory {parent.relative_to(repo_root)}")
parent = parent.parent
else:
break
except (OSError, ValueError):
break
else:
print(f" Skipped {target_file} (doesn't exist)")
# Use incremental sync to update docs.json
sync_log = synchronizer.sync_docs_json_incremental(
added_files=added_files,
deleted_files=deleted_files
)
print("\n".join(sync_log))
except Exception as e:
print(f"Error syncing docs.json structure: {e}")
import traceback
traceback.print_exc()
# 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
env:
GH_TOKEN: ${{ github.token }}
run: |
PR_NUMBER=${{ github.event.pull_request.number }}
SYNC_BRANCH="docs-sync-pr-${PR_NUMBER}"
HEAD_SHA="${{ steps.update-range.outputs.compare_head }}"
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
# Commit only translation changes (not English files)
git add cn/ jp/ docs.json
git commit -m "🔄 Update translations for commit ${HEAD_SHA:0:8}"$'\n\n'"Auto-generated translation updates for changes in commit ${HEAD_SHA}."$'\n\n'"Last-Processed-Commit: ${HEAD_SHA}"$'\n'"Original-PR: #${PR_NUMBER}"$'\n'"Languages: Chinese (cn), Japanese (jp)"$'\n\n'"🤖 Generated with GitHub Actions"
# Push updates to translation branch (no force - preserve history)
git push origin "$SYNC_BRANCH"
# Add tracking comment to translation PR
TRANSLATION_PR_NUMBER="${{ steps.find-translation-pr.outputs.translation_pr_number }}"
COMMENT_BODY="<!-- Last-Processed-Commit: ${HEAD_SHA} -->"$'\n'"🔄 **Updated for commit \`${HEAD_SHA:0:8}\`**"$'\n\n'"Latest source changes from PR #${PR_NUMBER} have been translated and committed."$'\n\n'"**Source commit:** [\`${HEAD_SHA:0:8}\`](https://github.com/${{ github.repository }}/commit/${HEAD_SHA})"$'\n'"**Original PR:** #${PR_NUMBER}"
gh pr comment "$TRANSLATION_PR_NUMBER" --body "$COMMENT_BODY" || echo "Failed to add comment"
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\n\n`
comment += `Your English documentation changes have been automatically translated and the translation PR has been updated.\n\n`
comment += `### 📝 Original Changes\n\n`;
comment += `- **Original PR**: #${prNumber}\n`;
comment += `- **Type**: English documentation updates\n`;
comment += `### 🔄 Synchronization\n\n`;
comment += `- **Automatic Updates**: This PR will be automatically updated if the original PR changes\n`;
comment += `- **Independent Review**: This translation PR can be reviewed and merged independently\n`;
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 cn and 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 (cn) and Japanese (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);
}