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 printf_pat = re.compile('%((?:(?P<ord>\d+)\$)*(?P<fullvar>[+#-]*(?:\d+)*(?:\.\d+)*(hh\|h\|l\|ll)*(?P<type>[\w%])))')
53
54
55 tagname_re = re.compile("<[\s]*([\w\/]*)")
56
57
58
59 property_re = re.compile(" (\w*)=((\\\\?\".*?\\\\?\")|(\\\\?'.*?\\\\?'))")
60
61
62 tag_re = re.compile("<[^>]+>")
63
65 """Returns the name of the XML/HTML tag in string"""
66 return tagname_re.match(string).groups(1)[0]
67
69 """Tests to see if pair == (a,b,c) is in list, but handles None entries in
70 list as wildcards (only allowed in positions "a" and "c"). We take a shortcut
71 by only considering "c" if "b" has already matched."""
72 a, b, c = pair
73 if (b, c) == (None, None):
74
75 return pair
76 for pattern in list:
77 x, y, z = pattern
78 if (x, y) in [(a, b), (None, b)]:
79 if z in [None, c]:
80 return pattern
81 return pair
82
84 """Returns all the properties in the XML/HTML tag string as
85 (tagname, propertyname, propertyvalue), but ignore those combinations
86 specified in ignore."""
87 properties = []
88 for string in strings:
89 tag = tagname(string)
90 properties += [(tag, None, None)]
91
92 pairs = property_re.findall(string)
93 for property, value, a, b in pairs:
94
95 value = value[1:-1]
96
97 canignore = False
98 if (tag, property, value) in ignore or \
99 intuplelist((tag,property,value), ignore) != (tag,property,value):
100 canignore = True
101 break
102 if not canignore:
103 properties += [(tag, property, value)]
104 return properties
105
106
108 """This exception signals that a Filter didn't pass, and gives an explanation
109 or a comment"""
111 if not isinstance(messages, list):
112 messages = [messages]
113 strmessages = []
114 for message in messages:
115 if isinstance(message, unicode):
116 message = message.encode("utf-8")
117 strmessages.append(message)
118 messages = ", ".join(strmessages)
119 Exception.__init__(self, messages)
120
122 """This exception signals that a Filter didn't pass, and the bad translation
123 might break an application (so the string will be marked fuzzy)"""
124 pass
125
126
127
128
129
130
131
132
133 common_ignoretags = [(None, "xml-lang", None)]
134 common_canchangetags = [("img", "alt", None)]
135
137 """object representing the configuration of a checker"""
138 - def __init__(self, targetlanguage=None, accelmarkers=None, varmatches=None,
139 notranslatewords=None, musttranslatewords=None, validchars=None,
140 punctuation=None, endpunctuation=None, ignoretags=None,
141 canchangetags=None, criticaltests=None, credit_sources=None):
184
185 - def update(self, otherconfig):
186 """combines the info in otherconfig into this config object"""
187 self.targetlanguage = otherconfig.targetlanguage or self.targetlanguage
188 self.updatetargetlanguage(self.targetlanguage)
189 self.accelmarkers.extend([c for c in otherconfig.accelmarkers if not c in self.accelmarkers])
190 self.varmatches.extend(otherconfig.varmatches)
191 self.notranslatewords.update(otherconfig.notranslatewords)
192 self.musttranslatewords.update(otherconfig.musttranslatewords)
193 self.validcharsmap.update(otherconfig.validcharsmap)
194 self.punctuation += otherconfig.punctuation
195 self.endpunctuation += otherconfig.endpunctuation
196
197 self.ignoretags = otherconfig.ignoretags
198 self.canchangetags = otherconfig.canchangetags
199 self.criticaltests.extend(otherconfig.criticaltests)
200 self.credit_sources = otherconfig.credit_sources
201
203 """updates the map that eliminates valid characters"""
204 if validchars is None:
205 return True
206 validcharsmap = dict([(ord(validchar), None) for validchar in data.forceunicode(validchars)])
207 self.validcharsmap.update(validcharsmap)
208
210 """Updates the target language in the config to the given target language"""
211 self.lang = factory.getlanguage(langcode)
212
214 """Parent Checker class which does the checking based on functions available
215 in derived classes."""
216 preconditions = {}
217
218 - def __init__(self, checkerconfig=None, excludefilters=None, limitfilters=None, errorhandler=None):
231
232 - def getfilters(self, excludefilters=None, limitfilters=None):
233 """returns dictionary of available filters, including/excluding those in
234 the given lists"""
235 filters = {}
236 if limitfilters is None:
237
238 limitfilters = dir(self)
239 if excludefilters is None:
240 excludefilters = {}
241 for functionname in limitfilters:
242 if functionname in excludefilters: continue
243 if functionname in self.helperfunctions: continue
244 if functionname == "errorhandler": continue
245 filterfunction = getattr(self, functionname, None)
246 if not callable(filterfunction): continue
247 filters[functionname] = filterfunction
248 return filters
249
258
260 """Sets the filename that a checker should use for evaluating suggestions."""
261 self.suggestion_store = store
262
264 """filter out variables from str1"""
265 return helpers.multifilter(str1, self.varfilters)
266
268 """remove variables from str1"""
269 return helpers.multifilter(str1, self.removevarfilter)
270
272 """filter out accelerators from str1"""
273 return helpers.multifilter(str1, self.accfilters)
274
278
280 """filter out XML from the string so only text remains"""
281 return tag_re.sub("", str1)
282
284 """Runs the given test on the given unit.
285
286 Note that this can raise a FilterFailure as part of normal operation"""
287 return test(unit)
288
290 """run all the tests in this suite, return failures as testname, message_or_exception"""
291 failures = {}
292 ignores = self.config.lang.ignoretests[:]
293 functionnames = self.defaultfilters.keys()
294 priorityfunctionnames = self.preconditions.keys()
295 otherfunctionnames = filter(lambda functionname: functionname not in self.preconditions, functionnames)
296 for functionname in priorityfunctionnames + otherfunctionnames:
297 if functionname in ignores:
298 continue
299 filterfunction = getattr(self, functionname, None)
300
301 if filterfunction is None:
302 continue
303 filtermessage = filterfunction.__doc__
304 try:
305 filterresult = self.run_test(filterfunction, unit)
306 except FilterFailure, e:
307 filterresult = False
308 filtermessage = str(e).decode('utf-8')
309 except Exception, e:
310 if self.errorhandler is None:
311 raise ValueError("error in filter %s: %r, %r, %s" % \
312 (functionname, unit.source, unit.target, e))
313 else:
314 filterresult = self.errorhandler(functionname, unit.source, unit.target, e)
315 if not filterresult:
316
317 if functionname in self.defaultfilters:
318 failures[functionname] = filtermessage
319 if functionname in self.preconditions:
320 for ignoredfunctionname in self.preconditions[functionname]:
321 ignores.append(ignoredfunctionname)
322 return failures
323
325 """A checker that passes source and target strings to the checks, not the
326 whole unit.
327
328 This provides some speedup and simplifies testing."""
329 - def __init__(self, checkerconfig=None, excludefilters=None, limitfilters=None, errorhandler=None):
331
333 """Runs the given test on the given unit.
334
335 Note that this can raise a FilterFailure as part of normal operation."""
336 if self.hasplural:
337 for pluralform in unit.target.strings:
338 if not test(self.str1, pluralform):
339 return False
340 else:
341 return True
342 else:
343 return test(self.str1, self.str2)
344
352
354 """A Checker that controls multiple checkers."""
355 - def __init__(self, checkerconfig=None, excludefilters=None, limitfilters=None,
356 checkerclasses=None, errorhandler=None, languagecode=None):
372
373 - def getfilters(self, excludefilters=None, limitfilters=None):
389
396
401
402
404 """The basic test suite for source -> target translations."""
406 """checks whether a string has been translated at all"""
407 str2 = prefilters.removekdecomments(str2)
408 return not (len(str1.strip()) > 0 and len(str2) == 0)
409
427
428 - def blank(self, str1, str2):
429 """checks whether a translation only contains spaces"""
430 len1 = len(str1.strip())
431 len2 = len(str2.strip())
432 return not (len1 > 0 and len(str2) != 0 and len2 == 0)
433
434 - def short(self, str1, str2):
435 """checks whether a translation is much shorter than the original string"""
436 len1 = len(str1.strip())
437 len2 = len(str2.strip())
438 return not ((len1 > 0) and (0 < len2 < (len1 * 0.1)) or ((len1 > 1) and (len2 == 1)))
439
440 - def long(self, str1, str2):
441 """checks whether a translation is much longer than the original string"""
442 len1 = len(str1.strip())
443 len2 = len(str2.strip())
444 return not ((len1 > 0) and (0 < len1 < (len2 * 0.1)) or ((len1 == 1) and (len2 > 1)))
445
447 """checks whether escaping is consistent between the two strings"""
448 if not helpers.countsmatch(str1, str2, ("\\", "\\\\")):
449 escapes1 = u", ".join([u"'%s'" % word for word in str1.split() if "\\" in word])
450 escapes2 = u", ".join([u"'%s'" % word for word in str2.split() if "\\" in word])
451 raise SeriousFilterFailure(u"escapes in original (%s) don't match escapes in translation (%s)" % (escapes1, escapes2))
452 else:
453 return True
454
456 """checks whether newlines are consistent between the two strings"""
457 if not helpers.countsmatch(str1, str2, ("\n", "\r")):
458 raise FilterFailure("line endings in original don't match line endings in translation")
459 else:
460 return True
461
462 - def tabs(self, str1, str2):
463 """checks whether tabs are consistent between the two strings"""
464 if not helpers.countmatch(str1, str2, "\t"):
465 raise SeriousFilterFailure("tabs in original don't match tabs in translation")
466 else:
467 return True
468
474
483
489
491 """checks for bad spacing after punctuation"""
492 if str1.find(u" ") == -1:
493 return True
494 str1 = self.filteraccelerators(self.filtervariables(str1))
495 str1 = self.config.lang.punctranslate(str1)
496 str2 = self.filteraccelerators(self.filtervariables(str2))
497 for puncchar in self.config.punctuation:
498 plaincount1 = str1.count(puncchar)
499 plaincount2 = str2.count(puncchar)
500 if not plaincount1 or plaincount1 != plaincount2:
501 continue
502 spacecount1 = str1.count(puncchar+" ")
503 spacecount2 = str2.count(puncchar+" ")
504 if spacecount1 != spacecount2:
505
506 if str1.endswith(puncchar) != str2.endswith(puncchar) and abs(spacecount1-spacecount2) == 1:
507 continue
508 return False
509 return True
510
511 - def printf(self, str1, str2):
512 """checks whether printf format strings match"""
513 count1 = count2 = None
514 for var_num2, match2 in enumerate(printf_pat.finditer(str2)):
515 count2 = var_num2 + 1
516 if match2.group('ord'):
517 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
518 count1 = var_num1 + 1
519 if int(match2.group('ord')) == var_num1 + 1:
520 if match2.group('fullvar') != match1.group('fullvar'):
521 return 0
522 else:
523 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
524 count1 = var_num1 + 1
525 if (var_num1 == var_num2) and (match1.group('fullvar') != match2.group('fullvar')):
526 return 0
527
528 if count2 is None:
529 if list(printf_pat.finditer(str1)):
530 return 0
531
532 if (count1 or count2) and (count1 != count2):
533 return 0
534 return 1
535
537 """checks whether accelerators are consistent between the two strings"""
538 str1 = self.filtervariables(str1)
539 str2 = self.filtervariables(str2)
540 messages = []
541 for accelmarker in self.config.accelmarkers:
542 counter = decoration.countaccelerators(accelmarker)
543 count1, countbad1 = counter(str1)
544 count2, countbad2 = counter(str2)
545 getaccel = decoration.getaccelerators(accelmarker)
546 accel2, bad2 = getaccel(str2)
547 if count1 == count2:
548 continue
549 if count1 == 1 and count2 == 0:
550 if countbad2 == 1:
551 messages.append("accelerator %s appears before an invalid accelerator character '%s' (eg. space)" % (accelmarker, bad2[0]))
552 else:
553 messages.append("accelerator %s is missing from translation" % accelmarker)
554 elif count1 == 0:
555 messages.append("accelerator %s does not occur in original and should not be in translation" % accelmarker)
556 elif count1 == 1 and count2 > count1:
557 messages.append("accelerator %s is repeated in translation" % accelmarker)
558 else:
559 messages.append("accelerator %s occurs %d time(s) in original and %d time(s) in translation" % (accelmarker, count1, count2))
560 if messages:
561 if "accelerators" in self.config.criticaltests:
562 raise SeriousFilterFailure(messages)
563 else:
564 raise FilterFailure(messages)
565 return True
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
582 """checks whether variables of various forms are consistent between the two strings"""
583 messages = []
584 mismatch1, mismatch2 = [], []
585 varnames1, varnames2 = [], []
586 for startmarker, endmarker in self.config.varmatches:
587 varchecker = decoration.getvariables(startmarker, endmarker)
588 if startmarker and endmarker:
589 if isinstance(endmarker, int):
590 redecorate = lambda var: startmarker + var
591 else:
592 redecorate = lambda var: startmarker + var + endmarker
593 elif startmarker:
594 redecorate = lambda var: startmarker + var
595 else:
596 redecorate = lambda var: var
597 vars1 = varchecker(str1)
598 vars2 = varchecker(str2)
599 if vars1 != vars2:
600
601 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)]
602
603 vars1, vars2 = [var for var in vars1 if var not in varnames1], [var for var in vars2 if var not in varnames2]
604 varnames1.extend(vars1)
605 varnames2.extend(vars2)
606 vars1 = map(redecorate, vars1)
607 vars2 = map(redecorate, vars2)
608 mismatch1.extend(vars1)
609 mismatch2.extend(vars2)
610 if mismatch1:
611 messages.append("do not translate: %s" % ", ".join(mismatch1))
612 elif mismatch2:
613 messages.append("translation contains variables not in original: %s" % ", ".join(mismatch2))
614 if messages and mismatch1:
615 raise SeriousFilterFailure(messages)
616 elif messages:
617 raise FilterFailure(messages)
618 return True
619
623
624 - def emails(self, str1, str2):
627
628 - def urls(self, str1, str2):
631
635
641
647
654
661
669
671 """checks that the number of brackets in both strings match"""
672 str1 = self.filtervariables(str1)
673 str2 = self.filtervariables(str2)
674 messages = []
675 missing = []
676 extra = []
677 for bracket in ("[", "]", "{", "}", "(", ")"):
678 count1 = str1.count(bracket)
679 count2 = str2.count(bracket)
680 if count2 < count1:
681 missing.append("'%s'" % bracket)
682 elif count2 > count1:
683 extra.append("'%s'" % bracket)
684 if missing:
685 messages.append("translation is missing %s" % ", ".join(missing))
686 if extra:
687 messages.append("translation has extra %s" % ", ".join(extra))
688 if messages:
689 raise FilterFailure(messages)
690 return True
691
693 """checks that the number of sentences in both strings match"""
694 sentences1 = len(self.config.sourcelang.sentences(str1))
695 sentences2 = len(self.config.lang.sentences(str2))
696 if not sentences1 == sentences2:
697 raise FilterFailure("The number of sentences differ: %d versus %d" % (sentences1, sentences2))
698 return True
699
701 """checks that options are not translated"""
702 str1 = self.filtervariables(str1)
703 for word1 in str1.split():
704 if word1 != "--" and word1.startswith("--") and word1[-1].isalnum():
705 parts = word1.split("=")
706 if not parts[0] in str2:
707 raise FilterFailure("The option %s does not occur or is translated in the translation." % parts[0])
708 if len(parts) > 1 and parts[1] in str2:
709 raise FilterFailure("The parameter %(param)s in option %(option)s is not translated." % {"param": parts[0], "option": parts[1]})
710 return True
711
713 """checks that the message starts with the correct capitalisation"""
714 str1 = self.filteraccelerators(str1)
715 str2 = self.filteraccelerators(str2)
716 if len(str1) > 1 and len(str2) > 1:
717 return self.config.sourcelang.capsstart(str1) == self.config.lang.capsstart(str2)
718 if len(str1) == 0 and len(str2) == 0:
719 return True
720 if len(str1) == 0 or len(str2) == 0:
721 return False
722 return True
723
725 """checks the capitalisation of two strings isn't wildly different"""
726 str1 = self.removevariables(str1)
727 str2 = self.removevariables(str2)
728
729
730 str1 = re.sub(u"[^%s]( I )" % self.config.sourcelang.sentenceend, " i ", str1)
731 capitals1 = helpers.filtercount(str1, type(str1).isupper)
732 capitals2 = helpers.filtercount(str2, type(str2).isupper)
733 alpha1 = helpers.filtercount(str1, type(str1).isalpha)
734 alpha2 = helpers.filtercount(str2, type(str2).isalpha)
735
736 if capitals1 == alpha1:
737 return capitals2 == alpha2
738
739 if capitals1 == 0 or capitals1 == 1:
740 return capitals2 == capitals1
741 elif capitals1 < len(str1) / 10:
742 return capitals2 < len(str2) / 8
743 elif len(str1) < 10:
744 return abs(capitals1 - capitals2) < 3
745 elif capitals1 > len(str1) * 6 / 10:
746 return capitals2 > len(str2) * 6 / 10
747 else:
748 return abs(capitals1 - capitals2) < (len(str1) + len(str2)) / 6
749
767
778
802
821
823 """checks that only characters specified as valid appear in the translation"""
824 if not self.config.validcharsmap:
825 return True
826 invalid1 = str1.translate(self.config.validcharsmap)
827 invalid2 = str2.translate(self.config.validcharsmap)
828 invalidchars = ["'%s' (\\u%04x)" % (invalidchar.encode('utf-8'), ord(invalidchar)) for invalidchar in invalid2 if invalidchar not in invalid1]
829 if invalidchars:
830 raise FilterFailure("invalid chars: %s" % (", ".join(invalidchars)))
831 return True
832
834 """checks that file paths have not been translated"""
835 for word1 in self.filteraccelerators(str1).split():
836 if word1.startswith("/"):
837 if not helpers.countsmatch(str1, str2, (word1,)):
838 return False
839 return True
840
867
871
873 """checks for Gettext compendium conflicts (#-#-#-#-#)"""
874 return str2.find("#-#-#-#-#") == -1
875
877 """checks for English style plural(s) for you to review"""
878 def numberofpatterns(string, patterns):
879 number = 0
880 for pattern in patterns:
881 number += len(re.findall(pattern, string))
882 return number
883
884 sourcepatterns = ["\(s\)"]
885 targetpatterns = ["\(s\)"]
886 sourcecount = numberofpatterns(str1, sourcepatterns)
887 targetcount = numberofpatterns(str2, targetpatterns)
888 if self.config.lang.nplurals == 1:
889 return not targetcount
890 return sourcecount == targetcount
891
893 """checks words that don't pass a spell check"""
894 if not self.config.targetlanguage:
895 return True
896 str1 = self.filterxml(self.filteraccelerators(self.filtervariables(str1)))
897 str2 = self.filterxml(self.filteraccelerators(self.filtervariables(str2)))
898 ignore1 = []
899 messages = []
900 for word, index, suggestions in spelling.check(str1, lang="en"):
901 ignore1.append(word)
902 for word, index, suggestions in spelling.check(str2, lang=self.config.targetlanguage):
903 if word in ignore1:
904 continue
905
906 if word in suggestions:
907 continue
908 if isinstance(str2, unicode) or isinstance(str1, unicode):
909 messages.append(u"check spelling of %s (could be %s)" % (word, u" / ".join(suggestions)))
910 else:
911 messages.append("check spelling of %s (could be %s)" % (word, " / ".join(suggestions)))
912 if messages:
913 raise FilterFailure(messages)
914 return True
915
917 """checks for messages containing translation credits instead of normal translations."""
918 return not str1 in self.config.credit_sources
919
920
921 preconditions = {"untranslated": ("simplecaps", "variables", "startcaps",
922 "accelerators", "brackets", "endpunc",
923 "acronyms", "xmltags", "startpunc",
924 "endwhitespace", "startwhitespace",
925 "escapes", "doublequoting", "singlequoting",
926 "filepaths", "purepunc", "doublespacing",
927 "sentencecount", "numbers", "isfuzzy",
928 "isreview", "notranslatewords", "musttranslatewords",
929 "emails", "simpleplurals", "urls", "printf",
930 "tabs", "newlines", "functions", "options",
931 "blank", "nplurals"),
932 "blank": ("simplecaps", "variables", "startcaps",
933 "accelerators", "brackets", "endpunc",
934 "acronyms", "xmltags", "startpunc",
935 "endwhitespace", "startwhitespace",
936 "escapes", "doublequoting", "singlequoting",
937 "filepaths", "purepunc", "doublespacing",
938 "sentencecount", "numbers", "isfuzzy",
939 "isreview", "notranslatewords", "musttranslatewords",
940 "emails", "simpleplurals", "urls", "printf",
941 "tabs", "newlines", "functions", "options"),
942 "credits": ("simplecaps", "variables", "startcaps",
943 "accelerators", "brackets", "endpunc",
944 "acronyms", "xmltags", "startpunc",
945 "escapes", "doublequoting", "singlequoting",
946 "filepaths", "doublespacing",
947 "sentencecount", "numbers",
948 "emails", "simpleplurals", "urls", "printf",
949 "tabs", "newlines", "functions", "options"),
950 "purepunc": ("startcaps", "options"),
951 "startcaps": ("simplecaps",),
952 "endwhitespace": ("endpunc",),
953 "startwhitespace":("startpunc",),
954 "unchanged": ("doublewords",),
955 "compendiumconflicts": ("accelerators", "brackets", "escapes",
956 "numbers", "startpunc", "long", "variables",
957 "startcaps", "sentencecount", "simplecaps",
958 "doublespacing", "endpunc", "xmltags",
959 "startwhitespace", "endwhitespace",
960 "singlequoting", "doublequoting",
961 "filepaths", "purepunc", "doublewords", "printf") }
962
963
964
965 openofficeconfig = CheckerConfig(
966 accelmarkers = ["~"],
967 varmatches = [("&", ";"), ("%", "%"), ("%", None), ("%", 0), ("$(", ")"), ("$", "$"), ("${", "}"), ("#", "#"), ("#", 1), ("#", 0), ("($", ")"), ("$[", "]"), ("[", "]"), ("$", None)],
968 ignoretags = [("alt", "xml-lang", None), ("ahelp", "visibility", "visible"), ("img", "width", None), ("img", "height", None)],
969 canchangetags = [("link", "name", None)]
970 )
971
980
981 mozillaconfig = CheckerConfig(
982 accelmarkers = ["&"],
983 varmatches = [("&", ";"), ("%", "%"), ("%", 1), ("$", "$"), ("$", None), ("#", 1), ("${", "}"), ("$(^", ")")],
984 criticaltests = ["accelerators"]
985 )
986
995
996 gnomeconfig = CheckerConfig(
997 accelmarkers = ["_"],
998 varmatches = [("%", 1), ("$(", ")")],
999 credit_sources = [u"translator-credits"]
1000 )
1001
1010
1011 kdeconfig = CheckerConfig(
1012 accelmarkers = ["&"],
1013 varmatches = [("%", 1)],
1014 credit_sources = [u"Your names", u"Your emails", u"ROLES_OF_TRANSLATORS"]
1015 )
1016
1027
1028 cclicenseconfig = CheckerConfig(varmatches = [("@", "@")])
1037
1038 projectcheckers = {
1039 "openoffice": OpenOfficeChecker,
1040 "mozilla": MozillaChecker,
1041 "kde": KdeChecker,
1042 "wx": KdeChecker,
1043 "gnome": GnomeChecker,
1044 "creativecommons": CCLicenseChecker
1045 }
1046
1047
1049 """The standard checks for common checks on translation units."""
1051 """Check if the unit has been marked fuzzy."""
1052 return not unit.isfuzzy()
1053
1055 """Check if the unit has been marked review."""
1056 return not unit.isreview()
1057
1066
1068 """Checks if there is at least one suggested translation for this unit."""
1069 self.suggestion_store = getattr(self, 'suggestion_store', None)
1070 suggestions = []
1071 if self.suggestion_store:
1072 source = unit.source
1073 suggestions = [unit for unit in self.suggestion_store.units if unit.source == source]
1074 elif xliff and isinstance(unit, xliff.xliffunit):
1075
1076 suggestions = unit.getalttrans()
1077 return not bool(suggestions)
1078
1079
1080 -def runtests(str1, str2, ignorelist=()):
1092
1094 """runs test on a batch of string pairs"""
1095 passed, numpairs = 0, len(pairs)
1096 for str1, str2 in pairs:
1097 if runtests(str1, str2):
1098 passed += 1
1099 print
1100 print "total: %d/%d pairs passed" % (passed, numpairs)
1101
1102 if __name__ == '__main__':
1103 testset = [(r"simple", r"somple"),
1104 (r"\this equals \that", r"does \this equal \that?"),
1105 (r"this \'equals\' that", r"this 'equals' that"),
1106 (r" start and end! they must match.", r"start and end! they must match."),
1107 (r"check for matching %variables marked like %this", r"%this %variable is marked"),
1108 (r"check for mismatching %variables marked like %this", r"%that %variable is marked"),
1109 (r"check for mismatching %variables% too", r"how many %variable% are marked"),
1110 (r"%% %%", r"%%"),
1111 (r"Row: %1, Column: %2", r"Mothalo: %1, Kholomo: %2"),
1112 (r"simple lowercase", r"it is all lowercase"),
1113 (r"simple lowercase", r"It Is All Lowercase"),
1114 (r"Simple First Letter Capitals", r"First Letters"),
1115 (r"SIMPLE CAPITALS", r"First Letters"),
1116 (r"SIMPLE CAPITALS", r"ALL CAPITALS"),
1117 (r"forgot to translate", r" ")
1118 ]
1119 batchruntests(testset)
1120