Commit 637a33b9 authored by Cheryl Sabella's avatar Cheryl Sabella Committed by Serhiy Storchaka

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

parent 5598cc90
......@@ -96,6 +96,18 @@ class-based API instead.
Like :func:`ngettext`, but look the message up in the specified *domain*.
.. function:: pgettext(context, message)
.. function:: dpgettext(domain, context, message)
.. function:: npgettext(context, singular, plural, n)
.. function:: dnpgettext(domain, context, singular, plural, n)
Similar to the corresponding functions without the ``p`` in the prefix (that
is, :func:`gettext`, :func:`dgettext`, :func:`ngettext`, :func:`dngettext`),
but the translation is restricted to the given message *context*.
.. versionadded:: 3.8
.. function:: lgettext(message)
.. function:: ldgettext(domain, message)
.. function:: lngettext(singular, plural, n)
......@@ -266,6 +278,22 @@ are the methods of :class:`!NullTranslations`:
Overridden in derived classes.
.. method:: pgettext(context, message)
If a fallback has been set, forward :meth:`pgettext` to the fallback.
Otherwise, return the translated message. Overridden in derived classes.
.. versionadded:: 3.8
.. method:: npgettext(context, singular, plural, n)
If a fallback has been set, forward :meth:`npgettext` to the fallback.
Otherwise, return the translated message. Overridden in derived classes.
.. versionadded:: 3.8
.. method:: lgettext(message)
.. method:: lngettext(singular, plural, n)
......@@ -316,7 +344,7 @@ are the methods of :class:`!NullTranslations`:
If the *names* parameter is given, it must be a sequence containing the
names of functions you want to install in the builtins namespace in
addition to :func:`_`. Supported names are ``'gettext'``, ``'ngettext'``,
``'lgettext'`` and ``'lngettext'``.
``'pgettext'``, ``'npgettext'``, ``'lgettext'``, and ``'lngettext'``.
Note that this is only one way, albeit the most convenient way, to make
the :func:`_` function available to your application. Because it affects
......@@ -331,6 +359,9 @@ are the methods of :class:`!NullTranslations`:
This puts :func:`_` only in the module's global namespace and so only
affects calls within this module.
.. versionchanged:: 3.8
Added ``'pgettext'`` and ``'npgettext'``.
The :class:`GNUTranslations` class
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
......@@ -394,6 +425,31 @@ unexpected, or if other problems occur while reading the file, instantiating a
n) % {'num': n}
.. method:: pgettext(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
:meth:`pgettext` method. Otherwise, the *message* id is returned.
.. versionadded:: 3.8
.. method:: npgettext(context, singular, plural, n)
Do a plural-forms lookup of a message id. *singular* is used as the
message id for purposes of lookup in the catalog, while *n* is used to
determine which plural form to use.
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
:meth:`npgettext` method. Otherwise, when *n* is 1 *singular* is
returned, and *plural* is returned in all other cases.
.. versionadded:: 3.8
.. method:: lgettext(message)
.. method:: lngettext(singular, plural, n)
......
......@@ -131,6 +131,12 @@ asyncio
On Windows, the default event loop is now :class:`~asyncio.ProactorEventLoop`.
gettext
-------
Added :func:`~gettext.pgettext` and its variants.
(Contributed by Franz Glasner, Éric Araujo, and Cheryl Sabella in :issue:`2504`.)
gzip
----
......
......@@ -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
......
This diff is collapsed.
......@@ -559,6 +559,7 @@ Julian Gindi
Yannick Gingras
Neil Girdhar
Matt Giuca
Franz Glasner
Wim Glenn
Michael Goderbauer
Karan Goel
......
......@@ -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 struct
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 @@ def usage(code, msg=''):
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:
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 @@ def generate():
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 @@ def make(filename, outfile):
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 @@ def make(filename, outfile):
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 @@ def make(filename, outfile):
# 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(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(msgid, msgstr, fuzzy)
add(msgctxt, msgid, msgstr, fuzzy)
if not msgid:
# See whether there is an encoding declaration
p = HeaderParser()
......@@ -183,7 +190,9 @@ def make(filename, outfile):
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 @@ def make(filename, outfile):
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 @@ def make(filename, outfile):
print(msg, file=sys.stderr)
def main():
try:
opts, args = getopt.getopt(sys.argv[1:], 'hVo:',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment