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