summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorCheryl Sabella <cheryl.sabella@gmail.com>2018-11-07 14:12:20 (GMT)
committerSerhiy Storchaka <storchaka@gmail.com>2018-11-07 14:12:20 (GMT)
commit637a33b99685fd5d1032670fbe29c7c8a8f0ff63 (patch)
treee72cdff2802199ccd6cde261644fa630ee36be6f /Lib
parent5598cc90c745dab827e55fadded42dbe85e31d33 (diff)
downloadcpython-637a33b99685fd5d1032670fbe29c7c8a8f0ff63.zip
cpython-637a33b99685fd5d1032670fbe29c7c8a8f0ff63.tar.gz
cpython-637a33b99685fd5d1032670fbe29c7c8a8f0ff63.tar.bz2
bpo-2504: Add pgettext() and variants to gettext. (GH-7253)
Diffstat (limited to 'Lib')
-rw-r--r--Lib/gettext.py84
-rw-r--r--Lib/test/test_gettext.py164
2 files changed, 214 insertions, 34 deletions
diff --git a/Lib/gettext.py b/Lib/gettext.py
index 920742c..72a313a 100644
--- a/Lib/gettext.py
+++ b/Lib/gettext.py
@@ -57,6 +57,7 @@ __all__ = ['NullTranslations', 'GNUTranslations', 'Catalog',
'bind_textdomain_codeset',
'dgettext', 'dngettext', 'gettext', 'lgettext', 'ldgettext',
'ldngettext', 'lngettext', 'ngettext',
+ 'pgettext', 'dpgettext', 'npgettext', 'dnpgettext',
]
_default_localedir = os.path.join(sys.base_prefix, 'share', 'locale')
@@ -311,6 +312,19 @@ class NullTranslations:
return tmsg.encode(self._output_charset)
return tmsg.encode(locale.getpreferredencoding())
+ def pgettext(self, context, message):
+ if self._fallback:
+ return self._fallback.pgettext(context, message)
+ return message
+
+ def npgettext(self, context, msgid1, msgid2, n):
+ if self._fallback:
+ return self._fallback.npgettext(context, msgid1, msgid2, n)
+ if n == 1:
+ return msgid1
+ else:
+ return msgid2
+
def info(self):
return self._info
@@ -332,15 +346,11 @@ class NullTranslations:
def install(self, names=None):
import builtins
builtins.__dict__['_'] = self.gettext
- if hasattr(names, "__contains__"):
- if "gettext" in names:
- builtins.__dict__['gettext'] = builtins.__dict__['_']
- if "ngettext" in names:
- builtins.__dict__['ngettext'] = self.ngettext
- if "lgettext" in names:
- builtins.__dict__['lgettext'] = self.lgettext
- if "lngettext" in names:
- builtins.__dict__['lngettext'] = self.lngettext
+ if names is not None:
+ allowed = {'gettext', 'lgettext', 'lngettext',
+ 'ngettext', 'npgettext', 'pgettext'}
+ for name in allowed & set(names):
+ builtins.__dict__[name] = getattr(self, name)
class GNUTranslations(NullTranslations):
@@ -348,6 +358,10 @@ class GNUTranslations(NullTranslations):
LE_MAGIC = 0x950412de
BE_MAGIC = 0xde120495
+ # The encoding of a msgctxt and a msgid in a .mo file is
+ # msgctxt + "\x04" + msgid (gettext version >= 0.15)
+ CONTEXT = "%s\x04%s"
+
# Acceptable .mo versions
VERSIONS = (0, 1)
@@ -493,6 +507,29 @@ class GNUTranslations(NullTranslations):
tmsg = msgid2
return tmsg
+ def pgettext(self, context, message):
+ ctxt_msg_id = self.CONTEXT % (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
+ return tmsg
+
+ def npgettext(self, context, msgid1, msgid2, n):
+ ctxt_msg_id = self.CONTEXT % (context, msgid1)
+ try:
+ tmsg = self._catalog[ctxt_msg_id, self.plural(n)]
+ except KeyError:
+ if self._fallback:
+ return self._fallback.npgettext(context, msgid1, msgid2, n)
+ if n == 1:
+ tmsg = msgid1
+ else:
+ tmsg = msgid2
+ return tmsg
+
# Locate a .mo file using the gettext strategy
def find(domain, localedir=None, languages=None, all=False):
@@ -672,6 +709,26 @@ def ldngettext(domain, msgid1, msgid2, n):
DeprecationWarning)
return t.lngettext(msgid1, msgid2, n)
+
+def dpgettext(domain, context, message):
+ try:
+ t = translation(domain, _localedirs.get(domain, None))
+ except OSError:
+ return message
+ return t.pgettext(context, message)
+
+
+def dnpgettext(domain, context, msgid1, msgid2, n):
+ try:
+ t = translation(domain, _localedirs.get(domain, None))
+ except OSError:
+ if n == 1:
+ return msgid1
+ else:
+ return msgid2
+ return t.npgettext(context, msgid1, msgid2, n)
+
+
def gettext(message):
return dgettext(_current_domain, message)
@@ -696,6 +753,15 @@ def lngettext(msgid1, msgid2, n):
DeprecationWarning)
return ldngettext(_current_domain, msgid1, msgid2, n)
+
+def pgettext(context, message):
+ return dpgettext(_current_domain, context, message)
+
+
+def npgettext(context, msgid1, msgid2, n):
+ return dnpgettext(_current_domain, context, msgid1, msgid2, n)
+
+
# dcgettext() has been deemed unnecessary and is not implemented.
# James Henstridge's Catalog constructor from GNOME gettext. Documented usage
diff --git a/Lib/test/test_gettext.py b/Lib/test/test_gettext.py
index bbad102..8c0250e 100644
--- a/Lib/test/test_gettext.py
+++ b/Lib/test/test_gettext.py
@@ -15,23 +15,27 @@ from test import support
# - Tests should have only one assert.
GNU_MO_DATA = b'''\
-3hIElQAAAAAGAAAAHAAAAEwAAAALAAAAfAAAAAAAAACoAAAAFQAAAKkAAAAjAAAAvwAAAKEAAADj
-AAAABwAAAIUBAAALAAAAjQEAAEUBAACZAQAAFgAAAN8CAAAeAAAA9gIAAKEAAAAVAwAABQAAALcD
-AAAJAAAAvQMAAAEAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABQAAAAYAAAACAAAAAFJh
-eW1vbmQgTHV4dXJ5IFlhY2gtdABUaGVyZSBpcyAlcyBmaWxlAFRoZXJlIGFyZSAlcyBmaWxlcwBU
-aGlzIG1vZHVsZSBwcm92aWRlcyBpbnRlcm5hdGlvbmFsaXphdGlvbiBhbmQgbG9jYWxpemF0aW9u
-CnN1cHBvcnQgZm9yIHlvdXIgUHl0aG9uIHByb2dyYW1zIGJ5IHByb3ZpZGluZyBhbiBpbnRlcmZh
-Y2UgdG8gdGhlIEdOVQpnZXR0ZXh0IG1lc3NhZ2UgY2F0YWxvZyBsaWJyYXJ5LgBtdWxsdXNrAG51
-ZGdlIG51ZGdlAFByb2plY3QtSWQtVmVyc2lvbjogMi4wClBPLVJldmlzaW9uLURhdGU6IDIwMDAt
-MDgtMjkgMTI6MTktMDQ6MDAKTGFzdC1UcmFuc2xhdG9yOiBKLiBEYXZpZCBJYsOhw7FleiA8ai1k
-YXZpZEBub29zLmZyPgpMYW5ndWFnZS1UZWFtOiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpN
-SU1FLVZlcnNpb246IDEuMApDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9aXNvLTg4
-NTktMQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBub25lCkdlbmVyYXRlZC1CeTogcHlnZXR0
-ZXh0LnB5IDEuMQpQbHVyYWwtRm9ybXM6IG5wbHVyYWxzPTI7IHBsdXJhbD1uIT0xOwoAVGhyb2F0
-d29iYmxlciBNYW5ncm92ZQBIYXkgJXMgZmljaGVybwBIYXkgJXMgZmljaGVyb3MAR3V2ZiB6YnFo
-eXIgY2ViaXZxcmYgdmFncmVhbmd2YmFueXZtbmd2YmEgbmFxIHlicG55dm1uZ3ZiYQpmaGNjYmVn
-IHNiZSBsYmhlIENsZ3ViYSBjZWJ0ZW56ZiBvbCBjZWJpdnF2YXQgbmEgdmFncmVzbnByIGdiIGd1
-ciBUQUgKdHJnZ3JrZyB6cmZmbnRyIHBuZ255YnQgeXZvZW5lbC4AYmFjb24Ad2luayB3aW5rAA==
+3hIElQAAAAAJAAAAHAAAAGQAAAAAAAAArAAAAAAAAACsAAAAFQAAAK0AAAAjAAAAwwAAAKEAAADn
+AAAAMAAAAIkBAAAHAAAAugEAABYAAADCAQAAHAAAANkBAAALAAAA9gEAAEIBAAACAgAAFgAAAEUD
+AAAeAAAAXAMAAKEAAAB7AwAAMgAAAB0EAAAFAAAAUAQAABsAAABWBAAAIQAAAHIEAAAJAAAAlAQA
+AABSYXltb25kIEx1eHVyeSBZYWNoLXQAVGhlcmUgaXMgJXMgZmlsZQBUaGVyZSBhcmUgJXMgZmls
+ZXMAVGhpcyBtb2R1bGUgcHJvdmlkZXMgaW50ZXJuYXRpb25hbGl6YXRpb24gYW5kIGxvY2FsaXph
+dGlvbgpzdXBwb3J0IGZvciB5b3VyIFB5dGhvbiBwcm9ncmFtcyBieSBwcm92aWRpbmcgYW4gaW50
+ZXJmYWNlIHRvIHRoZSBHTlUKZ2V0dGV4dCBtZXNzYWdlIGNhdGFsb2cgbGlicmFyeS4AV2l0aCBj
+b250ZXh0BFRoZXJlIGlzICVzIGZpbGUAVGhlcmUgYXJlICVzIGZpbGVzAG11bGx1c2sAbXkgY29u
+dGV4dARudWRnZSBudWRnZQBteSBvdGhlciBjb250ZXh0BG51ZGdlIG51ZGdlAG51ZGdlIG51ZGdl
+AFByb2plY3QtSWQtVmVyc2lvbjogMi4wClBPLVJldmlzaW9uLURhdGU6IDIwMDMtMDQtMTEgMTQ6
+MzItMDQwMApMYXN0LVRyYW5zbGF0b3I6IEouIERhdmlkIEliYW5leiA8ai1kYXZpZEBub29zLmZy
+PgpMYW5ndWFnZS1UZWFtOiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpNSU1FLVZlcnNpb246
+IDEuMApDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9aXNvLTg4NTktMQpDb250ZW50
+LVRyYW5zZmVyLUVuY29kaW5nOiA4Yml0CkdlbmVyYXRlZC1CeTogcHlnZXR0ZXh0LnB5IDEuMQpQ
+bHVyYWwtRm9ybXM6IG5wbHVyYWxzPTI7IHBsdXJhbD1uIT0xOwoAVGhyb2F0d29iYmxlciBNYW5n
+cm92ZQBIYXkgJXMgZmljaGVybwBIYXkgJXMgZmljaGVyb3MAR3V2ZiB6YnFoeXIgY2ViaXZxcmYg
+dmFncmVhbmd2YmFueXZtbmd2YmEgbmFxIHlicG55dm1uZ3ZiYQpmaGNjYmVnIHNiZSBsYmhlIENs
+Z3ViYSBjZWJ0ZW56ZiBvbCBjZWJpdnF2YXQgbmEgdmFncmVzbnByIGdiIGd1ciBUQUgKdHJnZ3Jr
+ZyB6cmZmbnRyIHBuZ255YnQgeXZvZW5lbC4ASGF5ICVzIGZpY2hlcm8gKGNvbnRleHQpAEhheSAl
+cyBmaWNoZXJvcyAoY29udGV4dCkAYmFjb24Ad2luayB3aW5rIChpbiAibXkgY29udGV4dCIpAHdp
+bmsgd2luayAoaW4gIm15IG90aGVyIGNvbnRleHQiKQB3aW5rIHdpbmsA
'''
# This data contains an invalid major version number (5)
@@ -84,13 +88,13 @@ ciBUQUgKdHJnZ3JrZyB6cmZmbnRyIHBuZ255YnQgeXZvZW5lbC4AYmFjb24Ad2luayB3aW5rAA==
UMO_DATA = b'''\
-3hIElQAAAAACAAAAHAAAACwAAAAFAAAAPAAAAAAAAABQAAAABAAAAFEAAAAPAQAAVgAAAAQAAABm
-AQAAAQAAAAIAAAAAAAAAAAAAAAAAAAAAYWLDngBQcm9qZWN0LUlkLVZlcnNpb246IDIuMApQTy1S
-ZXZpc2lvbi1EYXRlOiAyMDAzLTA0LTExIDEyOjQyLTA0MDAKTGFzdC1UcmFuc2xhdG9yOiBCYXJy
-eSBBLiBXQXJzYXcgPGJhcnJ5QHB5dGhvbi5vcmc+Ckxhbmd1YWdlLVRlYW06IFhYIDxweXRob24t
-ZGV2QHB5dGhvbi5vcmc+Ck1JTUUtVmVyc2lvbjogMS4wCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFp
-bjsgY2hhcnNldD11dGYtOApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiA3Yml0CkdlbmVyYXRl
-ZC1CeTogbWFudWFsbHkKAMKkeXoA
+3hIElQAAAAADAAAAHAAAADQAAAAAAAAAAAAAAAAAAABMAAAABAAAAE0AAAAQAAAAUgAAAA8BAABj
+AAAABAAAAHMBAAAWAAAAeAEAAABhYsOeAG15Y29udGV4dMOeBGFiw54AUHJvamVjdC1JZC1WZXJz
+aW9uOiAyLjAKUE8tUmV2aXNpb24tRGF0ZTogMjAwMy0wNC0xMSAxMjo0Mi0wNDAwCkxhc3QtVHJh
+bnNsYXRvcjogQmFycnkgQS4gV0Fyc2F3IDxiYXJyeUBweXRob24ub3JnPgpMYW5ndWFnZS1UZWFt
+OiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpNSU1FLVZlcnNpb246IDEuMApDb250ZW50LVR5
+cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9dXRmLTgKQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzog
+N2JpdApHZW5lcmF0ZWQtQnk6IG1hbnVhbGx5CgDCpHl6AMKkeXogKGNvbnRleHQgdmVyc2lvbikA
'''
MMO_DATA = b'''\
@@ -147,7 +151,7 @@ class GettextTestCase1(GettextBaseTest):
GettextBaseTest.setUp(self)
self.localedir = os.curdir
self.mofile = MOFILE
- gettext.install('gettext', self.localedir)
+ gettext.install('gettext', self.localedir, names=['pgettext'])
def test_some_translations(self):
eq = self.assertEqual
@@ -157,6 +161,13 @@ class GettextTestCase1(GettextBaseTest):
eq(_(r'Raymond Luxury Yach-t'), 'Throatwobbler Mangrove')
eq(_(r'nudge nudge'), 'wink wink')
+ def test_some_translations_with_context(self):
+ eq = self.assertEqual
+ eq(pgettext('my context', 'nudge nudge'),
+ 'wink wink (in "my context")')
+ eq(pgettext('my other context', 'nudge nudge'),
+ 'wink wink (in "my other context")')
+
def test_double_quotes(self):
eq = self.assertEqual
# double quotes
@@ -251,6 +262,20 @@ class GettextTestCase2(GettextBaseTest):
eq(self._(r'Raymond Luxury Yach-t'), 'Throatwobbler Mangrove')
eq(self._(r'nudge nudge'), 'wink wink')
+ def test_some_translations_with_context(self):
+ eq = self.assertEqual
+ eq(gettext.pgettext('my context', 'nudge nudge'),
+ 'wink wink (in "my context")')
+ eq(gettext.pgettext('my other context', 'nudge nudge'),
+ 'wink wink (in "my other context")')
+
+ def test_some_translations_with_context_and_domain(self):
+ eq = self.assertEqual
+ eq(gettext.dpgettext('gettext', 'my context', 'nudge nudge'),
+ 'wink wink (in "my context")')
+ eq(gettext.dpgettext('gettext', 'my other context', 'nudge nudge'),
+ 'wink wink (in "my other context")')
+
def test_double_quotes(self):
eq = self.assertEqual
# double quotes
@@ -298,6 +323,15 @@ class PluralFormsTestCase(GettextBaseTest):
x = gettext.ngettext('There is %s file', 'There are %s files', 2)
eq(x, 'Hay %s ficheros')
+ def test_plural_context_forms1(self):
+ eq = self.assertEqual
+ x = gettext.npgettext('With context',
+ 'There is %s file', 'There are %s files', 1)
+ eq(x, 'Hay %s fichero (context)')
+ x = gettext.npgettext('With context',
+ 'There is %s file', 'There are %s files', 2)
+ eq(x, 'Hay %s ficheros (context)')
+
def test_plural_forms2(self):
eq = self.assertEqual
with open(self.mofile, 'rb') as fp:
@@ -307,6 +341,17 @@ class PluralFormsTestCase(GettextBaseTest):
x = t.ngettext('There is %s file', 'There are %s files', 2)
eq(x, 'Hay %s ficheros')
+ def test_plural_context_forms2(self):
+ eq = self.assertEqual
+ with open(self.mofile, 'rb') as fp:
+ t = gettext.GNUTranslations(fp)
+ x = t.npgettext('With context',
+ 'There is %s file', 'There are %s files', 1)
+ eq(x, 'Hay %s fichero (context)')
+ x = t.npgettext('With context',
+ 'There is %s file', 'There are %s files', 2)
+ eq(x, 'Hay %s ficheros (context)')
+
# Examples from http://www.gnu.org/software/gettext/manual/gettext.html
def test_ja(self):
@@ -646,6 +691,7 @@ class UnicodeTranslationsTest(GettextBaseTest):
with open(UMOFILE, 'rb') as fp:
self.t = gettext.GNUTranslations(fp)
self._ = self.t.gettext
+ self.pgettext = self.t.pgettext
def test_unicode_msgid(self):
self.assertIsInstance(self._(''), str)
@@ -653,6 +699,53 @@ class UnicodeTranslationsTest(GettextBaseTest):
def test_unicode_msgstr(self):
self.assertEqual(self._('ab\xde'), '\xa4yz')
+ def test_unicode_context_msgstr(self):
+ t = self.pgettext('mycontext\xde', 'ab\xde')
+ self.assertTrue(isinstance(t, str))
+ self.assertEqual(t, '\xa4yz (context version)')
+
+
+class UnicodeTranslationsPluralTest(GettextBaseTest):
+ def setUp(self):
+ GettextBaseTest.setUp(self)
+ with open(MOFILE, 'rb') as fp:
+ self.t = gettext.GNUTranslations(fp)
+ self.ngettext = self.t.ngettext
+ self.npgettext = self.t.npgettext
+
+ def test_unicode_msgid(self):
+ unless = self.assertTrue
+ unless(isinstance(self.ngettext('', '', 1), str))
+ unless(isinstance(self.ngettext('', '', 2), str))
+
+ def test_unicode_context_msgid(self):
+ unless = self.assertTrue
+ unless(isinstance(self.npgettext('', '', '', 1), str))
+ unless(isinstance(self.npgettext('', '', '', 2), str))
+
+ def test_unicode_msgstr(self):
+ eq = self.assertEqual
+ unless = self.assertTrue
+ t = self.ngettext("There is %s file", "There are %s files", 1)
+ unless(isinstance(t, str))
+ eq(t, "Hay %s fichero")
+ unless(isinstance(t, str))
+ t = self.ngettext("There is %s file", "There are %s files", 5)
+ unless(isinstance(t, str))
+ eq(t, "Hay %s ficheros")
+
+ def test_unicode_msgstr_with_context(self):
+ eq = self.assertEqual
+ unless = self.assertTrue
+ t = self.npgettext("With context",
+ "There is %s file", "There are %s files", 1)
+ unless(isinstance(t, str))
+ eq(t, "Hay %s fichero (context)")
+ t = self.npgettext("With context",
+ "There is %s file", "There are %s files", 5)
+ unless(isinstance(t, str))
+ eq(t, "Hay %s ficheros (context)")
+
class WeirdMetadataTest(GettextBaseTest):
def setUp(self):
@@ -750,6 +843,14 @@ msgstr ""
msgid "nudge nudge"
msgstr "wink wink"
+msgctxt "my context"
+msgid "nudge nudge"
+msgstr "wink wink (in \"my context\")"
+
+msgctxt "my other context"
+msgid "nudge nudge"
+msgstr "wink wink (in \"my other context\")"
+
#: test_gettext.py:16 test_gettext.py:22 test_gettext.py:28 test_gettext.py:34
#: test_gettext.py:77 test_gettext.py:83 test_gettext.py:89 test_gettext.py:95
msgid "albatross"
@@ -782,6 +883,14 @@ msgid "There is %s file"
msgid_plural "There are %s files"
msgstr[0] "Hay %s fichero"
msgstr[1] "Hay %s ficheros"
+
+# Manually added, as neither pygettext nor xgettext support plural forms
+# and context in Python.
+msgctxt "With context"
+msgid "There is %s file"
+msgid_plural "There are %s files"
+msgstr[0] "Hay %s fichero (context)"
+msgstr[1] "Hay %s ficheros (context)"
'''
# Here's the second example po file example, used to generate the UMO_DATA
@@ -806,6 +915,11 @@ msgstr ""
#: nofile:0
msgid "ab\xc3\x9e"
msgstr "\xc2\xa4yz"
+
+#: nofile:1
+msgctxt "mycontext\xc3\x9e"
+msgid "ab\xc3\x9e"
+msgstr "\xc2\xa4yz (context version)"
'''
# Here's the third example po file, used to generate MMO_DATA