1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """This is a set of validation checks that can be performed on translation
23 units.
24
25 Derivatives of UnitChecker (like StandardUnitChecker) check translation units,
26 and derivatives of TranslationChecker (like StandardChecker) check
27 (source, target) translation pairs.
28
29 When adding a new test here, please document and explain the behaviour on the
30 U{wiki <http://translate.sourceforge.net/wiki/toolkit/pofilter_tests>}.
31 """
32
33 from translate.filters import helpers
34 from translate.filters import decoration
35 from translate.filters import prefilters
36 from translate.filters import spelling
37 from translate.lang import factory
38 from translate.lang import data
39
40
41
42 try:
43 from translate.storage import xliff
44 except ImportError, e:
45 xliff = None
46
47
48 if not hasattr(xliff, "xliffunit"):
49 xliff = None
50 import re
51
52
53
54
55
56
57 printf_pat = re.compile('%((?:(?P<ord>\d+)\$|\((?P<key>\w+)\))?(?P<fullvar>[+#-]*(?:\d+)?(?:\.\d+)?(hh\|h\|l\|ll)?(?P<type>[\w%])))')
58
59
60 tagname_re = re.compile("<[\s]*([\w\/]*)")
61
62
63
64 property_re = re.compile(" (\w*)=((\\\\?\".*?\\\\?\")|(\\\\?'.*?\\\\?'))")
65
66
67 tag_re = re.compile("<[^>]+>")
68
69 gconf_attribute_re = re.compile('"[a-z_]+?"')
70
72 """Returns the name of the XML/HTML tag in string"""
73 return tagname_re.match(string).groups(1)[0]
74
76 """Tests to see if pair == (a,b,c) is in list, but handles None entries in
77 list as wildcards (only allowed in positions "a" and "c"). We take a shortcut
78 by only considering "c" if "b" has already matched."""
79 a, b, c = pair
80 if (b, c) == (None, None):
81
82 return pair
83 for pattern in list:
84 x, y, z = pattern
85 if (x, y) in [(a, b), (None, b)]:
86 if z in [None, c]:
87 return pattern
88 return pair
89
91 """Returns all the properties in the XML/HTML tag string as
92 (tagname, propertyname, propertyvalue), but ignore those combinations
93 specified in ignore."""
94 properties = []
95 for string in strings:
96 tag = tagname(string)
97 properties += [(tag, None, None)]
98
99 pairs = property_re.findall(string)
100 for property, value, a, b in pairs:
101
102 value = value[1:-1]
103
104 canignore = False
105 if (tag, property, value) in ignore or \
106 intuplelist((tag,property,value), ignore) != (tag,property,value):
107 canignore = True
108 break
109 if not canignore:
110 properties += [(tag, property, value)]
111 return properties
112
113
115 """This exception signals that a Filter didn't pass, and gives an explanation
116 or a comment"""
118 if not isinstance(messages, list):
119 messages = [messages]
120 assert isinstance(messages[0], unicode)
121 joined = u", ".join(messages)
122 Exception.__init__(self, joined)
123
124 if not hasattr(self, "args"):
125 self.args = joined
126
128 """This exception signals that a Filter didn't pass, and the bad translation
129 might break an application (so the string will be marked fuzzy)"""
130 pass
131
132
133
134
135
136
137
138
139 common_ignoretags = [(None, "xml-lang", None)]
140 common_canchangetags = [("img", "alt", None)]
141
143 """object representing the configuration of a checker"""
144 - def __init__(self, targetlanguage=None, accelmarkers=None, varmatches=None,
145 notranslatewords=None, musttranslatewords=None, validchars=None,
146 punctuation=None, endpunctuation=None, ignoretags=None,
147 canchangetags=None, criticaltests=None, credit_sources=None):
169
171 """initialise configuration paramaters that are lists
172
173 @type list: List
174 @param list: None (we'll initialise a blank list) or a list paramater
175 @rtype: List
176 """
177 if list is None:
178 list = []
179 return list
180
182 """initialise parameters that can have default options
183
184 @param param: the user supplied paramater value
185 @param default: default values when param is not specified
186 @return: the paramater as specified by the user of the default settings
187 """
188 if param is None:
189 return default
190 return param
191
192 - def update(self, otherconfig):
193 """combines the info in otherconfig into this config object"""
194 self.targetlanguage = otherconfig.targetlanguage or self.targetlanguage
195 self.updatetargetlanguage(self.targetlanguage)
196 self.accelmarkers.extend([c for c in otherconfig.accelmarkers if not c in self.accelmarkers])
197 self.varmatches.extend(otherconfig.varmatches)
198 self.notranslatewords.update(otherconfig.notranslatewords)
199 self.musttranslatewords.update(otherconfig.musttranslatewords)
200 self.validcharsmap.update(otherconfig.validcharsmap)
201 self.punctuation += otherconfig.punctuation
202 self.endpunctuation += otherconfig.endpunctuation
203
204 self.ignoretags = otherconfig.ignoretags
205 self.canchangetags = otherconfig.canchangetags
206 self.criticaltests.extend(otherconfig.criticaltests)
207 self.credit_sources = otherconfig.credit_sources
208
210 """updates the map that eliminates valid characters"""
211 if validchars is None:
212 return True
213 validcharsmap = dict([(ord(validchar), None) for validchar in data.normalized_unicode(validchars)])
214 self.validcharsmap.update(validcharsmap)
215
217 """Updates the target language in the config to the given target language"""
218 self.lang = factory.getlanguage(langcode)
219
221 def cached_f(self, param1):
222 key = (f.__name__, param1)
223 res_cache = self.results_cache
224 if key in res_cache:
225 return res_cache[key]
226 else:
227 value = f(self, param1)
228 res_cache[key] = value
229 return value
230 return cached_f
231
233 """Parent Checker class which does the checking based on functions available
234 in derived classes."""
235 preconditions = {}
236
237 - def __init__(self, checkerconfig=None, excludefilters=None, limitfilters=None, errorhandler=None):
238 self.errorhandler = errorhandler
239 if checkerconfig is None:
240 self.setconfig(CheckerConfig())
241 else:
242 self.setconfig(checkerconfig)
243
244 self.helperfunctions = {}
245 for functionname in dir(UnitChecker):
246 function = getattr(self, functionname)
247 if callable(function):
248 self.helperfunctions[functionname] = function
249 self.defaultfilters = self.getfilters(excludefilters, limitfilters)
250
251 self.results_cache = {}
252
253 - def getfilters(self, excludefilters=None, limitfilters=None):
254 """returns dictionary of available filters, including/excluding those in
255 the given lists"""
256 filters = {}
257 if limitfilters is None:
258
259 limitfilters = dir(self)
260 if excludefilters is None:
261 excludefilters = {}
262 for functionname in limitfilters:
263 if functionname in excludefilters: continue
264 if functionname in self.helperfunctions: continue
265 if functionname == "errorhandler": continue
266 filterfunction = getattr(self, functionname, None)
267 if not callable(filterfunction): continue
268 filters[functionname] = filterfunction
269 return filters
270
279
281 """Sets the filename that a checker should use for evaluating suggestions."""
282 self.suggestion_store = store
283
285 """filter out variables from str1"""
286 return helpers.multifilter(str1, self.varfilters)
287 filtervariables = cache_results(filtervariables)
288
290 """remove variables from str1"""
291 return helpers.multifilter(str1, self.removevarfilter)
292 removevariables = cache_results(removevariables)
293
295 """filter out accelerators from str1"""
296 return helpers.multifilter(str1, self.accfilters, None)
297 filteraccelerators = cache_results(filteraccelerators)
298
300 """filter out accelerators from str1"""
301 return helpers.multifilter(str1, self.accfilters, acceptlist)
302
306 filterwordswithpunctuation = cache_results(filterwordswithpunctuation)
307
309 """filter out XML from the string so only text remains"""
310 return tag_re.sub("", str1)
311 filterxml = cache_results(filterxml)
312
314 """Runs the given test on the given unit.
315
316 Note that this can raise a FilterFailure as part of normal operation"""
317 return test(unit)
318
320 """run all the tests in this suite, return failures as testname, message_or_exception"""
321 self.results_cache = {}
322 failures = {}
323 ignores = self.config.lang.ignoretests[:]
324 functionnames = self.defaultfilters.keys()
325 priorityfunctionnames = self.preconditions.keys()
326 otherfunctionnames = filter(lambda functionname: functionname not in self.preconditions, functionnames)
327 for functionname in priorityfunctionnames + otherfunctionnames:
328 if functionname in ignores:
329 continue
330 filterfunction = getattr(self, functionname, None)
331
332 if filterfunction is None:
333 continue
334 filtermessage = filterfunction.__doc__
335 try:
336 filterresult = self.run_test(filterfunction, unit)
337 except FilterFailure, e:
338 filterresult = False
339 filtermessage = e.args[0]
340 except Exception, e:
341 if self.errorhandler is None:
342 raise ValueError("error in filter %s: %r, %r, %s" % \
343 (functionname, unit.source, unit.target, e))
344 else:
345 filterresult = self.errorhandler(functionname, unit.source, unit.target, e)
346 if not filterresult:
347
348 if functionname in self.defaultfilters:
349 failures[functionname] = filtermessage
350 if functionname in self.preconditions:
351 for ignoredfunctionname in self.preconditions[functionname]:
352 ignores.append(ignoredfunctionname)
353 self.results_cache = {}
354 return failures
355
357 """A checker that passes source and target strings to the checks, not the
358 whole unit.
359
360 This provides some speedup and simplifies testing."""
361 - def __init__(self, checkerconfig=None, excludefilters=None, limitfilters=None, errorhandler=None):
363
365 """Runs the given test on the given unit.
366
367 Note that this can raise a FilterFailure as part of normal operation."""
368 if self.hasplural:
369 filtermessages = []
370 filterresult = True
371 for pluralform in unit.target.strings:
372 try:
373 if not test(self.str1, unicode(pluralform)):
374 filterresult = False
375 except FilterFailure, e:
376 filterresult = False
377 filtermessages.append( unicode(e.args) )
378 if not filterresult and filtermessages:
379 raise FilterFailure(filtermessages)
380 else:
381 return filterresult
382 else:
383 return test(self.str1, self.str2)
384
393
395 """A Checker that controls multiple checkers."""
396 - def __init__(self, checkerconfig=None, excludefilters=None, limitfilters=None,
397 checkerclasses=None, errorhandler=None, languagecode=None):
413
414 - def getfilters(self, excludefilters=None, limitfilters=None):
430
437
442
443
445 """The basic test suite for source -> target translations."""
447 """checks whether a string has been translated at all"""
448 str2 = prefilters.removekdecomments(str2)
449 return not (len(str1.strip()) > 0 and len(str2) == 0)
450
452 """checks whether a translation is basically identical to the original string"""
453 str1 = self.filteraccelerators(self.removevariables(str1)).strip()
454 str2 = self.filteraccelerators(self.removevariables(str2)).strip()
455 if len(str1) < 2:
456 return True
457
458
459
460 if (str1.isupper() or str1.upper() == str1) and str1 == str2:
461 return True
462 if self.config.notranslatewords:
463 words1 = str1.split()
464 if len(words1) == 1 and [word for word in words1 if word in self.config.notranslatewords]:
465
466
467
468 return True
469
470
471 if str1.lower() == str2.lower():
472 raise FilterFailure(u"please translate")
473 return True
474
475 - def blank(self, str1, str2):
476 """checks whether a translation only contains spaces"""
477 len1 = len(str1.strip())
478 len2 = len(str2.strip())
479 return not (len1 > 0 and len(str2) != 0 and len2 == 0)
480
481 - def short(self, str1, str2):
482 """checks whether a translation is much shorter than the original string"""
483 len1 = len(str1.strip())
484 len2 = len(str2.strip())
485 return not ((len1 > 0) and (0 < len2 < (len1 * 0.1)) or ((len1 > 1) and (len2 == 1)))
486
487 - def long(self, str1, str2):
488 """checks whether a translation is much longer than the original string"""
489 len1 = len(str1.strip())
490 len2 = len(str2.strip())
491 return not ((len1 > 0) and (0 < len1 < (len2 * 0.1)) or ((len1 == 1) and (len2 > 1)))
492
494 """checks whether escaping is consistent between the two strings"""
495 if not helpers.countsmatch(str1, str2, (u"\\", u"\\\\")):
496 escapes1 = u", ".join([u"'%s'" % word for word in str1.split() if u"\\" in word])
497 escapes2 = u", ".join([u"'%s'" % word for word in str2.split() if u"\\" in word])
498 raise SeriousFilterFailure(u"escapes in original (%s) don't match escapes in translation (%s)" % (escapes1, escapes2))
499 else:
500 return True
501
503 """checks whether newlines are consistent between the two strings"""
504 if not helpers.countsmatch(str1, str2, (u"\n", u"\r")):
505 raise FilterFailure(u"line endings in original don't match line endings in translation")
506 else:
507 return True
508
509 - def tabs(self, str1, str2):
510 """checks whether tabs are consistent between the two strings"""
511 if not helpers.countmatch(str1, str2, "\t"):
512 raise SeriousFilterFailure(u"tabs in original don't match tabs in translation")
513 else:
514 return True
515
521
530
536
538 """checks for bad spacing after punctuation"""
539 if str1.find(u" ") == -1:
540 return True
541 str1 = self.filteraccelerators(self.filtervariables(str1))
542 str1 = self.config.lang.punctranslate(str1)
543 str2 = self.filteraccelerators(self.filtervariables(str2))
544 for puncchar in self.config.punctuation:
545 plaincount1 = str1.count(puncchar)
546 plaincount2 = str2.count(puncchar)
547 if not plaincount1 or plaincount1 != plaincount2:
548 continue
549 spacecount1 = str1.count(puncchar + u" ")
550 spacecount2 = str2.count(puncchar + u" ")
551 if spacecount1 != spacecount2:
552
553 if str1.endswith(puncchar) != str2.endswith(puncchar) and abs(spacecount1-spacecount2) == 1:
554 continue
555 return False
556 return True
557
558 - def printf(self, str1, str2):
559 """checks whether printf format strings match"""
560 count1 = count2 = plural = None
561
562 if 'hasplural' in self.__dict__:
563 plural = self.hasplural
564 for var_num2, match2 in enumerate(printf_pat.finditer(str2)):
565 count2 = var_num2 + 1
566 str2key = match2.group('key')
567 if match2.group('ord'):
568 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
569 count1 = var_num1 + 1
570 if int(match2.group('ord')) == var_num1 + 1:
571 if match2.group('fullvar') != match1.group('fullvar'):
572 return 0
573 elif str2key:
574 str1key = None
575 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
576 count1 = var_num1 + 1
577 if match1.group('key') and str2key == match1.group('key'):
578 str1key = match1.group('key')
579
580 if plural and match2.group('fullvar') == '.0s':
581 continue
582 if match1.group('fullvar') != match2.group('fullvar'):
583 return 0
584 if str1key == None:
585 return 0
586 else:
587 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
588 count1 = var_num1 + 1
589
590 if plural and match2.group('fullvar') == '.0s':
591 continue
592 if (var_num1 == var_num2) and (match1.group('fullvar') != match2.group('fullvar')):
593 return 0
594
595 if count2 is None:
596 if list(printf_pat.finditer(str1)):
597 return 0
598
599 if (count1 or count2) and (count1 != count2):
600 return 0
601 return 1
602
604 """checks whether accelerators are consistent between the two strings"""
605 str1 = self.filtervariables(str1)
606 str2 = self.filtervariables(str2)
607 messages = []
608 for accelmarker in self.config.accelmarkers:
609 counter1 = decoration.countaccelerators(accelmarker, self.config.sourcelang.validaccel)
610 counter2 = decoration.countaccelerators(accelmarker, self.config.lang.validaccel)
611 count1, countbad1 = counter1(str1)
612 count2, countbad2 = counter2(str2)
613 getaccel = decoration.getaccelerators(accelmarker, self.config.lang.validaccel)
614 accel2, bad2 = getaccel(str2)
615 if count1 == count2:
616 continue
617 if count1 == 1 and count2 == 0:
618 if countbad2 == 1:
619 messages.append(u"accelerator %s appears before an invalid accelerator character '%s' (eg. space)" % (accelmarker, bad2[0]))
620 else:
621 messages.append(u"accelerator %s is missing from translation" % accelmarker)
622 elif count1 == 0:
623 messages.append(u"accelerator %s does not occur in original and should not be in translation" % accelmarker)
624 elif count1 == 1 and count2 > count1:
625 messages.append(u"accelerator %s is repeated in translation" % accelmarker)
626 else:
627 messages.append(u"accelerator %s occurs %d time(s) in original and %d time(s) in translation" % (accelmarker, count1, count2))
628 if messages:
629 if "accelerators" in self.config.criticaltests:
630 raise SeriousFilterFailure(messages)
631 else:
632 raise FilterFailure(messages)
633 return True
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
650 """checks whether variables of various forms are consistent between the two strings"""
651 messages = []
652 mismatch1, mismatch2 = [], []
653 varnames1, varnames2 = [], []
654 for startmarker, endmarker in self.config.varmatches:
655 varchecker = decoration.getvariables(startmarker, endmarker)
656 if startmarker and endmarker:
657 if isinstance(endmarker, int):
658 redecorate = lambda var: startmarker + var
659 else:
660 redecorate = lambda var: startmarker + var + endmarker
661 elif startmarker:
662 redecorate = lambda var: startmarker + var
663 else:
664 redecorate = lambda var: var
665 vars1 = varchecker(str1)
666 vars2 = varchecker(str2)
667 if vars1 != vars2:
668
669 vars1, vars2 = [var for var in vars1 if vars1.count(var) > vars2.count(var)], [var for var in vars2 if vars1.count(var) < vars2.count(var)]
670
671 vars1, vars2 = [var for var in vars1 if var not in varnames1], [var for var in vars2 if var not in varnames2]
672 varnames1.extend(vars1)
673 varnames2.extend(vars2)
674 vars1 = map(redecorate, vars1)
675 vars2 = map(redecorate, vars2)
676 mismatch1.extend(vars1)
677 mismatch2.extend(vars2)
678 if mismatch1:
679 messages.append(u"do not translate: %s" % u", ".join(mismatch1))
680 elif mismatch2:
681 messages.append(u"translation contains variables not in original: %s" % u", ".join(mismatch2))
682 if messages and mismatch1:
683 raise SeriousFilterFailure(messages)
684 elif messages:
685 raise FilterFailure(messages)
686 return True
687
691
692 - def emails(self, str1, str2):
695
696 - def urls(self, str1, str2):
699
703
707
712
719
728
736
738 """checks that the number of brackets in both strings match"""
739 str1 = self.filtervariables(str1)
740 str2 = self.filtervariables(str2)
741 messages = []
742 missing = []
743 extra = []
744 for bracket in (u"[", u"]", u"{", u"}", u"(", u")"):
745 count1 = str1.count(bracket)
746 count2 = str2.count(bracket)
747 if count2 < count1:
748 missing.append(u"'%s'" % bracket)
749 elif count2 > count1:
750 extra.append(u"'%s'" % bracket)
751 if missing:
752 messages.append(u"translation is missing %s" % u", ".join(missing))
753 if extra:
754 messages.append(u"translation has extra %s" % u", ".join(extra))
755 if messages:
756 raise FilterFailure(messages)
757 return True
758
760 """checks that the number of sentences in both strings match"""
761 str1 = self.filteraccelerators(str1)
762 str2 = self.filteraccelerators(str2)
763 sentences1 = len(self.config.sourcelang.sentences(str1))
764 sentences2 = len(self.config.lang.sentences(str2))
765 if not sentences1 == sentences2:
766 raise FilterFailure(u"The number of sentences differ: %d versus %d" % (sentences1, sentences2))
767 return True
768
770 """checks that options are not translated"""
771 str1 = self.filtervariables(str1)
772 for word1 in str1.split():
773 if word1 != u"--" and word1.startswith(u"--") and word1[-1].isalnum():
774 parts = word1.split(u"=")
775 if not parts[0] in str2:
776 raise FilterFailure(u"The option %s does not occur or is translated in the translation." % parts[0])
777 if len(parts) > 1 and parts[1] in str2:
778 raise FilterFailure(u"The parameter %(param)s in option %(option)s is not translated." % {"param": parts[1], "option": parts[0]})
779 return True
780
782 """checks that the message starts with the correct capitalisation"""
783 str1 = self.filteraccelerators(str1)
784 str2 = self.filteraccelerators(str2)
785 if len(str1) > 1 and len(str2) > 1:
786 return self.config.sourcelang.capsstart(str1) == self.config.lang.capsstart(str2)
787 if len(str1) == 0 and len(str2) == 0:
788 return True
789 if len(str1) == 0 or len(str2) == 0:
790 return False
791 return True
792
794 """checks the capitalisation of two strings isn't wildly different"""
795 str1 = self.removevariables(str1)
796 str2 = self.removevariables(str2)
797
798
799 str1 = re.sub(u"[^%s]( I )" % self.config.sourcelang.sentenceend, u" i ", str1)
800 capitals1 = helpers.filtercount(str1, unicode.isupper)
801 capitals2 = helpers.filtercount(str2, unicode.isupper)
802 alpha1 = helpers.filtercount(str1, unicode.isalpha)
803 alpha2 = helpers.filtercount(str2, unicode.isalpha)
804
805 if capitals1 == alpha1:
806 return capitals2 == alpha2
807
808 if capitals1 == 0 or capitals1 == 1:
809 return capitals2 == capitals1
810 elif capitals1 < len(str1) / 10:
811 return capitals2 <= len(str2) / 8
812 elif len(str1) < 10:
813 return abs(capitals1 - capitals2) < 3
814 elif capitals1 > len(str1) * 6 / 10:
815 return capitals2 > len(str2) * 6 / 10
816 else:
817 return abs(capitals1 - capitals2) < (len(str1) + len(str2)) / 6
818
840
851
869
888
890 """checks that only characters specified as valid appear in the translation"""
891 if not self.config.validcharsmap:
892 return True
893 invalid1 = str1.translate(self.config.validcharsmap)
894 invalid2 = str2.translate(self.config.validcharsmap)
895 invalidchars = [u"'%s' (\\u%04x)" % (invalidchar, ord(invalidchar)) for invalidchar in invalid2 if invalidchar not in invalid1]
896 if invalidchars:
897 raise FilterFailure(u"invalid chars: %s" % (u", ".join(invalidchars)))
898 return True
899
901 """checks that file paths have not been translated"""
902 for word1 in self.filteraccelerators(str1).split():
903 if word1.startswith(u"/"):
904 if not helpers.countsmatch(str1, str2, (word1,)):
905 return False
906 return True
907
934
938
940 """checks for Gettext compendium conflicts (#-#-#-#-#)"""
941 return str2.find(u"#-#-#-#-#") == -1
942
944 """checks for English style plural(s) for you to review"""
945 def numberofpatterns(string, patterns):
946 number = 0
947 for pattern in patterns:
948 number += len(re.findall(pattern, string))
949 return number
950
951 sourcepatterns = ["\(s\)"]
952 targetpatterns = ["\(s\)"]
953 sourcecount = numberofpatterns(str1, sourcepatterns)
954 targetcount = numberofpatterns(str2, targetpatterns)
955 if self.config.lang.nplurals == 1:
956 return not targetcount
957 return sourcecount == targetcount
958
984
986 """checks for messages containing translation credits instead of normal translations."""
987 return not str1 in self.config.credit_sources
988
989
990 preconditions = {"untranslated": ("simplecaps", "variables", "startcaps",
991 "accelerators", "brackets", "endpunc",
992 "acronyms", "xmltags", "startpunc",
993 "endwhitespace", "startwhitespace",
994 "escapes", "doublequoting", "singlequoting",
995 "filepaths", "purepunc", "doublespacing",
996 "sentencecount", "numbers", "isfuzzy",
997 "isreview", "notranslatewords", "musttranslatewords",
998 "emails", "simpleplurals", "urls", "printf",
999 "tabs", "newlines", "functions", "options",
1000 "blank", "nplurals", "gconf"),
1001 "blank": ("simplecaps", "variables", "startcaps",
1002 "accelerators", "brackets", "endpunc",
1003 "acronyms", "xmltags", "startpunc",
1004 "endwhitespace", "startwhitespace",
1005 "escapes", "doublequoting", "singlequoting",
1006 "filepaths", "purepunc", "doublespacing",
1007 "sentencecount", "numbers", "isfuzzy",
1008 "isreview", "notranslatewords", "musttranslatewords",
1009 "emails", "simpleplurals", "urls", "printf",
1010 "tabs", "newlines", "functions", "options",
1011 "gconf"),
1012 "credits": ("simplecaps", "variables", "startcaps",
1013 "accelerators", "brackets", "endpunc",
1014 "acronyms", "xmltags", "startpunc",
1015 "escapes", "doublequoting", "singlequoting",
1016 "filepaths", "doublespacing",
1017 "sentencecount", "numbers",
1018 "emails", "simpleplurals", "urls", "printf",
1019 "tabs", "newlines", "functions", "options"),
1020 "purepunc": ("startcaps", "options"),
1021
1022
1023
1024
1025
1026
1027
1028
1029 "endwhitespace": ("endpunc",),
1030 "startwhitespace":("startpunc",),
1031 "unchanged": ("doublewords",),
1032 "compendiumconflicts": ("accelerators", "brackets", "escapes",
1033 "numbers", "startpunc", "long", "variables",
1034 "startcaps", "sentencecount", "simplecaps",
1035 "doublespacing", "endpunc", "xmltags",
1036 "startwhitespace", "endwhitespace",
1037 "singlequoting", "doublequoting",
1038 "filepaths", "purepunc", "doublewords", "printf") }
1039
1040
1041
1042 openofficeconfig = CheckerConfig(
1043 accelmarkers = ["~"],
1044 varmatches = [("&", ";"), ("%", "%"), ("%", None), ("%", 0), ("$(", ")"), ("$", "$"), ("${", "}"), ("#", "#"), ("#", 1), ("#", 0), ("($", ")"), ("$[", "]"), ("[", "]"), ("$", None)],
1045 ignoretags = [("alt", "xml-lang", None), ("ahelp", "visibility", "visible"), ("img", "width", None), ("img", "height", None)],
1046 canchangetags = [("link", "name", None)]
1047 )
1048
1057
1058 mozillaconfig = CheckerConfig(
1059 accelmarkers = ["&"],
1060 varmatches = [("&", ";"), ("%", "%"), ("%", 1), ("$", "$"), ("$", None), ("#", 1), ("${", "}"), ("$(^", ")")],
1061 criticaltests = ["accelerators"]
1062 )
1063
1072
1074 """checks for messages containing translation credits instead of normal translations."""
1075 for location in self.locations:
1076 if location in ['MOZ_LANGPACK_CONTRIBUTORS', 'credit.translation']:
1077 return False
1078 return True
1079
1080 drupalconfig = CheckerConfig(
1081 varmatches = [("%", None), ("@", None)],
1082 )
1083
1092
1093 gnomeconfig = CheckerConfig(
1094 accelmarkers = ["_"],
1095 varmatches = [("%", 1), ("$(", ")")],
1096 credit_sources = [u"translator-credits"]
1097 )
1098
1107
1108 - def gconf(self, str1, str2):
1109 """Checks if we have any gconf config settings translated."""
1110 for location in self.locations:
1111 if location.find('schemas.in') != -1:
1112 gconf_attributes = gconf_attribute_re.findall(str1)
1113
1114 stopwords = [word for word in gconf_attributes if word[1:-1] not in str2]
1115 if stopwords:
1116 raise FilterFailure(u"do not translate gconf attribute: %s" % (u", ".join(stopwords)))
1117 return True
1118
1119 kdeconfig = CheckerConfig(
1120 accelmarkers = ["&"],
1121 varmatches = [("%", 1)],
1122 credit_sources = [u"Your names", u"Your emails", u"ROLES_OF_TRANSLATORS"]
1123 )
1124
1135
1136 cclicenseconfig = CheckerConfig(varmatches = [("@", "@")])
1145
1146 projectcheckers = {
1147 "openoffice": OpenOfficeChecker,
1148 "mozilla": MozillaChecker,
1149 "kde": KdeChecker,
1150 "wx": KdeChecker,
1151 "gnome": GnomeChecker,
1152 "creativecommons": CCLicenseChecker,
1153 "drupal": DrupalChecker,
1154 }
1155
1156
1158 """The standard checks for common checks on translation units."""
1160 """Check if the unit has been marked fuzzy."""
1161 return not unit.isfuzzy()
1162
1164 """Check if the unit has been marked review."""
1165 return not unit.isreview()
1166
1175
1177 """Checks if there is at least one suggested translation for this unit."""
1178 self.suggestion_store = getattr(self, 'suggestion_store', None)
1179 suggestions = []
1180 if self.suggestion_store:
1181 source = unit.source
1182 suggestions = [unit for unit in self.suggestion_store.units if unit.source == source]
1183 elif xliff and isinstance(unit, xliff.xliffunit):
1184
1185 suggestions = unit.getalttrans()
1186 return not bool(suggestions)
1187
1188
1189 -def runtests(str1, str2, ignorelist=()):
1201
1203 """runs test on a batch of string pairs"""
1204 passed, numpairs = 0, len(pairs)
1205 for str1, str2 in pairs:
1206 if runtests(str1, str2):
1207 passed += 1
1208 print
1209 print "total: %d/%d pairs passed" % (passed, numpairs)
1210
1211 if __name__ == '__main__':
1212 testset = [(r"simple", r"somple"),
1213 (r"\this equals \that", r"does \this equal \that?"),
1214 (r"this \'equals\' that", r"this 'equals' that"),
1215 (r" start and end! they must match.", r"start and end! they must match."),
1216 (r"check for matching %variables marked like %this", r"%this %variable is marked"),
1217 (r"check for mismatching %variables marked like %this", r"%that %variable is marked"),
1218 (r"check for mismatching %variables% too", r"how many %variable% are marked"),
1219 (r"%% %%", r"%%"),
1220 (r"Row: %1, Column: %2", r"Mothalo: %1, Kholomo: %2"),
1221 (r"simple lowercase", r"it is all lowercase"),
1222 (r"simple lowercase", r"It Is All Lowercase"),
1223 (r"Simple First Letter Capitals", r"First Letters"),
1224 (r"SIMPLE CAPITALS", r"First Letters"),
1225 (r"SIMPLE CAPITALS", r"ALL CAPITALS"),
1226 (r"forgot to translate", r" ")
1227 ]
1228 batchruntests(testset)
1229