Skip to content

Commit 637a33b

Browse files
csabellaserhiy-storchaka
authored andcommitted
bpo-2504: Add pgettext() and variants to gettext. (GH-7253)
1 parent 5598cc9 commit 637a33b

File tree

7 files changed

+304
-52
lines changed

7 files changed

+304
-52
lines changed

Doc/library/gettext.rst

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,18 @@ class-based API instead.
9696
Like :func:`ngettext`, but look the message up in the specified *domain*.
9797

9898

99+
.. function:: pgettext(context, message)
100+
.. function:: dpgettext(domain, context, message)
101+
.. function:: npgettext(context, singular, plural, n)
102+
.. function:: dnpgettext(domain, context, singular, plural, n)
103+
104+
Similar to the corresponding functions without the ``p`` in the prefix (that
105+
is, :func:`gettext`, :func:`dgettext`, :func:`ngettext`, :func:`dngettext`),
106+
but the translation is restricted to the given message *context*.
107+
108+
.. versionadded:: 3.8
109+
110+
99111
.. function:: lgettext(message)
100112
.. function:: ldgettext(domain, message)
101113
.. function:: lngettext(singular, plural, n)
@@ -266,6 +278,22 @@ are the methods of :class:`!NullTranslations`:
266278
Overridden in derived classes.
267279

268280

281+
.. method:: pgettext(context, message)
282+
283+
If a fallback has been set, forward :meth:`pgettext` to the fallback.
284+
Otherwise, return the translated message. Overridden in derived classes.
285+
286+
.. versionadded:: 3.8
287+
288+
289+
.. method:: npgettext(context, singular, plural, n)
290+
291+
If a fallback has been set, forward :meth:`npgettext` to the fallback.
292+
Otherwise, return the translated message. Overridden in derived classes.
293+
294+
.. versionadded:: 3.8
295+
296+
269297
.. method:: lgettext(message)
270298
.. method:: lngettext(singular, plural, n)
271299

@@ -316,7 +344,7 @@ are the methods of :class:`!NullTranslations`:
316344
If the *names* parameter is given, it must be a sequence containing the
317345
names of functions you want to install in the builtins namespace in
318346
addition to :func:`_`. Supported names are ``'gettext'``, ``'ngettext'``,
319-
``'lgettext'`` and ``'lngettext'``.
347+
``'pgettext'``, ``'npgettext'``, ``'lgettext'``, and ``'lngettext'``.
320348

321349
Note that this is only one way, albeit the most convenient way, to make
322350
the :func:`_` function available to your application. Because it affects
@@ -331,6 +359,9 @@ are the methods of :class:`!NullTranslations`:
331359
This puts :func:`_` only in the module's global namespace and so only
332360
affects calls within this module.
333361

362+
.. versionchanged:: 3.8
363+
Added ``'pgettext'`` and ``'npgettext'``.
364+
334365

335366
The :class:`GNUTranslations` class
336367
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -394,6 +425,31 @@ unexpected, or if other problems occur while reading the file, instantiating a
394425
n) % {'num': n}
395426

396427

428+
.. method:: pgettext(context, message)
429+
430+
Look up the *context* and *message* id in the catalog and return the
431+
corresponding message string, as a Unicode string. If there is no
432+
entry in the catalog for the *message* id and *context*, and a fallback
433+
has been set, the look up is forwarded to the fallback's
434+
:meth:`pgettext` method. Otherwise, the *message* id is returned.
435+
436+
.. versionadded:: 3.8
437+
438+
439+
.. method:: npgettext(context, singular, plural, n)
440+
441+
Do a plural-forms lookup of a message id. *singular* is used as the
442+
message id for purposes of lookup in the catalog, while *n* is used to
443+
determine which plural form to use.
444+
445+
If the message id for *context* is not found in the catalog, and a
446+
fallback is specified, the request is forwarded to the fallback's
447+
:meth:`npgettext` method. Otherwise, when *n* is 1 *singular* is
448+
returned, and *plural* is returned in all other cases.
449+
450+
.. versionadded:: 3.8
451+
452+
397453
.. method:: lgettext(message)
398454
.. method:: lngettext(singular, plural, n)
399455

Doc/whatsnew/3.8.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ asyncio
131131
On Windows, the default event loop is now :class:`~asyncio.ProactorEventLoop`.
132132

133133

134+
gettext
135+
-------
136+
137+
Added :func:`~gettext.pgettext` and its variants.
138+
(Contributed by Franz Glasner, Éric Araujo, and Cheryl Sabella in :issue:`2504`.)
139+
134140
gzip
135141
----
136142

Lib/gettext.py

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
'bind_textdomain_codeset',
5858
'dgettext', 'dngettext', 'gettext', 'lgettext', 'ldgettext',
5959
'ldngettext', 'lngettext', 'ngettext',
60+
'pgettext', 'dpgettext', 'npgettext', 'dnpgettext',
6061
]
6162

6263
_default_localedir = os.path.join(sys.base_prefix, 'share', 'locale')
@@ -311,6 +312,19 @@ def lngettext(self, msgid1, msgid2, n):
311312
return tmsg.encode(self._output_charset)
312313
return tmsg.encode(locale.getpreferredencoding())
313314

315+
def pgettext(self, context, message):
316+
if self._fallback:
317+
return self._fallback.pgettext(context, message)
318+
return message
319+
320+
def npgettext(self, context, msgid1, msgid2, n):
321+
if self._fallback:
322+
return self._fallback.npgettext(context, msgid1, msgid2, n)
323+
if n == 1:
324+
return msgid1
325+
else:
326+
return msgid2
327+
314328
def info(self):
315329
return self._info
316330

@@ -332,22 +346,22 @@ def set_output_charset(self, charset):
332346
def install(self, names=None):
333347
import builtins
334348
builtins.__dict__['_'] = self.gettext
335-
if hasattr(names, "__contains__"):
336-
if "gettext" in names:
337-
builtins.__dict__['gettext'] = builtins.__dict__['_']
338-
if "ngettext" in names:
339-
builtins.__dict__['ngettext'] = self.ngettext
340-
if "lgettext" in names:
341-
builtins.__dict__['lgettext'] = self.lgettext
342-
if "lngettext" in names:
343-
builtins.__dict__['lngettext'] = self.lngettext
349+
if names is not None:
350+
allowed = {'gettext', 'lgettext', 'lngettext',
351+
'ngettext', 'npgettext', 'pgettext'}
352+
for name in allowed & set(names):
353+
builtins.__dict__[name] = getattr(self, name)
344354

345355

346356
class GNUTranslations(NullTranslations):
347357
# Magic number of .mo files
348358
LE_MAGIC = 0x950412de
349359
BE_MAGIC = 0xde120495
350360

361+
# The encoding of a msgctxt and a msgid in a .mo file is
362+
# msgctxt + "\x04" + msgid (gettext version >= 0.15)
363+
CONTEXT = "%s\x04%s"
364+
351365
# Acceptable .mo versions
352366
VERSIONS = (0, 1)
353367

@@ -493,6 +507,29 @@ def ngettext(self, msgid1, msgid2, n):
493507
tmsg = msgid2
494508
return tmsg
495509

510+
def pgettext(self, context, message):
511+
ctxt_msg_id = self.CONTEXT % (context, message)
512+
missing = object()
513+
tmsg = self._catalog.get(ctxt_msg_id, missing)
514+
if tmsg is missing:
515+
if self._fallback:
516+
return self._fallback.pgettext(context, message)
517+
return message
518+
return tmsg
519+
520+
def npgettext(self, context, msgid1, msgid2, n):
521+
ctxt_msg_id = self.CONTEXT % (context, msgid1)
522+
try:
523+
tmsg = self._catalog[ctxt_msg_id, self.plural(n)]
524+
except KeyError:
525+
if self._fallback:
526+
return self._fallback.npgettext(context, msgid1, msgid2, n)
527+
if n == 1:
528+
tmsg = msgid1
529+
else:
530+
tmsg = msgid2
531+
return tmsg
532+
496533

497534
# Locate a .mo file using the gettext strategy
498535
def find(domain, localedir=None, languages=None, all=False):
@@ -672,6 +709,26 @@ def ldngettext(domain, msgid1, msgid2, n):
672709
DeprecationWarning)
673710
return t.lngettext(msgid1, msgid2, n)
674711

712+
713+
def dpgettext(domain, context, message):
714+
try:
715+
t = translation(domain, _localedirs.get(domain, None))
716+
except OSError:
717+
return message
718+
return t.pgettext(context, message)
719+
720+
721+
def dnpgettext(domain, context, msgid1, msgid2, n):
722+
try:
723+
t = translation(domain, _localedirs.get(domain, None))
724+
except OSError:
725+
if n == 1:
726+
return msgid1
727+
else:
728+
return msgid2
729+
return t.npgettext(context, msgid1, msgid2, n)
730+
731+
675732
def gettext(message):
676733
return dgettext(_current_domain, message)
677734

@@ -696,6 +753,15 @@ def lngettext(msgid1, msgid2, n):
696753
DeprecationWarning)
697754
return ldngettext(_current_domain, msgid1, msgid2, n)
698755

756+
757+
def pgettext(context, message):
758+
return dpgettext(_current_domain, context, message)
759+
760+
761+
def npgettext(context, msgid1, msgid2, n):
762+
return dnpgettext(_current_domain, context, msgid1, msgid2, n)
763+
764+
699765
# dcgettext() has been deemed unnecessary and is not implemented.
700766

701767
# James Henstridge's Catalog constructor from GNOME gettext. Documented usage

0 commit comments

Comments
 (0)