Skip to content

chinese - [zh](Top Open Source Contributors) #2

chinese - [zh](Top Open Source Contributors)

chinese - [zh](Top Open Source Contributors) #2

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