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

diff --git a/Tools/i18n/msgfmt.py b/Tools/i18n/msgfmt.py
index 63d52d1..3f731e9 100755
--- a/Tools/i18n/msgfmt.py
+++ b/Tools/i18n/msgfmt.py
@@ -5,7 +5,8 @@
 
 This program converts a textual Uniforum-style message catalog (.po file) into
 a binary GNU catalog (.mo file).  This is essentially the same function as the
-GNU msgfmt program, however, it is a simpler implementation.
+GNU msgfmt program, however, it is a simpler implementation.  Currently it
+does not handle plural forms but it does handle message contexts.
 
 Usage: msgfmt.py [OPTIONS] filename.po
 
@@ -32,12 +33,11 @@
 import array
 from email.parser import HeaderParser
 
-__version__ = "1.1"
+__version__ = "1.2"
 
 MESSAGES = {}
 
 
-
 def usage(code, msg=''):
     print(__doc__, file=sys.stderr)
     if msg:
@@ -45,15 +45,16 @@
     sys.exit(code)
 
 
-
-def add(id, str, fuzzy):
+def add(ctxt, id, str, fuzzy):
     "Add a non-fuzzy translation to the dictionary."
     global MESSAGES
     if not fuzzy and str:
-        MESSAGES[id] = str
+        if ctxt is None:
+            MESSAGES[id] = str
+        else:
+            MESSAGES[b"%b\x04%b" % (ctxt, id)] = str
 
 
-
 def generate():
     "Return the generated output."
     global MESSAGES
@@ -95,10 +96,10 @@
     return output
 
 
-
 def make(filename, outfile):
     ID = 1
     STR = 2
+    CTXT = 3
 
     # Compute .mo name from .po name and arguments
     if filename.endswith('.po'):
@@ -115,7 +116,7 @@
         print(msg, file=sys.stderr)
         sys.exit(1)
 
-    section = None
+    section = msgctxt = None
     fuzzy = 0
 
     # Start off assuming Latin-1, so everything decodes without failure,
@@ -129,8 +130,8 @@
         lno += 1
         # If we get a comment line after a msgstr, this is a new entry
         if l[0] == '#' and section == STR:
-            add(msgid, msgstr, fuzzy)
-            section = None
+            add(msgctxt, msgid, msgstr, fuzzy)
+            section = msgctxt = None
             fuzzy = 0
         # Record a fuzzy mark
         if l[:2] == '#,' and 'fuzzy' in l:
@@ -138,10 +139,16 @@
         # Skip comments
         if l[0] == '#':
             continue
-        # Now we are in a msgid section, output previous section
-        if l.startswith('msgid') and not l.startswith('msgid_plural'):
+        # Now we are in a msgid or msgctxt section, output previous section
+        if l.startswith('msgctxt'):
             if section == STR:
-                add(msgid, msgstr, fuzzy)
+                add(msgctxt, msgid, msgstr, fuzzy)
+            section = CTXT
+            l = l[7:]
+            msgctxt = b''
+        elif l.startswith('msgid') and not l.startswith('msgid_plural'):
+            if section == STR:
+                add(msgctxt, msgid, msgstr, fuzzy)
                 if not msgid:
                     # See whether there is an encoding declaration
                     p = HeaderParser()
@@ -183,7 +190,9 @@
         if not l:
             continue
         l = ast.literal_eval(l)
-        if section == ID:
+        if section == CTXT:
+            msgctxt += l.encode(encoding)
+        elif section == ID:
             msgid += l.encode(encoding)
         elif section == STR:
             msgstr += l.encode(encoding)
@@ -194,7 +203,7 @@
             sys.exit(1)
     # Add last entry
     if section == STR:
-        add(msgid, msgstr, fuzzy)
+        add(msgctxt, msgid, msgstr, fuzzy)
 
     # Compute output
     output = generate()
@@ -206,7 +215,6 @@
         print(msg, file=sys.stderr)
 
 
-
 def main():
     try:
         opts, args = getopt.getopt(sys.argv[1:], 'hVo:',