11name : Articles Auto Translate
2- run-name : ${{ github.event.label.name }} - ${{ github.event.issue.title }}
2+ run-name : auto - ${{ github.event.issue.title }}
33
44on :
55 issues :
66 types : [labeled]
77
88env :
9- # Git helper functions for push retry
9+ # Git helper functions for push retry with exponential backoff
1010 PUSH_RETRY_SCRIPT : |
1111 push_with_retry() {
1212 local branch=${1:-main}
13- local max_retries=${2:-3 }
14-
13+ local max_retries=${2:-4 }
14+
1515 for i in $(seq 1 $max_retries); do
1616 if git push origin "$branch"; then
1717 echo "Push to $branch successful"
1818 return 0
1919 else
20- echo "Push failed, attempt $i/$max_retries"
20+ wait_time=$((2 ** i))
21+ echo "Push failed, attempt $i/$max_retries. Waiting ${wait_time}s..."
2122 if [ $i -lt $max_retries ]; then
23+ sleep $wait_time
24+ git fetch origin "$branch" || true
2225 git pull --rebase origin "$branch" || true
23- sleep 5
2426 fi
2527 fi
2628 done
@@ -29,38 +31,100 @@ env:
2931 }
3032
3133jobs :
34+ # First job: Validate the trigger and extract information
35+ validate :
36+ name : Validate Auto-Translate Trigger
37+ runs-on : ubuntu-latest
38+ # Only run if the label added is "auto"
39+ if : github.event.label.name == 'auto'
40+ outputs :
41+ lang_code : ${{ steps.extract.outputs.lang_code }}
42+ lang_label : ${{ steps.extract.outputs.lang_label }}
43+ is_valid : ${{ steps.extract.outputs.is_valid }}
44+
45+ steps :
46+ - name : Validate issue and extract language
47+ id : extract
48+ run : |
49+ # Extract language code from issue title (e.g., "[zh] Article Title" -> "zh")
50+ title="${{ github.event.issue.title }}"
51+ LANG_CODE=$(echo "$title" | sed -E 's/^\[([a-zA-Z]+)\].*/\1/' | tr '[:upper:]' '[:lower:]')
52+
53+ # Map language codes to language label names
54+ declare -A LANG_MAP=(
55+ ["zh"]="chinese"
56+ ["es"]="spanish"
57+ ["pt"]="portuguese"
58+ ["it"]="italian"
59+ ["ja"]="japanese"
60+ ["ko"]="korean"
61+ ["uk"]="ukrainian"
62+ )
63+
64+ LANG_LABEL="${LANG_MAP[$LANG_CODE]:-}"
65+
66+ # Validate
67+ if [ -z "$LANG_CODE" ] || [ -z "$LANG_LABEL" ]; then
68+ echo "is_valid=false" >> $GITHUB_OUTPUT
69+ echo "::error::Could not extract valid language from title: $title"
70+ echo "::error::Expected format: [lang] Title (e.g., [zh] Article Title)"
71+ exit 1
72+ fi
73+
74+ # Validate issue body is not empty
75+ if [ -z "${{ github.event.issue.body }}" ]; then
76+ echo "is_valid=false" >> $GITHUB_OUTPUT
77+ echo "::error::Issue body is empty. Please provide article URL."
78+ exit 1
79+ fi
80+
81+ echo "lang_code=$LANG_CODE" >> $GITHUB_OUTPUT
82+ echo "lang_label=$LANG_LABEL" >> $GITHUB_OUTPUT
83+ echo "is_valid=true" >> $GITHUB_OUTPUT
84+ echo "Language code: $LANG_CODE, Language label: $LANG_LABEL"
85+
3286 auto-translate :
87+ name : Auto Translate Article
88+ needs : validate
89+ if : needs.validate.outputs.is_valid == 'true'
3390 runs-on : ubuntu-latest
3491 permissions :
3592 issues : write
3693 contents : write
3794
95+ # Use language-specific concurrency group so different languages can run in parallel
96+ # but same-language translations are queued
97+ concurrency :
98+ group : auto-translate-${{ needs.validate.outputs.lang_code }}
99+ cancel-in-progress : false
100+
101+ env :
102+ LANG_CODE : ${{ needs.validate.outputs.lang_code }}
103+ LANG_LABEL : ${{ needs.validate.outputs.lang_label }}
104+
38105 steps :
39- # ## Queue lock: prevent concurrent conflicts
106+ # ## Queue lock: additional protection for same-language concurrent runs
40107 - uses : softprops/turnstyle@v1
41108 with :
42- poll-interval-seconds : 10
109+ poll-interval-seconds : 15
43110 env :
44111 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
45112
46- # ## Check issue body
47- - name : Validate issue body
48- if : ${{ !github.event.issue.body }}
49- run : |
50- echo "::error::Issue body is empty. Please provide article URL."
51- exit 1
52-
53- # ## Extract language code
54- - name : Extract language code
55- id : lang
56- run : |
57- title="${{ github.event.issue.title }}"
58- LANG_CODE=$(echo "$title" | sed -E 's/^\[([a-zA-Z]+)\].*/\1/')
59- echo "lang=$LANG_CODE" >> $GITHUB_OUTPUT
60- echo "LANG_CODE=$LANG_CODE" >> $GITHUB_ENV
113+ - name : Add processing comment
114+ uses : actions/github-script@v7
115+ with :
116+ script : |
117+ await github.rest.issues.createComment({
118+ issue_number: context.issue.number,
119+ owner: context.repo.owner,
120+ repo: context.repo.repo,
121+ body: 'Auto-translation workflow started for language: **${{ env.LANG_CODE }}**. This may take a few minutes...'
122+ });
61123
62124 # ## Checkout repository
63125 - uses : actions/checkout@v5
126+ with :
127+ fetch-depth : 0
64128
65129 # ## Configure Git
66130 - name : Setup Git
@@ -88,24 +152,27 @@ jobs:
88152 run : |
89153 original_file="${{ steps.fetch.outputs.markdown_file_path }}"
90154 filename=$(basename "$original_file")
91- BACKUP_PATH="/tmp/$filename"
155+ # Sanitize filename
156+ safe_filename=$(echo "$filename" | sed 's/[^a-zA-Z0-9._-]/-/g')
157+ BACKUP_PATH="/tmp/$safe_filename"
92158 cp "$original_file" "$BACKUP_PATH"
93159 echo "path=$BACKUP_PATH" >> $GITHUB_OUTPUT
160+ echo "filename=$safe_filename" >> $GITHUB_OUTPUT
94161 echo "BACKUP_PATH=$BACKUP_PATH" >> $GITHUB_ENV
162+ echo "SAFE_FILENAME=$safe_filename" >> $GITHUB_ENV
95163 echo "Markdown backed up to $BACKUP_PATH"
96164
97- # ## Save English raw text and commit to main branch
165+ # ## Save English raw text ONLY to _raw folder on main branch
166+ # ## Do NOT copy to language folder yet - that happens after translation
98167 - name : Commit raw article to main
99168 run : |
100169 file="${{ steps.fetch.outputs.markdown_file_path }}"
101- base=$(basename "$file")
102- lang="${{ steps.lang.outputs.lang }}"
170+ base="${{ env.SAFE_FILENAME }}"
103171
104172 mkdir -p "./articles/_raw/"
105173 cp "$file" "./articles/_raw/$base"
106- [ -f "./articles/$lang/$base" ] || cp "$file" "./articles/$lang/$base"
107174
108- git add -f "./articles/_raw/$base" "./articles/$lang/$base" || true
175+ git add -f "./articles/_raw/$base" || true
109176 git commit -m "Add raw article: $base" || echo "Nothing to commit."
110177
111178 git fetch origin main
@@ -130,36 +197,67 @@ jobs:
130197 rm -rf ./articles/_tmp/
131198
132199 git fetch origin
133- git checkout -B auto-translate origin/auto-translate
134200
201+ # Create or checkout auto-translate branch
202+ if git show-ref --verify --quiet refs/remotes/origin/auto-translate; then
203+ git checkout -B auto-translate origin/auto-translate
204+ else
205+ git checkout -b auto-translate
206+ fi
207+
208+ # Merge main to get the raw article
135209 git merge --strategy=recursive --strategy-option=theirs main || true
136210
211+ # ## Ensure language directory exists
212+ - name : Prepare language directory
213+ run : |
214+ lang="${{ env.LANG_CODE }}"
215+ mkdir -p "./articles/$lang/"
216+
137217 # ## Auto-translate article (use secure path)
138218 - name : Translate article
139219 uses : freeCodeCamp/articles-auto-translate-action@main
140220 with :
141221 with_issue_title : " ${{ github.event.issue.title }}"
142222 with_issue_body : " ${{ github.event.issue.body }}"
143- with_label_name : " ${{ github.event.label.name }}"
223+ with_label_name : " ${{ env.LANG_LABEL }}"
144224 with_github_token : " ${{ github.token }}"
145- with_original_markdown_file_path : " ${{ steps.backup-md.outputs.path }}"
225+ with_original_markdown_file_path : " ${{ env.BACKUP_PATH }}"
146226 with_task_fetch_to_save_path : " ./articles/_raw/"
147227 with_task_translate_openai_api_key : " ${{ secrets.OPENAI_API_KEY }}"
148228 with_task_translate_to_save_path : " ./articles/{lang}/"
149229
150- # ## Commit translated file
230+ # ## Verify translation was created and commit
151231 - name : Commit translated article
232+ id : commit-translation
152233 run : |
153- base=$(basename "$BACKUP_PATH")
234+ base="${{ env.SAFE_FILENAME }}"
154235 lang="${{ env.LANG_CODE }}"
155236 translated="./articles/$lang/$base"
156237
157- [ -f "$translated" ] || { echo "Translated file missing."; exit 1; }
238+ echo "Checking for translated file: $translated"
239+
240+ if [ ! -f "$translated" ]; then
241+ echo "::error::Translated file not found at: $translated"
242+ echo "Listing articles directory contents:"
243+ find ./articles -type f -name "*.md" | head -20
244+ exit 1
245+ fi
246+
247+ # Verify translation actually happened (file should differ from original)
248+ original="./articles/_raw/$base"
249+ if [ -f "$original" ]; then
250+ if diff -q "$original" "$translated" > /dev/null 2>&1; then
251+ echo "::warning::Translated file is identical to original - translation may have failed"
252+ else
253+ echo "Translation verified: files differ from original"
254+ fi
255+ fi
158256
159257 git add "$translated"
160- git commit -m "Add translated article: $base" || echo "Nothing to commit."
258+ git commit -m "Add translated article ($lang) : $base" || echo "Nothing to commit."
161259
162- git fetch origin auto-translate
260+ git fetch origin auto-translate || true
163261
164262 # Safe rebase, resolve conflicts
165263 git stash push -u -m "Auto-stash before rebase" || true
@@ -174,9 +272,10 @@ jobs:
174272 eval "$PUSH_RETRY_SCRIPT"
175273 push_with_retry auto-translate
176274
177- # ## Clean up temp directory
275+ # ## Clean up temp directory on main
178276 - name : Cleanup temp directory
179277 run : |
278+ git checkout main
180279 rm -rf ./articles/_tmp/
181280 git add -u ./articles/_tmp/ || true
182281 git commit -m "Cleanup _tmp directory" || echo "Nothing to commit."
@@ -188,4 +287,47 @@ jobs:
188287
189288 # Use push retry function for final cleanup
190289 eval "$PUSH_RETRY_SCRIPT"
191- push_with_retry main
290+ push_with_retry main
291+
292+ # ## Add success comment
293+ - name : Add success comment
294+ if : success()
295+ uses : actions/github-script@v7
296+ with :
297+ script : |
298+ const langCode = '${{ env.LANG_CODE }}';
299+ const filename = '${{ env.SAFE_FILENAME }}';
300+
301+ const message = `Auto-translation completed successfully!
302+
303+ **Files created:**
304+ - Raw article: \`articles/_raw/${filename}\`
305+ - Translated article (auto-translate branch): \`articles/${langCode}/${filename}\`
306+
307+ **Next steps:**
308+ 1. Contributors can proofread the translation on the \`auto-translate\` branch
309+ 2. Use \`/postedit\` command to claim this article for post-editing
310+ 3. When ready, create a PR from \`auto-translate\` to \`main\`
311+
312+ [View translated file](https://github.com/${{ github.repository }}/blob/auto-translate/articles/${langCode}/${filename})
313+ [Edit in github.dev](https://github.dev/${{ github.repository }}/blob/auto-translate/articles/${langCode}/${filename})`;
314+
315+ await github.rest.issues.createComment({
316+ issue_number: context.issue.number,
317+ owner: context.repo.owner,
318+ repo: context.repo.repo,
319+ body: message
320+ });
321+
322+ # ## Add failure comment
323+ - name : Add failure comment
324+ if : failure()
325+ uses : actions/github-script@v7
326+ with :
327+ script : |
328+ await github.rest.issues.createComment({
329+ issue_number: context.issue.number,
330+ owner: context.repo.owner,
331+ repo: context.repo.repo,
332+ body: 'Auto-translation workflow failed. Please check the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.'
333+ });
0 commit comments