Add workflow for multi-language docs translation request and approval #6
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: 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" | |
| # Check which language directories have changes | |
| HAS_EN=false | |
| HAS_ZH=false | |
| HAS_JA=false | |
| while IFS= read -r file; do | |
| if [[ "$file" == docs/en/* ]]; then | |
| HAS_EN=true | |
| fi | |
| if [[ "$file" == docs/zh/* ]]; then | |
| HAS_ZH=true | |
| fi | |
| if [[ "$file" == docs/ja/* ]]; then | |
| HAS_JA=true | |
| fi | |
| done <<< "$CHANGED_FILES" | |
| echo "has_en=$HAS_EN" >> $GITHUB_OUTPUT | |
| echo "has_zh=$HAS_ZH" >> $GITHUB_OUTPUT | |
| echo "has_ja=$HAS_JA" >> $GITHUB_OUTPUT | |
| # Count how many languages have changes | |
| COUNT=0 | |
| if [ "$HAS_EN" = "true" ]; then ((COUNT++)); fi | |
| if [ "$HAS_ZH" = "true" ]; then ((COUNT++)); fi | |
| if [ "$HAS_JA" = "true" ]; then ((COUNT++)); fi | |
| echo "lang_count=$COUNT" >> $GITHUB_OUTPUT | |
| echo "Languages with changes: $COUNT (EN: $HAS_EN, ZH: $HAS_ZH, JA: $HAS_JA)" | |
| - name: Comment on PR if translation needed | |
| if: steps.changed-files.outputs.lang_count != '0' && steps.changed-files.outputs.lang_count != '3' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const langCount = '${{ steps.changed-files.outputs.lang_count }}'; | |
| const hasEn = '${{ steps.changed-files.outputs.has_en }}' === 'true'; | |
| const hasZh = '${{ steps.changed-files.outputs.has_zh }}' === 'true'; | |
| const hasJa = '${{ steps.changed-files.outputs.has_ja }}' === 'true'; | |
| const updatedLangs = []; | |
| const missingLangs = []; | |
| if (hasEn) updatedLangs.push('English (`/docs/en/`)'); | |
| else missingLangs.push('English (`/docs/en/`)'); | |
| if (hasZh) updatedLangs.push('Chinese (`/docs/zh/`)'); | |
| else missingLangs.push('Chinese (`/docs/zh/`)'); | |
| if (hasJa) updatedLangs.push('Japanese (`/docs/ja/`)'); | |
| else missingLangs.push('Japanese (`/docs/ja/`)'); | |
| const commentBody = `## π Translation Check | |
| This PR modifies documentation in **${langCount}** language(s): | |
| ${updatedLangs.map(l => `- β ${l}`).join('\n')} | |
| The following language(s) are not updated: | |
| ${missingLangs.map(l => `- β ${l}`).join('\n')} | |
| --- | |
| **Should a translation job be run to update the missing language(s)?** | |
| - 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 | |
| }); | |
| } |