Skip to content

Commit 9aab9ae

Browse files
committed
bpo-2504: Add pgettext() and variants to gettext
1 parent 143ce5c commit 9aab9ae

File tree

6 files changed

+479
-66
lines changed

6 files changed

+479
-66
lines changed

Doc/library/gettext.rst

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ class-based API instead.
4949
.. function:: bind_textdomain_codeset(domain, codeset=None)
5050

5151
Bind the *domain* to *codeset*, changing the encoding of byte strings
52-
returned by the :func:`lgettext`, :func:`ldgettext`, :func:`lngettext`
53-
and :func:`ldngettext` functions.
52+
returned by the :func:`lgettext`, :func:`ldgettext`, :func:`lngettext`,
53+
:func:`ldngettext`, :func:`lpgettext`, :func:`ldpgettext`,
54+
:func:`lnpgettext`, and :func:`ldnpgettext` functions.
5455
If *codeset* is omitted, then the current binding is returned.
5556

5657

@@ -93,16 +94,33 @@ class-based API instead.
9394
Like :func:`ngettext`, but look the message up in the specified *domain*.
9495

9596

97+
.. function:: pgettext(context, message)
98+
.. function:: dpgettext(domain, context, message)
99+
.. function:: npgettext(context, singular, plural, n)
100+
.. function:: dnpgettext(domain, context, singular, plural, n)
101+
102+
Similar to the corresponding functions without the ``p`` in the prefix (that
103+
is, :func:`gettext`, :func:`dgettext`, :func:`ngettext`, :func:`dngettext`),
104+
but the translation is restricted to the given message *context*.
105+
106+
.. versionadded:: 3.8
107+
108+
96109
.. function:: lgettext(message)
97110
.. function:: ldgettext(domain, message)
98111
.. function:: lngettext(singular, plural, n)
99112
.. function:: ldngettext(domain, singular, plural, n)
113+
.. function:: lpgettext(context, message)
114+
.. function:: ldpgettext(domain, context, message)
115+
.. function:: lnpgettext(context, singular, plural, n)
116+
.. function:: ldnpgettext(domain, context, singular, plural, n)
100117

101118
Equivalent to the corresponding functions without the ``l`` prefix
102-
(:func:`.gettext`, :func:`dgettext`, :func:`ngettext` and :func:`dngettext`),
103-
but the translation is returned as a byte string encoded in the preferred
104-
system encoding if no other encoding was explicitly set with
105-
:func:`bind_textdomain_codeset`.
119+
(:func:`.gettext`, :func:`dgettext`, :func:`ngettext`, :func:`dngettext`,
120+
:func:`pgettext`, :func:`dpgettext`, :func:`npgettext`, and
121+
:func:`dnpgettext`), but the translation is returned as a byte string
122+
encoded in the preferred system encoding if no other encoding was
123+
explicitly set with :func:`bind_textdomain_codeset`.
106124

107125
.. warning::
108126

@@ -258,12 +276,31 @@ are the methods of :class:`!NullTranslations`:
258276
Overridden in derived classes.
259277

260278

279+
.. method:: pgettext(context, message)
280+
281+
If a fallback has been set, forward :meth:`pgettext` to the fallback.
282+
Otherwise, return the translated message. Overridden in derived classes.
283+
284+
.. versionadded:: 3.8
285+
286+
287+
.. method:: npgettext(context, singular, plural, n)
288+
289+
If a fallback has been set, forward :meth:`npgettext` to the fallback.
290+
Otherwise, return the translated message. Overridden in derived classes.
291+
292+
.. versionadded:: 3.8
293+
294+
261295
.. method:: lgettext(message)
262296
.. method:: lngettext(singular, plural, n)
297+
.. method:: lpgettext(context, message)
298+
.. method:: lnpgettext(context, singular, plural, n)
263299

264-
Equivalent to :meth:`.gettext` and :meth:`.ngettext`, but the translation
265-
is returned as a byte string encoded in the preferred system encoding
266-
if no encoding was explicitly set with :meth:`set_output_charset`.
300+
Equivalent to :meth:`.gettext`, :meth:`.ngettext`, :meth:`.pgettext`,
301+
and :meth:`npgettext`, but the translation is returned as a byte string
302+
encoded in the preferred system encoding if no encoding was explicitly
303+
set with :meth:`set_output_charset`.
267304
Overridden in derived classes.
268305

269306
.. warning::
@@ -284,8 +321,9 @@ are the methods of :class:`!NullTranslations`:
284321

285322
.. method:: output_charset()
286323

287-
Return the encoding used to return translated messages in :meth:`.lgettext`
288-
and :meth:`.lngettext`.
324+
Return the encoding used to return translated messages in
325+
:meth:`.lgettext`, :meth:`.lngettext`, :meth:`.lpgettext`, and
326+
:meth:`.lnpgettext`.
289327

290328

291329
.. method:: set_output_charset(charset)
@@ -301,7 +339,8 @@ are the methods of :class:`!NullTranslations`:
301339
If the *names* parameter is given, it must be a sequence containing the
302340
names of functions you want to install in the builtins namespace in
303341
addition to :func:`_`. Supported names are ``'gettext'``, ``'ngettext'``,
304-
``'lgettext'`` and ``'lngettext'``.
342+
``'pgettext'``, ``'lgettext'``, ``'lngettext'``, ``'lpgettext'``, and
343+
``'lnpgettext'``.
305344

306345
Note that this is only one way, albeit the most convenient way, to make
307346
the :func:`_` function available to your application. Because it affects
@@ -379,8 +418,37 @@ unexpected, or if other problems occur while reading the file, instantiating a
379418
n) % {'num': n}
380419

381420

421+
.. method:: pgettext(context, message)
422+
423+
Look up the *context* and *message* id in the catalog and return the
424+
corresponding message string, as an 8-bit string encoded with the
425+
catalog's encoding, if known. If there is no entry in the catalog
426+
for the *message* id and *context*, and a fallback has been set, the
427+
look up is forwarded to the fallback's :meth:`pgettext` method.
428+
Otherwise, the *message* id is returned.
429+
430+
.. versionadded:: 3.8
431+
432+
433+
.. method:: npgettext(context, singular, plural, n)
434+
435+
Do a plural-forms lookup of a message id. *singular* is used as the
436+
message id for purposes of lookup in the catalog, while *n* is used to
437+
determine which plural form to use. The returned message string is an
438+
8-bit string encoded with the catalog's encoding, if known.
439+
440+
If the message id for *context* is not found in the catalog, and a
441+
fallback is specified, the request is forwarded to the fallback's
442+
:meth:`npgettext` method. Otherwise, when *n* is 1 *singular* is
443+
returned, and *plural* is returned in all other cases.
444+
445+
.. versionadded:: 3.8
446+
447+
382448
.. method:: lgettext(message)
383449
.. method:: lngettext(singular, plural, n)
450+
.. method:: lpgettext(context, message)
451+
.. method:: lnpgettext(context, singular, plural, n)
384452

385453
Equivalent to :meth:`.gettext` and :meth:`.ngettext`, but the translation
386454
is returned as a byte string encoded in the preferred system encoding

Lib/gettext.py

Lines changed: 159 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
'bind_textdomain_codeset',
5858
'dgettext', 'dngettext', 'gettext', 'lgettext', 'ldgettext',
5959
'ldngettext', 'lngettext', 'ngettext',
60+
'pgettext', 'dpgettext', 'npgettext', 'dnpgettext',
61+
'lpgettext', 'ldpgettext', 'lnpgettext', 'ldnpgettext',
6062
]
6163

6264
_default_localedir = os.path.join(sys.base_prefix, 'share', 'locale')
@@ -299,6 +301,37 @@ def lngettext(self, msgid1, msgid2, n):
299301
return tmsg.encode(self._output_charset)
300302
return tmsg.encode(locale.getpreferredencoding())
301303

304+
def pgettext(self, context, message):
305+
if self._fallback:
306+
return self._fallback.pgettext(context, message)
307+
return message
308+
309+
def lpgettext(self, context, message):
310+
if self._fallback:
311+
return self._fallback.lpgettext(context, message)
312+
if self._output_charset:
313+
return message.encode(self._output_charset)
314+
return message.encode(locale.getpreferredencoding())
315+
316+
def npgettext(self, context, msgid1, msgid2, n):
317+
if self._fallback:
318+
return self._fallback.npgettext(context, msgid1, msgid2, n)
319+
if n == 1:
320+
return msgid1
321+
else:
322+
return msgid2
323+
324+
def lnpgettext(self, context, msgid1, msgid2, n):
325+
if self._fallback:
326+
return self._fallback.lnpgettext(context, msgid1, msgid2, n)
327+
if n == 1:
328+
tmsg = msgid1
329+
else:
330+
tmsg = msgid2
331+
if self._output_charset:
332+
return tmsg.encode(self._output_charset)
333+
return tmsg.encode(locale.getpreferredencoding())
334+
302335
def info(self):
303336
return self._info
304337

@@ -314,22 +347,22 @@ def set_output_charset(self, charset):
314347
def install(self, names=None):
315348
import builtins
316349
builtins.__dict__['_'] = self.gettext
317-
if hasattr(names, "__contains__"):
318-
if "gettext" in names:
319-
builtins.__dict__['gettext'] = builtins.__dict__['_']
320-
if "ngettext" in names:
321-
builtins.__dict__['ngettext'] = self.ngettext
322-
if "lgettext" in names:
323-
builtins.__dict__['lgettext'] = self.lgettext
324-
if "lngettext" in names:
325-
builtins.__dict__['lngettext'] = self.lngettext
350+
if names is not None:
351+
allowed = {'gettext', 'lgettext', 'lngettext', 'lnpgettext',
352+
'lpgettext', 'ngettext', 'npgettext', 'pgettext'}
353+
for name in allowed & set(names):
354+
builtins.__dict__[name] = getattr(self, name)
326355

327356

328357
class GNUTranslations(NullTranslations):
329358
# Magic number of .mo files
330359
LE_MAGIC = 0x950412de
331360
BE_MAGIC = 0xde120495
332361

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

@@ -469,6 +502,56 @@ def ngettext(self, msgid1, msgid2, n):
469502
tmsg = msgid2
470503
return tmsg
471504

505+
def pgettext(self, context, message):
506+
ctxt_msg_id = self.CONTEXT % (context, message)
507+
missing = object()
508+
tmsg = self._catalog.get(ctxt_msg_id, missing)
509+
if tmsg is missing:
510+
if self._fallback:
511+
return self._fallback.pgettext(context, message)
512+
return message
513+
return tmsg
514+
515+
def npgettext(self, context, msgid1, msgid2, n):
516+
ctxt_msg_id = self.CONTEXT % (context, msgid1)
517+
try:
518+
tmsg = self._catalog[(ctxt_msg_id, self.plural(n))]
519+
except KeyError:
520+
if self._fallback:
521+
return self._fallback.npgettext(context, msgid1, msgid2, n)
522+
if n == 1:
523+
tmsg = msgid1
524+
else:
525+
tmsg = msgid2
526+
return tmsg
527+
528+
def lpgettext(self, context, message):
529+
ctxt_msg_id = self.CONTEXT % (context, message)
530+
missing = object()
531+
tmsg = self._catalog.get(ctxt_msg_id, missing)
532+
if tmsg is missing:
533+
if self._fallback:
534+
return self._fallback.lpgettext(context, message)
535+
tmsg = message
536+
if self._output_charset:
537+
return tmsg.encode(self._output_charset)
538+
return tmsg.encode(locale.getpreferredencoding())
539+
540+
def lnpgettext(self, context, msgid1, msgid2, n):
541+
ctxt_msg_id = self.CONTEXT % (context, msgid1)
542+
try:
543+
tmsg = self._catalog[(ctxt_msg_id, self.plural(n))]
544+
except KeyError:
545+
if self._fallback:
546+
return self._fallback.lnpgettext(context, msgid1, msgid2, n)
547+
if n == 1:
548+
tmsg = msgid1
549+
else:
550+
tmsg = msgid2
551+
if self._output_charset:
552+
return tmsg.encode(self._output_charset)
553+
return tmsg.encode(locale.getpreferredencoding())
554+
472555

473556
# Locate a .mo file using the gettext strategy
474557
def find(domain, localedir=None, languages=None, all=False):
@@ -590,6 +673,7 @@ def dgettext(domain, message):
590673
return message
591674
return t.gettext(message)
592675

676+
593677
def ldgettext(domain, message):
594678
codeset = _localecodesets.get(domain)
595679
try:
@@ -598,6 +682,7 @@ def ldgettext(domain, message):
598682
return message.encode(codeset or locale.getpreferredencoding())
599683
return t.lgettext(message)
600684

685+
601686
def dngettext(domain, msgid1, msgid2, n):
602687
try:
603688
t = translation(domain, _localedirs.get(domain, None),
@@ -609,6 +694,7 @@ def dngettext(domain, msgid1, msgid2, n):
609694
return msgid2
610695
return t.ngettext(msgid1, msgid2, n)
611696

697+
612698
def ldngettext(domain, msgid1, msgid2, n):
613699
codeset = _localecodesets.get(domain)
614700
try:
@@ -621,18 +707,82 @@ def ldngettext(domain, msgid1, msgid2, n):
621707
return tmsg.encode(codeset or locale.getpreferredencoding())
622708
return t.lngettext(msgid1, msgid2, n)
623709

710+
711+
def dpgettext(domain, context, message):
712+
try:
713+
t = translation(domain, _localedirs.get(domain, None),
714+
codeset=_localecodesets.get(domain))
715+
except OSError:
716+
return message
717+
return t.pgettext(context, message)
718+
719+
720+
def ldpgettext(domain, context, message):
721+
codeset = _localecodesets.get(domain)
722+
try:
723+
t = translation(domain, _localedirs.get(domain, None), codeset=codeset)
724+
except OSError:
725+
return message.encode(codeset or locale.getpreferredencoding())
726+
return t.lpgettext(context, message)
727+
728+
729+
def dnpgettext(domain, context, msgid1, msgid2, n):
730+
try:
731+
t = translation(domain, _localedirs.get(domain, None),
732+
codeset=_localecodesets.get(domain))
733+
except OSError:
734+
if n == 1:
735+
return msgid1
736+
else:
737+
return msgid2
738+
return t.npgettext(context, msgid1, msgid2, n)
739+
740+
741+
def ldnpgettext(domain, context, msgid1, msgid2, n):
742+
codeset = _localecodesets.get(domain)
743+
try:
744+
t = translation(domain, _localedirs.get(domain, None), codeset=codeset)
745+
except OSError:
746+
if n == 1:
747+
tmsg = msgid1
748+
else:
749+
tmsg = msgid2
750+
return tmsg.encode(codeset or locale.getpreferredencoding())
751+
return t.lnpgettext(context, msgid1, msgid2, n)
752+
753+
624754
def gettext(message):
625755
return dgettext(_current_domain, message)
626756

757+
627758
def lgettext(message):
628759
return ldgettext(_current_domain, message)
629760

761+
630762
def ngettext(msgid1, msgid2, n):
631763
return dngettext(_current_domain, msgid1, msgid2, n)
632764

765+
633766
def lngettext(msgid1, msgid2, n):
634767
return ldngettext(_current_domain, msgid1, msgid2, n)
635768

769+
770+
def pgettext(context, message):
771+
return dpgettext(_current_domain, context, message)
772+
773+
774+
def lpgettext(context, message):
775+
return ldpgettext(_current_domain, context, message)
776+
777+
778+
def npgettext(context, msgid1, msgid2, n):
779+
return dnpgettext(_current_domain, context, msgid1, msgid2, n)
780+
781+
782+
def lnpgettext(context, msgid1, msgid2, n):
783+
return ldnpgettext(_current_domain, context, msgid1, msgid2, n)
784+
785+
636786
# dcgettext() has been deemed unnecessary and is not implemented.
637787

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

0 commit comments

Comments
 (0)