@@ -23,10 +23,13 @@ jobs:
2323 contents : write # for creating branches and commits
2424 pull-requests : write # for creating PRs
2525 runs-on : ubuntu-latest
26+ outputs :
27+ po-files-changed : ${{ steps.check-changes.outputs.po-files-changed }}
28+ translations-to-process : ${{ steps.prepare-translations.outputs.translations-to-process }}
29+ translation-matrix : ${{ steps.prepare-translations.outputs.translation-matrix }}
2630 env :
2731 PYGETTEXT_DOMAIN : ardupilot_methodic_configurator
2832 PYGETTEXT_LOCALEDIR : ardupilot_methodic_configurator/locale
29- PO_FILES_CHANGED : false
3033
3134 steps :
3235 - name : Harden the runner (Audit all outbound calls)
8487 run : |
8588 python create_pot_file.py
8689
87- - name : Stage changes
90+ - name : Stage changes and check for updates
91+ id : check-changes
8892 run : |
8993 git add $PYGETTEXT_LOCALEDIR/$PYGETTEXT_DOMAIN.pot
9094 if [ -n "$(git status --porcelain)" ]; then
@@ -95,25 +99,288 @@ jobs:
9599 git add $PYGETTEXT_LOCALEDIR/**/$PYGETTEXT_DOMAIN.po
96100 PO_CHANGES=$(git status --porcelain | grep -E "\.po$" | wc -l)
97101 if [ $PO_CHANGES -gt 0 ]; then
98- echo "PO_FILES_CHANGED=true" >> $GITHUB_ENV
102+ echo "po-files-changed=true" >> $GITHUB_OUTPUT
103+ echo "✅ PO files have been updated with new strings"
104+ else
105+ echo "po-files-changed=false" >> $GITHUB_OUTPUT
106+ echo "No PO file changes detected"
99107 fi
100108 else
109+ echo "po-files-changed=false" >> $GITHUB_OUTPUT
101110 echo "Not enough changes to commit (only $CHANGED_LINES lines changed)"
102111 fi
103112 else
113+ echo "po-files-changed=false" >> $GITHUB_OUTPUT
104114 echo "No changes to commit"
105115 fi
106116
117+ - name : Prepare translation matrix
118+ id : prepare-translations
119+ if : steps.check-changes.outputs.po-files-changed == 'true'
120+ run : |
121+ python extract_missing_translations.py --lang-code all --max-translations 50
122+
123+ # Check if any missing translation files were created
124+ if ls missing_translations_*.txt 1> /dev/null 2>&1; then
125+ echo "translations-to-process=true" >> $GITHUB_OUTPUT
126+ echo "✅ Found missing translation files to process with AI"
127+
128+ # Create matrix configuration for all translation files
129+ matrix_entries="["
130+ first_entry=true
131+
132+ for file in missing_translations_*.txt; do
133+ if [ -f "$file" ]; then
134+ # Extract language code and file number from filename
135+ base_name=$(basename "$file" .txt)
136+ if [[ "$base_name" =~ missing_translations_([^_]+)(_[0-9]+)?$ ]]; then
137+ lang_code="${BASH_REMATCH[1]}"
138+ file_suffix="${BASH_REMATCH[2]:-}"
139+
140+ # Define language name for better context
141+ case $lang_code in
142+ "pt") language="Portuguese (Portugal)";;
143+ "de") language="German";;
144+ "it") language="Italian";;
145+ "ja") language="Japanese";;
146+ "zh_CN") language="Chinese (Simplified)";;
147+ *) language="$lang_code";;
148+ esac
149+
150+ if [ "$first_entry" = true ]; then
151+ first_entry=false
152+ else
153+ matrix_entries+=","
154+ fi
155+
156+ matrix_entries+="{\"lang_code\":\"$lang_code\",\"language\":\"$language\",\"file\":\"$file\",\"suffix\":\"$file_suffix\"}"
157+ fi
158+ fi
159+ done
160+ matrix_entries+="]"
161+
162+ echo "translation-matrix=$matrix_entries" >> $GITHUB_OUTPUT
163+ echo "Matrix configuration: $matrix_entries"
164+ else
165+ echo "translations-to-process=false" >> $GITHUB_OUTPUT
166+ echo "translation-matrix=[]" >> $GITHUB_OUTPUT
167+ echo "No missing translations found"
168+ fi
169+
170+ - name : Upload translation files as artifacts
171+ if : steps.prepare-translations.outputs.translations-to-process == 'true'
172+ uses : actions/upload-artifact@v4
173+ with :
174+ name : translation-files
175+ path : |
176+ missing_translations_*.txt
177+ retention-days : 1
178+
179+ # Matrix job to process translations in parallel for all languages and numbered files
180+ ai_translate :
181+ needs : extract_strings
182+ if : needs.extract_strings.outputs.translations-to-process == 'true'
183+ permissions :
184+ models : read # for AI inference
185+ runs-on : ubuntu-latest
186+ strategy :
187+ matrix :
188+ include : ${{ fromJson(needs.extract_strings.outputs.translation-matrix) }}
189+ fail-fast : false # Continue processing other languages even if one fails
190+ max-parallel : 5 # Limit concurrent AI requests
191+
192+ steps :
193+ - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
194+
195+ - name : Download translation files
196+ uses : actions/download-artifact@v4
197+ with :
198+ name : translation-files
199+
200+ - name : Create AI translation prompt
201+ run : |
202+ cat > "translate_${{ matrix.lang_code }}${{ matrix.suffix }}.prompt.yml" << 'EOF'
203+ messages:
204+ - role: system
205+ content: |
206+ You are a professional translator specializing in technical software localization for ArduPilot flight controller configuration software.
207+
208+ CRITICAL INSTRUCTIONS:
209+ 1. You will receive a list of English strings to translate in the format "line_number:English text"
210+ 2. You MUST preserve the exact line number and colon format: "line_number:Translated text"
211+ 3. Translate ONLY the text after the colon, keeping the line number and colon unchanged
212+ 4. Preserve all placeholders like {variable_name}, {0}, etc. exactly as they appear
213+ 5. Consider the technical aviation/drone context when translating
214+ 6. Use formal register appropriate for technical documentation
215+ 7. Maintain consistent terminology throughout
216+
217+ LANGUAGE-SPECIFIC GUIDELINES:
218+ - Portuguese (pt): Use European Portuguese (Portugal) conventions, "ficheiro" not "arquivo" for "file", "transferir" not "baixar" for "download"
219+ - German (de): Use formal "Sie" form, compound technical terms appropriately
220+ - Italian (it): Use formal register, preserve technical aviation terminology
221+ - Japanese (ja): Use polite form (です/ます), katakana for foreign technical terms
222+ - Chinese (zh_CN): Use simplified characters, technical aviation terminology in Chinese
223+
224+ EXAMPLE:
225+ Input: "123:Copy vehicle image from template"
226+ Output: "123:Copiar imagem do veiculo do modelo" (for Portuguese)
227+
228+ Translate all strings while preserving the exact format.
229+
230+ - role: user
231+ content: |
232+ Language: ${{ matrix.language }}
233+ Language code: ${{ matrix.lang_code }}
234+ File: ${{ matrix.file }}
235+
236+ Translate these strings from English to ${{ matrix.language }}:
237+
238+ model: openai/gpt-4o
239+ max_tokens: 4000
240+ EOF
241+
242+ # Append the actual translation content to the prompt
243+ cat "${{ matrix.file }}" >> "translate_${{ matrix.lang_code }}${{ matrix.suffix }}.prompt.yml"
244+
245+ - name : Run AI translation
246+ id : ai_translate
247+ uses : actions/ai-inference@v2
248+ with :
249+ prompt-file : ' translate_${{ matrix.lang_code }}${{ matrix.suffix }}.prompt.yml'
250+
251+ - name : Save translation result
252+ run : |
253+ # Save the AI response back to the original translation file
254+ if [ -n "${{ steps.ai_translate.outputs.response-file }}" ]; then
255+ cp "${{ steps.ai_translate.outputs.response-file }}" "${{ matrix.file }}"
256+ elif [ -n "${{ steps.ai_translate.outputs.response }}" ]; then
257+ echo "${{ steps.ai_translate.outputs.response }}" > "${{ matrix.file }}"
258+ else
259+ echo "⚠️ No AI response received for ${{ matrix.file }}"
260+ exit 1
261+ fi
262+
263+ echo "✅ AI translation completed for ${{ matrix.language }} (${{ matrix.file }})"
264+
265+ - name : Upload translated file
266+ uses : actions/upload-artifact@v4
267+ with :
268+ name : translated-${{ matrix.lang_code }}${{ matrix.suffix }}
269+ path : ${{ matrix.file }}
270+ retention-days : 1
271+
272+ # Job to collect all translations and create the final PR
273+ finalize_translations :
274+ needs : [extract_strings, ai_translate]
275+ if : needs.extract_strings.outputs.po-files-changed == 'true'
276+ permissions :
277+ contents : write # for creating branches and commits
278+ pull-requests : write # for creating PRs
279+ runs-on : ubuntu-latest
280+ env :
281+ PYGETTEXT_DOMAIN : ardupilot_methodic_configurator
282+ PYGETTEXT_LOCALEDIR : ardupilot_methodic_configurator/locale
283+
284+ steps :
285+ - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
286+
287+ - name : Set up Python
288+ uses : actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
289+ with :
290+ python-version : ' 3.x'
291+
292+ - name : Install apt gettext package
293+ run : |
294+ sudo apt-get update
295+ sudo apt-get install -y gettext=0.21-14ubuntu2
296+
297+ - name : Install python-gettext requirement
298+ continue-on-error : true
299+ run : |
300+ export PIP_VERSION=$(grep -oP 'pip\s*==\s*\K[0-9]+(\.[0-9]+)*' pyproject.toml || echo '')
301+ export PYTHON_GETTEXT_VERSION=$(grep -oP 'python-gettext\s*==\s*\K[0-9]+(\.[0-9]+)*' pyproject.toml || echo '')
302+
303+ if [ -z "$PIP_VERSION" ]; then
304+ echo "::warning::Could not detect pip version in pyproject.toml; falling back to latest."
305+ PIP_INSTALL="pip"
306+ else
307+ echo "Will install pip version $PIP_VERSION."
308+ PIP_INSTALL="pip==$PIP_VERSION"
309+ fi
310+
311+ if [ -z "$PYTHON_GETTEXT_VERSION" ]; then
312+ echo "::warning::Could not detect python-gettext version in pyproject.toml; falling back to 5.0."
313+ PYTHON_GETTEXT_INSTALL="python-gettext==5.0"
314+ else
315+ echo "Will install python-gettext version $PYTHON_GETTEXT_VERSION."
316+ PYTHON_GETTEXT_INSTALL="python-gettext==$PYTHON_GETTEXT_VERSION"
317+ fi
318+
319+ python -m pip install "$PIP_INSTALL" "$PYTHON_GETTEXT_INSTALL"
320+
321+ - name : Download all translated files
322+ if : needs.extract_strings.outputs.translations-to-process == 'true'
323+ uses : actions/download-artifact@v4
324+ with :
325+ pattern : translated-*
326+ merge-multiple : true
327+
328+ - name : Insert AI translations into .po files
329+ if : needs.extract_strings.outputs.translations-to-process == 'true'
330+ run : |
331+ # Check if we have any translated files
332+ if ls missing_translations_*.txt 1> /dev/null 2>&1; then
333+ echo "📥 Processing AI translations..."
334+ python insert_missing_translations.py
335+ echo "✅ AI translations inserted into .po files"
336+ else
337+ echo "ℹ️ No AI translations to process"
338+ fi
339+
340+ - name : Compile .mo files
341+ run : |
342+ python create_mo_files.py
343+ echo "✅ .mo files compiled successfully"
344+
345+ - name : Stage all changes
346+ run : |
347+ git add $PYGETTEXT_LOCALEDIR/$PYGETTEXT_DOMAIN.pot
348+ git add $PYGETTEXT_LOCALEDIR/**/$PYGETTEXT_DOMAIN.po
349+ git add $PYGETTEXT_LOCALEDIR/**/$PYGETTEXT_DOMAIN.mo
350+
107351 - name : Create Pull Request
108- if : env.PO_FILES_CHANGED == 'true'
109352 uses : peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
110353 with :
111354 labels : i18n, automated-pr
112355 token : ${{ secrets.GITHUB_TOKEN }}
113356 branch : merge-i18n-po-strings
114- title : " Merge new un-translated string(s) to existing .po files"
115- commit-message : " chore(translations): merge new un-translated string(s) to existing .po files"
357+ title : " Merge new un-translated string(s) to existing .po files with AI translations "
358+ commit-message : " chore(translations): merge new un-translated string(s) to existing .po files with AI translations "
116359 body : |
117360 Update .pot file with new un-translated string(s) from the source code
118361 Merge .pot file strings into existing .po files
362+
363+ 🤖 **AI-Powered Translation Applied with Matrix Processing**:
364+ - Automatically extracted missing translations using `extract_missing_translations.py`
365+ - Used GitHub Actions matrix strategy to process numbered files in parallel
366+ - Applied AI-powered translations using GitHub Models (GPT-4o) for multiple languages
367+ - Supported processing of >50 translations per language with numbered files
368+ - Inserted translated strings into .po files using `insert_missing_translations.py`
369+ - Compiled binary .mo files for immediate use
370+
371+ **Languages processed**: Portuguese (pt), German (de), Italian (it), Japanese (ja), Chinese Simplified (zh_CN)
372+
373+ **Matrix Processing**:
374+ - Parallel processing of translation files for better performance
375+ - Support for numbered files when >50 strings per language
376+ - Automatic handling of file chunks with proper naming
377+
378+ **Translation Guidelines Applied**:
379+ - Technical aviation/drone context preservation
380+ - Formal register for technical documentation
381+ - Language-specific conventions (e.g., European Portuguese, formal German)
382+ - Consistent terminology maintenance
383+ - Placeholder preservation ({variable_name} patterns)
384+
385+ Please review the AI-generated translations for accuracy and cultural appropriateness before merging.
119386 delete-branch : true
0 commit comments