##// END OF EJS Templates
i18n: add the tool to check Mercurial specific translation problems in *.po...
FUJIWARA Katsunori -
r20152:84939b72 stable
parent child Browse files
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 &amp$$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