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
45
46
47
48 printf_pat = re.compile('''
49 %( # initial %
50 (?:(?P<ord>\d+)\$| # variable order, like %1$s
51 \((?P<key>\w+)\))? # Python style variables, like %(var)s
52 (?P<fullvar>
53 [+#-]* # flags
54 (?:\d+)? # width
55 (?:\.\d+)? # precision
56 (hh\|h\|l\|ll)? # length formatting
57 (?P<type>[\w%])) # type (%s, %d, etc.)
58 )''', re.VERBOSE)
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 self.messages = messages
127
129 return unicode(u", ".join(self.messages))
130
132 return str(u", ".join(self.messages))
133
134
136 """This exception signals that a Filter didn't pass, and the bad translation
137 might break an application (so the string will be marked fuzzy)"""
138 pass
139
140
141
142
143
144
145
146
147 common_ignoretags = [(None, "xml-lang", None)]
148 common_canchangetags = [("img", "alt", None),
149 (None, "title", None),
150 (None, "dir", None),
151 (None, "lang", None),
152 ]
153
154
155
157 """object representing the configuration of a checker"""
158
159 - def __init__(self, targetlanguage=None, accelmarkers=None, varmatches=None,
160 notranslatewords=None, musttranslatewords=None,
161 validchars=None, punctuation=None, endpunctuation=None,
162 ignoretags=None, canchangetags=None, criticaltests=None,
163 credit_sources=None):
187
189 """initialise configuration paramaters that are lists
190
191 @type list: List
192 @param list: None (we'll initialise a blank list) or a list paramater
193 @rtype: List
194 """
195 if list is None:
196 list = []
197 return list
198
200 """initialise parameters that can have default options
201
202 @param param: the user supplied paramater value
203 @param default: default values when param is not specified
204 @return: the paramater as specified by the user of the default settings
205 """
206 if param is None:
207 return default
208 return param
209
210 - def update(self, otherconfig):
226
228 """updates the map that eliminates valid characters"""
229 if validchars is None:
230 return True
231 validcharsmap = dict([(ord(validchar), None) for validchar in data.normalized_unicode(validchars)])
232 self.validcharsmap.update(validcharsmap)
233
235 """Updates the target language in the config to the given target
236 language"""
237 self.lang = factory.getlanguage(langcode)
238
239
241
242 def cached_f(self, param1):
243 key = (f.__name__, param1)
244 res_cache = self.results_cache
245 if key in res_cache:
246 return res_cache[key]
247 else:
248 value = f(self, param1)
249 res_cache[key] = value
250 return value
251 return cached_f
252
253
255 """Parent Checker class which does the checking based on functions available
256 in derived classes."""
257 preconditions = {}
258
259 - def __init__(self, checkerconfig=None, excludefilters=None,
260 limitfilters=None, errorhandler=None):
261 self.errorhandler = errorhandler
262 if checkerconfig is None:
263 self.setconfig(CheckerConfig())
264 else:
265 self.setconfig(checkerconfig)
266
267 self.helperfunctions = {}
268 for functionname in dir(UnitChecker):
269 function = getattr(self, functionname)
270 if callable(function):
271 self.helperfunctions[functionname] = function
272 self.defaultfilters = self.getfilters(excludefilters, limitfilters)
273 self.results_cache = {}
274
275 - def getfilters(self, excludefilters=None, limitfilters=None):
276 """returns dictionary of available filters, including/excluding those in
277 the given lists"""
278 filters = {}
279 if limitfilters is None:
280
281 limitfilters = dir(self)
282 if excludefilters is None:
283 excludefilters = {}
284 for functionname in limitfilters:
285 if functionname in excludefilters:
286 continue
287 if functionname in self.helperfunctions:
288 continue
289 if functionname == "errorhandler":
290 continue
291 filterfunction = getattr(self, functionname, None)
292 if not callable(filterfunction):
293 continue
294 filters[functionname] = filterfunction
295 return filters
296
306
308 """Sets the filename that a checker should use for evaluating
309 suggestions."""
310 self.suggestion_store = store
311 if self.suggestion_store:
312 self.suggestion_store.require_index()
313
315 """filter out variables from str1"""
316 return helpers.multifilter(str1, self.varfilters)
317 filtervariables = cache_results(filtervariables)
318
320 """remove variables from str1"""
321 return helpers.multifilter(str1, self.removevarfilter)
322 removevariables = cache_results(removevariables)
323
325 """filter out accelerators from str1"""
326 return helpers.multifilter(str1, self.accfilters, None)
327 filteraccelerators = cache_results(filteraccelerators)
328
330 """filter out accelerators from str1"""
331 return helpers.multifilter(str1, self.accfilters, acceptlist)
332
337 filterwordswithpunctuation = cache_results(filterwordswithpunctuation)
338
340 """filter out XML from the string so only text remains"""
341 return tag_re.sub("", str1)
342 filterxml = cache_results(filterxml)
343
345 """Runs the given test on the given unit.
346
347 Note that this can raise a FilterFailure as part of normal operation"""
348 return test(unit)
349
351 """run all the tests in this suite, return failures as testname,
352 message_or_exception"""
353 self.results_cache = {}
354 failures = {}
355 ignores = self.config.lang.ignoretests[:]
356 functionnames = self.defaultfilters.keys()
357 priorityfunctionnames = self.preconditions.keys()
358 otherfunctionnames = filter(lambda functionname: functionname not in self.preconditions, functionnames)
359 for functionname in priorityfunctionnames + otherfunctionnames:
360 if functionname in ignores:
361 continue
362 filterfunction = getattr(self, functionname, None)
363
364
365 if filterfunction is None:
366 continue
367 filtermessage = filterfunction.__doc__
368 try:
369 filterresult = self.run_test(filterfunction, unit)
370 except FilterFailure, e:
371 filterresult = False
372 filtermessage = unicode(e)
373 except Exception, e:
374 if self.errorhandler is None:
375 raise ValueError("error in filter %s: %r, %r, %s" % \
376 (functionname, unit.source, unit.target, e))
377 else:
378 filterresult = self.errorhandler(functionname, unit.source,
379 unit.target, e)
380 if not filterresult:
381
382
383 if functionname in self.defaultfilters:
384 failures[functionname] = filtermessage
385 if functionname in self.preconditions:
386 for ignoredfunctionname in self.preconditions[functionname]:
387 ignores.append(ignoredfunctionname)
388 self.results_cache = {}
389 return failures
390
391
393 """A checker that passes source and target strings to the checks, not the
394 whole unit.
395
396 This provides some speedup and simplifies testing."""
397
398 - def __init__(self, checkerconfig=None, excludefilters=None,
399 limitfilters=None, errorhandler=None):
402
404 """Runs the given test on the given unit.
405
406 Note that this can raise a FilterFailure as part of normal operation."""
407 if self.hasplural:
408 filtermessages = []
409 filterresult = True
410 for pluralform in unit.target.strings:
411 try:
412 if not test(self.str1, unicode(pluralform)):
413 filterresult = False
414 except FilterFailure, e:
415 filterresult = False
416 filtermessages.extend(e.messages)
417 if not filterresult and filtermessages:
418 raise FilterFailure(filtermessages)
419 else:
420 return filterresult
421 else:
422 return test(self.str1, self.str2)
423
432
433
435 """A Checker that controls multiple checkers."""
436
437 - def __init__(self, checkerconfig=None, excludefilters=None,
438 limitfilters=None, checkerclasses=None, errorhandler=None,
439 languagecode=None):
458
459 - def getfilters(self, excludefilters=None, limitfilters=None):
475
482
488
489
491 """The basic test suite for source -> target translations."""
492
494 """checks whether a string has been translated at all"""
495 str2 = prefilters.removekdecomments(str2)
496 return not (len(str1.strip()) > 0 and len(str2) == 0)
497
499 """checks whether a translation is basically identical to the original
500 string"""
501 str1 = self.filteraccelerators(self.removevariables(str1)).strip()
502 str2 = self.filteraccelerators(self.removevariables(str2)).strip()
503 if len(str1) < 2:
504 return True
505
506
507
508 if (str1.isupper() or str1.upper() == str1) and str1 == str2:
509 return True
510 if self.config.notranslatewords:
511 words1 = str1.split()
512 if len(words1) == 1 and [word for word in words1 if word in self.config.notranslatewords]:
513
514
515
516 return True
517
518
519 if str1.lower() == str2.lower():
520 raise FilterFailure(u"Consider translating")
521 return True
522
523 - def blank(self, str1, str2):
524 """checks whether a translation only contains spaces"""
525 len1 = len(str1.strip())
526 len2 = len(str2.strip())
527 if len1 > 0 and len(str2) != 0 and len2 == 0:
528 raise FilterFailure(u"Translation is empty")
529 else:
530 return True
531
532 - def short(self, str1, str2):
533 """checks whether a translation is much shorter than the original
534 string"""
535 len1 = len(str1.strip())
536 len2 = len(str2.strip())
537 if (len1 > 0) and (0 < len2 < (len1 * 0.1)) or ((len1 > 1) and (len2 == 1)):
538 raise FilterFailure(u"The translation is much shorter than the original")
539 else:
540 return True
541
542 - def long(self, str1, str2):
543 """checks whether a translation is much longer than the original
544 string"""
545 len1 = len(str1.strip())
546 len2 = len(str2.strip())
547 if (len1 > 0) and (0 < len1 < (len2 * 0.1)) or ((len1 == 1) and (len2 > 1)):
548 raise FilterFailure(u"The translation is much longer than the original")
549 else:
550 return True
551
553 """checks whether escaping is consistent between the two strings"""
554 if not helpers.countsmatch(str1, str2, (u"\\", u"\\\\")):
555 escapes1 = u", ".join([u"'%s'" % word for word in str1.split() if u"\\" in word])
556 escapes2 = u", ".join([u"'%s'" % word for word in str2.split() if u"\\" in word])
557 raise SeriousFilterFailure(u"Escapes in original (%s) don't match "
558 "escapes in translation (%s)" %
559 (escapes1, escapes2))
560 else:
561 return True
562
564 """checks whether newlines are consistent between the two strings"""
565 if not helpers.countsmatch(str1, str2, (u"\n", u"\r")):
566 raise FilterFailure(u"Different line endings")
567 else:
568 return True
569
570 - def tabs(self, str1, str2):
571 """checks whether tabs are consistent between the two strings"""
572 if not helpers.countmatch(str1, str2, "\t"):
573 raise SeriousFilterFailure(u"Different tabs")
574 else:
575 return True
576
586
599
608
610 """checks for bad spacing after punctuation"""
611
612
613 str1 = self.filteraccelerators(self.filtervariables(str1))
614 str1 = self.config.lang.punctranslate(str1)
615 str1 = str1.replace(u"\u00a0", u" ")
616 if str1.find(u" ") == -1:
617 return True
618 str2 = self.filteraccelerators(self.filtervariables(str2))
619 str2 = str2.replace(u"\u00a0", u" ")
620 for puncchar in self.config.punctuation:
621 plaincount1 = str1.count(puncchar)
622 if not plaincount1:
623 continue
624 plaincount2 = str2.count(puncchar)
625 if plaincount1 != plaincount2:
626 continue
627 spacecount1 = str1.count(puncchar + u" ")
628 spacecount2 = str2.count(puncchar + u" ")
629 if spacecount1 != spacecount2:
630
631 if abs(spacecount1 - spacecount2) == 1 and str1.endswith(puncchar) != str2.endswith(puncchar):
632 continue
633 raise FilterFailure(u"Different spacing around punctuation")
634 return True
635
636 - def printf(self, str1, str2):
637 """checks whether printf format strings match"""
638 count1 = count2 = plural = None
639
640 if 'hasplural' in self.__dict__:
641 plural = self.hasplural
642 for var_num2, match2 in enumerate(printf_pat.finditer(str2)):
643 count2 = var_num2 + 1
644 str2ord = match2.group('ord')
645 str2key = match2.group('key')
646 if str2ord:
647 str1ord = None
648 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
649 count1 = var_num1 + 1
650 if match1.group('ord'):
651 if str2ord == match1.group('ord'):
652 str1ord = str2ord
653 if match2.group('fullvar') != match1.group('fullvar'):
654 raise FilterFailure(u"Different printf variable: %s" % match2.group())
655 elif int(str2ord) == var_num1 + 1:
656 str1ord = str2ord
657 if match2.group('fullvar') != match1.group('fullvar'):
658 raise FilterFailure(u"Different printf variable: %s" % match2.group())
659 if str1ord == None:
660 raise FilterFailure(u"Added printf variable: %s" % match2.group())
661 elif str2key:
662 str1key = None
663 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
664 count1 = var_num1 + 1
665 if match1.group('key') and str2key == match1.group('key'):
666 str1key = match1.group('key')
667
668 if plural and match2.group('fullvar') == '.0s':
669 continue
670 if match1.group('fullvar') != match2.group('fullvar'):
671 raise FilterFailure(u"Different printf variable: %s" % match2.group())
672 if str1key == None:
673 raise FilterFailure(u"Added printf variable: %s" % match2.group())
674 else:
675 for var_num1, match1 in enumerate(printf_pat.finditer(str1)):
676 count1 = var_num1 + 1
677
678 if plural and match2.group('fullvar') == '.0s':
679 continue
680 if (var_num1 == var_num2) and (match1.group('fullvar') != match2.group('fullvar')):
681 raise FilterFailure(u"Different printf variable: %s" % match2.group())
682
683 if count2 is None:
684 str1_variables = list(m.group() for m in printf_pat.finditer(str1))
685 if str1_variables:
686 raise FilterFailure(u"Missing printf variable: %s" % u", ".join(str1_variables))
687
688 if (count1 or count2) and (count1 != count2):
689 raise FilterFailure(u"Different number of printf variables")
690 return 1
691
693 """checks whether accelerators are consistent between the two strings"""
694 str1 = self.filtervariables(str1)
695 str2 = self.filtervariables(str2)
696 messages = []
697 for accelmarker in self.config.accelmarkers:
698 counter1 = decoration.countaccelerators(accelmarker, self.config.sourcelang.validaccel)
699 counter2 = decoration.countaccelerators(accelmarker, self.config.lang.validaccel)
700 count1, countbad1 = counter1(str1)
701 count2, countbad2 = counter2(str2)
702 getaccel = decoration.getaccelerators(accelmarker, self.config.lang.validaccel)
703 accel2, bad2 = getaccel(str2)
704 if count1 == count2:
705 continue
706 if count1 == 1 and count2 == 0:
707 if countbad2 == 1:
708 messages.append(u"Accelerator '%s' appears before an invalid "
709 "accelerator character '%s'" %
710 (accelmarker, bad2[0]))
711 else:
712 messages.append(u"Missing accelerator '%s'" %
713 accelmarker)
714 elif count1 == 0:
715 messages.append(u"Added accelerator '%s'" % accelmarker)
716 elif count1 == 1 and count2 > count1:
717 messages.append(u"Accelerator '%s' is repeated in translation" %
718 accelmarker)
719 else:
720 messages.append(u"Accelerator '%s' occurs %d time(s) in original "
721 "and %d time(s) in translation" %
722 (accelmarker, count1, count2))
723 if messages:
724 if "accelerators" in self.config.criticaltests:
725 raise SeriousFilterFailure(messages)
726 else:
727 raise FilterFailure(messages)
728 return True
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
745 """checks whether variables of various forms are consistent between the
746 two strings"""
747 messages = []
748 mismatch1, mismatch2 = [], []
749 varnames1, varnames2 = [], []
750 for startmarker, endmarker in self.config.varmatches:
751 varchecker = decoration.getvariables(startmarker, endmarker)
752 if startmarker and endmarker:
753 if isinstance(endmarker, int):
754 redecorate = lambda var: startmarker + var
755 else:
756 redecorate = lambda var: startmarker + var + endmarker
757 elif startmarker:
758 redecorate = lambda var: startmarker + var
759 else:
760 redecorate = lambda var: var
761 vars1 = varchecker(str1)
762 vars2 = varchecker(str2)
763 if vars1 != vars2:
764
765 vars1, vars2 = [var for var in vars1 if vars1.count(var) > vars2.count(var)], \
766 [var for var in vars2 if vars1.count(var) < vars2.count(var)]
767
768
769 vars1, vars2 = [var for var in vars1 if var not in varnames1], [var for var in vars2 if var not in varnames2]
770 varnames1.extend(vars1)
771 varnames2.extend(vars2)
772 vars1 = map(redecorate, vars1)
773 vars2 = map(redecorate, vars2)
774 mismatch1.extend(vars1)
775 mismatch2.extend(vars2)
776 if mismatch1:
777 messages.append(u"Do not translate: %s" % u", ".join(mismatch1))
778 elif mismatch2:
779 messages.append(u"Added variables: %s" % u", ".join(mismatch2))
780 if messages and mismatch1:
781 raise SeriousFilterFailure(messages)
782 elif messages:
783 raise FilterFailure(messages)
784 return True
785
793
794 - def emails(self, str1, str2):
800
801 - def urls(self, str1, str2):
807
815
822
830
840
852
864
866 """checks that the number of brackets in both strings match"""
867 str1 = self.filtervariables(str1)
868 str2 = self.filtervariables(str2)
869 messages = []
870 missing = []
871 extra = []
872 for bracket in (u"[", u"]", u"{", u"}", u"(", u")"):
873 count1 = str1.count(bracket)
874 count2 = str2.count(bracket)
875 if count2 < count1:
876 missing.append(u"'%s'" % bracket)
877 elif count2 > count1:
878 extra.append(u"'%s'" % bracket)
879 if missing:
880 messages.append(u"Missing %s" % u", ".join(missing))
881 if extra:
882 messages.append(u"Added %s" % u", ".join(extra))
883 if messages:
884 raise FilterFailure(messages)
885 return True
886
888 """checks that the number of sentences in both strings match"""
889 str1 = self.filteraccelerators(str1)
890 str2 = self.filteraccelerators(str2)
891 sentences1 = len(self.config.sourcelang.sentences(str1))
892 sentences2 = len(self.config.lang.sentences(str2))
893 if not sentences1 == sentences2:
894 raise FilterFailure(u"Different number of sentences: "
895 u"%d ≠ %d" % (sentences1, sentences2))
896 return True
897
899 """checks that options are not translated"""
900 str1 = self.filtervariables(str1)
901 for word1 in str1.split():
902 if word1 != u"--" and word1.startswith(u"--") and word1[-1].isalnum():
903 parts = word1.split(u"=")
904 if not parts[0] in str2:
905 raise FilterFailure(u"Missing or translated option '%s'" % parts[0])
906 if len(parts) > 1 and parts[1] in str2:
907 raise FilterFailure(u"Consider translating parameter "
908 u"'%(param)s' of option '%(option)s'"
909 % {"param": parts[1],
910 "option": parts[0]})
911 return True
912
914 """checks that the message starts with the correct capitalisation"""
915 str1 = self.filteraccelerators(str1)
916 str2 = self.filteraccelerators(str2)
917 if len(str1) > 1 and len(str2) > 1:
918 if self.config.sourcelang.capsstart(str1) == self.config.lang.capsstart(str2):
919 return True
920 else:
921 raise FilterFailure(u"Different capitalization at the start")
922 if len(str1) == 0 and len(str2) == 0:
923 return True
924 if len(str1) == 0 or len(str2) == 0:
925 raise FilterFailure(u"Different capitalization at the start")
926 return True
927
929 """checks the capitalisation of two strings isn't wildly different"""
930 str1 = self.removevariables(str1)
931 str2 = self.removevariables(str2)
932
933
934 str1 = re.sub(u"[^%s]( I )" % self.config.sourcelang.sentenceend, u" i ", str1)
935 capitals1 = helpers.filtercount(str1, unicode.isupper)
936 capitals2 = helpers.filtercount(str2, unicode.isupper)
937 alpha1 = helpers.filtercount(str1, unicode.isalpha)
938 alpha2 = helpers.filtercount(str2, unicode.isalpha)
939
940 if capitals1 == alpha1:
941 if capitals2 == alpha2:
942 return True
943 else:
944 raise FilterFailure(u"Different capitalization")
945
946
947 if capitals1 == 0 or capitals1 == 1:
948 success = capitals2 == capitals1
949 elif capitals1 < len(str1) / 10:
950 success = capitals2 <= len(str2) / 8
951 elif len(str1) < 10:
952 success = abs(capitals1 - capitals2) < 3
953 elif capitals1 > len(str1) * 6 / 10:
954 success = capitals2 > len(str2) * 6 / 10
955 else:
956 success = abs(capitals1 - capitals2) < (len(str1) + len(str2)) / 6
957 if success:
958 return True
959 else:
960 raise FilterFailure(u"Different capitalization")
961
984
995
1015
1034
1036 """checks that only characters specified as valid appear in the
1037 translation"""
1038 if not self.config.validcharsmap:
1039 return True
1040 invalid1 = str1.translate(self.config.validcharsmap)
1041 invalid2 = str2.translate(self.config.validcharsmap)
1042 invalidchars = [u"'%s' (\\u%04x)" % (invalidchar, ord(invalidchar)) for invalidchar in invalid2 if invalidchar not in invalid1]
1043 if invalidchars:
1044 raise FilterFailure(u"Invalid characters: %s" % (u", ".join(invalidchars)))
1045 return True
1046
1048 """checks that file paths have not been translated"""
1049 for word1 in self.filteraccelerators(str1).split():
1050 if word1.startswith(u"/"):
1051 if not helpers.countsmatch(str1, str2, (word1,)):
1052 raise FilterFailure(u"Different file paths")
1053 return True
1054
1082
1087
1089 """checks for Gettext compendium conflicts (#-#-#-#-#)"""
1090 return str2.find(u"#-#-#-#-#") == -1
1091
1093 """checks for English style plural(s) for you to review"""
1094
1095 def numberofpatterns(string, patterns):
1096 number = 0
1097 for pattern in patterns:
1098 number += len(re.findall(pattern, string))
1099 return number
1100
1101 sourcepatterns = ["\(s\)"]
1102 targetpatterns = ["\(s\)"]
1103 sourcecount = numberofpatterns(str1, sourcepatterns)
1104 targetcount = numberofpatterns(str2, targetpatterns)
1105 if self.config.lang.nplurals == 1:
1106 if targetcount:
1107 raise FilterFailure(u"Plural(s) were kept in translation")
1108 else:
1109 return True
1110 if sourcecount == targetcount:
1111 return True
1112 else:
1113 raise FilterFailure(u"The original uses plural(s)")
1114
1143
1145 """checks for messages containing translation credits instead of normal
1146 translations."""
1147 if str1 in self.config.credit_sources:
1148 raise FilterFailure(u"Don't translate. Just credit the translators.")
1149 else:
1150 return True
1151
1152
1153 preconditions = {
1154 "untranslated": ("simplecaps", "variables", "startcaps",
1155 "accelerators", "brackets", "endpunc",
1156 "acronyms", "xmltags", "startpunc",
1157 "endwhitespace", "startwhitespace",
1158 "escapes", "doublequoting", "singlequoting",
1159 "filepaths", "purepunc", "doublespacing",
1160 "sentencecount", "numbers", "isfuzzy",
1161 "isreview", "notranslatewords", "musttranslatewords",
1162 "emails", "simpleplurals", "urls", "printf",
1163 "tabs", "newlines", "functions", "options",
1164 "blank", "nplurals", "gconf"),
1165 "blank": ("simplecaps", "variables", "startcaps",
1166 "accelerators", "brackets", "endpunc",
1167 "acronyms", "xmltags", "startpunc",
1168 "endwhitespace", "startwhitespace",
1169 "escapes", "doublequoting", "singlequoting",
1170 "filepaths", "purepunc", "doublespacing",
1171 "sentencecount", "numbers", "isfuzzy",
1172 "isreview", "notranslatewords", "musttranslatewords",
1173 "emails", "simpleplurals", "urls", "printf",
1174 "tabs", "newlines", "functions", "options",
1175 "gconf"),
1176 "credits": ("simplecaps", "variables", "startcaps",
1177 "accelerators", "brackets", "endpunc",
1178 "acronyms", "xmltags", "startpunc",
1179 "escapes", "doublequoting", "singlequoting",
1180 "filepaths", "doublespacing",
1181 "sentencecount", "numbers",
1182 "emails", "simpleplurals", "urls", "printf",
1183 "tabs", "newlines", "functions", "options"),
1184 "purepunc": ("startcaps", "options"),
1185
1186
1187
1188
1189
1190
1191
1192
1193 "endwhitespace": ("endpunc",),
1194 "startwhitespace": ("startpunc",),
1195 "unchanged": ("doublewords",),
1196 "compendiumconflicts": ("accelerators", "brackets", "escapes",
1197 "numbers", "startpunc", "long", "variables",
1198 "startcaps", "sentencecount", "simplecaps",
1199 "doublespacing", "endpunc", "xmltags",
1200 "startwhitespace", "endwhitespace",
1201 "singlequoting", "doublequoting",
1202 "filepaths", "purepunc", "doublewords", "printf"),
1203 }
1204
1205
1206
1207 openofficeconfig = CheckerConfig(
1208 accelmarkers=["~"],
1209 varmatches=[("&", ";"), ("%", "%"), ("%", None), ("%", 0), ("$(", ")"),
1210 ("$", "$"), ("${", "}"), ("#", "#"), ("#", 1), ("#", 0),
1211 ("($", ")"), ("$[", "]"), ("[", "]"), ("$", None)],
1212 ignoretags=[("alt", "xml-lang", None), ("ahelp", "visibility", "visible"),
1213 ("img", "width", None), ("img", "height", None)],
1214 canchangetags=[("link", "name", None)],
1215 )
1216
1217
1227
1228 mozillaconfig = CheckerConfig(
1229 accelmarkers=["&"],
1230 varmatches=[("&", ";"), ("%", "%"), ("%", 1), ("$", "$"), ("$", None),
1231 ("#", 1), ("${", "}"), ("$(^", ")")],
1232 criticaltests=["accelerators"],
1233 )
1234
1235
1237
1245
1247 """checks for messages containing translation credits instead of normal
1248 translations."""
1249 for location in self.locations:
1250 if location in ['MOZ_LANGPACK_CONTRIBUTORS', 'credit.translation']:
1251 raise FilterFailure(u"Don't translate. Just credit the translators.")
1252 return True
1253
1254 drupalconfig = CheckerConfig(
1255 varmatches=[("%", None), ("@", None), ("!", None)],
1256 )
1257
1258
1268
1269 gnomeconfig = CheckerConfig(
1270 accelmarkers=["_"],
1271 varmatches=[("%", 1), ("$(", ")")],
1272 credit_sources=[u"translator-credits"],
1273 )
1274
1275
1277
1285
1286 - def gconf(self, str1, str2):
1287 """Checks if we have any gconf config settings translated."""
1288 for location in self.locations:
1289 if location.find('schemas.in') != -1:
1290 gconf_attributes = gconf_attribute_re.findall(str1)
1291
1292 stopwords = [word for word in gconf_attributes if word[1:-1] not in str2]
1293 if stopwords:
1294 raise FilterFailure(u"Do not translate gconf attributes: %s" %
1295 (u", ".join(stopwords)))
1296 return True
1297
1298 kdeconfig = CheckerConfig(
1299 accelmarkers=["&"],
1300 varmatches=[("%", 1)],
1301 credit_sources=[u"Your names", u"Your emails", u"ROLES_OF_TRANSLATORS"],
1302 )
1303
1304
1316
1317 cclicenseconfig = CheckerConfig(varmatches=[("@", "@")])
1318
1319
1329
1330 projectcheckers = {
1331 "openoffice": OpenOfficeChecker,
1332 "mozilla": MozillaChecker,
1333 "kde": KdeChecker,
1334 "wx": KdeChecker,
1335 "gnome": GnomeChecker,
1336 "creativecommons": CCLicenseChecker,
1337 "drupal": DrupalChecker,
1338 }
1339
1340
1342 """The standard checks for common checks on translation units."""
1343
1345 """Check if the unit has been marked fuzzy."""
1346 return not unit.isfuzzy()
1347
1349 """Check if the unit has been marked review."""
1350 return not unit.isreview()
1351
1361
1363 """Checks if there is at least one suggested translation for this
1364 unit."""
1365 self.suggestion_store = getattr(self, 'suggestion_store', None)
1366 suggestions = []
1367 if self.suggestion_store:
1368 suggestions = self.suggestion_store.findunits(unit.source)
1369 elif getattr(unit, "getalttrans", None):
1370
1371 suggestions = unit.getalttrans()
1372 return not bool(suggestions)
1373
1374
1375 -def runtests(str1, str2, ignorelist=()):
1388
1389
1391 """runs test on a batch of string pairs"""
1392 passed, numpairs = 0, len(pairs)
1393 for str1, str2 in pairs:
1394 if runtests(str1, str2):
1395 passed += 1
1396 print
1397 print "total: %d/%d pairs passed" % (passed, numpairs)
1398
1399
1400 if __name__ == '__main__':
1401 testset = [(r"simple", r"somple"),
1402 (r"\this equals \that", r"does \this equal \that?"),
1403 (r"this \'equals\' that", r"this 'equals' that"),
1404 (r" start and end! they must match.",
1405 r"start and end! they must match."),
1406 (r"check for matching %variables marked like %this",
1407 r"%this %variable is marked"),
1408 (r"check for mismatching %variables marked like %this",
1409 r"%that %variable is marked"),
1410 (r"check for mismatching %variables% too",
1411 r"how many %variable% are marked"),
1412 (r"%% %%", r"%%"),
1413 (r"Row: %1, Column: %2", r"Mothalo: %1, Kholomo: %2"),
1414 (r"simple lowercase", r"it is all lowercase"),
1415 (r"simple lowercase", r"It Is All Lowercase"),
1416 (r"Simple First Letter Capitals", r"First Letters"),
1417 (r"SIMPLE CAPITALS", r"First Letters"),
1418 (r"SIMPLE CAPITALS", r"ALL CAPITALS"),
1419 (r"forgot to translate", r" "),
1420 ]
1421 batchruntests(testset)
1422