Skip to content

Commit 1f7183e

Browse files
committed
Better script for checking translations
Don't just run msgfmt to detect errors, print the affected entries too. This makes it much easier to find and correct them at Crowdin.
1 parent 4e72c8d commit 1f7183e

File tree

4 files changed

+106
-21
lines changed

4 files changed

+106
-21
lines changed

.github/workflows/check-translations.yml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@ jobs:
1919
- name: Install GNU gettext
2020
run: sudo apt-get install gettext
2121

22-
- name: Check PO files with msgfmt -v -c
23-
run: |
24-
for i in locales/*.po ; do
25-
echo "checking $i..."
26-
msgfmt -v -c -o /dev/null $i
27-
done
22+
- name: Install uv
23+
uses: astral-sh/setup-uv@v5
24+
25+
- name: Check translations
26+
run: uv run scripts/check-translations.py --github

scripts/check-translations.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#!/usr/bin/env python3
2+
# /// script
3+
# requires-python = ">=3.13"
4+
# dependencies = [
5+
# "polib",
6+
# "rich",
7+
# ]
8+
# ///
9+
10+
"""
11+
Checks correctness of translation PO files.
12+
"""
13+
14+
import argparse
15+
import polib
16+
import subprocess
17+
import sys
18+
import glob
19+
from rich import print
20+
21+
22+
23+
def _matching_po_entry(po, lineno):
24+
"""
25+
Find the PO entry that matches the given line number.
26+
Note that entries are multiline and lineno may be in the middle of an entry.
27+
"""
28+
last = None
29+
for entry in po:
30+
if entry.linenum > lineno:
31+
break
32+
last = entry
33+
return last
34+
35+
36+
def _print_error(fn, entry, lineno, text, github):
37+
"""
38+
Print the error message for the given entry.
39+
"""
40+
if github:
41+
if entry:
42+
details = text + '\n' + str(entry)
43+
details = details.replace('\n', '%0A')
44+
print(f'::error title="{fn}:{lineno}:{text}"::{details}')
45+
else:
46+
print(f'::error title="error in {fn}"::{lineno}:{text}')
47+
else:
48+
print(f'[red]{fn}:{lineno}:{text}[/red]')
49+
if entry:
50+
print(entry)
51+
52+
53+
def process_po(filename, stderr, github):
54+
"""
55+
Parse and pretty-print errors in the file.
56+
The error format is: <file>:<line>: <error>
57+
"""
58+
po = polib.pofile(filename)
59+
# dict indexed with POEntry and containing all error messages for it
60+
errors = []
61+
62+
for line in stderr.split('\n'):
63+
if not line:
64+
continue
65+
parts = line.split(':')
66+
fn = parts[0]
67+
if fn != filename:
68+
continue
69+
lineno = int(parts[1])
70+
text = ':'.join(parts[2:])
71+
entry = _matching_po_entry(po, lineno)
72+
_print_error(filename, entry, lineno, text, github)
73+
if entry:
74+
errors.append(entry)
75+
76+
77+
def check_translations(po_files, github=False):
78+
status = True
79+
for po_file in po_files:
80+
result = subprocess.run(['msgfmt', '-v', '-c', '-o', '/dev/null', po_file], capture_output=True, text=True)
81+
if result.returncode != 0:
82+
status = False
83+
process_po(po_file, result.stderr, github)
84+
return status
85+
86+
87+
88+
def main():
89+
parser = argparse.ArgumentParser(description='Check correctness of translation PO files.')
90+
parser.add_argument('--github', action='store_true', help='Format output for GitHub Actions')
91+
args = parser.parse_args()
92+
93+
po_files = glob.glob('locales/*.po')
94+
status = check_translations(po_files, args.github)
95+
sys.exit(0 if status else 1)
96+
97+
98+
if __name__ == "__main__":
99+
main()

scripts/travis-check-translations.sh

Lines changed: 0 additions & 10 deletions
This file was deleted.

scripts/update-translations-from-crowdin.sh

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,5 @@ scripts/do-update-translations-lists.sh
119119

120120
git status
121121
echo ""
122-
exit_code=0
123-
for i in locales/*.po ; do
124-
msgfmt -c -o /dev/null $i || exit_code=$?
125-
done
126-
exit $exit_code
122+
123+
uv run scripts/check-translations.sh

0 commit comments

Comments
 (0)