# HG changeset patch # User palgarvio # Date 1229559280 0 # Node ID 0f040e15ef6611571cfd949bce3272470357fc34 # Parent 3109e9d5f30f818b000f9494dd6203e92ae1453c Add support for `msgctxt`. See #54. diff --git a/babel/messages/mofile.py b/babel/messages/mofile.py --- a/babel/messages/mofile.py +++ b/babel/messages/mofile.py @@ -186,7 +186,8 @@ else: msgstr = message.string.encode(catalog.charset) if message.context: - msgid = '\x04'.join(message.context.encode(catalog.charset), msgid) + 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/support.py b/babel/support.py --- a/babel/support.py +++ b/babel/support.py @@ -19,6 +19,7 @@ from datetime import date, datetime, time, timedelta import gettext +import locale from babel.core import Locale from babel.dates import format_date, format_datetime, format_time, \ @@ -404,3 +405,175 @@ domain. """ return self._domains.get(domain, self).ungettext(singular, plural, num) + + # Most of the downwards code, until it get's included in stdlib, from: + # http://bugs.python.org/file10036/gettext-pgettext.patch + # + # The encoding of a msgctxt and a msgid in a .mo file is + # msgctxt + "\x04" + msgid (gettext version >= 0.15) + CONTEXT_ENCODING = '%s\x04%s' + + def pgettext(self, context, message): + """Look up the `context` and `message` id in the catalog and return the + corresponding message string, as an 8-bit string encoded with the + catalog's charset encoding, if known. If there is no entry in the + catalog for the `message` id and `context` , and a fallback has been + set, the look up is forwarded to the fallback's ``pgettext()`` + method. Otherwise, the `message` id is returned. + """ + ctxt_msg_id = self.CONTEXT_ENCODING % (context, message) + missing = object() + tmsg = self._catalog.get(ctxt_msg_id, missing) + if tmsg is missing: + if self._fallback: + return self._fallback.pgettext(context, message) + return message + # Encode the Unicode tmsg back to an 8-bit string, if possible + if self._output_charset: + return tmsg.encode(self._output_charset) + elif self._charset: + return tmsg.encode(self._charset) + return tmsg + + def lpgettext(self, context, message): + """Equivalent to ``pgettext()``, but the translation is returned in the + preferred system encoding, if no other encoding was explicitly set with + ``bind_textdomain_codeset()``. + """ + ctxt_msg_id = self.CONTEXT_ENCODING % (context, message) + missing = object() + tmsg = self._catalog.get(ctxt_msg_id, missing) + if tmsg is missing: + if self._fallback: + return self._fallback.lpgettext(context, message) + return message + if self._output_charset: + return tmsg.encode(self._output_charset) + return tmsg.encode(locale.getpreferredencoding()) + + def npgettext(self, context, singular, plural, num): + """Do a plural-forms lookup of a message id. `singular` is used as the + message id for purposes of lookup in the catalog, while `num` is used to + determine which plural form to use. The returned message string is an + 8-bit string encoded with the catalog's charset encoding, if known. + + If the message id for `context` is not found in the catalog, and a + fallback is specified, the request is forwarded to the fallback's + ``npgettext()`` method. Otherwise, when ``num`` is 1 ``singular`` is + returned, and ``plural`` is returned in all other cases. + """ + ctxt_msg_id = self.CONTEXT_ENCODING % (context, singular) + try: + tmsg = self._catalog[(ctxt_msg_id, self.plural(num))] + if self._output_charset: + return tmsg.encode(self._output_charset) + elif self._charset: + return tmsg.encode(self._charset) + return tmsg + except KeyError: + if self._fallback: + return self._fallback.npgettext(context, singular, plural, num) + if num == 1: + return singular + else: + return plural + + def lnpgettext(self, context, singular, plural, num): + """Equivalent to ``npgettext()``, but the translation is returned in the + preferred system encoding, if no other encoding was explicitly set with + ``bind_textdomain_codeset()``. + """ + ctxt_msg_id = self.CONTEXT_ENCODING % (context, singular) + try: + tmsg = self._catalog[(ctxt_msg_id, self.plural(num))] + if self._output_charset: + return tmsg.encode(self._output_charset) + return tmsg.encode(locale.getpreferredencoding()) + except KeyError: + if self._fallback: + return self._fallback.lnpgettext(context, singular, plural, num) + if num == 1: + return singular + else: + return plural + + def upgettext(self, context, message): + """Look up the `context` and `message` id in the catalog and return the + corresponding message string, as a Unicode string. If there is no entry + in the catalog for the `message` id and `context`, and a fallback has + been set, the look up is forwarded to the fallback's ``upgettext()`` + method. Otherwise, the `message` id is returned. + """ + ctxt_message_id = self.CONTEXT_ENCODING % (context, message) + missing = object() + tmsg = self._catalog.get(ctxt_message_id, missing) + if tmsg is missing: + if self._fallback: + return self._fallback.upgettext(context, message) + return unicode(message) + return tmsg + + def unpgettext(self, context, singular, plural, num): + """Do a plural-forms lookup of a message id. `singular` is used as the + message id for purposes of lookup in the catalog, while `num` is used to + determine which plural form to use. The returned message string is a + Unicode string. + + If the message id for `context` is not found in the catalog, and a + fallback is specified, the request is forwarded to the fallback's + ``unpgettext()`` method. Otherwise, when `num` is 1 `singular` is + returned, and `plural` is returned in all other cases. + """ + ctxt_message_id = self.CONTEXT_ENCODING % (context, singular) + try: + tmsg = self._catalog[(ctxt_message_id, self.plural(num))] + except KeyError: + if self._fallback: + return self._fallback.unpgettext(context, singular, plural, num) + if num == 1: + tmsg = unicode(singular) + else: + tmsg = unicode(plural) + return tmsg + + def dpgettext(self, domain, context, message): + """Like `pgettext()`, but look the message up in the specified + `domain`. + """ + return self._domains.get(domain, self).pgettext(context, message) + + def dupgettext(self, domain, context, message): + """Like `upgettext()`, but look the message up in the specified + `domain`. + """ + return self._domains.get(domain, self).upgettext(context, message) + + def ldpgettext(self, domain, context, message): + """Equivalent to ``dpgettext()``, but the translation is returned in the + preferred system encoding, if no other encoding was explicitly set with + ``bind_textdomain_codeset()``. + """ + return self._domains.get(domain, self).lpgettext(context, message) + + def dnpgettext(self, domain, context, singular, plural, num): + """Like ``npgettext``, but look the message up in the specified + `domain`. + """ + return self._domains.get(domain, self).npgettext(context, singular, + plural, num) + + def dunpgettext(self, domain, context, singular, plural, num): + """Like ``unpgettext``, but look the message up in the specified + `domain`. + """ + return self._domains.get(domain, self).unpgettext(context, singular, + plural, num) + + def ldnpgettext(self, domain, context, singular, plural, num): + """Equivalent to ``dnpgettext()``, but the translation is returned in + the preferred system encoding, if no other encoding was explicitly set + with ``bind_textdomain_codeset()``. + """ + return self._domains.get(domain, self).lnpgettext(context, singular, + plural, num) + diff --git a/babel/tests/support.py b/babel/tests/support.py --- a/babel/tests/support.py +++ b/babel/tests/support.py @@ -12,13 +12,161 @@ # history and logs, available at http://babel.edgewall.org/log/. import doctest +import os +from StringIO import StringIO import unittest from babel import support +from babel.messages import Catalog +from babel.messages.mofile import write_mo + +class TranslationsTestCase(unittest.TestCase): + + def setUp(self): + # Use a locale which won't fail to run the tests + os.environ['LANG'] = 'en_US.UTF-8' + messages1 = [ + ('foo', {'string': 'Voh'}), + ('foo', {'string': 'VohCTX', 'context': 'foo'}), + (('foo1', 'foos1'), {'string': ('Voh1', 'Vohs1')}), + (('foo1', 'foos1'), {'string': ('VohCTX1', 'VohsCTX1'), 'context': 'foo'}), + ] + messages2 = [ + ('foo', {'string': 'VohD'}), + ('foo', {'string': 'VohCTXD', 'context': 'foo'}), + (('foo1', 'foos1'), {'string': ('VohD1', 'VohsD1')}), + (('foo1', 'foos1'), {'string': ('VohCTXD1', 'VohsCTXD1'), 'context': 'foo'}), + ] + catalog1 = Catalog(locale='en_GB', domain='messages') + catalog2 = Catalog(locale='en_GB', domain='messages1') + for ids, kwargs in messages1: + catalog1.add(ids, **kwargs) + for ids, kwargs in messages2: + catalog2.add(ids, **kwargs) + catalog1_fp = StringIO() + catalog2_fp = StringIO() + write_mo(catalog1_fp, catalog1) + catalog1_fp.seek(0) + write_mo(catalog2_fp, catalog2) + catalog2_fp.seek(0) + translations1 = support.Translations(catalog1_fp) + translations2 = support.Translations(catalog2_fp, domain='messages1') + self.translations = translations1.add(translations2, merge=False) + + def assertEqualTypeToo(self, expected, result): + self.assertEqual(expected, result) + assert type(expected) == type(result), "instance type's do not " + \ + "match: %r!=%r" % (type(expected), type(result)) + + def test_pgettext(self): + self.assertEqualTypeToo('Voh', self.translations.gettext('foo')) + self.assertEqualTypeToo('VohCTX', self.translations.pgettext('foo', + 'foo')) + + def test_upgettext(self): + self.assertEqualTypeToo(u'Voh', self.translations.ugettext('foo')) + self.assertEqualTypeToo(u'VohCTX', self.translations.upgettext('foo', + 'foo')) + + def test_lpgettext(self): + self.assertEqualTypeToo('Voh', self.translations.lgettext('foo')) + self.assertEqualTypeToo('VohCTX', self.translations.lpgettext('foo', + 'foo')) + + def test_npgettext(self): + self.assertEqualTypeToo('Voh1', + self.translations.ngettext('foo1', 'foos1', 1)) + self.assertEqualTypeToo('Vohs1', + self.translations.ngettext('foo1', 'foos1', 2)) + self.assertEqualTypeToo('VohCTX1', + self.translations.npgettext('foo', 'foo1', + 'foos1', 1)) + self.assertEqualTypeToo('VohsCTX1', + self.translations.npgettext('foo', 'foo1', + 'foos1', 2)) + + def test_unpgettext(self): + self.assertEqualTypeToo(u'Voh1', + self.translations.ungettext('foo1', 'foos1', 1)) + self.assertEqualTypeToo(u'Vohs1', + self.translations.ungettext('foo1', 'foos1', 2)) + self.assertEqualTypeToo(u'VohCTX1', + self.translations.unpgettext('foo', 'foo1', + 'foos1', 1)) + self.assertEqualTypeToo(u'VohsCTX1', + self.translations.unpgettext('foo', 'foo1', + 'foos1', 2)) + + def test_lnpgettext(self): + self.assertEqualTypeToo('Voh1', + self.translations.lngettext('foo1', 'foos1', 1)) + self.assertEqualTypeToo('Vohs1', + self.translations.lngettext('foo1', 'foos1', 2)) + self.assertEqualTypeToo('VohCTX1', + self.translations.lnpgettext('foo', 'foo1', + 'foos1', 1)) + self.assertEqualTypeToo('VohsCTX1', + self.translations.lnpgettext('foo', 'foo1', + 'foos1', 2)) + + def test_dpgettext(self): + self.assertEqualTypeToo( + 'VohD', self.translations.dgettext('messages1', 'foo')) + self.assertEqualTypeToo( + 'VohCTXD', self.translations.dpgettext('messages1', 'foo', 'foo')) + + def test_dupgettext(self): + self.assertEqualTypeToo( + u'VohD', self.translations.dugettext('messages1', 'foo')) + self.assertEqualTypeToo( + u'VohCTXD', self.translations.dupgettext('messages1', 'foo', 'foo')) + + def test_ldpgettext(self): + self.assertEqualTypeToo( + 'VohD', self.translations.ldgettext('messages1', 'foo')) + self.assertEqualTypeToo( + 'VohCTXD', self.translations.ldpgettext('messages1', 'foo', 'foo')) + + def test_dnpgettext(self): + self.assertEqualTypeToo( + 'VohD1', self.translations.dngettext('messages1', 'foo1', 'foos1', 1)) + self.assertEqualTypeToo( + 'VohsD1', self.translations.dngettext('messages1', 'foo1', 'foos1', 2)) + self.assertEqualTypeToo( + 'VohCTXD1', self.translations.dnpgettext('messages1', 'foo', 'foo1', + 'foos1', 1)) + self.assertEqualTypeToo( + 'VohsCTXD1', self.translations.dnpgettext('messages1', 'foo', 'foo1', + 'foos1', 2)) + + def test_dunpgettext(self): + self.assertEqualTypeToo( + u'VohD1', self.translations.dungettext('messages1', 'foo1', 'foos1', 1)) + self.assertEqualTypeToo( + u'VohsD1', self.translations.dungettext('messages1', 'foo1', 'foos1', 2)) + self.assertEqualTypeToo( + u'VohCTXD1', self.translations.dunpgettext('messages1', 'foo', 'foo1', + 'foos1', 1)) + self.assertEqualTypeToo( + u'VohsCTXD1', self.translations.dunpgettext('messages1', 'foo', 'foo1', + 'foos1', 2)) + + def test_ldnpgettext(self): + self.assertEqualTypeToo( + 'VohD1', self.translations.ldngettext('messages1', 'foo1', 'foos1', 1)) + self.assertEqualTypeToo( + 'VohsD1', self.translations.ldngettext('messages1', 'foo1', 'foos1', 2)) + self.assertEqualTypeToo( + 'VohCTXD1', self.translations.ldnpgettext('messages1', 'foo', 'foo1', + 'foos1', 1)) + self.assertEqualTypeToo( + 'VohsCTXD1', self.translations.ldnpgettext('messages1', 'foo', 'foo1', + 'foos1', 2)) def suite(): suite = unittest.TestSuite() suite.addTest(doctest.DocTestSuite(support)) + suite.addTest(unittest.makeSuite(TranslationsTestCase, 'test')) return suite if __name__ == '__main__':