chinese - [zh](Top Open Source Contributors) #2
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: Articles Auto Translate | |
| run-name: ${{ github.event.label.name }} - ${{ github.event.issue.title }} | |
| on: | |
| issues: | |
| types: [labeled] | |
| concurrency: | |
| group: auto-translate-${{ github.event.issue.number }} | |
| cancel-in-progress: false | |
| env: | |
| PUSH_RETRY_SCRIPT: | | |
| push_with_retry() { | |
| local branch=${1:-main} | |
| local max_retries=4 | |
| for i in $(seq 1 $max_retries); do | |
| if git push -u origin "$branch"; then | |
| echo "Push to $branch successful" | |
| return 0 | |
| else | |
| wait_time=$((2 ** i)) | |
| echo "Push failed, attempt $i/$max_retries. Waiting ${wait_time}s..." | |
| if [ $i -lt $max_retries ]; then | |
| sleep $wait_time | |
| git pull --rebase origin "$branch" || true | |
| fi | |
| fi | |
| done | |
| echo "Push failed after $max_retries attempts" | |
| return 1 | |
| } | |
| permissions: | |
| issues: write | |
| contents: write | |
| jobs: | |
| validate: | |
| name: Validate Input | |
| runs-on: ubuntu-latest | |
| permissions: | |
| issues: read | |
| outputs: | |
| is_valid: ${{ steps.validate.outputs.is_valid }} | |
| article_url: ${{ steps.validate.outputs.article_url }} | |
| lang_code: ${{ steps.validate.outputs.lang_code }} | |
| label_name: ${{ steps.validate.outputs.label_name }} | |
| skip_reason: ${{ steps.validate.outputs.skip_reason }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Validate and sanitize inputs | |
| id: validate | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const validator = require('./scripts/validateAutoTranslateInput.js'); | |
| await validator({ github, context, core }); | |
| auto-translate: | |
| name: Auto Translate Article | |
| needs: validate | |
| runs-on: ubuntu-latest | |
| if: needs.validate.outputs.is_valid == 'true' | |
| permissions: | |
| issues: write | |
| contents: write | |
| steps: | |
| - uses: softprops/turnstyle@v1 | |
| with: | |
| poll-interval-seconds: 10 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Add processing comment | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: 'Auto-translation workflow started. This may take a few minutes...' | |
| }); | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Git | |
| run: | | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git config user.name "github-actions[bot]" | |
| git config pull.rebase true | |
| - name: Set environment variables | |
| run: | | |
| echo "ARTICLE_URL=${{ needs.validate.outputs.article_url }}" >> $GITHUB_ENV | |
| echo "LANG_CODE=${{ needs.validate.outputs.lang_code }}" >> $GITHUB_ENV | |
| echo "LABEL_NAME=${{ needs.validate.outputs.label_name }}" >> $GITHUB_ENV | |
| - name: Fetch article and convert to Markdown | |
| id: fetch | |
| uses: freecodecamp/article-webpage-to-markdown-action@dev | |
| with: | |
| newsLink: "${{ env.ARTICLE_URL }}" | |
| includeSelector: 'span.author-card-name,section.post-content' | |
| ignoreSelector: '.ad-wrapper' | |
| skipSameArticleCheck: true | |
| skipIssueComment: true | |
| markDownFilePath: './articles/_tmp/' | |
| githubToken: "${{ github.token }}" | |
| - name: Validate and sanitize filename | |
| id: sanitize | |
| run: | | |
| FETCHED_FILE="${{ steps.fetch.outputs.markdown_file_path }}" | |
| if [ ! -f "$FETCHED_FILE" ]; then | |
| echo "Fetched file does not exist: $FETCHED_FILE" | |
| exit 1 | |
| fi | |
| BASENAME=$(basename "$FETCHED_FILE") | |
| SAFE_BASENAME=$(echo "$BASENAME" | sed 's/[^a-zA-Z0-9._-]/-/g') | |
| if [[ ! "$SAFE_BASENAME" =~ \.md$ ]]; then | |
| SAFE_BASENAME="${SAFE_BASENAME%.md}.md" | |
| fi | |
| SAFE_BASENAME="${SAFE_BASENAME#.}" | |
| if [ -z "$SAFE_BASENAME" ] || [ "$SAFE_BASENAME" = ".md" ]; then | |
| echo "Invalid filename after sanitization" | |
| exit 1 | |
| fi | |
| echo "filename=$SAFE_BASENAME" >> $GITHUB_OUTPUT | |
| echo "Sanitized filename: $SAFE_BASENAME" | |
| - name: Backup fetched Markdown to /tmp | |
| id: backup | |
| run: | | |
| FETCHED_FILE="${{ steps.fetch.outputs.markdown_file_path }}" | |
| SAFE_FILENAME="${{ steps.sanitize.outputs.filename }}" | |
| BACKUP_PATH="/tmp/$SAFE_FILENAME" | |
| cp "$FETCHED_FILE" "$BACKUP_PATH" | |
| echo "path=$BACKUP_PATH" >> $GITHUB_OUTPUT | |
| echo "Markdown backed up to: $BACKUP_PATH" | |
| - name: Commit raw article to main | |
| run: | | |
| SAFE_FILENAME="${{ steps.sanitize.outputs.filename }}" | |
| LANG_CODE="${{ env.LANG_CODE }}" | |
| SOURCE_FILE="${{ steps.fetch.outputs.markdown_file_path }}" | |
| if [[ ! "$LANG_CODE" =~ ^[a-z]{2,3}$ ]]; then | |
| echo "Invalid language code: $LANG_CODE" | |
| exit 1 | |
| fi | |
| mkdir -p "./articles/_raw/" | |
| mkdir -p "./articles/$LANG_CODE/" | |
| cp "$SOURCE_FILE" "./articles/_raw/$SAFE_FILENAME" | |
| if [ ! -f "./articles/$LANG_CODE/$SAFE_FILENAME" ]; then | |
| cp "$SOURCE_FILE" "./articles/$LANG_CODE/$SAFE_FILENAME" | |
| fi | |
| git add -f "./articles/_raw/$SAFE_FILENAME" "./articles/$LANG_CODE/$SAFE_FILENAME" | |
| git commit -m "Add raw article: $SAFE_FILENAME" || echo "Nothing to commit" | |
| git fetch origin main | |
| git stash push -u -m "Auto-stash before rebase" || true | |
| git pull --rebase origin main || true | |
| git stash pop || true | |
| eval "$PUSH_RETRY_SCRIPT" | |
| push_with_retry main | |
| - name: Checkout auto-translate branch | |
| run: | | |
| if [ -f ".git/MERGE_HEAD" ]; then | |
| echo "Unfinished merge detected. Aborting..." | |
| git merge --abort || git reset --merge | |
| fi | |
| rm -rf ./articles/_tmp/ | |
| git fetch origin | |
| if git show-ref --verify --quiet refs/remotes/origin/auto-translate; then | |
| git checkout -B auto-translate origin/auto-translate | |
| else | |
| git checkout -b auto-translate | |
| fi | |
| git merge --strategy=recursive --strategy-option=theirs main || true | |
| - name: Translate article | |
| uses: freeCodeCamp/articles-auto-translate-action@main | |
| with: | |
| with_issue_title: "${{ github.event.issue.title }}" | |
| with_issue_body: "${{ github.event.issue.body }}" | |
| with_label_name: "${{ env.LABEL_NAME }}" | |
| with_github_token: "${{ github.token }}" | |
| with_original_markdown_file_path: "${{ steps.backup.outputs.path }}" | |
| with_task_fetch_to_save_path: "./articles/_raw/" | |
| with_task_translate_openai_api_key: "${{ secrets.OPENAI_API_KEY }}" | |
| with_task_translate_to_save_path: "./articles/{lang}/" | |
| - name: Commit translated article | |
| run: | | |
| SAFE_FILENAME="${{ steps.sanitize.outputs.filename }}" | |
| LANG_CODE="${{ env.LANG_CODE }}" | |
| TRANSLATED_FILE="./articles/$LANG_CODE/$SAFE_FILENAME" | |
| if [ ! -f "$TRANSLATED_FILE" ]; then | |
| echo "Translated file not found: $TRANSLATED_FILE" | |
| exit 1 | |
| fi | |
| git add "$TRANSLATED_FILE" | |
| git commit -m "Add translated article ($LANG_CODE): $SAFE_FILENAME" || echo "Nothing to commit" | |
| git fetch origin auto-translate || true | |
| if [ -f ".git/MERGE_HEAD" ]; then | |
| echo "Unfinished merge detected. Aborting..." | |
| git merge --abort || git reset --merge | |
| fi | |
| git stash push -u -m "Auto-stash before rebase" || true | |
| git pull --rebase origin auto-translate || true | |
| git stash pop || true | |
| eval "$PUSH_RETRY_SCRIPT" | |
| push_with_retry auto-translate | |
| - name: Cleanup temp directory | |
| run: | | |
| rm -rf ./articles/_tmp/ | |
| git add -u ./articles/_tmp/ || true | |
| git commit -m "Cleanup _tmp directory" || echo "Nothing to commit" | |
| git checkout main | |
| git pull --rebase origin main || true | |
| git add -u ./articles/_tmp/ || true | |
| git commit -m "Cleanup _tmp directory" || echo "Nothing to commit" | |
| eval "$PUSH_RETRY_SCRIPT" | |
| push_with_retry main | |
| - name: Add success comment | |
| if: success() | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const langCode = '${{ env.LANG_CODE }}'; | |
| const filename = '${{ steps.sanitize.outputs.filename }}'; | |
| const message = `Auto-translation completed successfully! | |
| Files created: | |
| - Raw article: \`articles/_raw/${filename}\` | |
| - Translated article (auto-translate branch): \`articles/${langCode}/${filename}\` | |
| Next steps: | |
| 1. Contributors can now proofread the translation on the \`auto-translate\` branch | |
| 2. When ready, create a PR from \`auto-translate\` to \`main\` to compare with the English original | |
| [View translated file on auto-translate branch](https://github.com/${{ github.repository }}/blob/auto-translate/articles/${langCode}/${filename}) | |
| [Open github.dev to edit](https://github.dev/${{ github.repository }}/blob/auto-translate/articles/${langCode}/${filename})`; | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: message | |
| }); | |
| - name: Add failure comment | |
| if: failure() | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: 'Auto-translation workflow failed. Please check the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.' | |
| }); | |
| notify-skip: | |
| name: Notify Skip | |
| needs: validate | |
| runs-on: ubuntu-latest | |
| if: needs.validate.outputs.is_valid != 'true' | |
| permissions: | |
| issues: write | |
| steps: | |
| - name: Add skip comment | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const skipReason = '${{ needs.validate.outputs.skip_reason }}'; | |
| if (!skipReason.includes('not an auto-translate language label')) { | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: `Auto-translation workflow skipped: ${skipReason}\n\nPlease ensure:\n- Issue body contains a valid freeCodeCamp news article URL in markdown format: \`[Title](URL)\`\n- Issue title starts with language code in brackets, e.g., \`[zh]\` or \`[es]\`\n- Label is one of the supported language labels: chinese, spanish, portuguese, italian, japanese, korean, ukrainian` | |
| }); | |
| } |