Skip to content

Add workflow for multi-language docs translation request and approval #9

Add workflow for multi-language docs translation request and approval

Add workflow for multi-language docs translation request and approval #9

name: Check Translation Coverage
on:
pull_request:
paths:
- 'docs/en/**'
- 'docs/zh/**'
- 'docs/ja/**'
issue_comment:
types: [created]
permissions:
pull-requests: write
contents: read
jobs:
check-translation:
name: Check if all languages are updated
# This job only runs on pull_request events, not on issue_comment
# This ensures we never checkout untrusted code from comments
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Get changed files
id: changed-files
run: |
# Get the list of changed files in the PR
CHANGED_FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }})
echo "Changed files:"
echo "$CHANGED_FILES"
# Extract unique basenames from docs files and check which languages have them
declare -A file_langs
while IFS= read -r file; do
if [[ "$file" == docs/en/* ]]; then
basename="${file#docs/en/}"
file_langs["$basename"]+="en,"
elif [[ "$file" == docs/zh/* ]]; then
basename="${file#docs/zh/}"
file_langs["$basename"]+="zh,"
elif [[ "$file" == docs/ja/* ]]; then
basename="${file#docs/ja/}"
file_langs["$basename"]+="ja,"
fi
done <<< "$CHANGED_FILES"
# Check if any doc file is missing from languages
INCOMPLETE_FILES=""
for basename in "${!file_langs[@]}"; do
langs="${file_langs[$basename]}"
has_en=false
has_zh=false
has_ja=false
if [[ "$langs" == *"en,"* ]]; then has_en=true; fi
if [[ "$langs" == *"zh,"* ]]; then has_zh=true; fi
if [[ "$langs" == *"ja,"* ]]; then has_ja=true; fi
# If the file exists in only 1 or 2 languages, mark as incomplete
count=0
if [ "$has_en" = true ]; then ((count++)); fi
if [ "$has_zh" = true ]; then ((count++)); fi
if [ "$has_ja" = true ]; then ((count++)); fi
if [ $count -gt 0 ] && [ $count -lt 3 ]; then
missing_langs=""
if [ "$has_en" = false ]; then missing_langs+="en,"; fi
if [ "$has_zh" = false ]; then missing_langs+="zh,"; fi
if [ "$has_ja" = false ]; then missing_langs+="ja,"; fi
INCOMPLETE_FILES+="$basename:$missing_langs;"
fi
done
if [ -n "$INCOMPLETE_FILES" ]; then
echo "incomplete_files=$INCOMPLETE_FILES" >> $GITHUB_OUTPUT
echo "has_incomplete=true" >> $GITHUB_OUTPUT
else
echo "has_incomplete=false" >> $GITHUB_OUTPUT
fi
echo "Incomplete files: $INCOMPLETE_FILES"
# Ensure the script exits successfully
exit 0
- name: Comment on PR if translation needed
if: steps.changed-files.outputs.has_incomplete == 'true'
uses: actions/github-script@v7
with:
script: |
const incompleteFiles = '${{ steps.changed-files.outputs.incomplete_files }}';
// Parse the incomplete files string
const files = incompleteFiles.split(';').filter(f => f.trim());
let fileDetails = '';
for (const fileEntry of files) {
if (!fileEntry) continue;
const [filename, missingLangs] = fileEntry.split(':');
const langs = missingLangs.split(',').filter(l => l.trim());
const langNames = langs.map(l => {
if (l === 'en') return 'English';
if (l === 'zh') return 'Chinese';
if (l === 'ja') return 'Japanese';
return l;
});
fileDetails += `\n- **${filename}** - Missing in: ${langNames.join(', ')}`;
}
const commentBody = `## 🌍 Translation Check
This PR contains documentation files that are not translated to all languages:
${fileDetails}
---
**Should a translation job be run to create the missing translations?**
- To **request** a translation job, comment: \`/translate-request\`
- To **approve** a translation job (docs-maintainer team only), comment: \`/translate-approve\`
> **Note:** Only members of the \`StarRocks/docs-maintainer\` team can approve translation jobs.`;
// Check if we already commented on this PR
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('🌍 Translation Check')
);
if (botComment) {
// Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: commentBody
});
} else {
// Create new comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: commentBody
});
}
handle-translation-commands:
name: Handle translation commands
if: |
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
(contains(github.event.comment.body, '/translate-request') || contains(github.event.comment.body, '/translate-approve'))
runs-on: ubuntu-latest
steps:
- name: Check if comment is a translation request
if: contains(github.event.comment.body, '/translate-request')
uses: actions/github-script@v7
with:
script: |
const commentBody = `βœ… Translation job requested by @${{ github.event.comment.user.login }}.
Waiting for approval from a member of the \`StarRocks/docs-maintainer\` team.
To approve, comment: \`/translate-approve\``;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: commentBody
});
- name: Check team membership and approve
if: contains(github.event.comment.body, '/translate-approve')
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ORG_READ_TOKEN || secrets.GITHUB_TOKEN }}
script: |
const commenter = '${{ github.event.comment.user.login }}';
try {
// Check if the commenter is a member of the docs-maintainer team
let membership = null;
try {
const response = await github.rest.teams.getMembershipForUserInOrg({
org: 'StarRocks',
team_slug: 'docs-maintainer',
username: commenter
});
membership = response.data;
} catch (error) {
if (error.status !== 404) {
throw error;
}
// 404 means user is not a member, membership stays null
}
if (membership && (membership.state === 'active')) {
// User is a team member, approve the translation
const commentBody = `βœ… Translation job **approved** by @${commenter} (docs-maintainer team member).
πŸš€ The translation job will be triggered.
_Note: Translation job execution is configured separately._`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: commentBody
});
// Add label to indicate translation is approved
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['translation-approved']
});
} else {
// User is not a team member
const commentBody = `❌ @${commenter}, you do not have permission to approve translation jobs.
Only members of the \`StarRocks/docs-maintainer\` team can approve translations.`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: commentBody
});
}
} catch (error) {
console.error('Error checking team membership:', error);
const commentBody = `⚠️ Error checking team membership for @${commenter}.
Error: ${error.message}
Please ensure:
1. The \`StarRocks/docs-maintainer\` team exists
2. The GitHub token has the necessary permissions to check team membership
3. You are a member of the \`StarRocks\` organization`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: commentBody
});
}