Skip to content

Commit 9e7aace

Browse files
author
Radjammin@gmail.com
committed
python coding style checker changes
1 parent 9cdeff0 commit 9e7aace

1 file changed

Lines changed: 61 additions & 110 deletions

File tree

tools/check-translations.py

Lines changed: 61 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@
3333
3434
This tool validates Qt `.ts` translation files according to Qt Linguist
3535
semantics.
36-
Warnings are reported with best-effort line numbers. In strict mode, the
37-
presence of any warning results in a non-zero exit code to allow CI failure.
3836
"""
3937

4038
import argparse
@@ -51,20 +49,14 @@
5149
HTML_TAG_RE = re.compile(r"<[^>]+>")
5250

5351
# ANSI escape codes
54-
BOLD = "\033[1m"
55-
CYAN = "\033[36m"
56-
YELLOW = "\033[33m"
57-
RED = "\033[31m"
58-
RESET = "\033[0m"
52+
BOLD, CYAN, YELLOW, RED, RESET = "\033[1m", "\033[36m", "\033[33m", "\033[31m", "\033[0m"
5953

6054

61-
# Severity Enum
6255
class Severity(IntEnum):
6356
WARNING = 1
6457
SEVERE = 2
6558

6659

67-
# Data structures
6860
@dataclass(frozen=True)
6961
class MessageContext:
7062
ts_file: Path
@@ -85,7 +77,6 @@ class WarningItem:
8577
severity: Severity
8678

8779

88-
# Helpers
8980
def approximate_message_lines(text: str):
9081
"""Yield approximate line numbers for <message> elements."""
9182
lines = text.splitlines()
@@ -103,31 +94,26 @@ def approximate_message_lines(text: str):
10394
def check_language_header(ts_file: Path, root, file_lang: str):
10495
header_lang = root.attrib.get("language", "")
10596
if header_lang != file_lang:
106-
return [WarningItem(ts_file, 0, file_lang,
107-
f"Language header mismatch '{header_lang}' != '{file_lang}'",
108-
Severity.WARNING)]
97+
msg = f"Language header mismatch '{header_lang}' != '{file_lang}'"
98+
return [WarningItem(ts_file, 0, file_lang, msg, Severity.WARNING)]
10999
return []
110100

111101

112102
def check_empty_translation(ctx: MessageContext):
113103
if not ctx.translation.strip() and ctx.tr_type != "unfinished":
114-
return [WarningItem(ctx.ts_file, ctx.line, ctx.lang,
115-
f"Empty translation for '{ctx.excerpt}'",
116-
Severity.SEVERE)]
104+
msg = f"Empty translation for '{ctx.excerpt}'"
105+
return [WarningItem(ctx.ts_file, ctx.line, ctx.lang, msg, Severity.SEVERE)]
117106
return []
118107

119108

120109
def check_placeholders(ctx: MessageContext):
121110
if ctx.tr_type == "unfinished":
122111
return []
123-
124-
src_counts = Counter(PLACEHOLDER_RE.findall(ctx.source))
125-
tr_counts = Counter(PLACEHOLDER_RE.findall(ctx.translation))
126-
127-
if src_counts != tr_counts:
112+
src_cnt = Counter(PLACEHOLDER_RE.findall(ctx.source))
113+
tr_cnt = Counter(PLACEHOLDER_RE.findall(ctx.translation))
114+
if src_cnt != tr_cnt:
128115
msg = (f"Placeholder mismatch for '{ctx.excerpt}'\n"
129-
f"Source: {ctx.source}\n"
130-
f"Trans: {ctx.translation}")
116+
f"Source: {ctx.source}\nTrans: {ctx.translation}")
131117
return [WarningItem(ctx.ts_file, ctx.line, ctx.lang, msg, Severity.WARNING)]
132118
return []
133119

@@ -136,149 +122,114 @@ def check_html(ctx: MessageContext):
136122
if (HTML_TAG_RE.search(ctx.source) and not HTML_TAG_RE.search(ctx.translation)
137123
and ctx.tr_type != "unfinished"):
138124
msg = (f"HTML missing for '{ctx.excerpt}'\n"
139-
f"Source: {ctx.source}\n"
140-
f"Trans: {ctx.translation}")
125+
f"Source: {ctx.source}\nTrans: {ctx.translation}")
141126
return [WarningItem(ctx.ts_file, ctx.line, ctx.lang, msg, Severity.WARNING)]
142127
return []
143128

144129

145130
def check_whitespace(ctx: MessageContext):
146131
if not ctx.translation or ctx.tr_type == "unfinished":
147132
return []
148-
149133
src_lead = ctx.source != ctx.source.lstrip()
150134
src_trail = ctx.source != ctx.source.rstrip()
151135
tr_lead = ctx.translation != ctx.translation.lstrip()
152136
tr_trail = ctx.translation != ctx.translation.rstrip()
153-
154137
if src_lead != tr_lead or src_trail != tr_trail:
155-
return [WarningItem(ctx.ts_file, ctx.line, ctx.lang,
156-
f"Leading/trailing whitespace mismatch for '{ctx.excerpt}'",
157-
Severity.WARNING)]
138+
msg = f"Leading/trailing whitespace mismatch for '{ctx.excerpt}'"
139+
return [WarningItem(ctx.ts_file, ctx.line, ctx.lang, msg, Severity.WARNING)]
158140
return []
159141

160142

161143
def check_newline_consistency(ctx: MessageContext):
162144
if ctx.source.endswith("\n") != ctx.translation.endswith("\n"):
163-
return [WarningItem(ctx.ts_file, ctx.line, ctx.lang,
164-
f"Newline mismatch for '{ctx.excerpt}'",
165-
Severity.WARNING)]
145+
msg = f"Newline mismatch for '{ctx.excerpt}'"
146+
return [WarningItem(ctx.ts_file, ctx.line, ctx.lang, msg, Severity.WARNING)]
166147
return []
167148

168149

169150
def _extract_message_data(message):
170-
"""Helper to extract translation data from a message element."""
171-
source_elem = message.find("source")
172-
source = "".join(source_elem.itertext()) if source_elem is not None else ""
173-
151+
src_node = message.find("source")
152+
source = "".join(src_node.itertext()) if src_node is not None else ""
174153
tr_elem = message.find("translation")
175-
tr_type = ""
176-
translation = ""
154+
tr_type, translation = "", ""
177155
if tr_elem is not None:
178156
tr_type = tr_elem.attrib.get("type", "")
179-
numerus_forms = tr_elem.findall("numerusform")
180-
if numerus_forms:
181-
translation = " ".join("".join(n.itertext()) for n in numerus_forms)
157+
forms = tr_elem.findall("numerusform")
158+
if forms:
159+
translation = " ".join("".join(n.itertext()) for n in forms)
182160
else:
183161
translation = "".join(tr_elem.itertext())
184-
185162
return source, translation, tr_type
186163

187164

188-
# Detect warnings
165+
def _process_context(ts_file, file_lang, context, line_gen):
166+
warnings = []
167+
for message in context.findall("message"):
168+
line = next(line_gen, 0)
169+
src, trans, tr_type = _extract_message_data(message)
170+
clean = src.strip().replace("\n", " ")
171+
excerpt = clean[:30] + ("..." if len(clean) > 30 else "")
172+
ctx = MessageContext(ts_file, line, file_lang, src, trans, tr_type, excerpt)
173+
for check in [check_empty_translation, check_placeholders, check_html,
174+
check_whitespace, check_newline_consistency]:
175+
warnings.extend(check(ctx))
176+
return warnings
177+
178+
189179
def detect_warnings(ts_file: Path, file_lang: str):
190180
try:
191181
text = ts_file.read_text(encoding="utf-8")
192182
root = ET.fromstring(text)
193183
except (OSError, ET.ParseError) as exc:
194-
return [WarningItem(ts_file, 0, file_lang,
195-
f"Error reading or parsing XML: {exc}",
196-
Severity.SEVERE)]
197-
198-
warnings = []
199-
warnings.extend(check_language_header(ts_file, root, file_lang))
200-
message_lines = approximate_message_lines(text)
184+
return [WarningItem(ts_file, 0, file_lang, f"Error parsing XML: {exc}", Severity.SEVERE)]
201185

186+
warnings = check_language_header(ts_file, root, file_lang)
187+
line_gen = approximate_message_lines(text)
202188
for context in root.findall("context"):
203-
for message, line in zip(context.findall("message"), message_lines):
204-
source, translation, tr_type = _extract_message_data(message)
205-
206-
source_clean = source.strip().replace("\n", " ")
207-
excerpt = source_clean[:30] + ("..." if len(source_clean) > 30 else "")
208-
209-
ctx = MessageContext(ts_file, line, file_lang, source, translation, tr_type, excerpt)
189+
warnings.extend(_process_context(ts_file, file_lang, context, line_gen))
190+
return warnings
210191

211-
# All checks
212-
for check in [check_empty_translation, check_placeholders, check_html,
213-
check_whitespace, check_newline_consistency]:
214-
warnings.extend(check(ctx))
215192

216-
return warnings
193+
def _print_results(grouped):
194+
for file in sorted(grouped.keys()):
195+
print(f"\n{BOLD}File: {file.name}{RESET}")
196+
for w in sorted(grouped[file], key=lambda x: x.line):
197+
color, sev = (RED, "SEVERE ") if w.severity == Severity.SEVERE else (YELLOW, "WARNING")
198+
lines = w.message.split("\n")
199+
print(f" {CYAN}Line {w.line:<4}{RESET} | {color}{sev}{RESET} | {lines[0]}")
200+
for extra in lines[1:]:
201+
print(f" | | {extra}")
217202

218203

219-
# CLI
220204
def main():
221-
parser = argparse.ArgumentParser(description="Qt TS translation checker")
222-
parser.add_argument("--ts-dir", type=Path, default=Path("../src/translation"),
223-
help="Directory containing translation_*.ts files")
224-
parser.add_argument("--strict", action="store_true",
225-
help="Exit non-zero if any warning is found")
205+
parser = argparse.ArgumentParser()
206+
parser.add_argument("--ts-dir", type=Path, default=Path("../src/translation"))
207+
parser.add_argument("--strict", action="store_true")
226208
args = parser.parse_args()
227209

228-
if not args.ts_dir.exists():
229-
print(f"Directory not found: {args.ts_dir}", file=sys.stderr)
230-
return 2
231-
232210
ts_files = sorted(args.ts_dir.glob("translation_*.ts"))
233211
if not ts_files:
234-
print(f"No TS files found in {args.ts_dir}", file=sys.stderr)
235212
return 2
236213

237-
all_warnings = []
238-
failures_by_language = defaultdict(lambda: {"severe": 0, "warning": 0})
214+
all_warnings, stats = [], defaultdict(lambda: {"severe": 0, "warning": 0})
215+
for f in ts_files:
216+
all_warnings.extend(detect_warnings(f, f.stem.replace("translation_", "")))
239217

240-
for ts_file in ts_files:
241-
lang = ts_file.stem.replace("translation_", "")
242-
all_warnings.extend(detect_warnings(ts_file, lang))
243-
244-
# Group output by file
245218
grouped = defaultdict(list)
246219
for w in all_warnings:
247220
grouped[w.ts_file].append(w)
221+
stats[w.lang]["severe" if w.severity == Severity.SEVERE else "warning"] += 1
248222

249-
if w.severity == Severity.SEVERE:
250-
failures_by_language[w.lang]["severe"] += 1
251-
else:
252-
failures_by_language[w.lang]["warning"] += 1
253-
254-
# Detailed clean column output
255-
for file in sorted(grouped.keys()):
256-
messages = grouped[file]
257-
print(f"\n{BOLD}File: {file.name}{RESET}")
258-
259-
for w in sorted(messages, key=lambda x: x.line):
260-
color = RED if w.severity == Severity.SEVERE else YELLOW
261-
sev_text = "SEVERE " if w.severity == Severity.SEVERE else "WARNING"
223+
_print_results(grouped)
262224

263-
msg_lines = w.message.split("\n")
264-
print(f" {CYAN}Line {w.line:<4}{RESET} | {color}{sev_text}{RESET} | {msg_lines[0]}")
265-
266-
for extra_line in msg_lines[1:]:
267-
print(f" | | {extra_line}")
268-
269-
# Test summary
270225
print("\n== Test Summary ==")
271-
for lang in sorted(failures_by_language.keys()):
272-
counts = failures_by_language[lang]
273-
print(f"{BOLD}[{lang}]{RESET} Severe: {counts['severe']}, Warnings: {counts['warning']}")
226+
for lang in sorted(stats.keys()):
227+
print(f"{BOLD}[{lang}]{RESET} Severe: {stats[lang]['severe']}, "
228+
f"Warnings: {stats[lang]['warning']}")
274229

275-
total_severe = sum(f["severe"] for f in failures_by_language.values())
276-
total_warning = sum(f["warning"] for f in failures_by_language.values())
277-
print(f"\nTotal Severe: {total_severe}, Total Warnings: {total_warning}")
278-
279-
if total_severe > 0 or (args.strict and total_warning > 0):
230+
if sum(s["severe"] for s in stats.values()) > 0 or (
231+
args.strict and sum(s["warning"] for s in stats.values()) > 0):
280232
return 1
281-
282233
return 0
283234

284235

0 commit comments

Comments
 (0)