##// END OF EJS Templates
i18n: look translation of both "DEPRECATED" and "(DEPRECATED)" up...
FUJIWARA Katsunori -
r26838:47dd34f2 3.6 stable
parent child Browse files
Show More
@@ -1,244 +1,246
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 scanners = []
8 scanners = []
9 checkers = []
9 checkers = []
10
10
11 def scanner():
11 def scanner():
12 def decorator(func):
12 def decorator(func):
13 scanners.append(func)
13 scanners.append(func)
14 return func
14 return func
15 return decorator
15 return decorator
16
16
17 def levelchecker(level, msgidpat):
17 def levelchecker(level, msgidpat):
18 def decorator(func):
18 def decorator(func):
19 if msgidpat:
19 if msgidpat:
20 match = re.compile(msgidpat).search
20 match = re.compile(msgidpat).search
21 else:
21 else:
22 match = lambda msgid: True
22 match = lambda msgid: True
23 checkers.append((func, level))
23 checkers.append((func, level))
24 func.match = match
24 func.match = match
25 return func
25 return func
26 return decorator
26 return decorator
27
27
28 def match(checker, pe):
28 def match(checker, pe):
29 """Examine whether POEntry "pe" is target of specified checker or not
29 """Examine whether POEntry "pe" is target of specified checker or not
30 """
30 """
31 if not checker.match(pe.msgid):
31 if not checker.match(pe.msgid):
32 return
32 return
33 # examine suppression by translator comment
33 # examine suppression by translator comment
34 nochecker = 'no-%s-check' % checker.__name__
34 nochecker = 'no-%s-check' % checker.__name__
35 for tc in pe.tcomment.split():
35 for tc in pe.tcomment.split():
36 if nochecker == tc:
36 if nochecker == tc:
37 return
37 return
38 return True
38 return True
39
39
40 ####################
40 ####################
41
41
42 def fatalchecker(msgidpat=None):
42 def fatalchecker(msgidpat=None):
43 return levelchecker('fatal', msgidpat)
43 return levelchecker('fatal', msgidpat)
44
44
45 @fatalchecker(r'\$\$')
45 @fatalchecker(r'\$\$')
46 def promptchoice(pe):
46 def promptchoice(pe):
47 """Check translation of the string given to "ui.promptchoice()"
47 """Check translation of the string given to "ui.promptchoice()"
48
48
49 >>> pe = polib.POEntry(
49 >>> pe = polib.POEntry(
50 ... msgid ='prompt$$missing &sep$$missing &amp$$followed by &none',
50 ... msgid ='prompt$$missing &sep$$missing &amp$$followed by &none',
51 ... msgstr='prompt missing &sep$$missing amp$$followed by none&')
51 ... msgstr='prompt missing &sep$$missing amp$$followed by none&')
52 >>> match(promptchoice, pe)
52 >>> match(promptchoice, pe)
53 True
53 True
54 >>> for e in promptchoice(pe): print e
54 >>> for e in promptchoice(pe): print e
55 number of choices differs between msgid and msgstr
55 number of choices differs between msgid and msgstr
56 msgstr has invalid choice missing '&'
56 msgstr has invalid choice missing '&'
57 msgstr has invalid '&' followed by none
57 msgstr has invalid '&' followed by none
58 """
58 """
59 idchoices = [c.rstrip(' ') for c in pe.msgid.split('$$')[1:]]
59 idchoices = [c.rstrip(' ') for c in pe.msgid.split('$$')[1:]]
60 strchoices = [c.rstrip(' ') for c in pe.msgstr.split('$$')[1:]]
60 strchoices = [c.rstrip(' ') for c in pe.msgstr.split('$$')[1:]]
61
61
62 if len(idchoices) != len(strchoices):
62 if len(idchoices) != len(strchoices):
63 yield "number of choices differs between msgid and msgstr"
63 yield "number of choices differs between msgid and msgstr"
64
64
65 indices = [(c, c.find('&')) for c in strchoices]
65 indices = [(c, c.find('&')) for c in strchoices]
66 if [c for c, i in indices if i == -1]:
66 if [c for c, i in indices if i == -1]:
67 yield "msgstr has invalid choice missing '&'"
67 yield "msgstr has invalid choice missing '&'"
68 if [c for c, i in indices if len(c) == i + 1]:
68 if [c for c, i in indices if len(c) == i + 1]:
69 yield "msgstr has invalid '&' followed by none"
69 yield "msgstr has invalid '&' followed by none"
70
70
71 deprecatedpe = None
71 deprecatedpe = None
72 @scanner()
72 @scanner()
73 def deprecatedsetup(pofile):
73 def deprecatedsetup(pofile):
74 pes = [p for p in pofile if p.msgid == 'DEPRECATED']
74 pes = [p for p in pofile
75 if ((p.msgid == 'DEPRECATED' or p.msgid == '(DEPRECATED)') and
76 p.msgstr)]
75 if len(pes):
77 if len(pes):
76 global deprecatedpe
78 global deprecatedpe
77 deprecatedpe = pes[0]
79 deprecatedpe = pes[0]
78
80
79 @fatalchecker(r'\(DEPRECATED\)')
81 @fatalchecker(r'\(DEPRECATED\)')
80 def deprecated(pe):
82 def deprecated(pe):
81 """Check for DEPRECATED
83 """Check for DEPRECATED
82 >>> ped = polib.POEntry(
84 >>> ped = polib.POEntry(
83 ... msgid = 'DEPRECATED',
85 ... msgid = 'DEPRECATED',
84 ... msgstr= 'DETACERPED')
86 ... msgstr= 'DETACERPED')
85 >>> deprecatedsetup([ped])
87 >>> deprecatedsetup([ped])
86 >>> pe = polib.POEntry(
88 >>> pe = polib.POEntry(
87 ... msgid = 'Something (DEPRECATED)',
89 ... msgid = 'Something (DEPRECATED)',
88 ... msgstr= 'something (DEPRECATED)')
90 ... msgstr= 'something (DEPRECATED)')
89 >>> match(deprecated, pe)
91 >>> match(deprecated, pe)
90 True
92 True
91 >>> for e in deprecated(pe): print e
93 >>> for e in deprecated(pe): print e
92 >>> pe = polib.POEntry(
94 >>> pe = polib.POEntry(
93 ... msgid = 'Something (DEPRECATED)',
95 ... msgid = 'Something (DEPRECATED)',
94 ... msgstr= 'something (DETACERPED)')
96 ... msgstr= 'something (DETACERPED)')
95 >>> match(deprecated, pe)
97 >>> match(deprecated, pe)
96 True
98 True
97 >>> for e in deprecated(pe): print e
99 >>> for e in deprecated(pe): print e
98 >>> pe = polib.POEntry(
100 >>> pe = polib.POEntry(
99 ... msgid = 'Something (DEPRECATED)',
101 ... msgid = 'Something (DEPRECATED)',
100 ... msgstr= 'something')
102 ... msgstr= 'something')
101 >>> match(deprecated, pe)
103 >>> match(deprecated, pe)
102 True
104 True
103 >>> for e in deprecated(pe): print e
105 >>> for e in deprecated(pe): print e
104 msgstr inconsistently translated (DEPRECATED)
106 msgstr inconsistently translated (DEPRECATED)
105 >>> pe = polib.POEntry(
107 >>> pe = polib.POEntry(
106 ... msgid = 'Something (DEPRECATED, foo bar)',
108 ... msgid = 'Something (DEPRECATED, foo bar)',
107 ... msgstr= 'something (DETACERPED, foo bar)')
109 ... msgstr= 'something (DETACERPED, foo bar)')
108 >>> match(deprecated, pe)
110 >>> match(deprecated, pe)
109 """
111 """
110 if not ('(DEPRECATED)' in pe.msgstr or
112 if not ('(DEPRECATED)' in pe.msgstr or
111 (deprecatedpe and deprecatedpe.msgstr and
113 (deprecatedpe and
112 deprecatedpe.msgstr in pe.msgstr)):
114 deprecatedpe.msgstr in pe.msgstr)):
113 yield "msgstr inconsistently translated (DEPRECATED)"
115 yield "msgstr inconsistently translated (DEPRECATED)"
114
116
115 ####################
117 ####################
116
118
117 def warningchecker(msgidpat=None):
119 def warningchecker(msgidpat=None):
118 return levelchecker('warning', msgidpat)
120 return levelchecker('warning', msgidpat)
119
121
120 @warningchecker()
122 @warningchecker()
121 def taildoublecolons(pe):
123 def taildoublecolons(pe):
122 """Check equality of tail '::'-ness between msgid and msgstr
124 """Check equality of tail '::'-ness between msgid and msgstr
123
125
124 >>> pe = polib.POEntry(
126 >>> pe = polib.POEntry(
125 ... msgid ='ends with ::',
127 ... msgid ='ends with ::',
126 ... msgstr='ends with ::')
128 ... msgstr='ends with ::')
127 >>> for e in taildoublecolons(pe): print e
129 >>> for e in taildoublecolons(pe): print e
128 >>> pe = polib.POEntry(
130 >>> pe = polib.POEntry(
129 ... msgid ='ends with ::',
131 ... msgid ='ends with ::',
130 ... msgstr='ends without double-colons')
132 ... msgstr='ends without double-colons')
131 >>> for e in taildoublecolons(pe): print e
133 >>> for e in taildoublecolons(pe): print e
132 tail '::'-ness differs between msgid and msgstr
134 tail '::'-ness differs between msgid and msgstr
133 >>> pe = polib.POEntry(
135 >>> pe = polib.POEntry(
134 ... msgid ='ends without double-colons',
136 ... msgid ='ends without double-colons',
135 ... msgstr='ends with ::')
137 ... msgstr='ends with ::')
136 >>> for e in taildoublecolons(pe): print e
138 >>> for e in taildoublecolons(pe): print e
137 tail '::'-ness differs between msgid and msgstr
139 tail '::'-ness differs between msgid and msgstr
138 """
140 """
139 if pe.msgid.endswith('::') != pe.msgstr.endswith('::'):
141 if pe.msgid.endswith('::') != pe.msgstr.endswith('::'):
140 yield "tail '::'-ness differs between msgid and msgstr"
142 yield "tail '::'-ness differs between msgid and msgstr"
141
143
142 @warningchecker()
144 @warningchecker()
143 def indentation(pe):
145 def indentation(pe):
144 """Check equality of initial indentation between msgid and msgstr
146 """Check equality of initial indentation between msgid and msgstr
145
147
146 This may report unexpected warning, because this doesn't aware
148 This may report unexpected warning, because this doesn't aware
147 the syntax of rst document and the context of msgstr.
149 the syntax of rst document and the context of msgstr.
148
150
149 >>> pe = polib.POEntry(
151 >>> pe = polib.POEntry(
150 ... msgid =' indented text',
152 ... msgid =' indented text',
151 ... msgstr=' narrowed indentation')
153 ... msgstr=' narrowed indentation')
152 >>> for e in indentation(pe): print e
154 >>> for e in indentation(pe): print e
153 initial indentation width differs betweeen msgid and msgstr
155 initial indentation width differs betweeen msgid and msgstr
154 """
156 """
155 idindent = len(pe.msgid) - len(pe.msgid.lstrip())
157 idindent = len(pe.msgid) - len(pe.msgid.lstrip())
156 strindent = len(pe.msgstr) - len(pe.msgstr.lstrip())
158 strindent = len(pe.msgstr) - len(pe.msgstr.lstrip())
157 if idindent != strindent:
159 if idindent != strindent:
158 yield "initial indentation width differs betweeen msgid and msgstr"
160 yield "initial indentation width differs betweeen msgid and msgstr"
159
161
160 ####################
162 ####################
161
163
162 def check(pofile, fatal=True, warning=False):
164 def check(pofile, fatal=True, warning=False):
163 targetlevel = { 'fatal': fatal, 'warning': warning }
165 targetlevel = { 'fatal': fatal, 'warning': warning }
164 targetcheckers = [(checker, level)
166 targetcheckers = [(checker, level)
165 for checker, level in checkers
167 for checker, level in checkers
166 if targetlevel[level]]
168 if targetlevel[level]]
167 if not targetcheckers:
169 if not targetcheckers:
168 return []
170 return []
169
171
170 detected = []
172 detected = []
171 for checker in scanners:
173 for checker in scanners:
172 checker(pofile)
174 checker(pofile)
173 for pe in pofile.translated_entries():
175 for pe in pofile.translated_entries():
174 errors = []
176 errors = []
175 for checker, level in targetcheckers:
177 for checker, level in targetcheckers:
176 if match(checker, pe):
178 if match(checker, pe):
177 errors.extend((level, checker.__name__, error)
179 errors.extend((level, checker.__name__, error)
178 for error in checker(pe))
180 for error in checker(pe))
179 if errors:
181 if errors:
180 detected.append((pe, errors))
182 detected.append((pe, errors))
181 return detected
183 return detected
182
184
183 ########################################
185 ########################################
184
186
185 if __name__ == "__main__":
187 if __name__ == "__main__":
186 import sys
188 import sys
187 import optparse
189 import optparse
188
190
189 optparser = optparse.OptionParser("""%prog [options] pofile ...
191 optparser = optparse.OptionParser("""%prog [options] pofile ...
190
192
191 This checks Mercurial specific translation problems in specified
193 This checks Mercurial specific translation problems in specified
192 '*.po' files.
194 '*.po' files.
193
195
194 Each detected problems are shown in the format below::
196 Each detected problems are shown in the format below::
195
197
196 filename:linenum:type(checker): problem detail .....
198 filename:linenum:type(checker): problem detail .....
197
199
198 "type" is "fatal" or "warning". "checker" is the name of the function
200 "type" is "fatal" or "warning". "checker" is the name of the function
199 detecting corresponded error.
201 detecting corresponded error.
200
202
201 Checking by checker "foo" on the specific msgstr can be suppressed by
203 Checking by checker "foo" on the specific msgstr can be suppressed by
202 the "translator comment" like below. Multiple "no-xxxx-check" should
204 the "translator comment" like below. Multiple "no-xxxx-check" should
203 be separated by whitespaces::
205 be separated by whitespaces::
204
206
205 # no-foo-check
207 # no-foo-check
206 msgid = "....."
208 msgid = "....."
207 msgstr = "....."
209 msgstr = "....."
208 """)
210 """)
209 optparser.add_option("", "--warning",
211 optparser.add_option("", "--warning",
210 help="show also warning level problems",
212 help="show also warning level problems",
211 action="store_true")
213 action="store_true")
212 optparser.add_option("", "--doctest",
214 optparser.add_option("", "--doctest",
213 help="run doctest of this tool, instead of check",
215 help="run doctest of this tool, instead of check",
214 action="store_true")
216 action="store_true")
215 (options, args) = optparser.parse_args()
217 (options, args) = optparser.parse_args()
216
218
217 if options.doctest:
219 if options.doctest:
218 import os
220 import os
219 if 'TERM' in os.environ:
221 if 'TERM' in os.environ:
220 del os.environ['TERM']
222 del os.environ['TERM']
221 import doctest
223 import doctest
222 failures, tests = doctest.testmod()
224 failures, tests = doctest.testmod()
223 sys.exit(failures and 1 or 0)
225 sys.exit(failures and 1 or 0)
224
226
225 # replace polib._POFileParser to show linenum of problematic msgstr
227 # replace polib._POFileParser to show linenum of problematic msgstr
226 class ExtPOFileParser(polib._POFileParser):
228 class ExtPOFileParser(polib._POFileParser):
227 def process(self, symbol, linenum):
229 def process(self, symbol, linenum):
228 super(ExtPOFileParser, self).process(symbol, linenum)
230 super(ExtPOFileParser, self).process(symbol, linenum)
229 if symbol == 'MS': # msgstr
231 if symbol == 'MS': # msgstr
230 self.current_entry.linenum = linenum
232 self.current_entry.linenum = linenum
231 polib._POFileParser = ExtPOFileParser
233 polib._POFileParser = ExtPOFileParser
232
234
233 detected = []
235 detected = []
234 warning = options.warning
236 warning = options.warning
235 for f in args:
237 for f in args:
236 detected.extend((f, pe, errors)
238 detected.extend((f, pe, errors)
237 for pe, errors in check(polib.pofile(f),
239 for pe, errors in check(polib.pofile(f),
238 warning=warning))
240 warning=warning))
239 if detected:
241 if detected:
240 for f, pe, errors in detected:
242 for f, pe, errors in detected:
241 for level, checker, error in errors:
243 for level, checker, error in errors:
242 sys.stderr.write('%s:%d:%s(%s): %s\n'
244 sys.stderr.write('%s:%d:%s(%s): %s\n'
243 % (f, pe.linenum, level, checker, error))
245 % (f, pe.linenum, level, checker, error))
244 sys.exit(1)
246 sys.exit(1)
General Comments 0
You need to be logged in to leave comments. Login now