# HG changeset patch # User cmlenz # Date 1213210587 0 # Node ID 4db404d0c19bb7753b91250471ee64d1f29dffb7 # Parent 1786dce4b1b0de9c51ccf0d27407a9f48cb94bd4 More preparation for msgctxt support (#54). diff --git a/babel/messages/catalog.py b/babel/messages/catalog.py --- a/babel/messages/catalog.py +++ b/babel/messages/catalog.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007 Edgewall Software +# Copyright (C) 2007-2008 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which @@ -41,7 +41,7 @@ """Representation of a single message in a catalog.""" def __init__(self, id, string=u'', locations=(), flags=(), auto_comments=(), - user_comments=(), previous_id=(), lineno=None): + user_comments=(), previous_id=(), lineno=None, context=None): """Create the message object. :param id: the message ID, or a ``(singular, plural)`` tuple for @@ -56,6 +56,7 @@ tuple for pluralizable messages :param lineno: the line number on which the msgid line was found in the PO file, if any + :param context: the message context """ self.id = id #: The message ID if not string and self.pluralizable: @@ -74,6 +75,7 @@ else: self.previous_id = list(previous_id) self.lineno = lineno + self.context = context def __repr__(self): return '<%s %r (flags: %r)>' % (type(self).__name__, self.id, @@ -95,7 +97,7 @@ def clone(self): return Message(self.id, self.string, self.locations, self.flags, self.auto_comments, self.user_comments, - self.previous_id, self.lineno) + self.previous_id, self.lineno, self.context) def fuzzy(self): return 'fuzzy' in self.flags @@ -534,7 +536,7 @@ self._messages[key] = message def add(self, id, string=None, locations=(), flags=(), auto_comments=(), - user_comments=(), previous_id=(), lineno=None): + user_comments=(), previous_id=(), lineno=None, context=None): """Add or update the message with the specified ID. >>> catalog = Catalog() @@ -557,9 +559,11 @@ tuple for pluralizable messages :param lineno: the line number on which the msgid line was found in the PO file, if any + :param context: the message context """ self[id] = Message(id, string, list(locations), flags, auto_comments, - user_comments, previous_id, lineno=lineno) + user_comments, previous_id, lineno=lineno, + context=context) def check(self): """Run various validation checks on the translations in the catalog. diff --git a/babel/messages/frontend.py b/babel/messages/frontend.py --- a/babel/messages/frontend.py +++ b/babel/messages/frontend.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# Copyright (C) 2007 Edgewall Software +# Copyright (C) 2007-2008 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which diff --git a/babel/messages/mofile.py b/babel/messages/mofile.py --- a/babel/messages/mofile.py +++ b/babel/messages/mofile.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007 Edgewall Software +# Copyright (C) 2007-2008 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which @@ -45,21 +45,21 @@ catalog = Catalog() headers = {} - unpack = struct.unpack filename = getattr(fileobj, 'name', '') charset = None buf = fileobj.read() buflen = len(buf) + unpack = struct.unpack # Parse the .mo file header, which consists of 5 little endian 32 # bit words. magic = unpack('4I', buf[4:20]) + version, msgcount, origidx, transidx = unpack('>4I', buf[4:20]) ii = '>II' else: raise IOError(0, 'Bad magic number', filename) @@ -67,7 +67,7 @@ # Now put all messages from the .mo file buffer into the catalog # dictionary for i in xrange(0, msgcount): - mlen, moff = unpack(ii, buf[masteridx:masteridx + 8]) + mlen, moff = unpack(ii, buf[origidx:origidx + 8]) mend = moff + mlen tlen, toff = unpack(ii, buf[transidx:transidx + 8]) tend = toff + tlen @@ -88,37 +88,29 @@ if ':' in item: key, value = item.split(':', 1) lastkey = key = key.strip().lower() - value = value.strip() - headers[key] = value - if key == 'content-type': - charset = value.split('charset=')[1] + headers[key] = value.strip() elif lastkey: - self._info[lastkey] += '\n' + item + headers[lastkey] += '\n' + item - # Note: we unconditionally convert both msgids and msgstrs to - # Unicode using the character encoding specified in the charset - # parameter of the Content-Type header. The gettext documentation - # strongly encourages msgids to be us-ascii, but some appliations - # require alternative encodings (e.g. Zope's ZCML and ZPT). For - # traditional gettext applications, the msgid conversion will - # cause no problems since us-ascii should always be a subset of - # the charset encoding. We may want to fall back to 8-bit msgids - # if the Unicode conversion fails. - if '\x00' in msg: - # Plural forms + if '\x04' in msg: # context + ctxt, msg = msg.split('\x04') + else: + ctxt = None + + if '\x00' in msg: # plural forms msg = msg.split('\x00') tmsg = tmsg.split('\x00') - if charset: - msg = [unicode(x, charset) for x in msg] - tmsg = [unicode(x, charset) for x in tmsg] + if catalog.charset: + msg = [x.decode(catalog.charset) for x in msg] + tmsg = [x.decode(catalog.charset) for x in tmsg] else: - if charset: - msg = unicode(msg, charset) - tmsg = unicode(tmsg, charset) - catalog[msg] = Message(msg, tmsg) + if catalog.charset: + msg = msg.decode(catalog.charset) + tmsg = tmsg.decode(catalog.charset) + catalog[msg] = Message(msg, tmsg, context=ctxt) # advance to next entry in the seek tables - masteridx += 8 + origidx += 8 transidx += 8 catalog.mime_headers = headers.items() @@ -193,6 +185,8 @@ msgstr = message.id.encode(catalog.charset) else: msgstr = message.string.encode(catalog.charset) + if message.context: + msgid = '\x04'.join(message.context.encode(catalog.charset), msgid) offsets.append((len(ids), len(msgid), len(strs), len(msgstr))) ids += msgid + '\x00' strs += msgstr + '\x00' diff --git a/babel/messages/plurals.py b/babel/messages/plurals.py --- a/babel/messages/plurals.py +++ b/babel/messages/plurals.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007 Edgewall Software +# Copyright (C) 2007-2008 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which @@ -75,7 +75,7 @@ # Chuvash 'cv': (1, '0'), # Welsh - 'cy': (5, 'n==1 ? 1 : n==2 ? 2 : n==3 ? 3 : n==6 ? 4 : 0'), + 'cy': (5, '(n==1 ? 1 : n==2 ? 2 : n==3 ? 3 : n==6 ? 4 : 0)'), # Danish 'da': (2, '(n != 1)'), # German @@ -105,15 +105,15 @@ # Friulian - From Pootle's PO's 'fur': (2, '(n > 1)'), # Irish - 'ga': (3, 'n==1 ? 0 : n==2 ? 1 : 2'), + 'ga': (3, '(n==1 ? 0 : n==2 ? 1 : 2)'), # Galician - From Pootle's PO's 'gl': (2, '(n != 1)'), # Hausa - From Pootle's PO's - 'ha': (2, '(n != 1)'), + 'ha': (2, '(n != 1)'), # Hebrew 'he': (2, '(n != 1)'), # Hindi - From Pootle's PO's - 'hi': (2, '(n != 1)'), + 'hi': (2, '(n != 1)'), # Croatian 'hr': (3, '(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)'), # Hungarian diff --git a/babel/messages/pofile.py b/babel/messages/pofile.py --- a/babel/messages/pofile.py +++ b/babel/messages/pofile.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007 Edgewall Software +# Copyright (C) 2007-2008 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which @@ -136,6 +136,7 @@ user_comments = [] auto_comments = [] obsolete = [False] + context = [] in_msgid = [False] in_msgstr = [False] @@ -149,15 +150,20 @@ string = tuple([denormalize(t[1]) for t in translations]) else: string = denormalize(translations[0][1]) + if context: + msgctxt = denormalize('\n'.join(context)) + else: + msgctxt = None message = Message(msgid, string, list(locations), set(flags), - auto_comments, user_comments, lineno=offset[0] + 1) + auto_comments, user_comments, lineno=offset[0] + 1, + context=msgctxt) if obsolete[0]: if not ignore_obsolete: catalog.obsolete[msgid] = message else: catalog[msgid] = message - del messages[:]; del translations[:]; del locations[:]; - del flags[:]; del auto_comments[:]; del user_comments[:] + del messages[:]; del translations[:]; del context[:]; del locations[:]; + del flags[:]; del auto_comments[:]; del user_comments[:]; obsolete[0] = False counter[0] += 1 @@ -182,11 +188,16 @@ translations.append([int(idx), msg.lstrip()]) else: translations.append([0, msg]) + elif line.startswith('msgctxt'): + in_msgid[0] = in_msgstr[0] = False + context.append(line[7:].lstrip()) elif line.startswith('"'): if in_msgid[0]: messages[-1] += u'\n' + line.rstrip() elif in_msgstr[0]: translations[-1][1] += u'\n' + line.rstrip() + elif in_msgctxt[0]: + context.append(line.rstrip()) for lineno, line in enumerate(fileobj.readlines()): line = line.strip().decode(catalog.charset) diff --git a/babel/messages/tests/__init__.py b/babel/messages/tests/__init__.py --- a/babel/messages/tests/__init__.py +++ b/babel/messages/tests/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007 Edgewall Software +# Copyright (C) 2007-2008 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which @@ -14,12 +14,14 @@ import unittest def suite(): - from babel.messages.tests import catalog, extract, frontend, mofile, pofile + from babel.messages.tests import catalog, extract, frontend, mofile, \ + plurals, pofile suite = unittest.TestSuite() suite.addTest(catalog.suite()) suite.addTest(extract.suite()) suite.addTest(frontend.suite()) suite.addTest(mofile.suite()) + suite.addTest(plurals.suite()) suite.addTest(pofile.suite()) return suite diff --git a/babel/messages/tests/frontend.py b/babel/messages/tests/frontend.py --- a/babel/messages/tests/frontend.py +++ b/babel/messages/tests/frontend.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007 Edgewall Software +# Copyright (C) 2007-2008 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which diff --git a/babel/messages/tests/mofile.py b/babel/messages/tests/mofile.py --- a/babel/messages/tests/mofile.py +++ b/babel/messages/tests/mofile.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007 Edgewall Software +# Copyright (C) 2007-2008 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which diff --git a/babel/messages/tests/plurals.py b/babel/messages/tests/plurals.py new file mode 100644 --- /dev/null +++ b/babel/messages/tests/plurals.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2008 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://babel.edgewall.org/wiki/License. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://babel.edgewall.org/log/. + +import doctest +import unittest + +from babel.messages import plurals + +def suite(): + suite = unittest.TestSuite() + suite.addTest(doctest.DocTestSuite(plurals)) + return suite + +if __name__ == '__main__': + unittest.main(defaultTest='suite') diff --git a/babel/messages/tests/pofile.py b/babel/messages/tests/pofile.py --- a/babel/messages/tests/pofile.py +++ b/babel/messages/tests/pofile.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007 Edgewall Software +# Copyright (C) 2007-2008 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which @@ -143,6 +143,26 @@ self.assertEqual(1, len(catalog)) self.assertEqual(0, len(catalog.obsolete)) + def test_with_context(self): + buf = StringIO(r'''# Some string in the menu +#: main.py:1 +msgctxt "Menu" +msgid "foo" +msgstr "Voh" + +# Another string in the menu +#: main.py:2 +msgctxt "Menu" +msgid "bar" +msgstr "Bahr" +''') + catalog = pofile.read_po(buf, ignore_obsolete=True) + self.assertEqual(2, len(catalog)) + message = catalog['foo'] + self.assertEqual('Menu', message.context) + message = catalog['bar'] + self.assertEqual('Menu', message.context) + class WritePoTestCase(unittest.TestCase):