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 import re
47
48
49
50
51
52
53 printf_pat = re.compile('%((?:(?P<ord>\d+)\$|\((?P<key>\w+)\))?(?P<fullvar>[+#-]*(?:\d+)?(?:\.\d+)?(hh\|h\|l\|ll)?(?P<type>[\w%])))')
54
55
56 tagname_re = re.compile("<[\s]*([\w\/]*)")
57
58
59
60 property_re = re.compile(" (\w*)=((\\\\?\".*?\\\\?\")|(\\\\?'.*?\\\\?'))")
61
62
63 tag_re = re.compile("<[^>]+>")
64
66 """Returns the name of the XML/HTML tag in string"""
67 return tagname_re.match(string).groups(1)[0]
68
70 """Tests to see if pair == (a,b,c) is in list, but handles None entries in
71 list as wildcards (only allowed in positions "a" and "c"). We take a shortcut
72 by only considering "c" if "b" has already matched."""
73 a, b, c = pair
74 if (b, c) == (None, None):
75
76 return pair
77 for pattern in list:
78 x, y, z = pattern
79 if (x, y) in [(a, b), (None, b)]:
80 if z in [None, c]:
81 return pattern
82 return pair
83
85 """Returns all the properties in the XML/HTML tag string as
86 (tagname, propertyname, propertyvalue), but ignore those combinations
87 specified in ignore."""
88 properties = []
89 for string in strings:
90 tag = tagname(string)
91 properties += [(tag, None, None)]
92
93 pairs = property_re.findall(string)
94 for property, value, a, b in pairs:
95
96 value = value[1:-1]
97
98 canignore = False
99 if (tag, property, value) in ignore or \
100 intuplelist((tag,property,value), ignore) != (tag,property,value):
101 canignore = True
102 break
103 if not canignore:
104 properties += [(tag, property, value)]
105 return properties
106
107
109 """This exception signals that a Filter didn't pass, and gives an explanation
110 or a comment"""
112 if not isinstance(messages, list):
113 messages = [messages]
114 assert isinstance(messages[0], unicode)
115 Exception.__init__(self, u", ".join(messages))
116
118 """This exception signals that a Filter didn't pass, and the bad translation
119 might break an application (so the string will be marked fuzzy)"""
120 pass
121
122
123
124
125
126
127
128
129 common_ignoretags = [(None, "xml-lang", None)]
130 common_canchangetags = [("img", "alt", None)]
131
133 """object representing the configuration of a checker"""
134 - def __init__(self, targetlanguage=None, accelmarkers=None, varmatches=None,
135 notranslatewords=None, musttranslatewords=None, validchars=None,
136 punctuation=None, endpunctuation=None, ignoretags=None,
137 canchangetags=None, criticaltests=None, credit_sources=None):
159
161 """initialise configuration paramaters that are lists
162
163 @type list: List
164 @param list: None (we'll initialise a blank list) or a list paramater
165 @rtype: List
166 """
167 if list is None:
168 list = []
169 return list
170
172 """initialise parameters that can have default options
173
174 @param param: the user supplied paramater value
175 @param default: default values when param is not specified
176 @return: the paramater as specified by the user of the default settings
177 """
178 if param is None:
179 return default
180 return param
181
182 - def update(self, otherconfig):
183 """combines the info in otherconfig into this config object"""
184 self.targetlanguage = otherconfig.targetlanguage or self.targetlanguage
185 self.updatetargetlanguage(self.targetlanguage)
186 self.accelmarkers.extend([c for c in otherconfig.accelmarkers if not c in self.accelmarkers])
187 self.varmatches.extend(otherconfig.varmatches)
188 self.notranslatewords.update(otherconfig.notranslatewords)
189 self.musttranslatewords.update(otherconfig.musttranslatewords)
190 self.validcharsmap.update(otherconfig.validcharsmap)
191 self.punctuation += otherconfig.punctuation
192 self.endpunctuation += otherconfig.endpunctuation
193
194 self.ignoretags = otherconfig.ignoretags
195 self.canchangetags = otherconfig.canchangetags
196 self.criticaltests.extend(otherconfig.criticaltests)
197 self.credit_sources = otherconfig.credit_sources
198
200 """updates the map that eliminates valid characters"""
201 if validchars is None:
202 return True
203 validcharsmap = dict([(ord(validchar), None) for validchar in data.forceunicode(validchars)])
204 self.validcharsmap.update(validcharsmap)
205
207 """Updates the target language in the config to the given target language"""
208 self.lang = factory.getlanguage(langcode)
209
211 def cached_f(self, param1):
212 key = (f.__name__, param1)
213 res_cache = self.results_cache
214 if key in res_cache:
215 return res_cache[key]
216 else:
217 value = f(self, param1)
218 res_cache[key] = value
219 return value
220 return cached_f
221
223 """Parent Checker class which does the checking based on functions available
224 in derived classes."""
225 preconditions = {}
226
227 - def __init__(self, checkerconfig=None, excludefilters=None, limitfilters=None, errorhandler=None):
228 self.errorhandler = errorhandler
229 if checkerconfig is None:
230 self.setconfig(CheckerConfig())
231 else:
232 self.setconfig(checkerconfig)
233
234 self.helperfunctions = {}
235 for functionname in dir(UnitChecker):
236 function = getattr(self, functionname)
237 if callable(function):
238 self.helperfunctions[functionname] = function
239 self.defaultfilters = self.getfilters(excludefilters, limitfilters)
240
241 self.results_cache = {}
242
243 - def getfilters(self, excludefilters=None, limitfilters=None):
244 """returns dictionary of available filters, including/excluding those in
245 the given lists"""
246 filters = {}
247 if limitfilters is None:
248
249 limitfilters = dir(self)
250 if excludefilters is None:
251 excludefilters = {}
252 for functionname in limitfilters:
253 if functionname in excludefilters: continue
254 if functionname in self.helperfunctions: continue
255 if functionname == "errorhandler": continue
256 filterfunction = getattr(self, functionname, None)
257 if not callable(filterfunction): continue
258 filters[functionname] = filterfunction
259 return filters
260
269
271 """Sets the filename that a checker should use for evaluating suggestions."""
272 self.suggestion_store = store
273
275 """filter out variables from str1"""
276 return helpers.multifilter(str1, self.varfilters)
277 filtervariables = cache_results(filtervariables)
278
280 """remove variables from str1"""
281 return helpers.multifilter(str1, self.removevarfilter)
282 removevariables = cache_results(removevariables)
283
285 """filter out accelerators from str1"""
286 return helpers.multifilter(str1, self.accfilters, None)
287 filteraccelerators = cache_results(filteraccelerators)
288
290 """filter out accelerators from str1"""
291 return helpers.multifilter(str1, self.accfilters, acceptlist)
292
296 filterwordswithpunctuation = cache_results(filterwordswithpunctuation)
297
299 """filter out XML from the string so only text remains"""
300 return tag_re.sub("", str1)
301 filterxml = cache_results(filterxml)
302
304 """Runs the given test on the given unit.
305
306 Note that this can raise a FilterFailure as part of normal operation"""
307 return test(unit)
308
310 """run all the tests in this suite, return failures as testname, message_or_exception"""
311 self.results_cache = {}
312 failures = {}
313 ignores = self.config.lang.ignoretests[:]
314 functionnames = self.defaultfilters.keys()
315 priorityfunctionnames = self.preconditions.keys()
316 otherfunctionnames = filter(lambda functionname: functionname not in self.preconditions, functionnames)
317 for functionname in priorityfunctionnames + otherfunctionnames:
318 if functionname in ignores:
319 continue
320 filterfunction = getattr(self, functionname, None)
321
322 if filterfunction is None:
323 continue
324 filtermessage = filterfunction.__doc__
325 try:
326 filterresult = self.run_test(filterfunction, unit)
327 except FilterFailure, e:
328 filterresult = False
329 filtermessage = e.args[0]
330 except Exception, e:
331 if self.errorhandler is None:
332 raise ValueError("error in filter %s: %r, %r, %s" % \
333 (functionname, unit.source, unit.target, e))
334 else:
335 filterresult = self.errorhandler(functionname, unit.source, unit.target, e)
336 if not filterresult:
337
338 if functionname in self.defaultfilters:
339 failures[functionname] = filtermessage
340 if functionname in self.preconditions:
341 for ignoredfunctionname in self.preconditions[functionname]:
342 ignores.append(ignoredfunctionname)
343 self.results_cache = {}
344 return failures
345
347 """A checker that passes source and target strings to the checks, not the
348 whole unit.
349
350 This provides some speedup and simplifies testing."""
351 - def __init__(self, checkerconfig=None, excludefilters=None, limitfilters=None, errorhandler=None):
353
355 """Runs the given test on the given unit.
356
357 Note that this can raise a FilterFailure as part of normal operation."""
358 if self.hasplural:
359 filtermessages = []
360 filterresult = True
361 for pluralform in unit.target.strings:
362 try:
363 if not test(self.str1, pluralform):
364 filterresult = False
365 except FilterFailure, e:
366 filterresult = False
367 filtermessages.append( str(e).decode('utf-8') )
368 if not filterresult and filtermessages:
369 raise FilterFailure(filtermessages)
370 else:
371 return filterresult
372 else:
373 return test(self.str1, self.str2)
374
382
384 """A Checker that controls multiple checkers."""
385 - def __init__(self, checkerconfig=None, excludefilters=None, limitfilters=None,
386 checkerclasses=None, errorhandler=None, languagecode=None):
402
403 - def getfilters(self, excludefilters=None, limitfilters=None):
404 """returns dictionary of available filters, including/excluding those in
405 the given lists"""
406 if excludefilters is None:
407 excludefilters = {}
408 filterslist = [checker.getfilters(excludefilters, limitfilters) for checker in self.checkers]
409 self.combinedfilters = {}
410 for filters in filterslist:
411 self.combinedfilters.update(filters)
412
413 if limitfilters is not None:
414 for filtername in limitfilters:
415 if not filtername in self.combinedfilters:
416 import sys
417 print >> sys.stderr, "warning: could not find filter %s" % filtername
418 return self.combinedfilters
419
421 """run all the tests in the checker's suites"""
422 failures = {}
423 for checker in self.checkers:
424 failures.update(checker.run_filters(unit))
425 return failures
426
428 """Sets the filename that a checker should use for evaluating suggestions."""
429 for checker in self.checkers:
430 checker.setsuggestionstore(store)
431
432
434 """The basic test suite for source -> target translations."""
436 """checks whether a string has been translated at all"""
437 str2 = prefilters.removekdecomments(str2)
438 return not (len(str1.strip()) > 0 and len(str2) == 0)
439
441 """checks whether a translation is basically identical to the original string"""
442 str1 = self.removevariables(self.filteraccelerators(str1)).strip()
443 str2 = self.removevariables(self.filteraccelerators(str2)).strip()
444 if len(str1) < 2:
445 return True
446 if str1.isupper() and str1 == str2:
447 return True
448 if self.config.notranslatewords:
449 words1 = str1.split()
450 if len(words1) == 1 and [word for word in words1 if word in self.config.notranslatewords]:
451
452
453
454 return True
455 if str1.isalpha() and str1.lower() == str2.lower():
456 raise FilterFailure(u"please translate")
457 return True
458
459 - def blank(self, str1, str2):
460 """checks whether a translation only contains spaces"""
461 len1 = len(str1.strip())
462 len2 = len(str2.strip())
463 return not (len1 > 0 and len(str2) != 0 and len2 == 0)
464
465 - def short(self, str1, str2):
466 """checks whether a translation is much shorter than the original string"""
467 len1 = len(str1.strip())
468 len2 = len(str2.strip())
469 return not ((len1 > 0) and (0 < len2 < (len1 * 0.1)) or ((len1 > 1) and (len2 == 1)))
470
471 - def long(self, str1, str2):
472 """checks whether a translation is much longer than the original string"""
473 len1 = len(str1.strip())
474 len2 = len(str2.strip())
475 return not ((len1 > 0) and (0 < len1 < (len2 * 0.1)) or ((len1 == 1) and (len2 > 1)))
476
478 """checks whether escaping is consistent between the two strings"""
479 if not helpers.countsmatch(str1, str2, ("\\", "\\\\")):
480 escapes1 = u", ".join([u"'%s'" % word for word in str1.split() if "\\" in word])
481 escapes2 = u", ".join([u"'%s'" % word for word in str2.split() if "\\" in word])
482 raise SeriousFilterFailure(u"escapes in original (%s) don't match escapes in translation (%s)" % (escapes1, escapes2))
483 else:
484 return True
485
487 """checks whether newlines are consistent between the two strings"""
488 if not helpers.countsmatch(str1, str2, ("\n", "\r")):
489 raise FilterFailure(u"line endings in original don't match line endings in translation")
490 else:
491 return True
492
493 - def tabs(self, str1, str2):
494 """checks whether tabs are consistent between the two strings"""
495 if not helpers.countmatch(str1, str2, "\t"):
496 raise SeriousFilterFailure(u"tabs in original don't match tabs in translation")
497 else:
498 return True
499
500
506
515
521
523 """checks for bad spacing after punctuation"""
524 if str1.find(u" ") == -1:
525 return True
526 str1 = self.filteraccelerators(self.filtervariables(str1))
527 str1 = self.config.lang.punctranslate(str1)
528 str2 = self.filteraccelerators(self.filtervariables(str2))
529 for puncchar in self.config.punctuation:
530 plaincount1 = str1.count(puncchar)
531 plaincount2 = str2.count(puncchar)
532 if not plaincount1 or plaincount1 != plaincount2:
533 continue
534 spacecount1 = str1.count(puncchar+" ")
535 spacecount2 = str2.count(puncchar+" ")
536 if spacecount1 != spacecount2:
537
538 if str1.endswith(puncchar) != str2.endswith(puncchar) and abs(spacecount1-spacecount2) == 1:
539 continue
540 return False
541 return True
542
543 - def printf(self, str1, str2):
544 """checks whether printf format strings match"""
545 count1 = count2 = plural = None
546
547 if 'hasplural' in self.__dict__:
548 plural = self.hasplural
549 for var_num2, match2 in enumerate(printf_pat.finditer(str2)):
550 count2 = var_num2 + 1
551 str2key = match2.group('key')
552 if match2.group('ord'):
553 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
554 count1 = var_num1 + 1
555 if int(match2.group('ord')) == var_num1 + 1:
556 if match2.group('fullvar') != match1.group('fullvar'):
557 return 0
558 elif str2key:
559 str1key = None
560 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
561 count1 = var_num1 + 1
562 if match1.group('key') and str2key == match1.group('key'):
563 str1key = match1.group('key')
564
565 if plural and match2.group('fullvar') == '.0s':
566 continue
567 if match1.group('fullvar') != match2.group('fullvar'):
568 return 0
569 if str1key == None:
570 return 0
571 else:
572 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
573 count1 = var_num1 + 1
574
575 if plural and match2.group('fullvar') == '.0s':
576 continue
577 if (var_num1 == var_num2) and (match1.group('fullvar') != match2.group('fullvar')):
578 return 0
579
580 if count2 is None:
581 if list(printf_pat.finditer(str1)):
582 return 0
583
584 if (count1 or count2) and (count1 != count2):
585 return 0
586 return 1
587
589 """checks whether accelerators are consistent between the two strings"""
590 str1 = self.filtervariables(str1)
591 str2 = self.filtervariables(str2)
592 messages = []
593 for accelmarker in self.config.accelmarkers:
594 counter1 = decoration.countaccelerators(accelmarker, self.config.sourcelang.validaccel)
595 counter2 = decoration.countaccelerators(accelmarker, self.config.lang.validaccel)
596 count1, countbad1 = counter1(str1)
597 count2, countbad2 = counter2(str2)
598 getaccel = decoration.getaccelerators(accelmarker, self.config.lang.validaccel)
599 accel2, bad2 = getaccel(str2)
600 if count1 == count2:
601 continue
602 if count1 == 1 and count2 == 0:
603 if countbad2 == 1:
604 messages.append(u"accelerator %s appears before an invalid accelerator character '%s' (eg. space)" % (accelmarker, bad2[0]))
605 else:
606 messages.append(u"accelerator %s is missing from translation" % accelmarker)
607 elif count1 == 0:
608 messages.append(u"accelerator %s does not occur in original and should not be in translation" % accelmarker)
609 elif count1 == 1 and count2 > count1:
610 messages.append(u"accelerator %s is repeated in translation" % accelmarker)
611 else:
612 messages.append(u"accelerator %s occurs %d time(s) in original and %d time(s) in translation" % (accelmarker, count1, count2))
613 if messages:
614 if "accelerators" in self.config.criticaltests:
615 raise SeriousFilterFailure(messages)
616 else:
617 raise FilterFailure(messages)
618 return True
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
635 """checks whether variables of various forms are consistent between the two strings"""
636 messages = []
637 mismatch1, mismatch2 = [], []
638 varnames1, varnames2 = [], []
639 for startmarker, endmarker in self.config.varmatches:
640 varchecker = decoration.getvariables(startmarker, endmarker)
641 if startmarker and endmarker:
642 if isinstance(endmarker, int):
643 redecorate = lambda var: startmarker + var
644 else:
645 redecorate = lambda var: startmarker + var + endmarker
646 elif startmarker:
647 redecorate = lambda var: startmarker + var
648 else:
649 redecorate = lambda var: var
650 vars1 = varchecker(str1)
651 vars2 = varchecker(str2)
652 if vars1 != vars2:
653
654 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)]
655
656 vars1, vars2 = [var for var in vars1 if var not in varnames1], [var for var in vars2 if var not in varnames2]
657 varnames1.extend(vars1)
658 varnames2.extend(vars2)
659 vars1 = map(redecorate, vars1)
660 vars2 = map(redecorate, vars2)
661 mismatch1.extend(vars1)
662 mismatch2.extend(vars2)
663 if mismatch1:
664 messages.append(u"do not translate: %s" % ", ".join(mismatch1))
665 elif mismatch2:
666 messages.append(u"translation contains variables not in original: %s" % ", ".join(mismatch2))
667 if messages and mismatch1:
668 raise SeriousFilterFailure(messages)
669 elif messages:
670 raise FilterFailure(messages)
671 return True
672
676
677 - def emails(self, str1, str2):
680
681 - def urls(self, str1, str2):
684
688
694
700
707
714
722
724 """checks that the number of brackets in both strings match"""
725 str1 = self.filtervariables(str1)
726 str2 = self.filtervariables(str2)
727 messages = []
728 missing = []
729 extra = []
730 for bracket in ("[", "]", "{", "}", "(", ")"):
731 count1 = str1.count(bracket)
732 count2 = str2.count(bracket)
733 if count2 < count1:
734 missing.append("'%s'" % bracket)
735 elif count2 > count1:
736 extra.append("'%s'" % bracket)
737 if missing:
738 messages.append(u"translation is missing %s" % ", ".join(missing))
739 if extra:
740 messages.append(u"translation has extra %s" % ", ".join(extra))
741 if messages:
742 raise FilterFailure(messages)
743 return True
744
746 """checks that the number of sentences in both strings match"""
747 sentences1 = len(self.config.sourcelang.sentences(str1))
748 sentences2 = len(self.config.lang.sentences(str2))
749 if not sentences1 == sentences2:
750 raise FilterFailure(u"The number of sentences differ: %d versus %d" % (sentences1, sentences2))
751 return True
752
754 """checks that options are not translated"""
755 str1 = self.filtervariables(str1)
756 for word1 in str1.split():
757 if word1 != "--" and word1.startswith("--") and word1[-1].isalnum():
758 parts = word1.split("=")
759 if not parts[0] in str2:
760 raise FilterFailure("The option %s does not occur or is translated in the translation." % parts[0])
761 if len(parts) > 1 and parts[1] in str2:
762 raise FilterFailure("The parameter %(param)s in option %(option)s is not translated." % {"param": parts[0], "option": parts[1]})
763 return True
764
766 """checks that the message starts with the correct capitalisation"""
767 str1 = self.filteraccelerators(str1)
768 str2 = self.filteraccelerators(str2)
769 if len(str1) > 1 and len(str2) > 1:
770 return self.config.sourcelang.capsstart(str1) == self.config.lang.capsstart(str2)
771 if len(str1) == 0 and len(str2) == 0:
772 return True
773 if len(str1) == 0 or len(str2) == 0:
774 return False
775 return True
776
778 """checks the capitalisation of two strings isn't wildly different"""
779 str1 = self.removevariables(str1)
780 str2 = self.removevariables(str2)
781
782
783 str1 = re.sub(u"[^%s]( I )" % self.config.sourcelang.sentenceend, " i ", str1)
784 capitals1 = helpers.filtercount(str1, unicode.isupper)
785 capitals2 = helpers.filtercount(str2, unicode.isupper)
786 alpha1 = helpers.filtercount(str1, unicode.isalpha)
787 alpha2 = helpers.filtercount(str2, unicode.isalpha)
788
789 if capitals1 == alpha1:
790 return capitals2 == alpha2
791
792 if capitals1 == 0 or capitals1 == 1:
793 return capitals2 == capitals1
794 elif capitals1 < len(str1) / 10:
795 return capitals2 <= len(str2) / 8
796 elif len(str1) < 10:
797 return abs(capitals1 - capitals2) < 3
798 elif capitals1 > len(str1) * 6 / 10:
799 return capitals2 > len(str2) * 6 / 10
800 else:
801 return abs(capitals1 - capitals2) < (len(str1) + len(str2)) / 6
802
824
835
853
872
874 """checks that only characters specified as valid appear in the translation"""
875 if not self.config.validcharsmap:
876 return True
877 invalid1 = str1.translate(self.config.validcharsmap)
878 invalid2 = str2.translate(self.config.validcharsmap)
879 invalidchars = [u"'%s' (\\u%04x)" % (invalidchar, ord(invalidchar)) for invalidchar in invalid2 if invalidchar not in invalid1]
880 if invalidchars:
881 raise FilterFailure(u"invalid chars: %s" % (u", ".join(invalidchars)))
882 return True
883
885 """checks that file paths have not been translated"""
886 for word1 in self.filteraccelerators(str1).split():
887 if word1.startswith("/"):
888 if not helpers.countsmatch(str1, str2, (word1,)):
889 return False
890 return True
891
918
922
924 """checks for Gettext compendium conflicts (#-#-#-#-#)"""
925 return str2.find("#-#-#-#-#") == -1
926
928 """checks for English style plural(s) for you to review"""
929 def numberofpatterns(string, patterns):
930 number = 0
931 for pattern in patterns:
932 number += len(re.findall(pattern, string))
933 return number
934
935 sourcepatterns = ["\(s\)"]
936 targetpatterns = ["\(s\)"]
937 sourcecount = numberofpatterns(str1, sourcepatterns)
938 targetcount = numberofpatterns(str2, targetpatterns)
939 if self.config.lang.nplurals == 1:
940 return not targetcount
941 return sourcecount == targetcount
942
944 """checks words that don't pass a spell check"""
945 if not self.config.targetlanguage:
946 return True
947 if not spelling.available:
948 return True
949 str1 = self.filteraccelerators_by_list(self.filtervariables(str1), self.config.sourcelang.validaccel)
950 str2 = self.filteraccelerators_by_list(self.filtervariables(str2), self.config.lang.validaccel)
951 ignore1 = []
952 messages = []
953 for word, index, suggestions in spelling.check(str1, lang="en"):
954 ignore1.append(word)
955 for word, index, suggestions in spelling.check(str2, lang=self.config.targetlanguage):
956 if word in self.config.notranslatewords:
957 continue
958 if word in ignore1:
959 continue
960
961 if word in suggestions:
962 continue
963 messages.append(u"check spelling of %s (could be %s)" % (word, u" / ".join(suggestions)))
964 if messages:
965 raise FilterFailure(messages)
966 return True
967
969 """checks for messages containing translation credits instead of normal translations."""
970 return not str1 in self.config.credit_sources
971
972
973 preconditions = {"untranslated": ("simplecaps", "variables", "startcaps",
974 "accelerators", "brackets", "endpunc",
975 "acronyms", "xmltags", "startpunc",
976 "endwhitespace", "startwhitespace",
977 "escapes", "doublequoting", "singlequoting",
978 "filepaths", "purepunc", "doublespacing",
979 "sentencecount", "numbers", "isfuzzy",
980 "isreview", "notranslatewords", "musttranslatewords",
981 "emails", "simpleplurals", "urls", "printf",
982 "tabs", "newlines", "functions", "options",
983 "blank", "nplurals"),
984 "blank": ("simplecaps", "variables", "startcaps",
985 "accelerators", "brackets", "endpunc",
986 "acronyms", "xmltags", "startpunc",
987 "endwhitespace", "startwhitespace",
988 "escapes", "doublequoting", "singlequoting",
989 "filepaths", "purepunc", "doublespacing",
990 "sentencecount", "numbers", "isfuzzy",
991 "isreview", "notranslatewords", "musttranslatewords",
992 "emails", "simpleplurals", "urls", "printf",
993 "tabs", "newlines", "functions", "options"),
994 "credits": ("simplecaps", "variables", "startcaps",
995 "accelerators", "brackets", "endpunc",
996 "acronyms", "xmltags", "startpunc",
997 "escapes", "doublequoting", "singlequoting",
998 "filepaths", "doublespacing",
999 "sentencecount", "numbers",
1000 "emails", "simpleplurals", "urls", "printf",
1001 "tabs", "newlines", "functions", "options"),
1002 "purepunc": ("startcaps", "options"),
1003 "startcaps": ("simplecaps",),
1004 "endwhitespace": ("endpunc",),
1005 "startwhitespace":("startpunc",),
1006 "unchanged": ("doublewords",),
1007 "compendiumconflicts": ("accelerators", "brackets", "escapes",
1008 "numbers", "startpunc", "long", "variables",
1009 "startcaps", "sentencecount", "simplecaps",
1010 "doublespacing", "endpunc", "xmltags",
1011 "startwhitespace", "endwhitespace",
1012 "singlequoting", "doublequoting",
1013 "filepaths", "purepunc", "doublewords", "printf") }
1014
1015
1016
1017 openofficeconfig = CheckerConfig(
1018 accelmarkers = ["~"],
1019 varmatches = [("&", ";"), ("%", "%"), ("%", None), ("%", 0), ("$(", ")"), ("$", "$"), ("${", "}"), ("#", "#"), ("#", 1), ("#", 0), ("($", ")"), ("$[", "]"), ("[", "]"), ("$", None)],
1020 ignoretags = [("alt", "xml-lang", None), ("ahelp", "visibility", "visible"), ("img", "width", None), ("img", "height", None)],
1021 canchangetags = [("link", "name", None)]
1022 )
1023
1032
1033 mozillaconfig = CheckerConfig(
1034 accelmarkers = ["&"],
1035 varmatches = [("&", ";"), ("%", "%"), ("%", 1), ("$", "$"), ("$", None), ("#", 1), ("${", "}"), ("$(^", ")")],
1036 criticaltests = ["accelerators"]
1037 )
1038
1047
1048 gnomeconfig = CheckerConfig(
1049 accelmarkers = ["_"],
1050 varmatches = [("%", 1), ("$(", ")")],
1051 credit_sources = [u"translator-credits"]
1052 )
1053
1062
1063 kdeconfig = CheckerConfig(
1064 accelmarkers = ["&"],
1065 varmatches = [("%", 1)],
1066 credit_sources = [u"Your names", u"Your emails", u"ROLES_OF_TRANSLATORS"]
1067 )
1068
1079
1080 cclicenseconfig = CheckerConfig(varmatches = [("@", "@")])
1089
1090 projectcheckers = {
1091 "openoffice": OpenOfficeChecker,
1092 "mozilla": MozillaChecker,
1093 "kde": KdeChecker,
1094 "wx": KdeChecker,
1095 "gnome": GnomeChecker,
1096 "creativecommons": CCLicenseChecker
1097 }
1098
1099
1101 """The standard checks for common checks on translation units."""
1103 """Check if the unit has been marked fuzzy."""
1104 return not unit.isfuzzy()
1105
1107 """Check if the unit has been marked review."""
1108 return not unit.isreview()
1109
1111 """Checks for the correct number of noun forms for plural translations."""
1112 if unit.hasplural():
1113
1114 nplurals = self.config.lang.nplurals
1115 if nplurals > 0:
1116 return len(unit.target.strings) == nplurals
1117 return True
1118
1120 """Checks if there is at least one suggested translation for this unit."""
1121 self.suggestion_store = getattr(self, 'suggestion_store', None)
1122 suggestions = []
1123 if self.suggestion_store:
1124 source = unit.source
1125 suggestions = [unit for unit in self.suggestion_store.units if unit.source == source]
1126 elif xliff and isinstance(unit, xliff.xliffunit):
1127
1128 suggestions = unit.getalttrans()
1129 return not bool(suggestions)
1130
1131
1132 -def runtests(str1, str2, ignorelist=()):
1144
1146 """runs test on a batch of string pairs"""
1147 passed, numpairs = 0, len(pairs)
1148 for str1, str2 in pairs:
1149 if runtests(str1, str2):
1150 passed += 1
1151 print
1152 print "total: %d/%d pairs passed" % (passed, numpairs)
1153
1154 if __name__ == '__main__':
1155 testset = [(r"simple", r"somple"),
1156 (r"\this equals \that", r"does \this equal \that?"),
1157 (r"this \'equals\' that", r"this 'equals' that"),
1158 (r" start and end! they must match.", r"start and end! they must match."),
1159 (r"check for matching %variables marked like %this", r"%this %variable is marked"),
1160 (r"check for mismatching %variables marked like %this", r"%that %variable is marked"),
1161 (r"check for mismatching %variables% too", r"how many %variable% are marked"),
1162 (r"%% %%", r"%%"),
1163 (r"Row: %1, Column: %2", r"Mothalo: %1, Kholomo: %2"),
1164 (r"simple lowercase", r"it is all lowercase"),
1165 (r"simple lowercase", r"It Is All Lowercase"),
1166 (r"Simple First Letter Capitals", r"First Letters"),
1167 (r"SIMPLE CAPITALS", r"First Letters"),
1168 (r"SIMPLE CAPITALS", r"ALL CAPITALS"),
1169 (r"forgot to translate", r" ")
1170 ]
1171 batchruntests(testset)
1172