Show More
@@ -0,0 +1,148 b'' | |||||
|
1 | #!/usr/bin/env python | |||
|
2 | # | |||
|
3 | # check-translation.py - check Mercurial specific translation problems | |||
|
4 | ||||
|
5 | import polib | |||
|
6 | import re | |||
|
7 | ||||
|
8 | checkers = [] | |||
|
9 | ||||
|
10 | def checker(level, msgidpat): | |||
|
11 | def decorator(func): | |||
|
12 | if msgidpat: | |||
|
13 | match = re.compile(msgidpat).search | |||
|
14 | else: | |||
|
15 | match = lambda msgid: True | |||
|
16 | checkers.append((func, level)) | |||
|
17 | func.match = match | |||
|
18 | return func | |||
|
19 | return decorator | |||
|
20 | ||||
|
21 | def match(checker, pe): | |||
|
22 | """Examine whether POEntry "pe" is target of specified checker or not | |||
|
23 | """ | |||
|
24 | if not checker.match(pe.msgid): | |||
|
25 | return | |||
|
26 | # examine suppression by translator comment | |||
|
27 | nochecker = 'no-%s-check' % checker.__name__ | |||
|
28 | for tc in pe.tcomment.split(): | |||
|
29 | if nochecker == tc: | |||
|
30 | return | |||
|
31 | return True | |||
|
32 | ||||
|
33 | #################### | |||
|
34 | ||||
|
35 | def fatalchecker(msgidpat=None): | |||
|
36 | return checker('fatal', msgidpat) | |||
|
37 | ||||
|
38 | @fatalchecker(r'\$\$') | |||
|
39 | def promptchoice(pe): | |||
|
40 | """Check translation of the string given to "ui.promptchoice()" | |||
|
41 | ||||
|
42 | >>> pe = polib.POEntry( | |||
|
43 | ... msgid ='prompt$$missing &sep$$missing &$$followed by &none', | |||
|
44 | ... msgstr='prompt missing &sep$$missing amp$$followed by none&') | |||
|
45 | >>> match(promptchoice, pe) | |||
|
46 | True | |||
|
47 | >>> for e in promptchoice(pe): print e | |||
|
48 | number of choices differs between msgid and msgstr | |||
|
49 | msgstr has invalid choice missing '&' | |||
|
50 | msgstr has invalid '&' followed by none | |||
|
51 | """ | |||
|
52 | idchoices = [c.rstrip(' ') for c in pe.msgid.split('$$')[1:]] | |||
|
53 | strchoices = [c.rstrip(' ') for c in pe.msgstr.split('$$')[1:]] | |||
|
54 | ||||
|
55 | if len(idchoices) != len(strchoices): | |||
|
56 | yield "number of choices differs between msgid and msgstr" | |||
|
57 | ||||
|
58 | indices = [(c, c.find('&')) for c in strchoices] | |||
|
59 | if [c for c, i in indices if i == -1]: | |||
|
60 | yield "msgstr has invalid choice missing '&'" | |||
|
61 | if [c for c, i in indices if len(c) == i + 1]: | |||
|
62 | yield "msgstr has invalid '&' followed by none" | |||
|
63 | ||||
|
64 | #################### | |||
|
65 | ||||
|
66 | def warningchecker(msgidpat=None): | |||
|
67 | return checker('warning', msgidpat) | |||
|
68 | ||||
|
69 | #################### | |||
|
70 | ||||
|
71 | def check(pofile, fatal=True, warning=False): | |||
|
72 | targetlevel = { 'fatal': fatal, 'warning': warning } | |||
|
73 | targetcheckers = [(checker, level) | |||
|
74 | for checker, level in checkers | |||
|
75 | if targetlevel[level]] | |||
|
76 | if not targetcheckers: | |||
|
77 | return [] | |||
|
78 | ||||
|
79 | detected = [] | |||
|
80 | for pe in pofile.translated_entries(): | |||
|
81 | errors = [] | |||
|
82 | for checker, level in targetcheckers: | |||
|
83 | if match(checker, pe): | |||
|
84 | errors.extend((level, checker.__name__, error) | |||
|
85 | for error in checker(pe)) | |||
|
86 | if errors: | |||
|
87 | detected.append((pe, errors)) | |||
|
88 | return detected | |||
|
89 | ||||
|
90 | ######################################## | |||
|
91 | ||||
|
92 | if __name__ == "__main__": | |||
|
93 | import sys | |||
|
94 | import optparse | |||
|
95 | ||||
|
96 | optparser = optparse.OptionParser("""%prog [options] pofile ... | |||
|
97 | ||||
|
98 | This checks Mercurial specific translation problems in specified | |||
|
99 | '*.po' files. | |||
|
100 | ||||
|
101 | Each detected problems are shown in the format below:: | |||
|
102 | ||||
|
103 | filename:linenum:type(checker): problem detail ..... | |||
|
104 | ||||
|
105 | "type" is "fatal" or "warning". "checker" is the name of the function | |||
|
106 | detecting corresponded error. | |||
|
107 | ||||
|
108 | Checking by checker "foo" on the specific msgstr can be suppressed by | |||
|
109 | the "translator comment" like below. Multiple "no-xxxx-check" should | |||
|
110 | be separated by whitespaces:: | |||
|
111 | ||||
|
112 | # no-foo-check | |||
|
113 | msgid = "....." | |||
|
114 | msgstr = "....." | |||
|
115 | """) | |||
|
116 | optparser.add_option("", "--warning", | |||
|
117 | help="show also warning level problems", | |||
|
118 | action="store_true") | |||
|
119 | optparser.add_option("", "--doctest", | |||
|
120 | help="run doctest of this tool, instead of check", | |||
|
121 | action="store_true") | |||
|
122 | (options, args) = optparser.parse_args() | |||
|
123 | ||||
|
124 | if options.doctest: | |||
|
125 | import doctest | |||
|
126 | failures, tests = doctest.testmod() | |||
|
127 | sys.exit(failures and 1 or 0) | |||
|
128 | ||||
|
129 | # replace polib._POFileParser to show linenum of problematic msgstr | |||
|
130 | class ExtPOFileParser(polib._POFileParser): | |||
|
131 | def process(self, symbol, linenum): | |||
|
132 | super(ExtPOFileParser, self).process(symbol, linenum) | |||
|
133 | if symbol == 'MS': # msgstr | |||
|
134 | self.current_entry.linenum = linenum | |||
|
135 | polib._POFileParser = ExtPOFileParser | |||
|
136 | ||||
|
137 | detected = [] | |||
|
138 | warning = options.warning | |||
|
139 | for f in args: | |||
|
140 | detected.extend((f, pe, errors) | |||
|
141 | for pe, errors in check(polib.pofile(f), | |||
|
142 | warning=warning)) | |||
|
143 | if detected: | |||
|
144 | for f, pe, errors in detected: | |||
|
145 | for level, checker, error in errors: | |||
|
146 | sys.stderr.write('%s:%d:%s(%s): %s\n' | |||
|
147 | % (f, pe.linenum, level, checker, error)) | |||
|
148 | sys.exit(1) |
@@ -38,3 +38,10 b' Test keyword search in translated help t' | |||||
38 |
|
38 | |||
39 | pager Verwendet einen externen Pager zum Bl\xc3\xa4ttern in der Ausgabe von Befehlen (esc) |
|
39 | pager Verwendet einen externen Pager zum Bl\xc3\xa4ttern in der Ausgabe von Befehlen (esc) | |
40 |
|
40 | |||
|
41 | Check Mercurial specific translation problems in each *.po files, and | |||
|
42 | tool itself by doctest | |||
|
43 | ||||
|
44 | $ cd "$TESTDIR"/../i18n | |||
|
45 | $ python check-translation.py *.po | |||
|
46 | $ python check-translation.py --doctest | |||
|
47 | $ cd $TESTTMP |
General Comments 0
You need to be logged in to leave comments.
Login now