except NameError:
from sets import Set as set
+from babel.core import Locale
from babel.util import odict
__all__ = ['Message', 'Catalog']
class Catalog(object):
"""Representation a message catalog."""
- def __init__(self, domain=None):
- self.domain = domain
- self.messages = odict()
+ def __init__(self, domain=None, locale=None):
+ """Initialize the catalog object.
+
+ :param domain: the message domain
+ :param locale: the locale identifier or `Locale` object, or `None`
+ if the catalog is not bound to a locale (which basically
+ means it's a template)
+ """
+ self.domain = domain #: the message domain
+ if locale:
+ locale = Locale.parse(locale)
+ self.locale = locale #: the locale or `None`
+ self.messages = odict() #: the actual `Message` entries by ID
def __iter__(self):
+ """Iterates through all the entries in the catalog, in the order they
+ were added, yielding a `Message` object for every entry.
+
+ :rtype: ``iterator``
+ """
for id in self.messages:
yield self.messages[id]
def __repr__(self):
- return '<%s %r>' % (type(self).__name__, self.domain)
+ locale = ''
+ if self.locale:
+ locale = ' %s' % self.locale
+ return '<%s %r%s>' % (type(self).__name__, self.domain, locale)
def __delitem__(self, id):
+ """Delete the message with the specified ID."""
if id in self.messaages:
del self.messages[id]
def __getitem__(self, id):
+ """Return the message with the specified ID.
+
+ :param id: the message ID
+ :return: the message with the specified ID, or `None` if no such message
+ is in the catalog
+ :rytpe: `Message`
+ """
return self.messages.get(id)
def __setitem__(self, id, message):
+ """Add or update the message with the specified ID.
+
+ >>> catalog = Catalog()
+ >>> catalog[u'foo'] = Message(u'foo')
+ >>> catalog[u'foo']
+ <Message u'foo'>
+
+ If a message with that ID is already in the catalog, it is updated
+ to include the locations and flags of the new message.
+
+ >>> catalog = Catalog()
+ >>> catalog[u'foo'] = Message(u'foo', locations=[('main.py', 1)])
+ >>> catalog[u'foo'].locations
+ [('main.py', 1)]
+ >>> catalog[u'foo'] = Message(u'foo', locations=[('utils.py', 5)])
+ >>> catalog[u'foo'].locations
+ [('main.py', 1), ('utils.py', 5)]
+
+ :param id: the message ID
+ :param message: the `Message` object
+ """
assert isinstance(message, Message), 'expected a Message object'
current = self.messages.get(id)
if current:
self.messages[id] = message
def add(self, id, string=None, locations=(), flags=()):
+ """Add or update the message with the specified ID.
+
+ >>> catalog = Catalog()
+ >>> catalog.add(u'foo')
+ >>> catalog[u'foo']
+ <Message u'foo'>
+
+ This method simply constructs a `Message` object with the given
+ arguments and invokes `__setitem__` with that object.
+
+ :param id: the message ID, or a ``(singular, plural)`` tuple for
+ pluralizable messages
+ :param string: the translated message string, or a
+ ``(singular, plural)`` tuple for pluralizable messages
+ :param locations: a sequence of ``(filenname, lineno)`` tuples
+ :param flags: a set or sequence of flags
+ """
self[id] = Message(id, string, locations, flags)
def read_po(fileobj):
"""Read messages from a ``gettext`` PO (portable object) file from the given
- file-like object.
-
- This function yields tuples of the form:
-
- ``(message, translation, locations, flags)``
-
- where:
-
- * ``message`` is the original (untranslated) message, or a
- ``(singular, plural)`` tuple for pluralizable messages
- * ``translation`` is the translation of the message, or a tuple of
- translations for pluralizable messages
- * ``locations`` is a sequence of ``(filename, lineno)`` tuples
- * ``flags`` is a set of strings (for exampe, "fuzzy")
+ file-like object and return a `Catalog`.
>>> from StringIO import StringIO
>>> buf = StringIO('''
... msgstr[0] ""
... msgstr[1] ""
... ''')
- >>> for message, translation, locations, flags in read_po(buf):
- ... print (message, translation)
- ... print ' ', (locations, flags)
- (('foo %(name)s',), ('',))
- ((('main.py', 1),), set(['fuzzy', 'python-format']))
+ >>> catalog = read_po(buf)
+ >>> for message in catalog:
+ ... print (message.id, message.string)
+ ... print ' ', (message.locations, message.flags)
+ ('foo %(name)s', '')
+ ([('main.py', 1)], set(['fuzzy', 'python-format']))
(('bar', 'baz'), ('', ''))
- ((('main.py', 3),), set([]))
+ ([('main.py', 3)], set([]))
:param fileobj: the file-like object to read the PO file from
:return: an iterator over ``(message, translation, location)`` tuples
:rtype: ``iterator``
"""
+ catalog = Catalog()
+
messages = []
translations = []
locations = []
flags = []
in_msgid = in_msgstr = False
- def pack():
+ def _add_message():
translations.sort()
- retval = (tuple(messages), tuple([t[1] for t in translations]),
- tuple(locations), set(flags))
- del messages[:]
- del translations[:]
- del locations[:]
- del flags[:]
- return retval
+ if len(messages) > 1:
+ msgid = tuple(messages)
+ else:
+ msgid = messages[0]
+ if len(translations) > 1:
+ string = tuple([t[1] for t in translations])
+ else:
+ string = translations[0][1]
+ catalog.add(msgid, string, list(locations), set(flags))
+ del messages[:]; del translations[:]; del locations[:]; del flags[:]
for line in fileobj.readlines():
line = line.strip()
if line.startswith('#'):
in_msgid = in_msgstr = False
if messages:
- yield pack()
+ _add_message()
if line[1:].startswith(':'):
for location in line[2:].lstrip().split():
filename, lineno = location.split(':', 1)
elif line.startswith('msgid'):
in_msgid = True
if messages:
- yield pack()
+ _add_message()
msg = line[5:].lstrip()
messages.append(msg[1:-1])
elif line.startswith('msgstr'):
translations[-1][1] += line.rstrip()[1:-1]
if messages:
- yield pack()
+ _add_message()
+ return catalog
POT_HEADER = """\
# Translations Template for %%(project)s.