Skip to content

Commit 05660ae

Browse files
committed
matrix
1 parent d717f30 commit 05660ae

3 files changed

Lines changed: 120 additions & 39 deletions

File tree

.github/workflows/auto-translate-language.yml

Lines changed: 91 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -30,67 +30,118 @@ on:
3030
required: true
3131
default: "all"
3232
max_entries:
33-
description: "Max unfinished entries to translate (0 = all)"
33+
description: "Max unfinished entries per language (0 = all)"
3434
required: true
3535
default: "0"
36+
max_parallel:
37+
description: "Maximum parallel language jobs"
38+
required: true
39+
default: "5"
3640

3741
jobs:
38-
auto-translate:
42+
prepare:
3943
runs-on: ubuntu-latest
40-
env:
41-
TARGET_LANGUAGE: ${{ github.event.inputs.target_language || 'all' }}
42-
MAX_ENTRIES: ${{ github.event.inputs.max_entries || '0' }}
44+
outputs:
45+
matrix: ${{ steps.matrix.outputs.matrix }}
46+
target_language: ${{ steps.vars.outputs.target_language }}
47+
max_entries: ${{ steps.vars.outputs.max_entries }}
48+
max_parallel: ${{ steps.vars.outputs.max_parallel }}
49+
steps:
50+
- name: Checkout repository
51+
uses: actions/checkout@v4
52+
with:
53+
fetch-depth: 1
54+
55+
- name: Resolve input defaults
56+
id: vars
57+
run: |
58+
TARGET_LANGUAGE="${{ github.event.inputs.target_language || 'all' }}"
59+
MAX_ENTRIES="${{ github.event.inputs.max_entries || '0' }}"
60+
MAX_PARALLEL="${{ github.event.inputs.max_parallel || '5' }}"
61+
echo "target_language=$TARGET_LANGUAGE" >> "$GITHUB_OUTPUT"
62+
echo "max_entries=$MAX_ENTRIES" >> "$GITHUB_OUTPUT"
63+
echo "max_parallel=$MAX_PARALLEL" >> "$GITHUB_OUTPUT"
4364
65+
- name: Build language matrix
66+
id: matrix
67+
run: |
68+
python3 - <<'PY'
69+
import glob, json, os, re, sys
70+
target = "${{ steps.vars.outputs.target_language }}"
71+
files = sorted(glob.glob("src/translations/qdomyos-zwift_*.ts"))
72+
langs = [re.sub(r"^qdomyos-zwift_|\.ts$", "", os.path.basename(f)) for f in files]
73+
if target != "all":
74+
if target not in langs:
75+
print(f"Unsupported language: {target}")
76+
sys.exit(1)
77+
langs = [target]
78+
print(f"Languages selected: {', '.join(langs)}")
79+
matrix = {"language": langs}
80+
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as out:
81+
out.write("matrix=" + json.dumps(matrix) + "\n")
82+
PY
83+
84+
translate:
85+
needs: prepare
86+
runs-on: ubuntu-latest
87+
strategy:
88+
fail-fast: false
89+
max-parallel: ${{ fromJson(needs.prepare.outputs.max_parallel) }}
90+
matrix: ${{ fromJson(needs.prepare.outputs.matrix) }}
4491
steps:
4592
- name: Checkout repository
4693
uses: actions/checkout@v4
4794
with:
48-
fetch-depth: 0
49-
token: ${{ secrets.GITHUB_TOKEN }}
95+
fetch-depth: 1
5096

5197
- name: Setup Python
5298
uses: actions/setup-python@v5
5399
with:
54100
python-version: "3.11"
55101

56-
- name: Resolve target TS file(s)
57-
id: resolve
102+
- name: Auto translate one language
58103
run: |
59-
if [ "${TARGET_LANGUAGE}" = "all" ]; then
60-
TS_FILES="$(ls src/translations/qdomyos-zwift_*.ts | tr '\n' ' ')"
61-
else
62-
TS_FILE="src/translations/qdomyos-zwift_${TARGET_LANGUAGE}.ts"
63-
if [ ! -f "$TS_FILE" ]; then
64-
echo "Unsupported language or missing file: ${TARGET_LANGUAGE}"
65-
exit 1
66-
fi
67-
TS_FILES="$TS_FILE"
68-
fi
104+
TS_FILE="src/translations/qdomyos-zwift_${{ matrix.language }}.ts"
105+
echo "Auto-translating ${TS_FILE}"
106+
python3 scripts/autotranslate-ts.py \
107+
--ts-file "${TS_FILE}" \
108+
--source-lang en \
109+
--target-lang "${{ matrix.language }}" \
110+
--max-entries "${{ needs.prepare.outputs.max_entries }}" \
111+
--progress-every 250 \
112+
--cache-file ".translation-cache/${{ matrix.language }}.json"
69113
70-
echo "ts_files=$TS_FILES" >> "$GITHUB_OUTPUT"
71-
echo "Target files: $TS_FILES"
114+
- name: Upload translated file
115+
uses: actions/upload-artifact@v4
116+
with:
117+
name: translated-${{ matrix.language }}
118+
path: src/translations/qdomyos-zwift_${{ matrix.language }}.ts
119+
if-no-files-found: error
72120

73-
- name: Run auto translation
74-
run: |
75-
for ts_file in ${{ steps.resolve.outputs.ts_files }}; do
76-
lang_code="$(basename "$ts_file" .ts | sed 's/^qdomyos-zwift_//')"
77-
echo "Auto-translating $ts_file (lang=$lang_code)"
78-
python3 scripts/autotranslate-ts.py \
79-
--ts-file "$ts_file" \
80-
--source-lang en \
81-
--target-lang "$lang_code" \
82-
--max-entries "${MAX_ENTRIES}" \
83-
--cache-file ".translation-cache/${lang_code}.json"
84-
done
121+
commit:
122+
needs: [prepare, translate]
123+
runs-on: ubuntu-latest
124+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
125+
steps:
126+
- name: Checkout repository
127+
uses: actions/checkout@v4
128+
with:
129+
fetch-depth: 0
130+
token: ${{ secrets.GITHUB_TOKEN }}
131+
132+
- name: Download translated files
133+
uses: actions/download-artifact@v4
134+
with:
135+
pattern: translated-*
136+
merge-multiple: true
137+
path: .
85138

86139
- name: Show translation progress
87140
run: |
88141
python3 - <<'PY'
89142
import re
90143
from pathlib import Path
91-
files = "${{ steps.resolve.outputs.ts_files }}".split()
92-
for f in files:
93-
p = Path(f)
144+
for p in sorted(Path("src/translations").glob("qdomyos-zwift_*.ts")):
94145
s = p.read_text(encoding="utf-8", errors="ignore")
95146
msg = len(re.findall(r"<message>", s))
96147
unf = len(re.findall(r'type="unfinished"', s))
@@ -108,5 +159,7 @@ jobs:
108159
git config --local user.name "github-actions[bot]"
109160
110161
git add src/translations/qdomyos-zwift_*.ts
111-
git commit -m "i18n: auto-translate ${TARGET_LANGUAGE} (machine draft)"
112-
git push origin HEAD:refs/heads/${GITHUB_REF_NAME}
162+
git commit -m "i18n: auto-translate ${{ needs.prepare.outputs.target_language }} (machine draft)"
163+
164+
TARGET_BRANCH="${GITHUB_HEAD_REF:-$GITHUB_REF_NAME}"
165+
git push origin HEAD:refs/heads/${TARGET_BRANCH}

scripts/autotranslate-ts.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def main() -> int:
7171
parser.add_argument("--sleep", type=float, default=0.25, help="Sleep between API calls")
7272
parser.add_argument("--max-entries", type=int, default=0, help="0 = no limit")
7373
parser.add_argument("--cache-file", default="", help="Optional cache json path")
74+
parser.add_argument("--progress-every", type=int, default=200, help="Print progress every N processed entries")
7475
args = parser.parse_args()
7576

7677
ts_path = Path(args.ts_file)
@@ -92,6 +93,7 @@ def main() -> int:
9293
translated = 0
9394
skipped = 0
9495
failed = 0
96+
processed = 0
9597

9698
for msg in root.findall(".//message"):
9799
src_el = msg.find("source")
@@ -104,8 +106,15 @@ def main() -> int:
104106
continue
105107

106108
source = src_el.text or ""
109+
processed += 1
107110
if should_skip_source(source):
108111
skipped += 1
112+
if args.progress_every > 0 and processed % args.progress_every == 0:
113+
print(
114+
f"progress file={ts_path.name} processed={processed} translated={translated} "
115+
f"skipped={skipped} failed={failed}",
116+
flush=True,
117+
)
109118
continue
110119

111120
if args.max_entries and translated >= args.max_entries:
@@ -123,16 +132,34 @@ def main() -> int:
123132
time.sleep(args.sleep)
124133
except Exception:
125134
failed += 1
135+
if args.progress_every > 0 and processed % args.progress_every == 0:
136+
print(
137+
f"progress file={ts_path.name} processed={processed} translated={translated} "
138+
f"skipped={skipped} failed={failed}",
139+
flush=True,
140+
)
126141
continue
127142

128143
out = restore_placeholders(out, placeholders).strip()
129144
if not out or out == source:
130145
failed += 1
146+
if args.progress_every > 0 and processed % args.progress_every == 0:
147+
print(
148+
f"progress file={ts_path.name} processed={processed} translated={translated} "
149+
f"skipped={skipped} failed={failed}",
150+
flush=True,
151+
)
131152
continue
132153

133154
tr_el.text = out
134155
tr_el.attrib.pop("type", None)
135156
translated += 1
157+
if args.progress_every > 0 and processed % args.progress_every == 0:
158+
print(
159+
f"progress file={ts_path.name} processed={processed} translated={translated} "
160+
f"skipped={skipped} failed={failed}",
161+
flush=True,
162+
)
136163

137164
tree.write(ts_path, encoding="utf-8", xml_declaration=True)
138165

src/translations/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,9 @@ You can generate a first machine-translated draft (to be human-reviewed) from Gi
143143
2. Click `Run workflow`
144144
3. Choose `target_language`: any available locale code (e.g. `it`, `fr`, `de`, `es`) or `all`
145145
4. Set `max_entries` to `0` to process all unfinished strings
146+
5. Optional: tune `max_parallel` to control how many languages run at the same time
146147

147-
The workflow updates the selected `.ts` file(s) and commits the result to the current branch.
148+
The workflow runs one job per language (matrix), then commits updated `.ts` files to the current branch.
148149

149150
## Common Terms Glossary
150151

0 commit comments

Comments
 (0)