comparison babel/messages/catalog.py @ 222:bd8b1301b27e

Added infrastructure for adding catalog checkers, and implement a checker that validations Python format parameters in translations, closing #19.
author cmlenz
date Mon, 16 Jul 2007 16:57:49 +0000
parents 2c00a52bc073
children f358dd40a960
comparison
equal deleted inserted replaced
221:19eaa0f8fae5 222:bd8b1301b27e
28 from babel.core import Locale 28 from babel.core import Locale
29 from babel.dates import format_datetime 29 from babel.dates import format_datetime
30 from babel.messages.plurals import PLURALS 30 from babel.messages.plurals import PLURALS
31 from babel.util import odict, LOCALTZ, UTC, FixedOffsetTimezone 31 from babel.util import odict, LOCALTZ, UTC, FixedOffsetTimezone
32 32
33 __all__ = ['Message', 'Catalog'] 33 __all__ = ['Message', 'Catalog', 'TranslationError']
34 __docformat__ = 'restructuredtext en' 34 __docformat__ = 'restructuredtext en'
35 35
36 PYTHON_FORMAT = re.compile(r'\%(\([\w]+\))?[diouxXeEfFgGcrs]').search 36 PYTHON_FORMAT = re.compile(r'\%(\([\w]+\))?[diouxXeEfFgGcrs]')
37 37
38 38
39 class Message(object): 39 class Message(object):
40 """Representation of a single message in a catalog.""" 40 """Representation of a single message in a catalog."""
41 41
42 def __init__(self, id, string=u'', locations=(), flags=(), auto_comments=(), 42 def __init__(self, id, string=u'', locations=(), flags=(), auto_comments=(),
43 user_comments=(), previous_id=()): 43 user_comments=(), previous_id=(), lineno=None):
44 """Create the message object. 44 """Create the message object.
45 45
46 :param id: the message ID, or a ``(singular, plural)`` tuple for 46 :param id: the message ID, or a ``(singular, plural)`` tuple for
47 pluralizable messages 47 pluralizable messages
48 :param string: the translated message string, or a 48 :param string: the translated message string, or a
51 :param flags: a set or sequence of flags 51 :param flags: a set or sequence of flags
52 :param auto_comments: a sequence of automatic comments for the message 52 :param auto_comments: a sequence of automatic comments for the message
53 :param user_comments: a sequence of user comments for the message 53 :param user_comments: a sequence of user comments for the message
54 :param previous_id: the previous message ID, or a ``(singular, plural)`` 54 :param previous_id: the previous message ID, or a ``(singular, plural)``
55 tuple for pluralizable messages 55 tuple for pluralizable messages
56 :param lineno: the line number on which the msgid line was found in the
57 PO file, if any
56 """ 58 """
57 self.id = id #: The message ID 59 self.id = id #: The message ID
58 if not string and self.pluralizable: 60 if not string and self.pluralizable:
59 string = (u'', u'') 61 string = (u'', u'')
60 self.string = string #: The message translation 62 self.string = string #: The message translation
68 self.user_comments = list(user_comments) 70 self.user_comments = list(user_comments)
69 if isinstance(previous_id, basestring): 71 if isinstance(previous_id, basestring):
70 self.previous_id = [previous_id] 72 self.previous_id = [previous_id]
71 else: 73 else:
72 self.previous_id = list(previous_id) 74 self.previous_id = list(previous_id)
75 self.lineno = lineno
73 76
74 def __repr__(self): 77 def __repr__(self):
75 return '<%s %r (flags: %r)>' % (type(self).__name__, self.id, 78 return '<%s %r (flags: %r)>' % (type(self).__name__, self.id,
76 list(self.flags)) 79 list(self.flags))
77 80
106 109
107 def python_format(self): 110 def python_format(self):
108 ids = self.id 111 ids = self.id
109 if not isinstance(ids, (list, tuple)): 112 if not isinstance(ids, (list, tuple)):
110 ids = [ids] 113 ids = [ids]
111 return bool(filter(None, [PYTHON_FORMAT(id) for id in ids])) 114 return bool(filter(None, [PYTHON_FORMAT.search(id) for id in ids]))
112 python_format = property(python_format, doc="""\ 115 python_format = property(python_format, doc="""\
113 Whether the message contains Python-style parameters. 116 Whether the message contains Python-style parameters.
114 117
115 >>> Message('foo %(name)s bar').python_format 118 >>> Message('foo %(name)s bar').python_format
116 True 119 True
117 >>> Message(('foo %(name)s', 'foo %(name)s')).python_format 120 >>> Message(('foo %(name)s', 'foo %(name)s')).python_format
118 True 121 True
119 122
120 :type: `bool` 123 :type: `bool`
121 """) 124 """)
125
126
127 class TranslationError(Exception):
128 """Exception thrown by translation checkers when invalid message
129 translations are encountered."""
122 130
123 131
124 DEFAULT_HEADER = u"""\ 132 DEFAULT_HEADER = u"""\
125 # Translations template for PROJECT. 133 # Translations template for PROJECT.
126 # Copyright (C) YEAR ORGANIZATION 134 # Copyright (C) YEAR ORGANIZATION
478 if isinstance(id, (list, tuple)): 486 if isinstance(id, (list, tuple)):
479 assert isinstance(message.string, (list, tuple)) 487 assert isinstance(message.string, (list, tuple))
480 self._messages[key] = message 488 self._messages[key] = message
481 489
482 def add(self, id, string=None, locations=(), flags=(), auto_comments=(), 490 def add(self, id, string=None, locations=(), flags=(), auto_comments=(),
483 user_comments=(), previous_id=()): 491 user_comments=(), previous_id=(), lineno=None):
484 """Add or update the message with the specified ID. 492 """Add or update the message with the specified ID.
485 493
486 >>> catalog = Catalog() 494 >>> catalog = Catalog()
487 >>> catalog.add(u'foo') 495 >>> catalog.add(u'foo')
488 >>> catalog[u'foo'] 496 >>> catalog[u'foo']
499 :param flags: a set or sequence of flags 507 :param flags: a set or sequence of flags
500 :param auto_comments: a sequence of automatic comments 508 :param auto_comments: a sequence of automatic comments
501 :param user_comments: a sequence of user comments 509 :param user_comments: a sequence of user comments
502 :param previous_id: the previous message ID, or a ``(singular, plural)`` 510 :param previous_id: the previous message ID, or a ``(singular, plural)``
503 tuple for pluralizable messages 511 tuple for pluralizable messages
512 :param lineno: the line number on which the msgid line was found in the
513 PO file, if any
504 """ 514 """
505 self[id] = Message(id, string, list(locations), flags, auto_comments, 515 self[id] = Message(id, string, list(locations), flags, auto_comments,
506 user_comments, previous_id) 516 user_comments, previous_id, lineno=lineno)
517
518 def check(self):
519 """Run various validation checks on the translations in the catalog.
520
521 For every message which fails validation, this method yield a
522 ``(message, errors)`` tuple, where ``message`` is the `Message` object
523 and ``errors`` is a sequence of `TranslationError` objects.
524
525 :rtype: ``iterator``
526 """
527 checkers = []
528 from pkg_resources import working_set
529 for entry_point in working_set.iter_entry_points('babel.checkers'):
530 checkers.append(entry_point.load())
531
532 for message in self._messages.values():
533 errors = []
534 for checker in checkers:
535 try:
536 checker(self, message)
537 except TranslationError, e:
538 errors.append(e)
539 if errors:
540 yield message, errors
507 541
508 def update(self, template, no_fuzzy_matching=False): 542 def update(self, template, no_fuzzy_matching=False):
509 """Update the catalog based on the given template catalog. 543 """Update the catalog based on the given template catalog.
510 544
511 >>> from babel.messages import Catalog 545 >>> from babel.messages import Catalog
Copyright (C) 2012-2017 Edgewall Software