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), (None, "title", None)]
141
142
144 """object representing the configuration of a checker"""
145 - def __init__(self, targetlanguage=None, accelmarkers=None, varmatches=None,
146 notranslatewords=None, musttranslatewords=None, validchars=None,
147 punctuation=None, endpunctuation=None, ignoretags=None,
148 canchangetags=None, criticaltests=None, credit_sources=None):
170
172 """initialise configuration paramaters that are lists
173
174 @type list: List
175 @param list: None (we'll initialise a blank list) or a list paramater
176 @rtype: List
177 """
178 if list is None:
179 list = []
180 return list
181
183 """initialise parameters that can have default options
184
185 @param param: the user supplied paramater value
186 @param default: default values when param is not specified
187 @return: the paramater as specified by the user of the default settings
188 """
189 if param is None:
190 return default
191 return param
192
193 - def update(self, otherconfig):
209
211 """updates the map that eliminates valid characters"""
212 if validchars is None:
213 return True
214 validcharsmap = dict([(ord(validchar), None) for validchar in data.normalized_unicode(validchars)])
215 self.validcharsmap.update(validcharsmap)
216
218 """Updates the target language in the config to the given target language"""
219 self.lang = factory.getlanguage(langcode)
220
222 def cached_f(self, param1):
223 key = (f.__name__, param1)
224 res_cache = self.results_cache
225 if key in res_cache:
226 return res_cache[key]
227 else:
228 value = f(self, param1)
229 res_cache[key] = value
230 return value
231 return cached_f
232
234 """Parent Checker class which does the checking based on functions available
235 in derived classes."""
236 preconditions = {}
237
238 - def __init__(self, checkerconfig=None, excludefilters=None, limitfilters=None, errorhandler=None):
239 self.errorhandler = errorhandler
240 if checkerconfig is None:
241 self.setconfig(CheckerConfig())
242 else:
243 self.setconfig(checkerconfig)
244
245 self.helperfunctions = {}
246 for functionname in dir(UnitChecker):
247 function = getattr(self, functionname)
248 if callable(function):
249 self.helperfunctions[functionname] = function
250 self.defaultfilters = self.getfilters(excludefilters, limitfilters)
251
252 self.results_cache = {}
253
254 - def getfilters(self, excludefilters=None, limitfilters=None):
255 """returns dictionary of available filters, including/excluding those in
256 the given lists"""
257 filters = {}
258 if limitfilters is None:
259
260 limitfilters = dir(self)
261 if excludefilters is None:
262 excludefilters = {}
263 for functionname in limitfilters:
264 if functionname in excludefilters: continue
265 if functionname in self.helperfunctions: continue
266 if functionname == "errorhandler": continue
267 filterfunction = getattr(self, functionname, None)
268 if not callable(filterfunction): continue
269 filters[functionname] = filterfunction
270 return filters
271
280
282 """Sets the filename that a checker should use for evaluating suggestions."""
283 self.suggestion_store = store
284 if self.suggestion_store:
285 self.suggestion_store.require_index()
286
288 """filter out variables from str1"""
289 return helpers.multifilter(str1, self.varfilters)
290 filtervariables = cache_results(filtervariables)
291
293 """remove variables from str1"""
294 return helpers.multifilter(str1, self.removevarfilter)
295 removevariables = cache_results(removevariables)
296
298 """filter out accelerators from str1"""
299 return helpers.multifilter(str1, self.accfilters, None)
300 filteraccelerators = cache_results(filteraccelerators)
301
303 """filter out accelerators from str1"""
304 return helpers.multifilter(str1, self.accfilters, acceptlist)
305
309 filterwordswithpunctuation = cache_results(filterwordswithpunctuation)
310
312 """filter out XML from the string so only text remains"""
313 return tag_re.sub("", str1)
314 filterxml = cache_results(filterxml)
315
317 """Runs the given test on the given unit.
318
319 Note that this can raise a FilterFailure as part of normal operation"""
320 return test(unit)
321
323 """run all the tests in this suite, return failures as testname, message_or_exception"""
324 self.results_cache = {}
325 failures = {}
326 ignores = self.config.lang.ignoretests[:]
327 functionnames = self.defaultfilters.keys()
328 priorityfunctionnames = self.preconditions.keys()
329 otherfunctionnames = filter(lambda functionname: functionname not in self.preconditions, functionnames)
330 for functionname in priorityfunctionnames + otherfunctionnames:
331 if functionname in ignores:
332 continue
333 filterfunction = getattr(self, functionname, None)
334
335 if filterfunction is None:
336 continue
337 filtermessage = filterfunction.__doc__
338 try:
339 filterresult = self.run_test(filterfunction, unit)
340 except FilterFailure, e:
341 filterresult = False
342 filtermessage = e.args[0]
343 except Exception, e:
344 if self.errorhandler is None:
345 raise ValueError("error in filter %s: %r, %r, %s" % \
346 (functionname, unit.source, unit.target, e))
347 else:
348 filterresult = self.errorhandler(functionname, unit.source, unit.target, e)
349 if not filterresult:
350
351 if functionname in self.defaultfilters:
352 failures[functionname] = filtermessage
353 if functionname in self.preconditions:
354 for ignoredfunctionname in self.preconditions[functionname]:
355 ignores.append(ignoredfunctionname)
356 self.results_cache = {}
357 return failures
358
360 """A checker that passes source and target strings to the checks, not the
361 whole unit.
362
363 This provides some speedup and simplifies testing."""
364 - def __init__(self, checkerconfig=None, excludefilters=None, limitfilters=None, errorhandler=None):
366
368 """Runs the given test on the given unit.
369
370 Note that this can raise a FilterFailure as part of normal operation."""
371 if self.hasplural:
372 filtermessages = []
373 filterresult = True
374 for pluralform in unit.target.strings:
375 try:
376 if not test(self.str1, unicode(pluralform)):
377 filterresult = False
378 except FilterFailure, e:
379 filterresult = False
380 filtermessages.append( unicode(e.args) )
381 if not filterresult and filtermessages:
382 raise FilterFailure(filtermessages)
383 else:
384 return filterresult
385 else:
386 return test(self.str1, self.str2)
387
396
398 """A Checker that controls multiple checkers."""
399 - def __init__(self, checkerconfig=None, excludefilters=None, limitfilters=None,
400 checkerclasses=None, errorhandler=None, languagecode=None):
416
417 - def getfilters(self, excludefilters=None, limitfilters=None):
433
440
445
446
448 """The basic test suite for source -> target translations."""
450 """checks whether a string has been translated at all"""
451 str2 = prefilters.removekdecomments(str2)
452 return not (len(str1.strip()) > 0 and len(str2) == 0)
453
455 """checks whether a translation is basically identical to the original string"""
456 str1 = self.filteraccelerators(self.removevariables(str1)).strip()
457 str2 = self.filteraccelerators(self.removevariables(str2)).strip()
458 if len(str1) < 2:
459 return True
460
461
462
463 if (str1.isupper() or str1.upper() == str1) and str1 == str2:
464 return True
465 if self.config.notranslatewords:
466 words1 = str1.split()
467 if len(words1) == 1 and [word for word in words1 if word in self.config.notranslatewords]:
468
469
470
471 return True
472
473
474 if str1.lower() == str2.lower():
475 raise FilterFailure(u"please translate")
476 return True
477
478 - def blank(self, str1, str2):
479 """checks whether a translation only contains spaces"""
480 len1 = len(str1.strip())
481 len2 = len(str2.strip())
482 return not (len1 > 0 and len(str2) != 0 and len2 == 0)
483
484 - def short(self, str1, str2):
485 """checks whether a translation is much shorter than the original string"""
486 len1 = len(str1.strip())
487 len2 = len(str2.strip())
488 return not ((len1 > 0) and (0 < len2 < (len1 * 0.1)) or ((len1 > 1) and (len2 == 1)))
489
490 - def long(self, str1, str2):
491 """checks whether a translation is much longer than the original string"""
492 len1 = len(str1.strip())
493 len2 = len(str2.strip())
494 return not ((len1 > 0) and (0 < len1 < (len2 * 0.1)) or ((len1 == 1) and (len2 > 1)))
495
497 """checks whether escaping is consistent between the two strings"""
498 if not helpers.countsmatch(str1, str2, (u"\\", u"\\\\")):
499 escapes1 = u", ".join([u"'%s'" % word for word in str1.split() if u"\\" in word])
500 escapes2 = u", ".join([u"'%s'" % word for word in str2.split() if u"\\" in word])
501 raise SeriousFilterFailure(u"escapes in original (%s) don't match escapes in translation (%s)" % (escapes1, escapes2))
502 else:
503 return True
504
506 """checks whether newlines are consistent between the two strings"""
507 if not helpers.countsmatch(str1, str2, (u"\n", u"\r")):
508 raise FilterFailure(u"line endings in original don't match line endings in translation")
509 else:
510 return True
511
512 - def tabs(self, str1, str2):
513 """checks whether tabs are consistent between the two strings"""
514 if not helpers.countmatch(str1, str2, "\t"):
515 raise SeriousFilterFailure(u"tabs in original don't match tabs in translation")
516 else:
517 return True
518
525
534
540
542 """checks for bad spacing after punctuation"""
543
544 str1 = self.filteraccelerators(self.filtervariables(str1))
545 str1 = self.config.lang.punctranslate(str1)
546 str1 = str1.replace(u"\u00a0", u" ")
547 if str1.find(u" ") == -1:
548 return True
549 str2 = self.filteraccelerators(self.filtervariables(str2))
550 str2 = str2.replace(u"\u00a0", u" ")
551 for puncchar in self.config.punctuation:
552 plaincount1 = str1.count(puncchar)
553 plaincount2 = str2.count(puncchar)
554 if not plaincount1 or plaincount1 != plaincount2:
555 continue
556 spacecount1 = str1.count(puncchar + u" ")
557 spacecount2 = str2.count(puncchar + u" ")
558 if spacecount1 != spacecount2:
559
560 if str1.endswith(puncchar) != str2.endswith(puncchar) and abs(spacecount1-spacecount2) == 1:
561 continue
562 return False
563 return True
564
565 - def printf(self, str1, str2):
566 """checks whether printf format strings match"""
567 count1 = count2 = plural = None
568
569 if 'hasplural' in self.__dict__:
570 plural = self.hasplural
571 for var_num2, match2 in enumerate(printf_pat.finditer(str2)):
572 count2 = var_num2 + 1
573 str2key = match2.group('key')
574 if match2.group('ord'):
575 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
576 count1 = var_num1 + 1
577 if int(match2.group('ord')) == var_num1 + 1:
578 if match2.group('fullvar') != match1.group('fullvar'):
579 return 0
580 elif str2key:
581 str1key = None
582 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
583 count1 = var_num1 + 1
584 if match1.group('key') and str2key == match1.group('key'):
585 str1key = match1.group('key')
586
587 if plural and match2.group('fullvar') == '.0s':
588 continue
589 if match1.group('fullvar') != match2.group('fullvar'):
590 return 0
591 if str1key == None:
592 return 0
593 else:
594 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
595 count1 = var_num1 + 1
596
597 if plural and match2.group('fullvar') == '.0s':
598 continue
599 if (var_num1 == var_num2) and (match1.group('fullvar') != match2.group('fullvar')):
600 return 0
601
602 if count2 is None:
603 if list(printf_pat.finditer(str1)):
604 return 0
605
606 if (count1 or count2) and (count1 != count2):
607 return 0
608 return 1
609
611 """checks whether accelerators are consistent between the two strings"""
612 str1 = self.filtervariables(str1)
613 str2 = self.filtervariables(str2)
614 messages = []
615 for accelmarker in self.config.accelmarkers:
616 counter1 = decoration.countaccelerators(accelmarker, self.config.sourcelang.validaccel)
617 counter2 = decoration.countaccelerators(accelmarker, self.config.lang.validaccel)
618 count1, countbad1 = counter1(str1)
619 count2, countbad2 = counter2(str2)
620 getaccel = decoration.getaccelerators(accelmarker, self.config.lang.validaccel)
621 accel2, bad2 = getaccel(str2)
622 if count1 == count2:
623 continue
624 if count1 == 1 and count2 == 0:
625 if countbad2 == 1:
626 messages.append(u"accelerator %s appears before an invalid accelerator character '%s' (eg. space)" % (accelmarker, bad2[0]))
627 else:
628 messages.append(u"accelerator %s is missing from translation" % accelmarker)
629 elif count1 == 0:
630 messages.append(u"accelerator %s does not occur in original and should not be in translation" % accelmarker)
631 elif count1 == 1 and count2 > count1:
632 messages.append(u"accelerator %s is repeated in translation" % accelmarker)
633 else:
634 messages.append(u"accelerator %s occurs %d time(s) in original and %d time(s) in translation" % (accelmarker, count1, count2))
635 if messages:
636 if "accelerators" in self.config.criticaltests:
637 raise SeriousFilterFailure(messages)
638 else:
639 raise FilterFailure(messages)
640 return True
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
657 """checks whether variables of various forms are consistent between the two strings"""
658 messages = []
659 mismatch1, mismatch2 = [], []
660 varnames1, varnames2 = [], []
661 for startmarker, endmarker in self.config.varmatches:
662 varchecker = decoration.getvariables(startmarker, endmarker)
663 if startmarker and endmarker:
664 if isinstance(endmarker, int):
665 redecorate = lambda var: startmarker + var
666 else:
667 redecorate = lambda var: startmarker + var + endmarker
668 elif startmarker:
669 redecorate = lambda var: startmarker + var
670 else:
671 redecorate = lambda var: var
672 vars1 = varchecker(str1)
673 vars2 = varchecker(str2)
674 if vars1 != vars2:
675
676 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)]
677
678 vars1, vars2 = [var for var in vars1 if var not in varnames1], [var for var in vars2 if var not in varnames2]
679 varnames1.extend(vars1)
680 varnames2.extend(vars2)
681 vars1 = map(redecorate, vars1)
682 vars2 = map(redecorate, vars2)
683 mismatch1.extend(vars1)
684 mismatch2.extend(vars2)
685 if mismatch1:
686 messages.append(u"do not translate: %s" % u", ".join(mismatch1))
687 elif mismatch2:
688 messages.append(u"translation contains variables not in original: %s" % u", ".join(mismatch2))
689 if messages and mismatch1:
690 raise SeriousFilterFailure(messages)
691 elif messages:
692 raise FilterFailure(messages)
693 return True
694
698
699 - def emails(self, str1, str2):
702
703 - def urls(self, str1, str2):
706
710
714
719
726
735
743
745 """checks that the number of brackets in both strings match"""
746 str1 = self.filtervariables(str1)
747 str2 = self.filtervariables(str2)
748 messages = []
749 missing = []
750 extra = []
751 for bracket in (u"[", u"]", u"{", u"}", u"(", u")"):
752 count1 = str1.count(bracket)
753 count2 = str2.count(bracket)
754 if count2 < count1:
755 missing.append(u"'%s'" % bracket)
756 elif count2 > count1:
757 extra.append(u"'%s'" % bracket)
758 if missing:
759 messages.append(u"translation is missing %s" % u", ".join(missing))
760 if extra:
761 messages.append(u"translation has extra %s" % u", ".join(extra))
762 if messages:
763 raise FilterFailure(messages)
764 return True
765
767 """checks that the number of sentences in both strings match"""
768 str1 = self.filteraccelerators(str1)
769 str2 = self.filteraccelerators(str2)
770 sentences1 = len(self.config.sourcelang.sentences(str1))
771 sentences2 = len(self.config.lang.sentences(str2))
772 if not sentences1 == sentences2:
773 raise FilterFailure(u"The number of sentences differ: %d versus %d" % (sentences1, sentences2))
774 return True
775
777 """checks that options are not translated"""
778 str1 = self.filtervariables(str1)
779 for word1 in str1.split():
780 if word1 != u"--" and word1.startswith(u"--") and word1[-1].isalnum():
781 parts = word1.split(u"=")
782 if not parts[0] in str2:
783 raise FilterFailure(u"The option %s does not occur or is translated in the translation." % parts[0])
784 if len(parts) > 1 and parts[1] in str2:
785 raise FilterFailure(u"The parameter %(param)s in option %(option)s is not translated." % {"param": parts[1], "option": parts[0]})
786 return True
787
789 """checks that the message starts with the correct capitalisation"""
790 str1 = self.filteraccelerators(str1)
791 str2 = self.filteraccelerators(str2)
792 if len(str1) > 1 and len(str2) > 1:
793 return self.config.sourcelang.capsstart(str1) == self.config.lang.capsstart(str2)
794 if len(str1) == 0 and len(str2) == 0:
795 return True
796 if len(str1) == 0 or len(str2) == 0:
797 return False
798 return True
799
801 """checks the capitalisation of two strings isn't wildly different"""
802 str1 = self.removevariables(str1)
803 str2 = self.removevariables(str2)
804
805
806 str1 = re.sub(u"[^%s]( I )" % self.config.sourcelang.sentenceend, u" i ", str1)
807 capitals1 = helpers.filtercount(str1, unicode.isupper)
808 capitals2 = helpers.filtercount(str2, unicode.isupper)
809 alpha1 = helpers.filtercount(str1, unicode.isalpha)
810 alpha2 = helpers.filtercount(str2, unicode.isalpha)
811
812 if capitals1 == alpha1:
813 return capitals2 == alpha2
814
815 if capitals1 == 0 or capitals1 == 1:
816 return capitals2 == capitals1
817 elif capitals1 < len(str1) / 10:
818 return capitals2 <= len(str2) / 8
819 elif len(str1) < 10:
820 return abs(capitals1 - capitals2) < 3
821 elif capitals1 > len(str1) * 6 / 10:
822 return capitals2 > len(str2) * 6 / 10
823 else:
824 return abs(capitals1 - capitals2) < (len(str1) + len(str2)) / 6
825
847
858
876
895
897 """checks that only characters specified as valid appear in the translation"""
898 if not self.config.validcharsmap:
899 return True
900 invalid1 = str1.translate(self.config.validcharsmap)
901 invalid2 = str2.translate(self.config.validcharsmap)
902 invalidchars = [u"'%s' (\\u%04x)" % (invalidchar, ord(invalidchar)) for invalidchar in invalid2 if invalidchar not in invalid1]
903 if invalidchars:
904 raise FilterFailure(u"invalid chars: %s" % (u", ".join(invalidchars)))
905 return True
906
908 """checks that file paths have not been translated"""
909 for word1 in self.filteraccelerators(str1).split():
910 if word1.startswith(u"/"):
911 if not helpers.countsmatch(str1, str2, (word1,)):
912 return False
913 return True
914
941
945
947 """checks for Gettext compendium conflicts (#-#-#-#-#)"""
948 return str2.find(u"#-#-#-#-#") == -1
949
951 """checks for English style plural(s) for you to review"""
952 def numberofpatterns(string, patterns):
953 number = 0
954 for pattern in patterns:
955 number += len(re.findall(pattern, string))
956 return number
957
958 sourcepatterns = ["\(s\)"]
959 targetpatterns = ["\(s\)"]
960 sourcecount = numberofpatterns(str1, sourcepatterns)
961 targetcount = numberofpatterns(str2, targetpatterns)
962 if self.config.lang.nplurals == 1:
963 return not targetcount
964 return sourcecount == targetcount
965
991
993 """checks for messages containing translation credits instead of normal translations."""
994 return not str1 in self.config.credit_sources
995
996
997 preconditions = {"untranslated": ("simplecaps", "variables", "startcaps",
998 "accelerators", "brackets", "endpunc",
999 "acronyms", "xmltags", "startpunc",
1000 "endwhitespace", "startwhitespace",
1001 "escapes", "doublequoting", "singlequoting",
1002 "filepaths", "purepunc", "doublespacing",
1003 "sentencecount", "numbers", "isfuzzy",
1004 "isreview", "notranslatewords", "musttranslatewords",
1005 "emails", "simpleplurals", "urls", "printf",
1006 "tabs", "newlines", "functions", "options",
1007 "blank", "nplurals", "gconf"),
1008 "blank": ("simplecaps", "variables", "startcaps",
1009 "accelerators", "brackets", "endpunc",
1010 "acronyms", "xmltags", "startpunc",
1011 "endwhitespace", "startwhitespace",
1012 "escapes", "doublequoting", "singlequoting",
1013 "filepaths", "purepunc", "doublespacing",
1014 "sentencecount", "numbers", "isfuzzy",
1015 "isreview", "notranslatewords", "musttranslatewords",
1016 "emails", "simpleplurals", "urls", "printf",
1017 "tabs", "newlines", "functions", "options",
1018 "gconf"),
1019 "credits": ("simplecaps", "variables", "startcaps",
1020 "accelerators", "brackets", "endpunc",
1021 "acronyms", "xmltags", "startpunc",
1022 "escapes", "doublequoting", "singlequoting",
1023 "filepaths", "doublespacing",
1024 "sentencecount", "numbers",
1025 "emails", "simpleplurals", "urls", "printf",
1026 "tabs", "newlines", "functions", "options"),
1027 "purepunc": ("startcaps", "options"),
1028
1029
1030
1031
1032
1033
1034
1035
1036 "endwhitespace": ("endpunc",),
1037 "startwhitespace":("startpunc",),
1038 "unchanged": ("doublewords",),
1039 "compendiumconflicts": ("accelerators", "brackets", "escapes",
1040 "numbers", "startpunc", "long", "variables",
1041 "startcaps", "sentencecount", "simplecaps",
1042 "doublespacing", "endpunc", "xmltags",
1043 "startwhitespace", "endwhitespace",
1044 "singlequoting", "doublequoting",
1045 "filepaths", "purepunc", "doublewords", "printf") }
1046
1047
1048
1049 openofficeconfig = CheckerConfig(
1050 accelmarkers = ["~"],
1051 varmatches = [("&", ";"), ("%", "%"), ("%", None), ("%", 0), ("$(", ")"), ("$", "$"), ("${", "}"), ("#", "#"), ("#", 1), ("#", 0), ("($", ")"), ("$[", "]"), ("[", "]"), ("$", None)],
1052 ignoretags = [("alt", "xml-lang", None), ("ahelp", "visibility", "visible"), ("img", "width", None), ("img", "height", None)],
1053 canchangetags = [("link", "name", None)]
1054 )
1055
1064
1065 mozillaconfig = CheckerConfig(
1066 accelmarkers = ["&"],
1067 varmatches = [("&", ";"), ("%", "%"), ("%", 1), ("$", "$"), ("$", None), ("#", 1), ("${", "}"), ("$(^", ")")],
1068 criticaltests = ["accelerators"]
1069 )
1070
1079
1081 """checks for messages containing translation credits instead of normal translations."""
1082 for location in self.locations:
1083 if location in ['MOZ_LANGPACK_CONTRIBUTORS', 'credit.translation']:
1084 return False
1085 return True
1086
1087 drupalconfig = CheckerConfig(
1088 varmatches = [("%", None), ("@", None), ("!", None)],
1089 )
1090
1099
1100 gnomeconfig = CheckerConfig(
1101 accelmarkers = ["_"],
1102 varmatches = [("%", 1), ("$(", ")")],
1103 credit_sources = [u"translator-credits"]
1104 )
1105
1114
1115 - def gconf(self, str1, str2):
1116 """Checks if we have any gconf config settings translated."""
1117 for location in self.locations:
1118 if location.find('schemas.in') != -1:
1119 gconf_attributes = gconf_attribute_re.findall(str1)
1120
1121 stopwords = [word for word in gconf_attributes if word[1:-1] not in str2]
1122 if stopwords:
1123 raise FilterFailure(u"do not translate gconf attribute: %s" % (u", ".join(stopwords)))
1124 return True
1125
1126 kdeconfig = CheckerConfig(
1127 accelmarkers = ["&"],
1128 varmatches = [("%", 1)],
1129 credit_sources = [u"Your names", u"Your emails", u"ROLES_OF_TRANSLATORS"]
1130 )
1131
1142
1143 cclicenseconfig = CheckerConfig(varmatches = [("@", "@")])
1152
1153 projectcheckers = {
1154 "openoffice": OpenOfficeChecker,
1155 "mozilla": MozillaChecker,
1156 "kde": KdeChecker,
1157 "wx": KdeChecker,
1158 "gnome": GnomeChecker,
1159 "creativecommons": CCLicenseChecker,
1160 "drupal": DrupalChecker,
1161 }
1162
1163
1165 """The standard checks for common checks on translation units."""
1167 """Check if the unit has been marked fuzzy."""
1168 return not unit.isfuzzy()
1169
1171 """Check if the unit has been marked review."""
1172 return not unit.isreview()
1173
1182
1184 """Checks if there is at least one suggested translation for this unit."""
1185 self.suggestion_store = getattr(self, 'suggestion_store', None)
1186 suggestions = []
1187 if self.suggestion_store:
1188 suggestions = self.suggestion_store.findunits(unit.source)
1189 elif xliff and isinstance(unit, xliff.xliffunit):
1190
1191 suggestions = unit.getalttrans()
1192 return not bool(suggestions)
1193
1194
1195 -def runtests(str1, str2, ignorelist=()):
1207
1209 """runs test on a batch of string pairs"""
1210 passed, numpairs = 0, len(pairs)
1211 for str1, str2 in pairs:
1212 if runtests(str1, str2):
1213 passed += 1
1214 print
1215 print "total: %d/%d pairs passed" % (passed, numpairs)
1216
1217 if __name__ == '__main__':
1218 testset = [(r"simple", r"somple"),
1219 (r"\this equals \that", r"does \this equal \that?"),
1220 (r"this \'equals\' that", r"this 'equals' that"),
1221 (r" start and end! they must match.", r"start and end! they must match."),
1222 (r"check for matching %variables marked like %this", r"%this %variable is marked"),
1223 (r"check for mismatching %variables marked like %this", r"%that %variable is marked"),
1224 (r"check for mismatching %variables% too", r"how many %variable% are marked"),
1225 (r"%% %%", r"%%"),
1226 (r"Row: %1, Column: %2", r"Mothalo: %1, Kholomo: %2"),
1227 (r"simple lowercase", r"it is all lowercase"),
1228 (r"simple lowercase", r"It Is All Lowercase"),
1229 (r"Simple First Letter Capitals", r"First Letters"),
1230 (r"SIMPLE CAPITALS", r"First Letters"),
1231 (r"SIMPLE CAPITALS", r"ALL CAPITALS"),
1232 (r"forgot to translate", r" ")
1233 ]
1234 batchruntests(testset)
1235