bpo-2504: Add pgettext() and variants to gettext. (GH-7253)

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 @@
            '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 @@
             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 @@
     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 @@
     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 @@
                 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 @@
                                 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 @@
                                 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