Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Nov 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 57 additions & 1 deletion Doc/library/gettext.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand All @@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down Expand Up @@ -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)

Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
----

Expand Down
84 changes: 75 additions & 9 deletions Lib/gettext.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -311,6 +312,19 @@ def lngettext(self, msgid1, msgid2, n):
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

Expand All @@ -332,22 +346,22 @@ def set_output_charset(self, charset):
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):
# Magic number of .mo files
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)

Expand Down Expand Up @@ -493,6 +507,29 @@ def ngettext(self, msgid1, msgid2, n):
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):
Expand Down Expand Up @@ -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)

Expand All @@ -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
Expand Down
Loading