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
58 printf_pat = re.compile('%((?:(?P<ord>\d+)\$|\((?P<key>\w+)\))?(?P<fullvar>[+#-]*(?:\d+)?(?:\.\d+)?(hh\|h\|l\|ll)?(?P<type>[\w%])))')
59
60
61 tagname_re = re.compile("<[\s]*([\w\/]*)")
62
63
64
65 property_re = re.compile(" (\w*)=((\\\\?\".*?\\\\?\")|(\\\\?'.*?\\\\?'))")
66
67
68 tag_re = re.compile("<[^>]+>")
69
70 gconf_attribute_re = re.compile('"[a-z_]+?"')
71
72
74 """Returns the name of the XML/HTML tag in string"""
75 return tagname_re.match(string).groups(1)[0]
76
77
79 """Tests to see if pair == (a,b,c) is in list, but handles None entries in
80 list as wildcards (only allowed in positions "a" and "c"). We take a
81 shortcut by only considering "c" if "b" has already matched."""
82 a, b, c = pair
83 if (b, c) == (None, None):
84
85 return pair
86 for pattern in list:
87 x, y, z = pattern
88 if (x, y) in [(a, b), (None, b)]:
89 if z in [None, c]:
90 return pattern
91 return pair
92
93
95 """Returns all the properties in the XML/HTML tag string as
96 (tagname, propertyname, propertyvalue), but ignore those combinations
97 specified in ignore."""
98 properties = []
99 for string in strings:
100 tag = tagname(string)
101 properties += [(tag, None, None)]
102
103 pairs = property_re.findall(string)
104 for property, value, a, b in pairs:
105
106 value = value[1:-1]
107
108 canignore = False
109 if (tag, property, value) in ignore or \
110 intuplelist((tag, property, value), ignore) != (tag, property, value):
111 canignore = True
112 break
113 if not canignore:
114 properties += [(tag, property, value)]
115 return properties
116
117
119 """This exception signals that a Filter didn't pass, and gives an
120 explanation or a comment"""
121
123 if not isinstance(messages, list):
124 messages = [messages]
125 assert isinstance(messages[0], unicode)
126 joined = u", ".join(messages)
127 Exception.__init__(self, joined)
128
129 if not hasattr(self, "args"):
130 self.args = joined
131
132
134 """This exception signals that a Filter didn't pass, and the bad translation
135 might break an application (so the string will be marked fuzzy)"""
136 pass
137
138
139
140
141
142
143
144
145 common_ignoretags = [(None, "xml-lang", None)]
146 common_canchangetags = [("img", "alt", None),
147 (None, "title", None),
148 (None, "dir", None),
149 (None, "lang", None),
150 ]
151
152
153
155 """object representing the configuration of a checker"""
156
157 - def __init__(self, targetlanguage=None, accelmarkers=None, varmatches=None,
158 notranslatewords=None, musttranslatewords=None,
159 validchars=None, punctuation=None, endpunctuation=None,
160 ignoretags=None, canchangetags=None, criticaltests=None,
161 credit_sources=None):
185
187 """initialise configuration paramaters that are lists
188
189 @type list: List
190 @param list: None (we'll initialise a blank list) or a list paramater
191 @rtype: List
192 """
193 if list is None:
194 list = []
195 return list
196
198 """initialise parameters that can have default options
199
200 @param param: the user supplied paramater value
201 @param default: default values when param is not specified
202 @return: the paramater as specified by the user of the default settings
203 """
204 if param is None:
205 return default
206 return param
207
208 - def update(self, otherconfig):
224
226 """updates the map that eliminates valid characters"""
227 if validchars is None:
228 return True
229 validcharsmap = dict([(ord(validchar), None) for validchar in data.normalized_unicode(validchars)])
230 self.validcharsmap.update(validcharsmap)
231
233 """Updates the target language in the config to the given target
234 language"""
235 self.lang = factory.getlanguage(langcode)
236
237
239
240 def cached_f(self, param1):
241 key = (f.__name__, param1)
242 res_cache = self.results_cache
243 if key in res_cache:
244 return res_cache[key]
245 else:
246 value = f(self, param1)
247 res_cache[key] = value
248 return value
249 return cached_f
250
251
253 """Parent Checker class which does the checking based on functions available
254 in derived classes."""
255 preconditions = {}
256
257 - def __init__(self, checkerconfig=None, excludefilters=None,
258 limitfilters=None, errorhandler=None):
259 self.errorhandler = errorhandler
260 if checkerconfig is None:
261 self.setconfig(CheckerConfig())
262 else:
263 self.setconfig(checkerconfig)
264
265 self.helperfunctions = {}
266 for functionname in dir(UnitChecker):
267 function = getattr(self, functionname)
268 if callable(function):
269 self.helperfunctions[functionname] = function
270 self.defaultfilters = self.getfilters(excludefilters, limitfilters)
271 self.results_cache = {}
272
273 - def getfilters(self, excludefilters=None, limitfilters=None):
274 """returns dictionary of available filters, including/excluding those in
275 the given lists"""
276 filters = {}
277 if limitfilters is None:
278
279 limitfilters = dir(self)
280 if excludefilters is None:
281 excludefilters = {}
282 for functionname in limitfilters:
283 if functionname in excludefilters:
284 continue
285 if functionname in self.helperfunctions:
286 continue
287 if functionname == "errorhandler":
288 continue
289 filterfunction = getattr(self, functionname, None)
290 if not callable(filterfunction):
291 continue
292 filters[functionname] = filterfunction
293 return filters
294
304
306 """Sets the filename that a checker should use for evaluating
307 suggestions."""
308 self.suggestion_store = store
309 if self.suggestion_store:
310 self.suggestion_store.require_index()
311
313 """filter out variables from str1"""
314 return helpers.multifilter(str1, self.varfilters)
315 filtervariables = cache_results(filtervariables)
316
318 """remove variables from str1"""
319 return helpers.multifilter(str1, self.removevarfilter)
320 removevariables = cache_results(removevariables)
321
323 """filter out accelerators from str1"""
324 return helpers.multifilter(str1, self.accfilters, None)
325 filteraccelerators = cache_results(filteraccelerators)
326
328 """filter out accelerators from str1"""
329 return helpers.multifilter(str1, self.accfilters, acceptlist)
330
335 filterwordswithpunctuation = cache_results(filterwordswithpunctuation)
336
338 """filter out XML from the string so only text remains"""
339 return tag_re.sub("", str1)
340 filterxml = cache_results(filterxml)
341
343 """Runs the given test on the given unit.
344
345 Note that this can raise a FilterFailure as part of normal operation"""
346 return test(unit)
347
349 """run all the tests in this suite, return failures as testname,
350 message_or_exception"""
351 self.results_cache = {}
352 failures = {}
353 ignores = self.config.lang.ignoretests[:]
354 functionnames = self.defaultfilters.keys()
355 priorityfunctionnames = self.preconditions.keys()
356 otherfunctionnames = filter(lambda functionname: functionname not in self.preconditions, functionnames)
357 for functionname in priorityfunctionnames + otherfunctionnames:
358 if functionname in ignores:
359 continue
360 filterfunction = getattr(self, functionname, None)
361
362
363 if filterfunction is None:
364 continue
365 filtermessage = filterfunction.__doc__
366 try:
367 filterresult = self.run_test(filterfunction, unit)
368 except FilterFailure, e:
369 filterresult = False
370 filtermessage = e.args[0]
371 except Exception, e:
372 if self.errorhandler is None:
373 raise ValueError("error in filter %s: %r, %r, %s" % \
374 (functionname, unit.source, unit.target, e))
375 else:
376 filterresult = self.errorhandler(functionname, unit.source,
377 unit.target, e)
378 if not filterresult:
379
380
381 if functionname in self.defaultfilters:
382 failures[functionname] = filtermessage
383 if functionname in self.preconditions:
384 for ignoredfunctionname in self.preconditions[functionname]:
385 ignores.append(ignoredfunctionname)
386 self.results_cache = {}
387 return failures
388
389
391 """A checker that passes source and target strings to the checks, not the
392 whole unit.
393
394 This provides some speedup and simplifies testing."""
395
396 - def __init__(self, checkerconfig=None, excludefilters=None,
397 limitfilters=None, errorhandler=None):
400
402 """Runs the given test on the given unit.
403
404 Note that this can raise a FilterFailure as part of normal operation."""
405 if self.hasplural:
406 filtermessages = []
407 filterresult = True
408 for pluralform in unit.target.strings:
409 try:
410 if not test(self.str1, unicode(pluralform)):
411 filterresult = False
412 except FilterFailure, e:
413 filterresult = False
414 filtermessages.append(unicode(e.args))
415 if not filterresult and filtermessages:
416 raise FilterFailure(filtermessages)
417 else:
418 return filterresult
419 else:
420 return test(self.str1, self.str2)
421
430
431
433 """A Checker that controls multiple checkers."""
434
435 - def __init__(self, checkerconfig=None, excludefilters=None,
436 limitfilters=None, checkerclasses=None, errorhandler=None,
437 languagecode=None):
453
454 - def getfilters(self, excludefilters=None, limitfilters=None):
470
477
483
484
486 """The basic test suite for source -> target translations."""
487
489 """checks whether a string has been translated at all"""
490 str2 = prefilters.removekdecomments(str2)
491 return not (len(str1.strip()) > 0 and len(str2) == 0)
492
494 """checks whether a translation is basically identical to the original
495 string"""
496 str1 = self.filteraccelerators(self.removevariables(str1)).strip()
497 str2 = self.filteraccelerators(self.removevariables(str2)).strip()
498 if len(str1) < 2:
499 return True
500
501
502
503 if (str1.isupper() or str1.upper() == str1) and str1 == str2:
504 return True
505 if self.config.notranslatewords:
506 words1 = str1.split()
507 if len(words1) == 1 and [word for word in words1 if word in self.config.notranslatewords]:
508
509
510
511 return True
512
513
514 if str1.lower() == str2.lower():
515 raise FilterFailure(u"please translate")
516 return True
517
518 - def blank(self, str1, str2):
519 """checks whether a translation only contains spaces"""
520 len1 = len(str1.strip())
521 len2 = len(str2.strip())
522 return not (len1 > 0 and len(str2) != 0 and len2 == 0)
523
524 - def short(self, str1, str2):
525 """checks whether a translation is much shorter than the original
526 string"""
527 len1 = len(str1.strip())
528 len2 = len(str2.strip())
529 return not ((len1 > 0) and (0 < len2 < (len1 * 0.1)) or ((len1 > 1) and (len2 == 1)))
530
531 - def long(self, str1, str2):
532 """checks whether a translation is much longer than the original
533 string"""
534 len1 = len(str1.strip())
535 len2 = len(str2.strip())
536 return not ((len1 > 0) and (0 < len1 < (len2 * 0.1)) or ((len1 == 1) and (len2 > 1)))
537
539 """checks whether escaping is consistent between the two strings"""
540 if not helpers.countsmatch(str1, str2, (u"\\", u"\\\\")):
541 escapes1 = u", ".join([u"'%s'" % word for word in str1.split() if u"\\" in word])
542 escapes2 = u", ".join([u"'%s'" % word for word in str2.split() if u"\\" in word])
543 raise SeriousFilterFailure(u"escapes in original (%s) don't match escapes in translation (%s)" % (escapes1, escapes2))
544 else:
545 return True
546
548 """checks whether newlines are consistent between the two strings"""
549 if not helpers.countsmatch(str1, str2, (u"\n", u"\r")):
550 raise FilterFailure(u"line endings in original don't match line endings in translation")
551 else:
552 return True
553
554 - def tabs(self, str1, str2):
555 """checks whether tabs are consistent between the two strings"""
556 if not helpers.countmatch(str1, str2, "\t"):
557 raise SeriousFilterFailure(u"tabs in original don't match tabs in translation")
558 else:
559 return True
560
567
577
583
585 """checks for bad spacing after punctuation"""
586
587
588 str1 = self.filteraccelerators(self.filtervariables(str1))
589 str1 = self.config.lang.punctranslate(str1)
590 str1 = str1.replace(u"\u00a0", u" ")
591 if str1.find(u" ") == -1:
592 return True
593 str2 = self.filteraccelerators(self.filtervariables(str2))
594 str2 = str2.replace(u"\u00a0", u" ")
595 for puncchar in self.config.punctuation:
596 plaincount1 = str1.count(puncchar)
597 plaincount2 = str2.count(puncchar)
598 if not plaincount1 or plaincount1 != plaincount2:
599 continue
600 spacecount1 = str1.count(puncchar + u" ")
601 spacecount2 = str2.count(puncchar + u" ")
602 if spacecount1 != spacecount2:
603
604 if str1.endswith(puncchar) != str2.endswith(puncchar) and abs(spacecount1 - spacecount2) == 1:
605 continue
606 return False
607 return True
608
609 - def printf(self, str1, str2):
610 """checks whether printf format strings match"""
611 count1 = count2 = plural = None
612
613 if 'hasplural' in self.__dict__:
614 plural = self.hasplural
615 for var_num2, match2 in enumerate(printf_pat.finditer(str2)):
616 count2 = var_num2 + 1
617 str2key = match2.group('key')
618 if match2.group('ord'):
619 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
620 count1 = var_num1 + 1
621 if int(match2.group('ord')) == var_num1 + 1:
622 if match2.group('fullvar') != match1.group('fullvar'):
623 return 0
624 elif str2key:
625 str1key = None
626 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
627 count1 = var_num1 + 1
628 if match1.group('key') and str2key == match1.group('key'):
629 str1key = match1.group('key')
630
631 if plural and match2.group('fullvar') == '.0s':
632 continue
633 if match1.group('fullvar') != match2.group('fullvar'):
634 return 0
635 if str1key == None:
636 return 0
637 else:
638 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
639 count1 = var_num1 + 1
640
641 if plural and match2.group('fullvar') == '.0s':
642 continue
643 if (var_num1 == var_num2) and (match1.group('fullvar') != match2.group('fullvar')):
644 return 0
645
646 if count2 is None:
647 if list(printf_pat.finditer(str1)):
648 return 0
649
650 if (count1 or count2) and (count1 != count2):
651 return 0
652 return 1
653
655 """checks whether accelerators are consistent between the two strings"""
656 str1 = self.filtervariables(str1)
657 str2 = self.filtervariables(str2)
658 messages = []
659 for accelmarker in self.config.accelmarkers:
660 counter1 = decoration.countaccelerators(accelmarker, self.config.sourcelang.validaccel)
661 counter2 = decoration.countaccelerators(accelmarker, self.config.lang.validaccel)
662 count1, countbad1 = counter1(str1)
663 count2, countbad2 = counter2(str2)
664 getaccel = decoration.getaccelerators(accelmarker, self.config.lang.validaccel)
665 accel2, bad2 = getaccel(str2)
666 if count1 == count2:
667 continue
668 if count1 == 1 and count2 == 0:
669 if countbad2 == 1:
670 messages.append(u"accelerator %s appears before an invalid accelerator character '%s' (eg. space)" % (accelmarker, bad2[0]))
671 else:
672 messages.append(u"accelerator %s is missing from translation" % accelmarker)
673 elif count1 == 0:
674 messages.append(u"accelerator %s does not occur in original and should not be in translation" % accelmarker)
675 elif count1 == 1 and count2 > count1:
676 messages.append(u"accelerator %s is repeated in translation" % accelmarker)
677 else:
678 messages.append(u"accelerator %s occurs %d time(s) in original and %d time(s) in translation" % (accelmarker, count1, count2))
679 if messages:
680 if "accelerators" in self.config.criticaltests:
681 raise SeriousFilterFailure(messages)
682 else:
683 raise FilterFailure(messages)
684 return True
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
701 """checks whether variables of various forms are consistent between the
702 two strings"""
703 messages = []
704 mismatch1, mismatch2 = [], []
705 varnames1, varnames2 = [], []
706 for startmarker, endmarker in self.config.varmatches:
707 varchecker = decoration.getvariables(startmarker, endmarker)
708 if startmarker and endmarker:
709 if isinstance(endmarker, int):
710 redecorate = lambda var: startmarker + var
711 else:
712 redecorate = lambda var: startmarker + var + endmarker
713 elif startmarker:
714 redecorate = lambda var: startmarker + var
715 else:
716 redecorate = lambda var: var
717 vars1 = varchecker(str1)
718 vars2 = varchecker(str2)
719 if vars1 != vars2:
720
721 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)]
722
723
724 vars1, vars2 = [var for var in vars1 if var not in varnames1], [var for var in vars2 if var not in varnames2]
725 varnames1.extend(vars1)
726 varnames2.extend(vars2)
727 vars1 = map(redecorate, vars1)
728 vars2 = map(redecorate, vars2)
729 mismatch1.extend(vars1)
730 mismatch2.extend(vars2)
731 if mismatch1:
732 messages.append(u"do not translate: %s" % u", ".join(mismatch1))
733 elif mismatch2:
734 messages.append(u"translation contains variables not in original: %s" % u", ".join(mismatch2))
735 if messages and mismatch1:
736 raise SeriousFilterFailure(messages)
737 elif messages:
738 raise FilterFailure(messages)
739 return True
740
744
745 - def emails(self, str1, str2):
748
749 - def urls(self, str1, str2):
752
757
761
766
773
782
790
792 """checks that the number of brackets in both strings match"""
793 str1 = self.filtervariables(str1)
794 str2 = self.filtervariables(str2)
795 messages = []
796 missing = []
797 extra = []
798 for bracket in (u"[", u"]", u"{", u"}", u"(", u")"):
799 count1 = str1.count(bracket)
800 count2 = str2.count(bracket)
801 if count2 < count1:
802 missing.append(u"'%s'" % bracket)
803 elif count2 > count1:
804 extra.append(u"'%s'" % bracket)
805 if missing:
806 messages.append(u"translation is missing %s" % u", ".join(missing))
807 if extra:
808 messages.append(u"translation has extra %s" % u", ".join(extra))
809 if messages:
810 raise FilterFailure(messages)
811 return True
812
814 """checks that the number of sentences in both strings match"""
815 str1 = self.filteraccelerators(str1)
816 str2 = self.filteraccelerators(str2)
817 sentences1 = len(self.config.sourcelang.sentences(str1))
818 sentences2 = len(self.config.lang.sentences(str2))
819 if not sentences1 == sentences2:
820 raise FilterFailure(u"The number of sentences differ: %d versus %d" % (sentences1, sentences2))
821 return True
822
824 """checks that options are not translated"""
825 str1 = self.filtervariables(str1)
826 for word1 in str1.split():
827 if word1 != u"--" and word1.startswith(u"--") and word1[-1].isalnum():
828 parts = word1.split(u"=")
829 if not parts[0] in str2:
830 raise FilterFailure(u"The option %s does not occur or is translated in the translation." % parts[0])
831 if len(parts) > 1 and parts[1] in str2:
832 raise FilterFailure(u"The parameter %(param)s in option %(option)s is not translated." % {"param": parts[1], "option": parts[0]})
833 return True
834
836 """checks that the message starts with the correct capitalisation"""
837 str1 = self.filteraccelerators(str1)
838 str2 = self.filteraccelerators(str2)
839 if len(str1) > 1 and len(str2) > 1:
840 return self.config.sourcelang.capsstart(str1) == self.config.lang.capsstart(str2)
841 if len(str1) == 0 and len(str2) == 0:
842 return True
843 if len(str1) == 0 or len(str2) == 0:
844 return False
845 return True
846
848 """checks the capitalisation of two strings isn't wildly different"""
849 str1 = self.removevariables(str1)
850 str2 = self.removevariables(str2)
851
852
853 str1 = re.sub(u"[^%s]( I )" % self.config.sourcelang.sentenceend, u" i ", str1)
854 capitals1 = helpers.filtercount(str1, unicode.isupper)
855 capitals2 = helpers.filtercount(str2, unicode.isupper)
856 alpha1 = helpers.filtercount(str1, unicode.isalpha)
857 alpha2 = helpers.filtercount(str2, unicode.isalpha)
858
859 if capitals1 == alpha1:
860 return capitals2 == alpha2
861
862
863 if capitals1 == 0 or capitals1 == 1:
864 return capitals2 == capitals1
865 elif capitals1 < len(str1) / 10:
866 return capitals2 <= len(str2) / 8
867 elif len(str1) < 10:
868 return abs(capitals1 - capitals2) < 3
869 elif capitals1 > len(str1) * 6 / 10:
870 return capitals2 > len(str2) * 6 / 10
871 else:
872 return abs(capitals1 - capitals2) < (len(str1) + len(str2)) / 6
873
895
906
925
944
946 """checks that only characters specified as valid appear in the
947 translation"""
948 if not self.config.validcharsmap:
949 return True
950 invalid1 = str1.translate(self.config.validcharsmap)
951 invalid2 = str2.translate(self.config.validcharsmap)
952 invalidchars = [u"'%s' (\\u%04x)" % (invalidchar, ord(invalidchar)) for invalidchar in invalid2 if invalidchar not in invalid1]
953 if invalidchars:
954 raise FilterFailure(u"invalid chars: %s" % (u", ".join(invalidchars)))
955 return True
956
958 """checks that file paths have not been translated"""
959 for word1 in self.filteraccelerators(str1).split():
960 if word1.startswith(u"/"):
961 if not helpers.countsmatch(str1, str2, (word1,)):
962 return False
963 return True
964
992
997
999 """checks for Gettext compendium conflicts (#-#-#-#-#)"""
1000 return str2.find(u"#-#-#-#-#") == -1
1001
1003 """checks for English style plural(s) for you to review"""
1004
1005 def numberofpatterns(string, patterns):
1006 number = 0
1007 for pattern in patterns:
1008 number += len(re.findall(pattern, string))
1009 return number
1010
1011 sourcepatterns = ["\(s\)"]
1012 targetpatterns = ["\(s\)"]
1013 sourcecount = numberofpatterns(str1, sourcepatterns)
1014 targetcount = numberofpatterns(str2, targetpatterns)
1015 if self.config.lang.nplurals == 1:
1016 return not targetcount
1017 return sourcecount == targetcount
1018
1044
1046 """checks for messages containing translation credits instead of normal
1047 translations."""
1048 return not str1 in self.config.credit_sources
1049
1050
1051 preconditions = {"untranslated": ("simplecaps", "variables", "startcaps",
1052 "accelerators", "brackets", "endpunc",
1053 "acronyms", "xmltags", "startpunc",
1054 "endwhitespace", "startwhitespace",
1055 "escapes", "doublequoting", "singlequoting",
1056 "filepaths", "purepunc", "doublespacing",
1057 "sentencecount", "numbers", "isfuzzy",
1058 "isreview", "notranslatewords", "musttranslatewords",
1059 "emails", "simpleplurals", "urls", "printf",
1060 "tabs", "newlines", "functions", "options",
1061 "blank", "nplurals", "gconf"),
1062 "blank": ("simplecaps", "variables", "startcaps",
1063 "accelerators", "brackets", "endpunc",
1064 "acronyms", "xmltags", "startpunc",
1065 "endwhitespace", "startwhitespace",
1066 "escapes", "doublequoting", "singlequoting",
1067 "filepaths", "purepunc", "doublespacing",
1068 "sentencecount", "numbers", "isfuzzy",
1069 "isreview", "notranslatewords", "musttranslatewords",
1070 "emails", "simpleplurals", "urls", "printf",
1071 "tabs", "newlines", "functions", "options",
1072 "gconf"),
1073 "credits": ("simplecaps", "variables", "startcaps",
1074 "accelerators", "brackets", "endpunc",
1075 "acronyms", "xmltags", "startpunc",
1076 "escapes", "doublequoting", "singlequoting",
1077 "filepaths", "doublespacing",
1078 "sentencecount", "numbers",
1079 "emails", "simpleplurals", "urls", "printf",
1080 "tabs", "newlines", "functions", "options"),
1081 "purepunc": ("startcaps", "options"),
1082
1083
1084
1085
1086
1087
1088
1089
1090 "endwhitespace": ("endpunc",),
1091 "startwhitespace": ("startpunc",),
1092 "unchanged": ("doublewords",),
1093 "compendiumconflicts": ("accelerators", "brackets", "escapes",
1094 "numbers", "startpunc", "long", "variables",
1095 "startcaps", "sentencecount", "simplecaps",
1096 "doublespacing", "endpunc", "xmltags",
1097 "startwhitespace", "endwhitespace",
1098 "singlequoting", "doublequoting",
1099 "filepaths", "purepunc", "doublewords", "printf")}
1100
1101
1102
1103 openofficeconfig = CheckerConfig(
1104 accelmarkers=["~"],
1105 varmatches=[("&", ";"), ("%", "%"), ("%", None), ("%", 0), ("$(", ")"),
1106 ("$", "$"), ("${", "}"), ("#", "#"), ("#", 1), ("#", 0),
1107 ("($", ")"), ("$[", "]"), ("[", "]"), ("$", None)],
1108 ignoretags=[("alt", "xml-lang", None), ("ahelp", "visibility", "visible"),
1109 ("img", "width", None), ("img", "height", None)],
1110 canchangetags=[("link", "name", None)],
1111 )
1112
1122
1123 mozillaconfig = CheckerConfig(
1124 accelmarkers=["&"],
1125 varmatches=[("&", ";"), ("%", "%"), ("%", 1), ("$", "$"), ("$", None),
1126 ("#", 1), ("${", "}"), ("$(^", ")")],
1127 criticaltests=["accelerators"],
1128 )
1129
1131
1139
1141 """checks for messages containing translation credits instead of normal
1142 translations."""
1143 for location in self.locations:
1144 if location in ['MOZ_LANGPACK_CONTRIBUTORS', 'credit.translation']:
1145 return False
1146 return True
1147
1148 drupalconfig = CheckerConfig(
1149 varmatches=[("%", None), ("@", None), ("!", None)],
1150 )
1151
1161
1162 gnomeconfig = CheckerConfig(
1163 accelmarkers=["_"],
1164 varmatches=[("%", 1), ("$(", ")")],
1165 credit_sources=[u"translator-credits"],
1166 )
1167
1169
1177
1178 - def gconf(self, str1, str2):
1179 """Checks if we have any gconf config settings translated."""
1180 for location in self.locations:
1181 if location.find('schemas.in') != -1:
1182 gconf_attributes = gconf_attribute_re.findall(str1)
1183
1184 stopwords = [word for word in gconf_attributes if word[1:-1] not in str2]
1185 if stopwords:
1186 raise FilterFailure(u"do not translate gconf attribute: %s" % (u", ".join(stopwords)))
1187 return True
1188
1189 kdeconfig = CheckerConfig(
1190 accelmarkers=["&"],
1191 varmatches=[("%", 1)],
1192 credit_sources=[u"Your names", u"Your emails", u"ROLES_OF_TRANSLATORS"],
1193 )
1194
1206
1207 cclicenseconfig = CheckerConfig(varmatches=[("@", "@")])
1208
1218
1219 projectcheckers = {
1220 "openoffice": OpenOfficeChecker,
1221 "mozilla": MozillaChecker,
1222 "kde": KdeChecker,
1223 "wx": KdeChecker,
1224 "gnome": GnomeChecker,
1225 "creativecommons": CCLicenseChecker,
1226 "drupal": DrupalChecker,
1227 }
1228
1229
1231 """The standard checks for common checks on translation units."""
1232
1234 """Check if the unit has been marked fuzzy."""
1235 return not unit.isfuzzy()
1236
1238 """Check if the unit has been marked review."""
1239 return not unit.isreview()
1240
1250
1252 """Checks if there is at least one suggested translation for this
1253 unit."""
1254 self.suggestion_store = getattr(self, 'suggestion_store', None)
1255 suggestions = []
1256 if self.suggestion_store:
1257 suggestions = self.suggestion_store.findunits(unit.source)
1258 elif xliff and isinstance(unit, xliff.xliffunit):
1259
1260 suggestions = unit.getalttrans()
1261 return not bool(suggestions)
1262
1263
1264 -def runtests(str1, str2, ignorelist=()):
1276
1277
1279 """runs test on a batch of string pairs"""
1280 passed, numpairs = 0, len(pairs)
1281 for str1, str2 in pairs:
1282 if runtests(str1, str2):
1283 passed += 1
1284 print
1285 print "total: %d/%d pairs passed" % (passed, numpairs)
1286
1287 if __name__ == '__main__':
1288 testset = [(r"simple", r"somple"),
1289 (r"\this equals \that", r"does \this equal \that?"),
1290 (r"this \'equals\' that", r"this 'equals' that"),
1291 (r" start and end! they must match.", r"start and end! they must match."),
1292 (r"check for matching %variables marked like %this", r"%this %variable is marked"),
1293 (r"check for mismatching %variables marked like %this", r"%that %variable is marked"),
1294 (r"check for mismatching %variables% too", r"how many %variable% are marked"),
1295 (r"%% %%", r"%%"),
1296 (r"Row: %1, Column: %2", r"Mothalo: %1, Kholomo: %2"),
1297 (r"simple lowercase", r"it is all lowercase"),
1298 (r"simple lowercase", r"It Is All Lowercase"),
1299 (r"Simple First Letter Capitals", r"First Letters"),
1300 (r"SIMPLE CAPITALS", r"First Letters"),
1301 (r"SIMPLE CAPITALS", r"ALL CAPITALS"),
1302 (r"forgot to translate", r" "),
1303 ]
1304 batchruntests(testset)
1305