22.1.2.2. The GNUTranslations class
The gettext module provides one additional class derived from
NullTranslations: GNUTranslations. This class overrides
_parse() to enable reading GNU gettext format .mo files
in both big-endian and little-endian format. It also coerces both message ids
and message strings to Unicode.
GNUTranslations parses optional meta-data out of the translation
catalog. It is convention with GNU gettext to include meta-data as
the translation for the empty string. This meta-data is in RFC 822-style
key: value pairs, and should contain the Project-Id-Version key. If the
key Content-Type is found, then the charset property is used to
initialize the “protected” _charset instance variable, defaulting to
None if not found. If the charset encoding is specified, then all message
ids and message strings read from the catalog are converted to Unicode using
this encoding. The ugettext() method always returns a Unicode, while the
gettext() returns an encoded 8-bit string. For the message id arguments
of both methods, either Unicode strings or 8-bit strings containing only
US-ASCII characters are acceptable. Note that the Unicode version of the
methods (i.e. ugettext() and ungettext()) are the recommended
interface to use for internationalized Python programs.
The entire set of key/value pairs are placed into a dictionary and set as the
“protected” _info instance variable.
If the .mo file’s magic number is invalid, or if other problems occur
while reading the file, instantiating a GNUTranslations class can raise
IOError.
The following methods are overridden from the base class implementation:
-
GNUTranslations.gettext(message)
Look up the message id in the catalog and return the corresponding message
string, as an 8-bit string encoded with the catalog’s charset encoding, if
known. If there is no entry in the catalog for the message id, and a fallback
has been set, the look up is forwarded to the fallback’s gettext() method.
Otherwise, the message id is returned.
-
GNUTranslations.lgettext(message)
Equivalent to gettext(), but the translation is returned in the preferred
system encoding, if no other encoding was explicitly set with
set_output_charset().
New in version 2.4:
New in version 2.4.
22.1.3. Internationalizing your programs and modules
Internationalization (I18N) refers to the operation by which a program is made
aware of multiple languages. Localization (L10N) refers to the adaptation of
your program, once internationalized, to the local language and cultural habits.
In order to provide multilingual messages for your Python programs, you need to
take the following steps:
- prepare your program or module by specially marking translatable strings
- run a suite of tools over your marked files to generate raw messages catalogs
- create language specific translations of the message catalogs
- use the
gettext module so that message strings are properly translated
In order to prepare your code for I18N, you need to look at all the strings in
your files. Any string that needs to be translated should be marked by wrapping
it in _('...') — that is, a call to the function _(). For example:
filename = 'mylog.txt'
message = _('writing a log message')
fp = open(filename, 'w')
fp.write(message)
fp.close()
In this example, the string 'writing a log message' is marked as a candidate
for translation, while the strings 'mylog.txt' and 'w' are not.
The Python distribution comes with two tools which help you generate the message
catalogs once you’ve prepared your source code. These may or may not be
available from a binary distribution, but they can be found in a source
distribution, in the Tools/i18n directory.
The pygettext program scans all your Python source code looking
for the strings you previously marked as translatable. It is similar to the GNU
gettext program except that it understands all the intricacies of
Python source code, but knows nothing about C or C++ source code. You don’t
need GNU gettext unless you’re also going to be translating C code (such as
C extension modules).
pygettext generates textual Uniforum-style human readable message
catalog .pot files, essentially structured human readable files which
contain every marked string in the source code, along with a placeholder for the
translation strings. pygettext is a command line script that supports
a similar command line interface as xgettext; for details on its use,
run:
Copies of these .pot files are then handed over to the individual human
translators who write language-specific versions for every supported natural
language. They send you back the filled in language-specific versions as a
.po file. Using the msgfmt.py program (in the
Tools/i18n directory), you take the .po files from your
translators and generate the machine-readable .mo binary catalog files.
The .mo files are what the gettext module uses for the actual
translation processing during run-time.
How you use the gettext module in your code depends on whether you are
internationalizing a single module or your entire application. The next two
sections will discuss each case.
22.1.3.1. Localizing your module
If you are localizing your module, you must take care not to make global
changes, e.g. to the built-in namespace. You should not use the GNU gettext
API but instead the class-based API.
Let’s say your module is called “spam” and the module’s various natural language
translation .mo files reside in /usr/share/locale in GNU
gettext format. Here’s what you would put at the top of your
module:
import gettext
t = gettext.translation('spam', '/usr/share/locale')
_ = t.lgettext
If your translators were providing you with Unicode strings in their .po
files, you’d instead do:
import gettext
t = gettext.translation('spam', '/usr/share/locale')
_ = t.ugettext
22.1.3.2. Localizing your application
If you are localizing your application, you can install the _() function
globally into the built-in namespace, usually in the main driver file of your
application. This will let all your application-specific files just use
_('...') without having to explicitly install it in each file.
In the simple case then, you need only add the following bit of code to the main
driver file of your application:
import gettext
gettext.install('myapplication')
If you need to set the locale directory or the unicode flag, you can pass
these into the install() function:
import gettext
gettext.install('myapplication', '/usr/share/locale', unicode=1)
22.1.3.3. Changing languages on the fly
If your program needs to support many languages at the same time, you may want
to create multiple translation instances and then switch between them
explicitly, like so:
import gettext
lang1 = gettext.translation('myapplication', languages=['en'])
lang2 = gettext.translation('myapplication', languages=['fr'])
lang3 = gettext.translation('myapplication', languages=['de'])
# start by using language1
lang1.install()
# ... time goes by, user selects language 2
lang2.install()
# ... more time goes by, user selects language 3
lang3.install()
22.1.3.4. Deferred translations
In most coding situations, strings are translated where they are coded.
Occasionally however, you need to mark strings for translation, but defer actual
translation until later. A classic example is:
animals = ['mollusk',
'albatross',
'rat',
'penguin',
'python', ]
# ...
for a in animals:
print a
Here, you want to mark the strings in the animals list as being
translatable, but you don’t actually want to translate them until they are
printed.
Here is one way you can handle this situation:
def _(message): return message
animals = [_('mollusk'),
_('albatross'),
_('rat'),
_('penguin'),
_('python'), ]
del _
# ...
for a in animals:
print _(a)
This works because the dummy definition of _() simply returns the string
unchanged. And this dummy definition will temporarily override any definition
of _() in the built-in namespace (until the del command). Take
care, though if you have a previous definition of _() in the local
namespace.
Note that the second use of _() will not identify “a” as being
translatable to the pygettext program, since it is not a string.
Another way to handle this is with the following example:
def N_(message): return message
animals = [N_('mollusk'),
N_('albatross'),
N_('rat'),
N_('penguin'),
N_('python'), ]
# ...
for a in animals:
print _(a)
In this case, you are marking translatable strings with the function N_(),
which won’t conflict with any definition of _(). However, you will
need to teach your message extraction program to look for translatable strings
marked with N_(). pygettext and xpot both support
this through the use of command line switches.
In Python 2.4 the lgettext() family of functions were introduced. The
intention of these functions is to provide an alternative which is more
compliant with the current implementation of GNU gettext. Unlike
gettext(), which returns strings encoded with the same codeset used in the
translation file, lgettext() will return strings encoded with the
preferred system encoding, as returned by locale.getpreferredencoding().
Also notice that Python 2.4 introduces new functions to explicitly choose the
codeset used in translated strings. If a codeset is explicitly set, even
lgettext() will return translated strings in the requested codeset, as
would be expected in the GNU gettext implementation.