Merge branch 'stable' into devel
[tails.git] / bin / check-po-msgfmt
blob076642a06243c3f4f8af47d52afffb698bb38481
1 #!/usr/bin/python3
3 # Iterate through all .po files, run msgfmt for each of them, and output any
4 # errors together with their context.
6 # This script can also be used to "sanitize" .po files to avoid Ikiwiki build
7 # failures. In that case, problematic translations are removed from the .po
8 # file and replaced by an empty string. The effect is that the string in the
9 # built website will not be translated.
11 import argparse
12 import glob
13 import re
14 import subprocess
15 import sys
18 WARNINGS = [
19     re.compile(r'^[^\s]+\.po:[0-9]: warning:'),
20     re.compile(r'^msgfmt: [^\s]+\.po: warning:'),
21     re.compile(r'^\s*warning:'),
24 ERRORS = re.compile(r'^([^\s]+\.po):([0-9]+): ')
27 def find_context_start(msgstr_line, content):
28     """
29     Find the line number of the `msgid` corresponding to the given `msgstr`.
30     """
31     start = msgstr_line - 1
32     while not content[start].startswith('msgid '):
33         start -= 1
34     return start
37 def find_context_end(msgstr_line, content):
38     """
39     Find the line number corresponding to the end of the given `msgstr`.
40     """
41     end = msgstr_line
42     while not end == len(content) and content[end] != '\n':
43         end += 1
44     return end
47 def print_error_context(file, msgstr_line):
48     """
49     Print the full msgid and msgstr surrounding the `msgstr_line` in `file`.
50     """
52     with open(file) as f:
53         content = f.readlines()
55     start = find_context_start(msgstr_line, content)
56     end = find_context_end(msgstr_line, content)
58     for line in range(start, end):
59         print(content[line].strip())
62 def delete_msgstr(file, msgstr_line):
63     """
64     Delete the translation starting on `msgstr_line` of the file `file`.
65     """
67     with open(file) as f:
68         content = f.readlines()
70     end = find_context_end(msgstr_line, content)
72     content[msgstr_line-1] = 'msgstr ""\n'
73     content = content[:msgstr_line] + content[end:]
75     with open(file, 'w') as f:
76         f.writelines(content)
79 def check_po_msgfmt(sanitize=False):
80     """
81     Run `msgfmt` for all .po files in the current directory and print any
82     errors found. If `sanitize` is `True`, also delete problematic
83     translations from corresponding .po files.
84     """
86     errors = False
88     for f in glob.glob('**/*.po', recursive=True):
89         proc = subprocess.Popen(['msgfmt', '-c', '-o', '/dev/null', f],
90                                 stderr=subprocess.PIPE)
91         for line in proc.stderr:
92             line = line.strip().decode('utf-8')
94             # filter out warnings
95             if any(map(lambda m: m.match(line), WARNINGS)):
96                 continue
98             # filter out non-error messages
99             match = ERRORS.match(line)
100             if not match:
101                 continue
103             errors = True
105             print(line)
106             file, n = match.groups()
107             msgstr_line = int(n)
108             print_error_context(file, msgstr_line)
109             print('')
111             if sanitize:
112                 delete_msgstr(file, msgstr_line)
114     if errors:
115         sys.exit(1)
118 def parse_args():
119     parser = argparse.ArgumentParser()
120     parser.add_argument('--sanitize', action='store_true',
121                         help='Replace problematic translations with an empty string.')
122     return parser.parse_args()
125 if __name__ == '__main__':
126     args = parse_args()
127     check_po_msgfmt(sanitize=args.sanitize)