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), (None, "title", None)]
147
148
149
151 """object representing the configuration of a checker"""
152
153 - def __init__(self, targetlanguage=None, accelmarkers=None, varmatches=None,
154 notranslatewords=None, musttranslatewords=None,
155 validchars=None, punctuation=None, endpunctuation=None,
156 ignoretags=None, canchangetags=None, criticaltests=None,
157 credit_sources=None):
181
183 """initialise configuration paramaters that are lists
184
185 @type list: List
186 @param list: None (we'll initialise a blank list) or a list paramater
187 @rtype: List
188 """
189 if list is None:
190 list = []
191 return list
192
194 """initialise parameters that can have default options
195
196 @param param: the user supplied paramater value
197 @param default: default values when param is not specified
198 @return: the paramater as specified by the user of the default settings
199 """
200 if param is None:
201 return default
202 return param
203
204 - def update(self, otherconfig):
220
222 """updates the map that eliminates valid characters"""
223 if validchars is None:
224 return True
225 validcharsmap = dict([(ord(validchar), None) for validchar in data.normalized_unicode(validchars)])
226 self.validcharsmap.update(validcharsmap)
227
229 """Updates the target language in the config to the given target
230 language"""
231 self.lang = factory.getlanguage(langcode)
232
233
235
236 def cached_f(self, param1):
237 key = (f.__name__, param1)
238 res_cache = self.results_cache
239 if key in res_cache:
240 return res_cache[key]
241 else:
242 value = f(self, param1)
243 res_cache[key] = value
244 return value
245 return cached_f
246
247
249 """Parent Checker class which does the checking based on functions available
250 in derived classes."""
251 preconditions = {}
252
253 - def __init__(self, checkerconfig=None, excludefilters=None,
254 limitfilters=None, errorhandler=None):
255 self.errorhandler = errorhandler
256 if checkerconfig is None:
257 self.setconfig(CheckerConfig())
258 else:
259 self.setconfig(checkerconfig)
260
261 self.helperfunctions = {}
262 for functionname in dir(UnitChecker):
263 function = getattr(self, functionname)
264 if callable(function):
265 self.helperfunctions[functionname] = function
266 self.defaultfilters = self.getfilters(excludefilters, limitfilters)
267 self.results_cache = {}
268
269 - def getfilters(self, excludefilters=None, limitfilters=None):
270 """returns dictionary of available filters, including/excluding those in
271 the given lists"""
272 filters = {}
273 if limitfilters is None:
274
275 limitfilters = dir(self)
276 if excludefilters is None:
277 excludefilters = {}
278 for functionname in limitfilters:
279 if functionname in excludefilters:
280 continue
281 if functionname in self.helperfunctions:
282 continue
283 if functionname == "errorhandler":
284 continue
285 filterfunction = getattr(self, functionname, None)
286 if not callable(filterfunction):
287 continue
288 filters[functionname] = filterfunction
289 return filters
290
300
302 """Sets the filename that a checker should use for evaluating
303 suggestions."""
304 self.suggestion_store = store
305 if self.suggestion_store:
306 self.suggestion_store.require_index()
307
309 """filter out variables from str1"""
310 return helpers.multifilter(str1, self.varfilters)
311 filtervariables = cache_results(filtervariables)
312
314 """remove variables from str1"""
315 return helpers.multifilter(str1, self.removevarfilter)
316 removevariables = cache_results(removevariables)
317
319 """filter out accelerators from str1"""
320 return helpers.multifilter(str1, self.accfilters, None)
321 filteraccelerators = cache_results(filteraccelerators)
322
324 """filter out accelerators from str1"""
325 return helpers.multifilter(str1, self.accfilters, acceptlist)
326
331 filterwordswithpunctuation = cache_results(filterwordswithpunctuation)
332
334 """filter out XML from the string so only text remains"""
335 return tag_re.sub("", str1)
336 filterxml = cache_results(filterxml)
337
339 """Runs the given test on the given unit.
340
341 Note that this can raise a FilterFailure as part of normal operation"""
342 return test(unit)
343
345 """run all the tests in this suite, return failures as testname,
346 message_or_exception"""
347 self.results_cache = {}
348 failures = {}
349 ignores = self.config.lang.ignoretests[:]
350 functionnames = self.defaultfilters.keys()
351 priorityfunctionnames = self.preconditions.keys()
352 otherfunctionnames = filter(lambda functionname: functionname not in self.preconditions, functionnames)
353 for functionname in priorityfunctionnames + otherfunctionnames:
354 if functionname in ignores:
355 continue
356 filterfunction = getattr(self, functionname, None)
357
358
359 if filterfunction is None:
360 continue
361 filtermessage = filterfunction.__doc__
362 try:
363 filterresult = self.run_test(filterfunction, unit)
364 except FilterFailure, e:
365 filterresult = False
366 filtermessage = e.args[0]
367 except Exception, e:
368 if self.errorhandler is None:
369 raise ValueError("error in filter %s: %r, %r, %s" % \
370 (functionname, unit.source, unit.target, e))
371 else:
372 filterresult = self.errorhandler(functionname, unit.source,
373 unit.target, e)
374 if not filterresult:
375
376
377 if functionname in self.defaultfilters:
378 failures[functionname] = filtermessage
379 if functionname in self.preconditions:
380 for ignoredfunctionname in self.preconditions[functionname]:
381 ignores.append(ignoredfunctionname)
382 self.results_cache = {}
383 return failures
384
385
387 """A checker that passes source and target strings to the checks, not the
388 whole unit.
389
390 This provides some speedup and simplifies testing."""
391
392 - def __init__(self, checkerconfig=None, excludefilters=None,
393 limitfilters=None, errorhandler=None):
396
398 """Runs the given test on the given unit.
399
400 Note that this can raise a FilterFailure as part of normal operation."""
401 if self.hasplural:
402 filtermessages = []
403 filterresult = True
404 for pluralform in unit.target.strings:
405 try:
406 if not test(self.str1, unicode(pluralform)):
407 filterresult = False
408 except FilterFailure, e:
409 filterresult = False
410 filtermessages.append(unicode(e.args))
411 if not filterresult and filtermessages:
412 raise FilterFailure(filtermessages)
413 else:
414 return filterresult
415 else:
416 return test(self.str1, self.str2)
417
426
427
429 """A Checker that controls multiple checkers."""
430
431 - def __init__(self, checkerconfig=None, excludefilters=None,
432 limitfilters=None, checkerclasses=None, errorhandler=None,
433 languagecode=None):
449
450 - def getfilters(self, excludefilters=None, limitfilters=None):
466
473
479
480
482 """The basic test suite for source -> target translations."""
483
485 """checks whether a string has been translated at all"""
486 str2 = prefilters.removekdecomments(str2)
487 return not (len(str1.strip()) > 0 and len(str2) == 0)
488
490 """checks whether a translation is basically identical to the original
491 string"""
492 str1 = self.filteraccelerators(self.removevariables(str1)).strip()
493 str2 = self.filteraccelerators(self.removevariables(str2)).strip()
494 if len(str1) < 2:
495 return True
496
497
498
499 if (str1.isupper() or str1.upper() == str1) and str1 == str2:
500 return True
501 if self.config.notranslatewords:
502 words1 = str1.split()
503 if len(words1) == 1 and [word for word in words1 if word in self.config.notranslatewords]:
504
505
506
507 return True
508
509
510 if str1.lower() == str2.lower():
511 raise FilterFailure(u"please translate")
512 return True
513
514 - def blank(self, str1, str2):
515 """checks whether a translation only contains spaces"""
516 len1 = len(str1.strip())
517 len2 = len(str2.strip())
518 return not (len1 > 0 and len(str2) != 0 and len2 == 0)
519
520 - def short(self, str1, str2):
521 """checks whether a translation is much shorter than the original
522 string"""
523 len1 = len(str1.strip())
524 len2 = len(str2.strip())
525 return not ((len1 > 0) and (0 < len2 < (len1 * 0.1)) or ((len1 > 1) and (len2 == 1)))
526
527 - def long(self, str1, str2):
528 """checks whether a translation is much longer than the original
529 string"""
530 len1 = len(str1.strip())
531 len2 = len(str2.strip())
532 return not ((len1 > 0) and (0 < len1 < (len2 * 0.1)) or ((len1 == 1) and (len2 > 1)))
533
535 """checks whether escaping is consistent between the two strings"""
536 if not helpers.countsmatch(str1, str2, (u"\\", u"\\\\")):
537 escapes1 = u", ".join([u"'%s'" % word for word in str1.split() if u"\\" in word])
538 escapes2 = u", ".join([u"'%s'" % word for word in str2.split() if u"\\" in word])
539 raise SeriousFilterFailure(u"escapes in original (%s) don't match escapes in translation (%s)" % (escapes1, escapes2))
540 else:
541 return True
542
544 """checks whether newlines are consistent between the two strings"""
545 if not helpers.countsmatch(str1, str2, (u"\n", u"\r")):
546 raise FilterFailure(u"line endings in original don't match line endings in translation")
547 else:
548 return True
549
550 - def tabs(self, str1, str2):
551 """checks whether tabs are consistent between the two strings"""
552 if not helpers.countmatch(str1, str2, "\t"):
553 raise SeriousFilterFailure(u"tabs in original don't match tabs in translation")
554 else:
555 return True
556
563
573
579
581 """checks for bad spacing after punctuation"""
582
583
584 str1 = self.filteraccelerators(self.filtervariables(str1))
585 str1 = self.config.lang.punctranslate(str1)
586 str1 = str1.replace(u"\u00a0", u" ")
587 if str1.find(u" ") == -1:
588 return True
589 str2 = self.filteraccelerators(self.filtervariables(str2))
590 str2 = str2.replace(u"\u00a0", u" ")
591 for puncchar in self.config.punctuation:
592 plaincount1 = str1.count(puncchar)
593 plaincount2 = str2.count(puncchar)
594 if not plaincount1 or plaincount1 != plaincount2:
595 continue
596 spacecount1 = str1.count(puncchar + u" ")
597 spacecount2 = str2.count(puncchar + u" ")
598 if spacecount1 != spacecount2:
599
600 if str1.endswith(puncchar) != str2.endswith(puncchar) and abs(spacecount1 - spacecount2) == 1:
601 continue
602 return False
603 return True
604
605 - def printf(self, str1, str2):
606 """checks whether printf format strings match"""
607 count1 = count2 = plural = None
608
609 if 'hasplural' in self.__dict__:
610 plural = self.hasplural
611 for var_num2, match2 in enumerate(printf_pat.finditer(str2)):
612 count2 = var_num2 + 1
613 str2key = match2.group('key')
614 if match2.group('ord'):
615 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
616 count1 = var_num1 + 1
617 if int(match2.group('ord')) == var_num1 + 1:
618 if match2.group('fullvar') != match1.group('fullvar'):
619 return 0
620 elif str2key:
621 str1key = None
622 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
623 count1 = var_num1 + 1
624 if match1.group('key') and str2key == match1.group('key'):
625 str1key = match1.group('key')
626
627 if plural and match2.group('fullvar') == '.0s':
628 continue
629 if match1.group('fullvar') != match2.group('fullvar'):
630 return 0
631 if str1key == None:
632 return 0
633 else:
634 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
635 count1 = var_num1 + 1
636
637 if plural and match2.group('fullvar') == '.0s':
638 continue
639 if (var_num1 == var_num2) and (match1.group('fullvar') != match2.group('fullvar')):
640 return 0
641
642 if count2 is None:
643 if list(printf_pat.finditer(str1)):
644 return 0
645
646 if (count1 or count2) and (count1 != count2):
647 return 0
648 return 1
649
651 """checks whether accelerators are consistent between the two strings"""
652 str1 = self.filtervariables(str1)
653 str2 = self.filtervariables(str2)
654 messages = []
655 for accelmarker in self.config.accelmarkers:
656 counter1 = decoration.countaccelerators(accelmarker, self.config.sourcelang.validaccel)
657 counter2 = decoration.countaccelerators(accelmarker, self.config.lang.validaccel)
658 count1, countbad1 = counter1(str1)
659 count2, countbad2 = counter2(str2)
660 getaccel = decoration.getaccelerators(accelmarker, self.config.lang.validaccel)
661 accel2, bad2 = getaccel(str2)
662 if count1 == count2:
663 continue
664 if count1 == 1 and count2 == 0:
665 if countbad2 == 1:
666 messages.append(u"accelerator %s appears before an invalid accelerator character '%s' (eg. space)" % (accelmarker, bad2[0]))
667 else:
668 messages.append(u"accelerator %s is missing from translation" % accelmarker)
669 elif count1 == 0:
670 messages.append(u"accelerator %s does not occur in original and should not be in translation" % accelmarker)
671 elif count1 == 1 and count2 > count1:
672 messages.append(u"accelerator %s is repeated in translation" % accelmarker)
673 else:
674 messages.append(u"accelerator %s occurs %d time(s) in original and %d time(s) in translation" % (accelmarker, count1, count2))
675 if messages:
676 if "accelerators" in self.config.criticaltests:
677 raise SeriousFilterFailure(messages)
678 else:
679 raise FilterFailure(messages)
680 return True
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
697 """checks whether variables of various forms are consistent between the
698 two strings"""
699 messages = []
700 mismatch1, mismatch2 = [], []
701 varnames1, varnames2 = [], []
702 for startmarker, endmarker in self.config.varmatches:
703 varchecker = decoration.getvariables(startmarker, endmarker)
704 if startmarker and endmarker:
705 if isinstance(endmarker, int):
706 redecorate = lambda var: startmarker + var
707 else:
708 redecorate = lambda var: startmarker + var + endmarker
709 elif startmarker:
710 redecorate = lambda var: startmarker + var
711 else:
712 redecorate = lambda var: var
713 vars1 = varchecker(str1)
714 vars2 = varchecker(str2)
715 if vars1 != vars2:
716
717 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)]
718
719
720 vars1, vars2 = [var for var in vars1 if var not in varnames1], [var for var in vars2 if var not in varnames2]
721 varnames1.extend(vars1)
722 varnames2.extend(vars2)
723 vars1 = map(redecorate, vars1)
724 vars2 = map(redecorate, vars2)
725 mismatch1.extend(vars1)
726 mismatch2.extend(vars2)
727 if mismatch1:
728 messages.append(u"do not translate: %s" % u", ".join(mismatch1))
729 elif mismatch2:
730 messages.append(u"translation contains variables not in original: %s" % u", ".join(mismatch2))
731 if messages and mismatch1:
732 raise SeriousFilterFailure(messages)
733 elif messages:
734 raise FilterFailure(messages)
735 return True
736
740
741 - def emails(self, str1, str2):
744
745 - def urls(self, str1, str2):
748
753
757
762
769
778
786
788 """checks that the number of brackets in both strings match"""
789 str1 = self.filtervariables(str1)
790 str2 = self.filtervariables(str2)
791 messages = []
792 missing = []
793 extra = []
794 for bracket in (u"[", u"]", u"{", u"}", u"(", u")"):
795 count1 = str1.count(bracket)
796 count2 = str2.count(bracket)
797 if count2 < count1:
798 missing.append(u"'%s'" % bracket)
799 elif count2 > count1:
800 extra.append(u"'%s'" % bracket)
801 if missing:
802 messages.append(u"translation is missing %s" % u", ".join(missing))
803 if extra:
804 messages.append(u"translation has extra %s" % u", ".join(extra))
805 if messages:
806 raise FilterFailure(messages)
807 return True
808
810 """checks that the number of sentences in both strings match"""
811 str1 = self.filteraccelerators(str1)
812 str2 = self.filteraccelerators(str2)
813 sentences1 = len(self.config.sourcelang.sentences(str1))
814 sentences2 = len(self.config.lang.sentences(str2))
815 if not sentences1 == sentences2:
816 raise FilterFailure(u"The number of sentences differ: %d versus %d" % (sentences1, sentences2))
817 return True
818
820 """checks that options are not translated"""
821 str1 = self.filtervariables(str1)
822 for word1 in str1.split():
823 if word1 != u"--" and word1.startswith(u"--") and word1[-1].isalnum():
824 parts = word1.split(u"=")
825 if not parts[0] in str2:
826 raise FilterFailure(u"The option %s does not occur or is translated in the translation." % parts[0])
827 if len(parts) > 1 and parts[1] in str2:
828 raise FilterFailure(u"The parameter %(param)s in option %(option)s is not translated." % {"param": parts[1], "option": parts[0]})
829 return True
830
832 """checks that the message starts with the correct capitalisation"""
833 str1 = self.filteraccelerators(str1)
834 str2 = self.filteraccelerators(str2)
835 if len(str1) > 1 and len(str2) > 1:
836 return self.config.sourcelang.capsstart(str1) == self.config.lang.capsstart(str2)
837 if len(str1) == 0 and len(str2) == 0:
838 return True
839 if len(str1) == 0 or len(str2) == 0:
840 return False
841 return True
842
844 """checks the capitalisation of two strings isn't wildly different"""
845 str1 = self.removevariables(str1)
846 str2 = self.removevariables(str2)
847
848
849 str1 = re.sub(u"[^%s]( I )" % self.config.sourcelang.sentenceend, u" i ", str1)
850 capitals1 = helpers.filtercount(str1, unicode.isupper)
851 capitals2 = helpers.filtercount(str2, unicode.isupper)
852 alpha1 = helpers.filtercount(str1, unicode.isalpha)
853 alpha2 = helpers.filtercount(str2, unicode.isalpha)
854
855 if capitals1 == alpha1:
856 return capitals2 == alpha2
857
858
859 if capitals1 == 0 or capitals1 == 1:
860 return capitals2 == capitals1
861 elif capitals1 < len(str1) / 10:
862 return capitals2 <= len(str2) / 8
863 elif len(str1) < 10:
864 return abs(capitals1 - capitals2) < 3
865 elif capitals1 > len(str1) * 6 / 10:
866 return capitals2 > len(str2) * 6 / 10
867 else:
868 return abs(capitals1 - capitals2) < (len(str1) + len(str2)) / 6
869
891
902
921
940
942 """checks that only characters specified as valid appear in the
943 translation"""
944 if not self.config.validcharsmap:
945 return True
946 invalid1 = str1.translate(self.config.validcharsmap)
947 invalid2 = str2.translate(self.config.validcharsmap)
948 invalidchars = [u"'%s' (\\u%04x)" % (invalidchar, ord(invalidchar)) for invalidchar in invalid2 if invalidchar not in invalid1]
949 if invalidchars:
950 raise FilterFailure(u"invalid chars: %s" % (u", ".join(invalidchars)))
951 return True
952
954 """checks that file paths have not been translated"""
955 for word1 in self.filteraccelerators(str1).split():
956 if word1.startswith(u"/"):
957 if not helpers.countsmatch(str1, str2, (word1,)):
958 return False
959 return True
960
988
993
995 """checks for Gettext compendium conflicts (#-#-#-#-#)"""
996 return str2.find(u"#-#-#-#-#") == -1
997
999 """checks for English style plural(s) for you to review"""
1000
1001 def numberofpatterns(string, patterns):
1002 number = 0
1003 for pattern in patterns:
1004 number += len(re.findall(pattern, string))
1005 return number
1006
1007 sourcepatterns = ["\(s\)"]
1008 targetpatterns = ["\(s\)"]
1009 sourcecount = numberofpatterns(str1, sourcepatterns)
1010 targetcount = numberofpatterns(str2, targetpatterns)
1011 if self.config.lang.nplurals == 1:
1012 return not targetcount
1013 return sourcecount == targetcount
1014
1040
1042 """checks for messages containing translation credits instead of normal
1043 translations."""
1044 return not str1 in self.config.credit_sources
1045
1046
1047 preconditions = {"untranslated": ("simplecaps", "variables", "startcaps",
1048 "accelerators", "brackets", "endpunc",
1049 "acronyms", "xmltags", "startpunc",
1050 "endwhitespace", "startwhitespace",
1051 "escapes", "doublequoting", "singlequoting",
1052 "filepaths", "purepunc", "doublespacing",
1053 "sentencecount", "numbers", "isfuzzy",
1054 "isreview", "notranslatewords", "musttranslatewords",
1055 "emails", "simpleplurals", "urls", "printf",
1056 "tabs", "newlines", "functions", "options",
1057 "blank", "nplurals", "gconf"),
1058 "blank": ("simplecaps", "variables", "startcaps",
1059 "accelerators", "brackets", "endpunc",
1060 "acronyms", "xmltags", "startpunc",
1061 "endwhitespace", "startwhitespace",
1062 "escapes", "doublequoting", "singlequoting",
1063 "filepaths", "purepunc", "doublespacing",
1064 "sentencecount", "numbers", "isfuzzy",
1065 "isreview", "notranslatewords", "musttranslatewords",
1066 "emails", "simpleplurals", "urls", "printf",
1067 "tabs", "newlines", "functions", "options",
1068 "gconf"),
1069 "credits": ("simplecaps", "variables", "startcaps",
1070 "accelerators", "brackets", "endpunc",
1071 "acronyms", "xmltags", "startpunc",
1072 "escapes", "doublequoting", "singlequoting",
1073 "filepaths", "doublespacing",
1074 "sentencecount", "numbers",
1075 "emails", "simpleplurals", "urls", "printf",
1076 "tabs", "newlines", "functions", "options"),
1077 "purepunc": ("startcaps", "options"),
1078
1079
1080
1081
1082
1083
1084
1085
1086 "endwhitespace": ("endpunc",),
1087 "startwhitespace": ("startpunc",),
1088 "unchanged": ("doublewords",),
1089 "compendiumconflicts": ("accelerators", "brackets", "escapes",
1090 "numbers", "startpunc", "long", "variables",
1091 "startcaps", "sentencecount", "simplecaps",
1092 "doublespacing", "endpunc", "xmltags",
1093 "startwhitespace", "endwhitespace",
1094 "singlequoting", "doublequoting",
1095 "filepaths", "purepunc", "doublewords", "printf")}
1096
1097
1098
1099 openofficeconfig = CheckerConfig(
1100 accelmarkers=["~"],
1101 varmatches=[("&", ";"), ("%", "%"), ("%", None), ("%", 0), ("$(", ")"),
1102 ("$", "$"), ("${", "}"), ("#", "#"), ("#", 1), ("#", 0),
1103 ("($", ")"), ("$[", "]"), ("[", "]"), ("$", None)],
1104 ignoretags=[("alt", "xml-lang", None), ("ahelp", "visibility", "visible"),
1105 ("img", "width", None), ("img", "height", None)],
1106 canchangetags=[("link", "name", None)],
1107 )
1108
1118
1119 mozillaconfig = CheckerConfig(
1120 accelmarkers=["&"],
1121 varmatches=[("&", ";"), ("%", "%"), ("%", 1), ("$", "$"), ("$", None),
1122 ("#", 1), ("${", "}"), ("$(^", ")")],
1123 criticaltests=["accelerators"],
1124 )
1125
1127
1135
1137 """checks for messages containing translation credits instead of normal
1138 translations."""
1139 for location in self.locations:
1140 if location in ['MOZ_LANGPACK_CONTRIBUTORS', 'credit.translation']:
1141 return False
1142 return True
1143
1144 drupalconfig = CheckerConfig(
1145 varmatches=[("%", None), ("@", None), ("!", None)],
1146 )
1147
1157
1158 gnomeconfig = CheckerConfig(
1159 accelmarkers=["_"],
1160 varmatches=[("%", 1), ("$(", ")")],
1161 credit_sources=[u"translator-credits"],
1162 )
1163
1165
1173
1174 - def gconf(self, str1, str2):
1175 """Checks if we have any gconf config settings translated."""
1176 for location in self.locations:
1177 if location.find('schemas.in') != -1:
1178 gconf_attributes = gconf_attribute_re.findall(str1)
1179
1180 stopwords = [word for word in gconf_attributes if word[1:-1] not in str2]
1181 if stopwords:
1182 raise FilterFailure(u"do not translate gconf attribute: %s" % (u", ".join(stopwords)))
1183 return True
1184
1185 kdeconfig = CheckerConfig(
1186 accelmarkers=["&"],
1187 varmatches=[("%", 1)],
1188 credit_sources=[u"Your names", u"Your emails", u"ROLES_OF_TRANSLATORS"],
1189 )
1190
1202
1203 cclicenseconfig = CheckerConfig(varmatches=[("@", "@")])
1204
1214
1215 projectcheckers = {
1216 "openoffice": OpenOfficeChecker,
1217 "mozilla": MozillaChecker,
1218 "kde": KdeChecker,
1219 "wx": KdeChecker,
1220 "gnome": GnomeChecker,
1221 "creativecommons": CCLicenseChecker,
1222 "drupal": DrupalChecker,
1223 }
1224
1225
1227 """The standard checks for common checks on translation units."""
1228
1230 """Check if the unit has been marked fuzzy."""
1231 return not unit.isfuzzy()
1232
1234 """Check if the unit has been marked review."""
1235 return not unit.isreview()
1236
1246
1248 """Checks if there is at least one suggested translation for this
1249 unit."""
1250 self.suggestion_store = getattr(self, 'suggestion_store', None)
1251 suggestions = []
1252 if self.suggestion_store:
1253 suggestions = self.suggestion_store.findunits(unit.source)
1254 elif xliff and isinstance(unit, xliff.xliffunit):
1255
1256 suggestions = unit.getalttrans()
1257 return not bool(suggestions)
1258
1259
1260 -def runtests(str1, str2, ignorelist=()):
1272
1273
1275 """runs test on a batch of string pairs"""
1276 passed, numpairs = 0, len(pairs)
1277 for str1, str2 in pairs:
1278 if runtests(str1, str2):
1279 passed += 1
1280 print
1281 print "total: %d/%d pairs passed" % (passed, numpairs)
1282
1283 if __name__ == '__main__':
1284 testset = [(r"simple", r"somple"),
1285 (r"\this equals \that", r"does \this equal \that?"),
1286 (r"this \'equals\' that", r"this 'equals' that"),
1287 (r" start and end! they must match.", r"start and end! they must match."),
1288 (r"check for matching %variables marked like %this", r"%this %variable is marked"),
1289 (r"check for mismatching %variables marked like %this", r"%that %variable is marked"),
1290 (r"check for mismatching %variables% too", r"how many %variable% are marked"),
1291 (r"%% %%", r"%%"),
1292 (r"Row: %1, Column: %2", r"Mothalo: %1, Kholomo: %2"),
1293 (r"simple lowercase", r"it is all lowercase"),
1294 (r"simple lowercase", r"It Is All Lowercase"),
1295 (r"Simple First Letter Capitals", r"First Letters"),
1296 (r"SIMPLE CAPITALS", r"First Letters"),
1297 (r"SIMPLE CAPITALS", r"ALL CAPITALS"),
1298 (r"forgot to translate", r" "),
1299 ]
1300 batchruntests(testset)
1301