Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
637a33b9
Commit
637a33b9
authored
Nov 07, 2018
by
Cheryl Sabella
Committed by
Serhiy Storchaka
Nov 07, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
bpo-2504: Add pgettext() and variants to gettext. (GH-7253)
parent
5598cc90
Changes
7
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
304 additions
and
52 deletions
+304
-52
Doc/library/gettext.rst
Doc/library/gettext.rst
+57
-1
Doc/whatsnew/3.8.rst
Doc/whatsnew/3.8.rst
+6
-0
Lib/gettext.py
Lib/gettext.py
+75
-9
Lib/test/test_gettext.py
Lib/test/test_gettext.py
+139
-25
Misc/ACKS
Misc/ACKS
+1
-0
Misc/NEWS.d/next/Library/2018-05-30-16-00-06.bpo-2504.BynUvU.rst
...WS.d/next/Library/2018-05-30-16-00-06.bpo-2504.BynUvU.rst
+1
-0
Tools/i18n/msgfmt.py
Tools/i18n/msgfmt.py
+25
-17
No files found.
Doc/library/gettext.rst
View file @
637a33b9
...
@@ -96,6 +96,18 @@ class-based API instead.
...
@@ -96,6 +96,18 @@ class-based API instead.
Like :func:`ngettext`, but look the message up in the specified *domain*.
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:: lgettext(message)
.. function:: ldgettext(domain, message)
.. function:: ldgettext(domain, message)
.. function:: lngettext(singular, plural, n)
.. function:: lngettext(singular, plural, n)
...
@@ -266,6 +278,22 @@ are the methods of :class:`!NullTranslations`:
...
@@ -266,6 +278,22 @@ are the methods of :class:`!NullTranslations`:
Overridden in derived classes.
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:: lgettext(message)
.. method:: lngettext(singular, plural, n)
.. method:: lngettext(singular, plural, n)
...
@@ -316,7 +344,7 @@ are the methods of :class:`!NullTranslations`:
...
@@ -316,7 +344,7 @@ are the methods of :class:`!NullTranslations`:
If the *names* parameter is given, it must be a sequence containing the
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
names of functions you want to install in the builtins namespace in
addition to :func:`_`. Supported names are ``'gettext'``, ``'ngettext'``,
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
Note that this is only one way, albeit the most convenient way, to make
the :func:`_` function available to your application. Because it affects
the :func:`_` function available to your application. Because it affects
...
@@ -331,6 +359,9 @@ are the methods of :class:`!NullTranslations`:
...
@@ -331,6 +359,9 @@ are the methods of :class:`!NullTranslations`:
This puts :func:`_` only in the module's global namespace and so only
This puts :func:`_` only in the module's global namespace and so only
affects calls within this module.
affects calls within this module.
.. versionchanged:: 3.8
Added ``'pgettext'`` and ``'npgettext'``.
The :class:`GNUTranslations` class
The :class:`GNUTranslations` class
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
@@ -394,6 +425,31 @@ unexpected, or if other problems occur while reading the file, instantiating a
...
@@ -394,6 +425,31 @@ unexpected, or if other problems occur while reading the file, instantiating a
n) % {'num': n}
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:: lgettext(message)
.. method:: lngettext(singular, plural, n)
.. method:: lngettext(singular, plural, n)
...
...
Doc/whatsnew/3.8.rst
View file @
637a33b9
...
@@ -131,6 +131,12 @@ asyncio
...
@@ -131,6 +131,12 @@ asyncio
On Windows, the default event loop is now :class:`~asyncio.ProactorEventLoop`.
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
gzip
----
----
...
...
Lib/gettext.py
View file @
637a33b9
...
@@ -57,6 +57,7 @@ __all__ = ['NullTranslations', 'GNUTranslations', 'Catalog',
...
@@ -57,6 +57,7 @@ __all__ = ['NullTranslations', 'GNUTranslations', 'Catalog',
'bind_textdomain_codeset'
,
'bind_textdomain_codeset'
,
'dgettext'
,
'dngettext'
,
'gettext'
,
'lgettext'
,
'ldgettext'
,
'dgettext'
,
'dngettext'
,
'gettext'
,
'lgettext'
,
'ldgettext'
,
'ldngettext'
,
'lngettext'
,
'ngettext'
,
'ldngettext'
,
'lngettext'
,
'ngettext'
,
'pgettext'
,
'dpgettext'
,
'npgettext'
,
'dnpgettext'
,
]
]
_default_localedir
=
os
.
path
.
join
(
sys
.
base_prefix
,
'share'
,
'locale'
)
_default_localedir
=
os
.
path
.
join
(
sys
.
base_prefix
,
'share'
,
'locale'
)
...
@@ -311,6 +312,19 @@ class NullTranslations:
...
@@ -311,6 +312,19 @@ class NullTranslations:
return
tmsg
.
encode
(
self
.
_output_charset
)
return
tmsg
.
encode
(
self
.
_output_charset
)
return
tmsg
.
encode
(
locale
.
getpreferredencoding
())
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
):
def
info
(
self
):
return
self
.
_info
return
self
.
_info
...
@@ -332,15 +346,11 @@ class NullTranslations:
...
@@ -332,15 +346,11 @@ class NullTranslations:
def
install
(
self
,
names
=
None
):
def
install
(
self
,
names
=
None
):
import
builtins
import
builtins
builtins
.
__dict__
[
'_'
]
=
self
.
gettext
builtins
.
__dict__
[
'_'
]
=
self
.
gettext
if
hasattr
(
names
,
"__contains__"
):
if
names
is
not
None
:
if
"gettext"
in
names
:
allowed
=
{
'gettext'
,
'lgettext'
,
'lngettext'
,
builtins
.
__dict__
[
'gettext'
]
=
builtins
.
__dict__
[
'_'
]
'ngettext'
,
'npgettext'
,
'pgettext'
}
if
"ngettext"
in
names
:
for
name
in
allowed
&
set
(
names
):
builtins
.
__dict__
[
'ngettext'
]
=
self
.
ngettext
builtins
.
__dict__
[
name
]
=
getattr
(
self
,
name
)
if
"lgettext"
in
names
:
builtins
.
__dict__
[
'lgettext'
]
=
self
.
lgettext
if
"lngettext"
in
names
:
builtins
.
__dict__
[
'lngettext'
]
=
self
.
lngettext
class
GNUTranslations
(
NullTranslations
):
class
GNUTranslations
(
NullTranslations
):
...
@@ -348,6 +358,10 @@ class GNUTranslations(NullTranslations):
...
@@ -348,6 +358,10 @@ class GNUTranslations(NullTranslations):
LE_MAGIC
=
0x950412de
LE_MAGIC
=
0x950412de
BE_MAGIC
=
0xde120495
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
# Acceptable .mo versions
VERSIONS
=
(
0
,
1
)
VERSIONS
=
(
0
,
1
)
...
@@ -493,6 +507,29 @@ class GNUTranslations(NullTranslations):
...
@@ -493,6 +507,29 @@ class GNUTranslations(NullTranslations):
tmsg
=
msgid2
tmsg
=
msgid2
return
tmsg
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
# Locate a .mo file using the gettext strategy
def
find
(
domain
,
localedir
=
None
,
languages
=
None
,
all
=
False
):
def
find
(
domain
,
localedir
=
None
,
languages
=
None
,
all
=
False
):
...
@@ -672,6 +709,26 @@ def ldngettext(domain, msgid1, msgid2, n):
...
@@ -672,6 +709,26 @@ def ldngettext(domain, msgid1, msgid2, n):
DeprecationWarning
)
DeprecationWarning
)
return
t
.
lngettext
(
msgid1
,
msgid2
,
n
)
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
):
def
gettext
(
message
):
return
dgettext
(
_current_domain
,
message
)
return
dgettext
(
_current_domain
,
message
)
...
@@ -696,6 +753,15 @@ def lngettext(msgid1, msgid2, n):
...
@@ -696,6 +753,15 @@ def lngettext(msgid1, msgid2, n):
DeprecationWarning
)
DeprecationWarning
)
return
ldngettext
(
_current_domain
,
msgid1
,
msgid2
,
n
)
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.
# dcgettext() has been deemed unnecessary and is not implemented.
# James Henstridge's Catalog constructor from GNOME gettext. Documented usage
# James Henstridge's Catalog constructor from GNOME gettext. Documented usage
...
...
Lib/test/test_gettext.py
View file @
637a33b9
This diff is collapsed.
Click to expand it.
Misc/ACKS
View file @
637a33b9
...
@@ -559,6 +559,7 @@ Julian Gindi
...
@@ -559,6 +559,7 @@ Julian Gindi
Yannick Gingras
Yannick Gingras
Neil Girdhar
Neil Girdhar
Matt Giuca
Matt Giuca
Franz Glasner
Wim Glenn
Wim Glenn
Michael Goderbauer
Michael Goderbauer
Karan Goel
Karan Goel
...
...
Misc/NEWS.d/next/Library/2018-05-30-16-00-06.bpo-2504.BynUvU.rst
0 → 100644
View file @
637a33b9
Add gettext.pgettext() and variants.
Tools/i18n/msgfmt.py
View file @
637a33b9
...
@@ -5,7 +5,8 @@
...
@@ -5,7 +5,8 @@
This program converts a textual Uniforum-style message catalog (.po file) into
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
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
Usage: msgfmt.py [OPTIONS] filename.po
...
@@ -32,12 +33,11 @@ import struct
...
@@ -32,12 +33,11 @@ import struct
import
array
import
array
from
email.parser
import
HeaderParser
from
email.parser
import
HeaderParser
__version__
=
"1.
1
"
__version__
=
"1.
2
"
MESSAGES
=
{}
MESSAGES
=
{}
def
usage
(
code
,
msg
=
''
):
def
usage
(
code
,
msg
=
''
):
print
(
__doc__
,
file
=
sys
.
stderr
)
print
(
__doc__
,
file
=
sys
.
stderr
)
if
msg
:
if
msg
:
...
@@ -45,15 +45,16 @@ def usage(code, msg=''):
...
@@ -45,15 +45,16 @@ def usage(code, msg=''):
sys
.
exit
(
code
)
sys
.
exit
(
code
)
def
add
(
ctxt
,
id
,
str
,
fuzzy
):
def
add
(
id
,
str
,
fuzzy
):
"Add a non-fuzzy translation to the dictionary."
"Add a non-fuzzy translation to the dictionary."
global
MESSAGES
global
MESSAGES
if
not
fuzzy
and
str
:
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
():
def
generate
():
"Return the generated output."
"Return the generated output."
global
MESSAGES
global
MESSAGES
...
@@ -95,10 +96,10 @@ def generate():
...
@@ -95,10 +96,10 @@ def generate():
return
output
return
output
def
make
(
filename
,
outfile
):
def
make
(
filename
,
outfile
):
ID
=
1
ID
=
1
STR
=
2
STR
=
2
CTXT
=
3
# Compute .mo name from .po name and arguments
# Compute .mo name from .po name and arguments
if
filename
.
endswith
(
'.po'
):
if
filename
.
endswith
(
'.po'
):
...
@@ -115,7 +116,7 @@ def make(filename, outfile):
...
@@ -115,7 +116,7 @@ def make(filename, outfile):
print
(
msg
,
file
=
sys
.
stderr
)
print
(
msg
,
file
=
sys
.
stderr
)
sys
.
exit
(
1
)
sys
.
exit
(
1
)
section
=
None
section
=
msgctxt
=
None
fuzzy
=
0
fuzzy
=
0
# Start off assuming Latin-1, so everything decodes without failure,
# Start off assuming Latin-1, so everything decodes without failure,
...
@@ -129,8 +130,8 @@ def make(filename, outfile):
...
@@ -129,8 +130,8 @@ def make(filename, outfile):
lno
+=
1
lno
+=
1
# If we get a comment line after a msgstr, this is a new entry
# If we get a comment line after a msgstr, this is a new entry
if
l
[
0
]
==
'#'
and
section
==
STR
:
if
l
[
0
]
==
'#'
and
section
==
STR
:
add
(
msgid
,
msgstr
,
fuzzy
)
add
(
msg
ctxt
,
msg
id
,
msgstr
,
fuzzy
)
section
=
None
section
=
msgctxt
=
None
fuzzy
=
0
fuzzy
=
0
# Record a fuzzy mark
# Record a fuzzy mark
if
l
[:
2
]
==
'#,'
and
'fuzzy'
in
l
:
if
l
[:
2
]
==
'#,'
and
'fuzzy'
in
l
:
...
@@ -138,10 +139,16 @@ def make(filename, outfile):
...
@@ -138,10 +139,16 @@ def make(filename, outfile):
# Skip comments
# Skip comments
if
l
[
0
]
==
'#'
:
if
l
[
0
]
==
'#'
:
continue
continue
# Now we are in a msgid section, output previous section
# Now we are in a msgid or msgctxt section, output previous section
if
l
.
startswith
(
'msgid'
)
and
not
l
.
startswith
(
'msgid_plural'
):
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
:
if
section
==
STR
:
add
(
msgid
,
msgstr
,
fuzzy
)
add
(
msg
ctxt
,
msg
id
,
msgstr
,
fuzzy
)
if
not
msgid
:
if
not
msgid
:
# See whether there is an encoding declaration
# See whether there is an encoding declaration
p
=
HeaderParser
()
p
=
HeaderParser
()
...
@@ -183,7 +190,9 @@ def make(filename, outfile):
...
@@ -183,7 +190,9 @@ def make(filename, outfile):
if
not
l
:
if
not
l
:
continue
continue
l
=
ast
.
literal_eval
(
l
)
l
=
ast
.
literal_eval
(
l
)
if
section
==
ID
:
if
section
==
CTXT
:
msgctxt
+=
l
.
encode
(
encoding
)
elif
section
==
ID
:
msgid
+=
l
.
encode
(
encoding
)
msgid
+=
l
.
encode
(
encoding
)
elif
section
==
STR
:
elif
section
==
STR
:
msgstr
+=
l
.
encode
(
encoding
)
msgstr
+=
l
.
encode
(
encoding
)
...
@@ -194,7 +203,7 @@ def make(filename, outfile):
...
@@ -194,7 +203,7 @@ def make(filename, outfile):
sys
.
exit
(
1
)
sys
.
exit
(
1
)
# Add last entry
# Add last entry
if
section
==
STR
:
if
section
==
STR
:
add
(
msgid
,
msgstr
,
fuzzy
)
add
(
msg
ctxt
,
msg
id
,
msgstr
,
fuzzy
)
# Compute output
# Compute output
output
=
generate
()
output
=
generate
()
...
@@ -206,7 +215,6 @@ def make(filename, outfile):
...
@@ -206,7 +215,6 @@ def make(filename, outfile):
print
(
msg
,
file
=
sys
.
stderr
)
print
(
msg
,
file
=
sys
.
stderr
)
def
main
():
def
main
():
try
:
try
:
opts
,
args
=
getopt
.
getopt
(
sys
.
argv
[
1
:],
'hVo:'
,
opts
,
args
=
getopt
.
getopt
(
sys
.
argv
[
1
:],
'hVo:'
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment