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 import re
34
35 from translate.filters import helpers
36 from translate.filters import decoration
37 from translate.filters import prefilters
38 from translate.filters import spelling
39 from translate.lang import factory
40 from translate.lang import data
41
42
43
44 try:
45 from translate.storage import xliff
46 except ImportError, e:
47 xliff = None
48
49
50 if not hasattr(xliff, "xliffunit"):
51 xliff = None
52
53
54
55
56
57
58
59 printf_pat = re.compile('%((?:(?P<ord>\d+)\$|\((?P<key>\w+)\))?(?P<fullvar>[+#-]*(?:\d+)?(?:\.\d+)?(hh\|h\|l\|ll)?(?P<type>[\w%])))')
60
61
62 tagname_re = re.compile("<[\s]*([\w\/]*)")
63
64
65
66 property_re = re.compile(" (\w*)=((\\\\?\".*?\\\\?\")|(\\\\?'.*?\\\\?'))")
67
68
69 tag_re = re.compile("<[^>]+>")
70
71 gconf_attribute_re = re.compile('"[a-z_]+?"')
72
73
75 """Returns the name of the XML/HTML tag in string"""
76 return tagname_re.match(string).groups(1)[0]
77
78
80 """Tests to see if pair == (a,b,c) is in list, but handles None entries in
81 list as wildcards (only allowed in positions "a" and "c"). We take a
82 shortcut by only considering "c" if "b" has already matched."""
83 a, b, c = pair
84 if (b, c) == (None, None):
85
86 return pair
87 for pattern in list:
88 x, y, z = pattern
89 if (x, y) in [(a, b), (None, b)]:
90 if z in [None, c]:
91 return pattern
92 return pair
93
94
96 """Returns all the properties in the XML/HTML tag string as
97 (tagname, propertyname, propertyvalue), but ignore those combinations
98 specified in ignore."""
99 properties = []
100 for string in strings:
101 tag = tagname(string)
102 properties += [(tag, None, None)]
103
104 pairs = property_re.findall(string)
105 for property, value, a, b in pairs:
106
107 value = value[1:-1]
108
109 canignore = False
110 if (tag, property, value) in ignore or \
111 intuplelist((tag, property, value), ignore) != (tag, property, value):
112 canignore = True
113 break
114 if not canignore:
115 properties += [(tag, property, value)]
116 return properties
117
118
120 """This exception signals that a Filter didn't pass, and gives an
121 explanation or a comment"""
122
124 if not isinstance(messages, list):
125 messages = [messages]
126 assert isinstance(messages[0], unicode)
127 joined = u", ".join(messages)
128 Exception.__init__(self, joined)
129
130 if not hasattr(self, "args"):
131 self.args = joined
132
133
135 """This exception signals that a Filter didn't pass, and the bad translation
136 might break an application (so the string will be marked fuzzy)"""
137 pass
138
139
140
141
142
143
144
145
146 common_ignoretags = [(None, "xml-lang", None)]
147 common_canchangetags = [("img", "alt", None),
148 (None, "title", None),
149 (None, "dir", None),
150 (None, "lang", None),
151 ]
152
153
154
156 """object representing the configuration of a checker"""
157
158 - def __init__(self, targetlanguage=None, accelmarkers=None, varmatches=None,
159 notranslatewords=None, musttranslatewords=None,
160 validchars=None, punctuation=None, endpunctuation=None,
161 ignoretags=None, canchangetags=None, criticaltests=None,
162 credit_sources=None):
186
188 """initialise configuration paramaters that are lists
189
190 @type list: List
191 @param list: None (we'll initialise a blank list) or a list paramater
192 @rtype: List
193 """
194 if list is None:
195 list = []
196 return list
197
199 """initialise parameters that can have default options
200
201 @param param: the user supplied paramater value
202 @param default: default values when param is not specified
203 @return: the paramater as specified by the user of the default settings
204 """
205 if param is None:
206 return default
207 return param
208
209 - def update(self, otherconfig):
225
227 """updates the map that eliminates valid characters"""
228 if validchars is None:
229 return True
230 validcharsmap = dict([(ord(validchar), None) for validchar in data.normalized_unicode(validchars)])
231 self.validcharsmap.update(validcharsmap)
232
234 """Updates the target language in the config to the given target
235 language"""
236 self.lang = factory.getlanguage(langcode)
237
238
240
241 def cached_f(self, param1):
242 key = (f.__name__, param1)
243 res_cache = self.results_cache
244 if key in res_cache:
245 return res_cache[key]
246 else:
247 value = f(self, param1)
248 res_cache[key] = value
249 return value
250 return cached_f
251
252
254 """Parent Checker class which does the checking based on functions available
255 in derived classes."""
256 preconditions = {}
257
258 - def __init__(self, checkerconfig=None, excludefilters=None,
259 limitfilters=None, errorhandler=None):
260 self.errorhandler = errorhandler
261 if checkerconfig is None:
262 self.setconfig(CheckerConfig())
263 else:
264 self.setconfig(checkerconfig)
265
266 self.helperfunctions = {}
267 for functionname in dir(UnitChecker):
268 function = getattr(self, functionname)
269 if callable(function):
270 self.helperfunctions[functionname] = function
271 self.defaultfilters = self.getfilters(excludefilters, limitfilters)
272 self.results_cache = {}
273
274 - def getfilters(self, excludefilters=None, limitfilters=None):
275 """returns dictionary of available filters, including/excluding those in
276 the given lists"""
277 filters = {}
278 if limitfilters is None:
279
280 limitfilters = dir(self)
281 if excludefilters is None:
282 excludefilters = {}
283 for functionname in limitfilters:
284 if functionname in excludefilters:
285 continue
286 if functionname in self.helperfunctions:
287 continue
288 if functionname == "errorhandler":
289 continue
290 filterfunction = getattr(self, functionname, None)
291 if not callable(filterfunction):
292 continue
293 filters[functionname] = filterfunction
294 return filters
295
305
307 """Sets the filename that a checker should use for evaluating
308 suggestions."""
309 self.suggestion_store = store
310 if self.suggestion_store:
311 self.suggestion_store.require_index()
312
314 """filter out variables from str1"""
315 return helpers.multifilter(str1, self.varfilters)
316 filtervariables = cache_results(filtervariables)
317
319 """remove variables from str1"""
320 return helpers.multifilter(str1, self.removevarfilter)
321 removevariables = cache_results(removevariables)
322
324 """filter out accelerators from str1"""
325 return helpers.multifilter(str1, self.accfilters, None)
326 filteraccelerators = cache_results(filteraccelerators)
327
329 """filter out accelerators from str1"""
330 return helpers.multifilter(str1, self.accfilters, acceptlist)
331
336 filterwordswithpunctuation = cache_results(filterwordswithpunctuation)
337
339 """filter out XML from the string so only text remains"""
340 return tag_re.sub("", str1)
341 filterxml = cache_results(filterxml)
342
344 """Runs the given test on the given unit.
345
346 Note that this can raise a FilterFailure as part of normal operation"""
347 return test(unit)
348
350 """run all the tests in this suite, return failures as testname,
351 message_or_exception"""
352 self.results_cache = {}
353 failures = {}
354 ignores = self.config.lang.ignoretests[:]
355 functionnames = self.defaultfilters.keys()
356 priorityfunctionnames = self.preconditions.keys()
357 otherfunctionnames = filter(lambda functionname: functionname not in self.preconditions, functionnames)
358 for functionname in priorityfunctionnames + otherfunctionnames:
359 if functionname in ignores:
360 continue
361 filterfunction = getattr(self, functionname, None)
362
363
364 if filterfunction is None:
365 continue
366 filtermessage = filterfunction.__doc__
367 try:
368 filterresult = self.run_test(filterfunction, unit)
369 except FilterFailure, e:
370 filterresult = False
371 filtermessage = e.args[0]
372 except Exception, e:
373 if self.errorhandler is None:
374 raise ValueError("error in filter %s: %r, %r, %s" % \
375 (functionname, unit.source, unit.target, e))
376 else:
377 filterresult = self.errorhandler(functionname, unit.source,
378 unit.target, e)
379 if not filterresult:
380
381
382 if functionname in self.defaultfilters:
383 failures[functionname] = filtermessage
384 if functionname in self.preconditions:
385 for ignoredfunctionname in self.preconditions[functionname]:
386 ignores.append(ignoredfunctionname)
387 self.results_cache = {}
388 return failures
389
390
392 """A checker that passes source and target strings to the checks, not the
393 whole unit.
394
395 This provides some speedup and simplifies testing."""
396
397 - def __init__(self, checkerconfig=None, excludefilters=None,
398 limitfilters=None, errorhandler=None):
401
403 """Runs the given test on the given unit.
404
405 Note that this can raise a FilterFailure as part of normal operation."""
406 if self.hasplural:
407 filtermessages = []
408 filterresult = True
409 for pluralform in unit.target.strings:
410 try:
411 if not test(self.str1, unicode(pluralform)):
412 filterresult = False
413 except FilterFailure, e:
414 filterresult = False
415 filtermessages.append(unicode(e.args))
416 if not filterresult and filtermessages:
417 raise FilterFailure(filtermessages)
418 else:
419 return filterresult
420 else:
421 return test(self.str1, self.str2)
422
431
432
434 """A Checker that controls multiple checkers."""
435
436 - def __init__(self, checkerconfig=None, excludefilters=None,
437 limitfilters=None, checkerclasses=None, errorhandler=None,
438 languagecode=None):
457
458 - def getfilters(self, excludefilters=None, limitfilters=None):
474
481
487
488
490 """The basic test suite for source -> target translations."""
491
493 """checks whether a string has been translated at all"""
494 str2 = prefilters.removekdecomments(str2)
495 return not (len(str1.strip()) > 0 and len(str2) == 0)
496
498 """checks whether a translation is basically identical to the original
499 string"""
500 str1 = self.filteraccelerators(self.removevariables(str1)).strip()
501 str2 = self.filteraccelerators(self.removevariables(str2)).strip()
502 if len(str1) < 2:
503 return True
504
505
506
507 if (str1.isupper() or str1.upper() == str1) and str1 == str2:
508 return True
509 if self.config.notranslatewords:
510 words1 = str1.split()
511 if len(words1) == 1 and [word for word in words1 if word in self.config.notranslatewords]:
512
513
514
515 return True
516
517
518 if str1.lower() == str2.lower():
519 raise FilterFailure(u"please translate")
520 return True
521
522 - def blank(self, str1, str2):
523 """checks whether a translation only contains spaces"""
524 len1 = len(str1.strip())
525 len2 = len(str2.strip())
526 return not (len1 > 0 and len(str2) != 0 and len2 == 0)
527
528 - def short(self, str1, str2):
529 """checks whether a translation is much shorter than the original
530 string"""
531 len1 = len(str1.strip())
532 len2 = len(str2.strip())
533 return not ((len1 > 0) and (0 < len2 < (len1 * 0.1)) or ((len1 > 1) and (len2 == 1)))
534
535 - def long(self, str1, str2):
536 """checks whether a translation is much longer than the original
537 string"""
538 len1 = len(str1.strip())
539 len2 = len(str2.strip())
540 return not ((len1 > 0) and (0 < len1 < (len2 * 0.1)) or ((len1 == 1) and (len2 > 1)))
541
543 """checks whether escaping is consistent between the two strings"""
544 if not helpers.countsmatch(str1, str2, (u"\\", u"\\\\")):
545 escapes1 = u", ".join([u"'%s'" % word for word in str1.split() if u"\\" in word])
546 escapes2 = u", ".join([u"'%s'" % word for word in str2.split() if u"\\" in word])
547 raise SeriousFilterFailure(u"escapes in original (%s) don't match "
548 "escapes in translation (%s)" %
549 (escapes1, escapes2))
550 else:
551 return True
552
554 """checks whether newlines are consistent between the two strings"""
555 if not helpers.countsmatch(str1, str2, (u"\n", u"\r")):
556 raise FilterFailure(u"line endings in original don't match "
557 "line endings in translation")
558 else:
559 return True
560
561 - def tabs(self, str1, str2):
562 """checks whether tabs are consistent between the two strings"""
563 if not helpers.countmatch(str1, str2, "\t"):
564 raise SeriousFilterFailure(u"tabs in original don't match "
565 "tabs in translation")
566 else:
567 return True
568
575
585
591
593 """checks for bad spacing after punctuation"""
594
595
596 str1 = self.filteraccelerators(self.filtervariables(str1))
597 str1 = self.config.lang.punctranslate(str1)
598 str1 = str1.replace(u"\u00a0", u" ")
599 if str1.find(u" ") == -1:
600 return True
601 str2 = self.filteraccelerators(self.filtervariables(str2))
602 str2 = str2.replace(u"\u00a0", u" ")
603 for puncchar in self.config.punctuation:
604 plaincount1 = str1.count(puncchar)
605 if not plaincount1:
606 continue
607 plaincount2 = str2.count(puncchar)
608 if plaincount1 != plaincount2:
609 continue
610 spacecount1 = str1.count(puncchar + u" ")
611 spacecount2 = str2.count(puncchar + u" ")
612 if spacecount1 != spacecount2:
613
614 if abs(spacecount1 - spacecount2) == 1 and str1.endswith(puncchar) != str2.endswith(puncchar):
615 continue
616 return False
617 return True
618
619 - def printf(self, str1, str2):
620 """checks whether printf format strings match"""
621 count1 = count2 = plural = None
622
623 if 'hasplural' in self.__dict__:
624 plural = self.hasplural
625 for var_num2, match2 in enumerate(printf_pat.finditer(str2)):
626 count2 = var_num2 + 1
627 str2key = match2.group('key')
628 if match2.group('ord'):
629 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
630 count1 = var_num1 + 1
631 if int(match2.group('ord')) == var_num1 + 1:
632 if match2.group('fullvar') != match1.group('fullvar'):
633 return 0
634 elif str2key:
635 str1key = None
636 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
637 count1 = var_num1 + 1
638 if match1.group('key') and str2key == match1.group('key'):
639 str1key = match1.group('key')
640
641 if plural and match2.group('fullvar') == '.0s':
642 continue
643 if match1.group('fullvar') != match2.group('fullvar'):
644 return 0
645 if str1key == None:
646 return 0
647 else:
648 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
649 count1 = var_num1 + 1
650
651 if plural and match2.group('fullvar') == '.0s':
652 continue
653 if (var_num1 == var_num2) and (match1.group('fullvar') != match2.group('fullvar')):
654 return 0
655
656 if count2 is None:
657 if list(printf_pat.finditer(str1)):
658 return 0
659
660 if (count1 or count2) and (count1 != count2):
661 return 0
662 return 1
663
665 """checks whether accelerators are consistent between the two strings"""
666 str1 = self.filtervariables(str1)
667 str2 = self.filtervariables(str2)
668 messages = []
669 for accelmarker in self.config.accelmarkers:
670 counter1 = decoration.countaccelerators(accelmarker, self.config.sourcelang.validaccel)
671 counter2 = decoration.countaccelerators(accelmarker, self.config.lang.validaccel)
672 count1, countbad1 = counter1(str1)
673 count2, countbad2 = counter2(str2)
674 getaccel = decoration.getaccelerators(accelmarker, self.config.lang.validaccel)
675 accel2, bad2 = getaccel(str2)
676 if count1 == count2:
677 continue
678 if count1 == 1 and count2 == 0:
679 if countbad2 == 1:
680 messages.append(u"accelerator %s appears before an invalid "
681 "accelerator character '%s' (eg. space)" %
682 (accelmarker, bad2[0]))
683 else:
684 messages.append(u"accelerator %s is missing from translation" %
685 accelmarker)
686 elif count1 == 0:
687 messages.append(u"accelerator %s does not occur in original "
688 "and should not be in translation" % accelmarker)
689 elif count1 == 1 and count2 > count1:
690 messages.append(u"accelerator %s is repeated in translation" %
691 accelmarker)
692 else:
693 messages.append(u"accelerator %s occurs %d time(s) in original "
694 "and %d time(s) in translation" %
695 (accelmarker, count1, count2))
696 if messages:
697 if "accelerators" in self.config.criticaltests:
698 raise SeriousFilterFailure(messages)
699 else:
700 raise FilterFailure(messages)
701 return True
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
718 """checks whether variables of various forms are consistent between the
719 two strings"""
720 messages = []
721 mismatch1, mismatch2 = [], []
722 varnames1, varnames2 = [], []
723 for startmarker, endmarker in self.config.varmatches:
724 varchecker = decoration.getvariables(startmarker, endmarker)
725 if startmarker and endmarker:
726 if isinstance(endmarker, int):
727 redecorate = lambda var: startmarker + var
728 else:
729 redecorate = lambda var: startmarker + var + endmarker
730 elif startmarker:
731 redecorate = lambda var: startmarker + var
732 else:
733 redecorate = lambda var: var
734 vars1 = varchecker(str1)
735 vars2 = varchecker(str2)
736 if vars1 != vars2:
737
738 vars1, vars2 = [var for var in vars1 if vars1.count(var) > vars2.count(var)], \
739 [var for var in vars2 if vars1.count(var) < vars2.count(var)]
740
741
742 vars1, vars2 = [var for var in vars1 if var not in varnames1], [var for var in vars2 if var not in varnames2]
743 varnames1.extend(vars1)
744 varnames2.extend(vars2)
745 vars1 = map(redecorate, vars1)
746 vars2 = map(redecorate, vars2)
747 mismatch1.extend(vars1)
748 mismatch2.extend(vars2)
749 if mismatch1:
750 messages.append(u"do not translate: %s" % u", ".join(mismatch1))
751 elif mismatch2:
752 messages.append(u"translation contains variables not in original: %s" % u", ".join(mismatch2))
753 if messages and mismatch1:
754 raise SeriousFilterFailure(messages)
755 elif messages:
756 raise FilterFailure(messages)
757 return True
758
763
764 - def emails(self, str1, str2):
767
768 - def urls(self, str1, str2):
771
776
780
785
792
801
809
811 """checks that the number of brackets in both strings match"""
812 str1 = self.filtervariables(str1)
813 str2 = self.filtervariables(str2)
814 messages = []
815 missing = []
816 extra = []
817 for bracket in (u"[", u"]", u"{", u"}", u"(", u")"):
818 count1 = str1.count(bracket)
819 count2 = str2.count(bracket)
820 if count2 < count1:
821 missing.append(u"'%s'" % bracket)
822 elif count2 > count1:
823 extra.append(u"'%s'" % bracket)
824 if missing:
825 messages.append(u"translation is missing %s" % u", ".join(missing))
826 if extra:
827 messages.append(u"translation has extra %s" % u", ".join(extra))
828 if messages:
829 raise FilterFailure(messages)
830 return True
831
833 """checks that the number of sentences in both strings match"""
834 str1 = self.filteraccelerators(str1)
835 str2 = self.filteraccelerators(str2)
836 sentences1 = len(self.config.sourcelang.sentences(str1))
837 sentences2 = len(self.config.lang.sentences(str2))
838 if not sentences1 == sentences2:
839 raise FilterFailure(u"The number of sentences differ: "
840 "%d versus %d" % (sentences1, sentences2))
841 return True
842
844 """checks that options are not translated"""
845 str1 = self.filtervariables(str1)
846 for word1 in str1.split():
847 if word1 != u"--" and word1.startswith(u"--") and word1[-1].isalnum():
848 parts = word1.split(u"=")
849 if not parts[0] in str2:
850 raise FilterFailure(u"The option %s does not occur or is "
851 "translated in the translation." % parts[0])
852 if len(parts) > 1 and parts[1] in str2:
853 raise FilterFailure(u"The parameter %(param)s in option %(option)s "
854 "is not translated." % {"param": parts[1],
855 "option": parts[0]})
856 return True
857
859 """checks that the message starts with the correct capitalisation"""
860 str1 = self.filteraccelerators(str1)
861 str2 = self.filteraccelerators(str2)
862 if len(str1) > 1 and len(str2) > 1:
863 return self.config.sourcelang.capsstart(str1) == self.config.lang.capsstart(str2)
864 if len(str1) == 0 and len(str2) == 0:
865 return True
866 if len(str1) == 0 or len(str2) == 0:
867 return False
868 return True
869
871 """checks the capitalisation of two strings isn't wildly different"""
872 str1 = self.removevariables(str1)
873 str2 = self.removevariables(str2)
874
875
876 str1 = re.sub(u"[^%s]( I )" % self.config.sourcelang.sentenceend, u" i ", str1)
877 capitals1 = helpers.filtercount(str1, unicode.isupper)
878 capitals2 = helpers.filtercount(str2, unicode.isupper)
879 alpha1 = helpers.filtercount(str1, unicode.isalpha)
880 alpha2 = helpers.filtercount(str2, unicode.isalpha)
881
882 if capitals1 == alpha1:
883 return capitals2 == alpha2
884
885
886 if capitals1 == 0 or capitals1 == 1:
887 return capitals2 == capitals1
888 elif capitals1 < len(str1) / 10:
889 return capitals2 <= len(str2) / 8
890 elif len(str1) < 10:
891 return abs(capitals1 - capitals2) < 3
892 elif capitals1 > len(str1) * 6 / 10:
893 return capitals2 > len(str2) * 6 / 10
894 else:
895 return abs(capitals1 - capitals2) < (len(str1) + len(str2)) / 6
896
919
930
950
969
971 """checks that only characters specified as valid appear in the
972 translation"""
973 if not self.config.validcharsmap:
974 return True
975 invalid1 = str1.translate(self.config.validcharsmap)
976 invalid2 = str2.translate(self.config.validcharsmap)
977 invalidchars = [u"'%s' (\\u%04x)" % (invalidchar, ord(invalidchar)) for invalidchar in invalid2 if invalidchar not in invalid1]
978 if invalidchars:
979 raise FilterFailure(u"invalid chars: %s" % (u", ".join(invalidchars)))
980 return True
981
983 """checks that file paths have not been translated"""
984 for word1 in self.filteraccelerators(str1).split():
985 if word1.startswith(u"/"):
986 if not helpers.countsmatch(str1, str2, (word1,)):
987 return False
988 return True
989
1017
1022
1024 """checks for Gettext compendium conflicts (#-#-#-#-#)"""
1025 return str2.find(u"#-#-#-#-#") == -1
1026
1028 """checks for English style plural(s) for you to review"""
1029
1030 def numberofpatterns(string, patterns):
1031 number = 0
1032 for pattern in patterns:
1033 number += len(re.findall(pattern, string))
1034 return number
1035
1036 sourcepatterns = ["\(s\)"]
1037 targetpatterns = ["\(s\)"]
1038 sourcecount = numberofpatterns(str1, sourcepatterns)
1039 targetcount = numberofpatterns(str2, targetpatterns)
1040 if self.config.lang.nplurals == 1:
1041 return not targetcount
1042 return sourcecount == targetcount
1043
1072
1074 """checks for messages containing translation credits instead of normal
1075 translations."""
1076 return not str1 in self.config.credit_sources
1077
1078
1079 preconditions = {
1080 "untranslated": ("simplecaps", "variables", "startcaps",
1081 "accelerators", "brackets", "endpunc",
1082 "acronyms", "xmltags", "startpunc",
1083 "endwhitespace", "startwhitespace",
1084 "escapes", "doublequoting", "singlequoting",
1085 "filepaths", "purepunc", "doublespacing",
1086 "sentencecount", "numbers", "isfuzzy",
1087 "isreview", "notranslatewords", "musttranslatewords",
1088 "emails", "simpleplurals", "urls", "printf",
1089 "tabs", "newlines", "functions", "options",
1090 "blank", "nplurals", "gconf"),
1091 "blank": ("simplecaps", "variables", "startcaps",
1092 "accelerators", "brackets", "endpunc",
1093 "acronyms", "xmltags", "startpunc",
1094 "endwhitespace", "startwhitespace",
1095 "escapes", "doublequoting", "singlequoting",
1096 "filepaths", "purepunc", "doublespacing",
1097 "sentencecount", "numbers", "isfuzzy",
1098 "isreview", "notranslatewords", "musttranslatewords",
1099 "emails", "simpleplurals", "urls", "printf",
1100 "tabs", "newlines", "functions", "options",
1101 "gconf"),
1102 "credits": ("simplecaps", "variables", "startcaps",
1103 "accelerators", "brackets", "endpunc",
1104 "acronyms", "xmltags", "startpunc",
1105 "escapes", "doublequoting", "singlequoting",
1106 "filepaths", "doublespacing",
1107 "sentencecount", "numbers",
1108 "emails", "simpleplurals", "urls", "printf",
1109 "tabs", "newlines", "functions", "options"),
1110 "purepunc": ("startcaps", "options"),
1111
1112
1113
1114
1115
1116
1117
1118
1119 "endwhitespace": ("endpunc",),
1120 "startwhitespace": ("startpunc",),
1121 "unchanged": ("doublewords",),
1122 "compendiumconflicts": ("accelerators", "brackets", "escapes",
1123 "numbers", "startpunc", "long", "variables",
1124 "startcaps", "sentencecount", "simplecaps",
1125 "doublespacing", "endpunc", "xmltags",
1126 "startwhitespace", "endwhitespace",
1127 "singlequoting", "doublequoting",
1128 "filepaths", "purepunc", "doublewords", "printf"),
1129 }
1130
1131
1132
1133 openofficeconfig = CheckerConfig(
1134 accelmarkers=["~"],
1135 varmatches=[("&", ";"), ("%", "%"), ("%", None), ("%", 0), ("$(", ")"),
1136 ("$", "$"), ("${", "}"), ("#", "#"), ("#", 1), ("#", 0),
1137 ("($", ")"), ("$[", "]"), ("[", "]"), ("$", None)],
1138 ignoretags=[("alt", "xml-lang", None), ("ahelp", "visibility", "visible"),
1139 ("img", "width", None), ("img", "height", None)],
1140 canchangetags=[("link", "name", None)],
1141 )
1142
1143
1153
1154 mozillaconfig = CheckerConfig(
1155 accelmarkers=["&"],
1156 varmatches=[("&", ";"), ("%", "%"), ("%", 1), ("$", "$"), ("$", None),
1157 ("#", 1), ("${", "}"), ("$(^", ")")],
1158 criticaltests=["accelerators"],
1159 )
1160
1161
1163
1171
1173 """checks for messages containing translation credits instead of normal
1174 translations."""
1175 for location in self.locations:
1176 if location in ['MOZ_LANGPACK_CONTRIBUTORS', 'credit.translation']:
1177 return False
1178 return True
1179
1180 drupalconfig = CheckerConfig(
1181 varmatches=[("%", None), ("@", None), ("!", None)],
1182 )
1183
1184
1194
1195 gnomeconfig = CheckerConfig(
1196 accelmarkers=["_"],
1197 varmatches=[("%", 1), ("$(", ")")],
1198 credit_sources=[u"translator-credits"],
1199 )
1200
1201
1203
1211
1212 - def gconf(self, str1, str2):
1213 """Checks if we have any gconf config settings translated."""
1214 for location in self.locations:
1215 if location.find('schemas.in') != -1:
1216 gconf_attributes = gconf_attribute_re.findall(str1)
1217
1218 stopwords = [word for word in gconf_attributes if word[1:-1] not in str2]
1219 if stopwords:
1220 raise FilterFailure(u"do not translate gconf attribute: %s" %
1221 (u", ".join(stopwords)))
1222 return True
1223
1224 kdeconfig = CheckerConfig(
1225 accelmarkers=["&"],
1226 varmatches=[("%", 1)],
1227 credit_sources=[u"Your names", u"Your emails", u"ROLES_OF_TRANSLATORS"],
1228 )
1229
1230
1242
1243 cclicenseconfig = CheckerConfig(varmatches=[("@", "@")])
1244
1245
1255
1256 projectcheckers = {
1257 "openoffice": OpenOfficeChecker,
1258 "mozilla": MozillaChecker,
1259 "kde": KdeChecker,
1260 "wx": KdeChecker,
1261 "gnome": GnomeChecker,
1262 "creativecommons": CCLicenseChecker,
1263 "drupal": DrupalChecker,
1264 }
1265
1266
1268 """The standard checks for common checks on translation units."""
1269
1271 """Check if the unit has been marked fuzzy."""
1272 return not unit.isfuzzy()
1273
1275 """Check if the unit has been marked review."""
1276 return not unit.isreview()
1277
1287
1289 """Checks if there is at least one suggested translation for this
1290 unit."""
1291 self.suggestion_store = getattr(self, 'suggestion_store', None)
1292 suggestions = []
1293 if self.suggestion_store:
1294 suggestions = self.suggestion_store.findunits(unit.source)
1295 elif xliff and isinstance(unit, xliff.xliffunit):
1296
1297 suggestions = unit.getalttrans()
1298 return not bool(suggestions)
1299
1300
1301 -def runtests(str1, str2, ignorelist=()):
1314
1315
1317 """runs test on a batch of string pairs"""
1318 passed, numpairs = 0, len(pairs)
1319 for str1, str2 in pairs:
1320 if runtests(str1, str2):
1321 passed += 1
1322 print
1323 print "total: %d/%d pairs passed" % (passed, numpairs)
1324
1325
1326 if __name__ == '__main__':
1327 testset = [(r"simple", r"somple"),
1328 (r"\this equals \that", r"does \this equal \that?"),
1329 (r"this \'equals\' that", r"this 'equals' that"),
1330 (r" start and end! they must match.",
1331 r"start and end! they must match."),
1332 (r"check for matching %variables marked like %this",
1333 r"%this %variable is marked"),
1334 (r"check for mismatching %variables marked like %this",
1335 r"%that %variable is marked"),
1336 (r"check for mismatching %variables% too",
1337 r"how many %variable% are marked"),
1338 (r"%% %%", r"%%"),
1339 (r"Row: %1, Column: %2", r"Mothalo: %1, Kholomo: %2"),
1340 (r"simple lowercase", r"it is all lowercase"),
1341 (r"simple lowercase", r"It Is All Lowercase"),
1342 (r"Simple First Letter Capitals", r"First Letters"),
1343 (r"SIMPLE CAPITALS", r"First Letters"),
1344 (r"SIMPLE CAPITALS", r"ALL CAPITALS"),
1345 (r"forgot to translate", r" "),
1346 ]
1347 batchruntests(testset)
1348