Skip to content

Commit 53dd79c

Browse files
authored
[CI] Soft fail if translate raise error (vllm-project#11625)
Do not always set to failure when running translate job. Just return the right one instead. - vLLM version: v0.23.0 - vLLM main: vllm-project/vllm@1f486d9 Signed-off-by: wangxiyuan <wangxiyuan1007@gmail.com>
1 parent e475908 commit 53dd79c

3 files changed

Lines changed: 108 additions & 49 deletions

File tree

.github/workflows/schedule_doc_translate.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ jobs:
110110
- name: Validate translation coverage
111111
if: steps.translate.outcome == 'success'
112112
id: validate
113+
continue-on-error: true
113114
run: |
114115
python .github/workflows/scripts/po_translate.py \
115116
--files "${{ steps.detect.outputs.files }}" \

.github/workflows/scripts/detect_po_changes.py

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,11 @@
4848
DETAILS_RE = __import__("re").compile(r'^\?\?\?(\s+"[^"]*")?\s*$')
4949
TAB_RE = __import__("re").compile(r'^===\s+"[^"]*"\s*$')
5050

51-
# Characters that indicate a line is purely structural (not translatable):
52-
# - Markdown headings and list markers
53-
# - Table separators
54-
# - HTML tags
55-
# - Blank lines (handled separately)
56-
NO_CN_CHARS = set(".,:;!?-_*+~|/<>[](){}#@$%^&= \t\r\n0123456789")
51+
# Table rows that are purely structural or data-only (no natural language
52+
# prose). Matches rows that look like markdown table rows where every
53+
# cell is either a number, a date, a link, a single word, a checkmark,
54+
# or a short identifier.
55+
TABLE_ROW_RE = __import__("re").compile(r"^\|.*\|$")
5756

5857

5958
def _is_translatable_paragraph(paragraph: str) -> bool:
@@ -71,6 +70,13 @@ def _is_translatable_paragraph(paragraph: str) -> bool:
7170
if ADMONITION_RE.match(text) and '"' not in text:
7271
return False
7372

73+
# Skip pure table data rows (contributor tables, feature matrices,
74+
# supported models, etc.) that contain structured data but no
75+
# natural language sentences. Only skip rows that are clearly
76+
# data-only: every cell is a token, number, date, link, or emoji.
77+
if TABLE_ROW_RE.match(text) and _is_table_data_row(text):
78+
return False
79+
7480
# Count characters that typically appear in natural language.
7581
alpha = sum(1 for c in text if c.isalpha())
7682
if alpha == 0:
@@ -92,6 +98,40 @@ def _is_translatable_paragraph(paragraph: str) -> bool:
9298
return not (unique <= {"-", "*", "=", "_", "~", "|", ":", "+"})
9399

94100

101+
def _is_table_data_row(row: str) -> bool:
102+
"""Return True if *row* is a markdown table row with structured data only.
103+
104+
A row is considered data-only when it has at least one cell that
105+
looks like structured data (number, date, URL, checkmark, etc.).
106+
Pure-text header rows like "| Feature | Support | Note |" are
107+
left for translation.
108+
"""
109+
cells = [c.strip() for c in row.split("|")[1:-1]]
110+
if not cells:
111+
return False
112+
113+
has_data_cell = False
114+
for cell in cells:
115+
cell = cell.strip()
116+
if not cell:
117+
continue
118+
if cell in {"✅", "❌", "❔", "✔", "✘", "🟠", "🔵", "—"}:
119+
has_data_cell = True
120+
continue
121+
if __import__("re").match(r"^[\d\s\.\/\-,:]+$", cell):
122+
has_data_cell = True
123+
continue
124+
if __import__("re").match(r"^\[.*\]\(.*\)$", cell):
125+
has_data_cell = True
126+
continue
127+
# Pure-text cell with a sentence (period/question/exclamation)
128+
# means this is a prose row, not a data row.
129+
if __import__("re").search(r"[.!?]", cell):
130+
return False
131+
132+
return has_data_cell
133+
134+
95135
def _extract_paragraphs(content: str) -> list[str]:
96136
"""Extract translatable paragraphs from a markdown source.
97137

.github/workflows/scripts/po_translate.py

Lines changed: 61 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,12 @@
5050
--- OUTPUT FORMAT ---
5151
1. Return ONLY the same list of msgid/msgstr pairs with msgstr filled in.
5252
No markdown code fences (```), no explanations, no summaries, no greetings.
53-
2. Every msgid from the input MUST appear exactly once in the output with its
54-
corresponding msgstr filled in. Do not drop, merge, split, or reorder entries.
55-
3. Keep msgid lines COMPLETELY UNCHANGED — never modify source text.
53+
2. The number of entries in your output MUST EQUAL the number in the input.
54+
Count them: if you received N entries, you must return exactly N entries.
55+
Do NOT drop, merge, split, reorder, or add entries under any circumstance.
56+
3. Keep msgid lines COMPLETELY UNCHANGED — copy them exactly as-is from input.
5657
57-
--- WHAT TO PRESERVE ---
58+
--- WHAT TO PRESERVE (keep EXACTLY as in msgid) ---
5859
4. All format specifiers: %s, %d, %f, {{}}, {{{{}}}}, {{name}}, etc.
5960
5. All markdown syntax: **bold**, *italic*, `inline code`, ```code blocks```,
6061
[links](urls), ![images](urls), # headings, - lists, 1. ordered lists,
@@ -64,41 +65,49 @@
6465
8. Proper nouns: person names, contributor names, author names, company names,
6566
product names (vLLM, Ascend, CANN, Huawei, etc.).
6667
68+
--- CONTENT THAT SHOULD NOT BE TRANSLATED ---
69+
9. DO NOT translate contributor names, GitHub usernames, or dates.
70+
These should be copied verbatim from msgid to msgstr.
71+
Example: "Xiyuan Wang [@wangxiyuan] 2025-01-15" → keep as-is.
72+
73+
10. DO NOT translate table headers or table separator rows that are purely
74+
structural. Copy them verbatim.
75+
Example: "| Name | GitHub ID | Date |" → keep as-is.
76+
Example: "|:-----------:|:-----:|:-----:|" → keep as-is.
77+
78+
11. DO NOT translate code identifiers, variable names, CLI flags, or shell
79+
commands. Copy them verbatim.
80+
Example: "--data-parallel-size" → keep as-is.
81+
Example: "vllm serve /path/to/model" → keep as-is.
82+
83+
12. DO NOT translate URLs, email addresses, or paths.
84+
Copy them verbatim from msgid to msgstr.
85+
6786
--- MkDocs MATERIAL EXTENSIONS ---
68-
These are special MkDocs syntax elements. Keep the KEYWORDS and STRUCTURE
69-
exactly as-is; only translate the human-readable TEXT parts.
70-
71-
9. ADMONITIONS: Lines starting with "!!! type" or "!!! type \"title\"".
72-
The type keyword (note, warning, tip, danger, etc.) and the "!!!" marker
73-
MUST stay in English.
74-
Examples:
75-
msgid "!!! note" → msgstr "!!! note" (no translatable text)
76-
msgid "!!! warning" → msgstr "!!! warning"
77-
msgid "!!! note \"Important\"" → msgstr "!!! note \"重要\""
78-
79-
10. COLLAPSIBLE BLOCKS: Lines starting with "??? \"title\"".
80-
Keep "???" and the quote syntax; translate only the title text inside quotes.
81-
Example:
82-
msgid "??? \"Click here to see 'Build from Dockerfile'\"" → msgstr "??? \"点击这里查看'从Dockerfile构建'\""
83-
84-
11. CONTENT TABS: Lines starting with "=== \"label\"".
85-
Keep "===" and the quote syntax; translate only the label text.
86-
Example:
87-
msgid "=== \"Before using pip\"" → msgstr "=== \"使用pip之前\""
87+
13. ADMONITIONS (!!! type): Keep "!!!" and type keyword (note, warning, tip)
88+
in English. Only translate the title text after type.
89+
Example: msgid "!!! note" → msgstr "!!! note"
90+
Example: msgid "!!! note \"Important\"" → msgstr "!!! note \"重要\""
91+
92+
14. COLLAPSIBLE BLOCKS (???): Keep "???" and quote syntax. Translate only
93+
the title text inside quotes.
94+
Example: msgid "??? \"Click here...\"" → msgstr "??? \"点击这里...\""
95+
96+
15. CONTENT TABS (===): Keep "===" and quote syntax. Translate only the label.
97+
Example: msgid "=== \"Before using pip\"" → msgstr "=== \"使用pip之前\""
8898
8999
--- TRANSLATION QUALITY ---
90-
12. Use natural, fluent Chinese technical documentation style. Avoid word-by-word
91-
literal translation. Restructure long English sentences into natural Chinese
92-
sentence flow.
93-
13. Use standard Chinese technical terminology consistently.
94-
14. For markdown links [text](url): translate the display text in [] but keep the
95-
URL in () exactly as-is. Example: [Quick Start](quick_start.md) → [快速开始](quick_start.md)
96-
15. For headings (# Title): translate the heading text.
97-
16. DO NOT add "#, fuzzy" markers.
98-
17. If a msgid is purely structural (symbols, code, file paths only), copy it
99-
verbatim to msgstr — do not attempt to translate.
100-
18. Never invent or guess content. If genuinely unsure about a term, leave it in
101-
English rather than creating a wrong translation.
100+
16. Use natural, fluent Chinese technical documentation style. Avoid word-by-word
101+
literal translation.
102+
17. Use standard Chinese technical terminology consistently.
103+
18. For markdown links [text](url): translate the display text in [] but keep
104+
the URL in () exactly as-is.
105+
Example: [Quick Start](quick_start.md) → [快速开始](quick_start.md)
106+
19. For headings (# Title): translate the heading text.
107+
20. DO NOT add "#, fuzzy" markers.
108+
21. If a msgid is purely structural (symbols, code, file paths only), copy it
109+
verbatim to msgstr.
110+
22. Never invent or guess content. If unsure about a term, leave it in English.
102111
103112
{content}"""
104113

@@ -166,13 +175,17 @@ async def translate_file(self, po_path: str) -> bool:
166175
return False
167176

168177
# Parse the translated snippet and merge back.
169-
if not self._merge_translations(po, untranslated, translated_snippet):
178+
merged = self._merge_translations(po, untranslated, translated_snippet)
179+
if merged == 0:
170180
shutil.copy2(backup, po_path)
171181
print("FAILED (merge)")
172182
return False
173183

174184
po.save(str(path))
175-
print("OK")
185+
if merged < len(untranslated):
186+
print(f"OK ({merged}/{len(untranslated)} merged)")
187+
else:
188+
print("OK")
176189
return True
177190
except Exception as e:
178191
print(f"ERROR: {e}")
@@ -252,26 +265,31 @@ async def do_chunk(idx: int) -> tuple[int, str | None, str | None]:
252265
return translated
253266

254267
@staticmethod
255-
def _merge_translations(po, untranslated: list[POEntry], translated_snippet: str) -> bool:
256-
"""Parse translated snippet and merge msgstr values back into *po*."""
268+
def _merge_translations(po, untranslated: list[POEntry], translated_snippet: str) -> int:
269+
"""Parse translated snippet and merge msgstr values back into *po*.
270+
271+
Returns the number of entries that were successfully merged.
272+
If zero entries could be merged, the translation is considered failed.
273+
"""
257274
try:
258275
translated_po = pofile(translated_snippet)
259276
except Exception:
260-
return False
277+
return 0
261278

262279
translated_map: dict[str, str] = {}
263280
for entry in translated_po:
264281
if entry.msgid and entry.msgstr:
265282
translated_map[entry.msgid] = entry.msgstr
266283

284+
merged = 0
267285
for entry in untranslated:
268286
if entry.msgid in translated_map:
269287
entry.msgstr = translated_map[entry.msgid]
288+
merged += 1
270289
else:
271290
print(f"\n Missing translation for: {entry.msgid[:60]}...")
272-
return False
273291

274-
return True
292+
return merged
275293

276294
@staticmethod
277295
def _clean_response(response: str) -> str:

0 commit comments

Comments
 (0)