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