mirror of
https://github.com/python/cpython.git
synced 2025-09-27 02:39:58 +00:00
Add a per-message fallback mechanism for translations.
This commit is contained in:
parent
1be6419871
commit
a55ffaeee9
3 changed files with 84 additions and 26 deletions
|
@ -95,7 +95,8 @@ for returning either standard 8-bit strings or Unicode strings.
|
||||||
Translations instances can also install themselves in the built-in
|
Translations instances can also install themselves in the built-in
|
||||||
namespace as the function \function{_()}.
|
namespace as the function \function{_()}.
|
||||||
|
|
||||||
\begin{funcdesc}{find}{domain\optional{, localedir\optional{, languages}}}
|
\begin{funcdesc}{find}{domain\optional{, localedir\optional{,
|
||||||
|
languages\optional{, all}}}}
|
||||||
This function implements the standard \file{.mo} file search
|
This function implements the standard \file{.mo} file search
|
||||||
algorithm. It takes a \var{domain}, identical to what
|
algorithm. It takes a \var{domain}, identical to what
|
||||||
\function{textdomain()} takes. Optional \var{localedir} is as in
|
\function{textdomain()} takes. Optional \var{localedir} is as in
|
||||||
|
@ -119,7 +120,9 @@ components:
|
||||||
\file{\var{localedir}/\var{language}/LC_MESSAGES/\var{domain}.mo}
|
\file{\var{localedir}/\var{language}/LC_MESSAGES/\var{domain}.mo}
|
||||||
|
|
||||||
The first such file name that exists is returned by \function{find()}.
|
The first such file name that exists is returned by \function{find()}.
|
||||||
If no such file is found, then \code{None} is returned.
|
If no such file is found, then \code{None} is returned. If \var{all}
|
||||||
|
is given, it returns a list of all file names, in the order in which
|
||||||
|
they appear in the languages list or the environment variables.
|
||||||
\end{funcdesc}
|
\end{funcdesc}
|
||||||
|
|
||||||
\begin{funcdesc}{translation}{domain\optional{, localedir\optional{,
|
\begin{funcdesc}{translation}{domain\optional{, localedir\optional{,
|
||||||
|
@ -127,15 +130,22 @@ If no such file is found, then \code{None} is returned.
|
||||||
class_,\optional{fallback}}}}}
|
class_,\optional{fallback}}}}}
|
||||||
Return a \class{Translations} instance based on the \var{domain},
|
Return a \class{Translations} instance based on the \var{domain},
|
||||||
\var{localedir}, and \var{languages}, which are first passed to
|
\var{localedir}, and \var{languages}, which are first passed to
|
||||||
\function{find()} to get the
|
\function{find()} to get a list of the
|
||||||
associated \file{.mo} file path. Instances with
|
associated \file{.mo} file paths. Instances with
|
||||||
identical \file{.mo} file names are cached. The actual class instantiated
|
identical \file{.mo} file names are cached. The actual class instantiated
|
||||||
is either \var{class_} if provided, otherwise
|
is either \var{class_} if provided, otherwise
|
||||||
\class{GNUTranslations}. The class's constructor must take a single
|
\class{GNUTranslations}. The class's constructor must take a single
|
||||||
file object argument. If no \file{.mo} file is found, this
|
file object argument.
|
||||||
function raises \exception{IOError} if \var{fallback} is false
|
|
||||||
(which is the default), and returns a \class{NullTranslations} instance
|
If multiple files are found, later files are used as fallbacks for
|
||||||
if \var{fallback} is true.
|
earlier ones. To allow setting the fallback, \function{copy.copy}
|
||||||
|
is used to clone each translation object from the cache; the actual
|
||||||
|
instance data is still shared with the cache.
|
||||||
|
|
||||||
|
If no \file{.mo} file is found, this function raises
|
||||||
|
\exception{IOError} if \var{fallback} is false (which is the default),
|
||||||
|
and returns a \class{NullTranslations} instance if \var{fallback} is
|
||||||
|
true.
|
||||||
\end{funcdesc}
|
\end{funcdesc}
|
||||||
|
|
||||||
\begin{funcdesc}{install}{domain\optional{, localedir\optional{, unicode}}}
|
\begin{funcdesc}{install}{domain\optional{, localedir\optional{, unicode}}}
|
||||||
|
@ -168,7 +178,8 @@ methods of \class{NullTranslations}:
|
||||||
\begin{methoddesc}[NullTranslations]{__init__}{\optional{fp}}
|
\begin{methoddesc}[NullTranslations]{__init__}{\optional{fp}}
|
||||||
Takes an optional file object \var{fp}, which is ignored by the base
|
Takes an optional file object \var{fp}, which is ignored by the base
|
||||||
class. Initializes ``protected'' instance variables \var{_info} and
|
class. Initializes ``protected'' instance variables \var{_info} and
|
||||||
\var{_charset} which are set by derived classes. It then calls
|
\var{_charset} which are set by derived classes, as well as \var{_fallback},
|
||||||
|
which is set through \method{add_fallback}. It then calls
|
||||||
\code{self._parse(fp)} if \var{fp} is not \code{None}.
|
\code{self._parse(fp)} if \var{fp} is not \code{None}.
|
||||||
\end{methoddesc}
|
\end{methoddesc}
|
||||||
|
|
||||||
|
@ -179,13 +190,21 @@ you have an unsupported message catalog file format, you should
|
||||||
override this method to parse your format.
|
override this method to parse your format.
|
||||||
\end{methoddesc}
|
\end{methoddesc}
|
||||||
|
|
||||||
|
\begin{methoddesc}{NullTranslations}{add_fallback}{fallback}
|
||||||
|
Add \var{fallback} as the fallback object for the current translation
|
||||||
|
object. A translation object should consult the fallback if it cannot
|
||||||
|
provide a translation for a given message.
|
||||||
|
\end{methoddesc}
|
||||||
|
|
||||||
\begin{methoddesc}[NullTranslations]{gettext}{message}
|
\begin{methoddesc}[NullTranslations]{gettext}{message}
|
||||||
Return the translated message. Overridden in derived classes.
|
If a fallback has been set, forward \method{gettext} to the fallback.
|
||||||
|
Otherwise, return the translated message. Overridden in derived classes.
|
||||||
\end{methoddesc}
|
\end{methoddesc}
|
||||||
|
|
||||||
\begin{methoddesc}[NullTranslations]{ugettext}{message}
|
\begin{methoddesc}[NullTranslations]{ugettext}{message}
|
||||||
Return the translated message as a Unicode string. Overridden in
|
If a fallback has been set, forward \method{ugettext} to the fallback.
|
||||||
derived classes.
|
Otherwise, return the translated message as a Unicode string.
|
||||||
|
Overridden in derived classes.
|
||||||
\end{methoddesc}
|
\end{methoddesc}
|
||||||
|
|
||||||
\begin{methoddesc}[NullTranslations]{info}{}
|
\begin{methoddesc}[NullTranslations]{info}{}
|
||||||
|
|
|
@ -46,6 +46,7 @@ internationalized, to the local language and cultural habits.
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import struct
|
import struct
|
||||||
|
import copy
|
||||||
from errno import ENOENT
|
from errno import ENOENT
|
||||||
|
|
||||||
__all__ = ["bindtextdomain","textdomain","gettext","dgettext",
|
__all__ = ["bindtextdomain","textdomain","gettext","dgettext",
|
||||||
|
@ -102,16 +103,27 @@ class NullTranslations:
|
||||||
def __init__(self, fp=None):
|
def __init__(self, fp=None):
|
||||||
self._info = {}
|
self._info = {}
|
||||||
self._charset = None
|
self._charset = None
|
||||||
|
self._fallback = None
|
||||||
if fp:
|
if fp:
|
||||||
self._parse(fp)
|
self._parse(fp)
|
||||||
|
|
||||||
def _parse(self, fp):
|
def _parse(self, fp):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def add_fallback(self, fallback):
|
||||||
|
if self._fallback:
|
||||||
|
self._fallback.add_fallback(fallback)
|
||||||
|
else:
|
||||||
|
self._fallback = fallback
|
||||||
|
|
||||||
def gettext(self, message):
|
def gettext(self, message):
|
||||||
|
if self._fallback:
|
||||||
|
return self._fallback.gettext(message)
|
||||||
return message
|
return message
|
||||||
|
|
||||||
def ugettext(self, message):
|
def ugettext(self, message):
|
||||||
|
if self._fallback:
|
||||||
|
return self._fallback.ugettext(message)
|
||||||
return unicode(message)
|
return unicode(message)
|
||||||
|
|
||||||
def info(self):
|
def info(self):
|
||||||
|
@ -188,16 +200,26 @@ class GNUTranslations(NullTranslations):
|
||||||
transidx += 8
|
transidx += 8
|
||||||
|
|
||||||
def gettext(self, message):
|
def gettext(self, message):
|
||||||
return self._catalog.get(message, message)
|
try:
|
||||||
|
return self._catalog[message]
|
||||||
|
except KeyError:
|
||||||
|
if self._fallback:
|
||||||
|
return self._fallback.gettext(message)
|
||||||
|
return message
|
||||||
|
|
||||||
def ugettext(self, message):
|
def ugettext(self, message):
|
||||||
tmsg = self._catalog.get(message, message)
|
try:
|
||||||
|
tmsg = self._catalog[message]
|
||||||
|
except KeyError:
|
||||||
|
if self._fallback:
|
||||||
|
return self._fallback.ugettext(message)
|
||||||
|
tmsg = message
|
||||||
return unicode(tmsg, self._charset)
|
return unicode(tmsg, self._charset)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Locate a .mo file using the gettext strategy
|
# Locate a .mo file using the gettext strategy
|
||||||
def find(domain, localedir=None, languages=None):
|
def find(domain, localedir=None, languages=None, all=0):
|
||||||
# Get some reasonable defaults for arguments that were not supplied
|
# Get some reasonable defaults for arguments that were not supplied
|
||||||
if localedir is None:
|
if localedir is None:
|
||||||
localedir = _default_localedir
|
localedir = _default_localedir
|
||||||
|
@ -217,13 +239,20 @@ def find(domain, localedir=None, languages=None):
|
||||||
if nelang not in nelangs:
|
if nelang not in nelangs:
|
||||||
nelangs.append(nelang)
|
nelangs.append(nelang)
|
||||||
# select a language
|
# select a language
|
||||||
|
if all:
|
||||||
|
result = []
|
||||||
|
else:
|
||||||
|
result = None
|
||||||
for lang in nelangs:
|
for lang in nelangs:
|
||||||
if lang == 'C':
|
if lang == 'C':
|
||||||
break
|
break
|
||||||
mofile = os.path.join(localedir, lang, 'LC_MESSAGES', '%s.mo' % domain)
|
mofile = os.path.join(localedir, lang, 'LC_MESSAGES', '%s.mo' % domain)
|
||||||
if os.path.exists(mofile):
|
if os.path.exists(mofile):
|
||||||
|
if all:
|
||||||
|
result.append(mofile)
|
||||||
|
else:
|
||||||
return mofile
|
return mofile
|
||||||
return None
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -234,20 +263,28 @@ def translation(domain, localedir=None, languages=None,
|
||||||
class_=None, fallback=0):
|
class_=None, fallback=0):
|
||||||
if class_ is None:
|
if class_ is None:
|
||||||
class_ = GNUTranslations
|
class_ = GNUTranslations
|
||||||
mofile = find(domain, localedir, languages)
|
mofiles = find(domain, localedir, languages, all=1)
|
||||||
if mofile is None:
|
if len(mofiles)==0:
|
||||||
if fallback:
|
if fallback:
|
||||||
return NullTranslations()
|
return NullTranslations()
|
||||||
raise IOError(ENOENT, 'No translation file found for domain', domain)
|
raise IOError(ENOENT, 'No translation file found for domain', domain)
|
||||||
key = os.path.abspath(mofile)
|
|
||||||
# TBD: do we need to worry about the file pointer getting collected?
|
# TBD: do we need to worry about the file pointer getting collected?
|
||||||
# Avoid opening, reading, and parsing the .mo file after it's been done
|
# Avoid opening, reading, and parsing the .mo file after it's been done
|
||||||
# once.
|
# once.
|
||||||
|
result = None
|
||||||
|
for mofile in mofiles:
|
||||||
|
key = os.path.abspath(mofile)
|
||||||
t = _translations.get(key)
|
t = _translations.get(key)
|
||||||
if t is None:
|
if t is None:
|
||||||
t = _translations.setdefault(key, class_(open(mofile, 'rb')))
|
t = _translations.setdefault(key, class_(open(mofile, 'rb')))
|
||||||
return t
|
# Copy the translation object to allow setting fallbacks.
|
||||||
|
# All other instance data is shared with the cached object.
|
||||||
|
t = copy.copy(t)
|
||||||
|
if result is None:
|
||||||
|
result = t
|
||||||
|
else:
|
||||||
|
result.add_fallback(t)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def install(domain, localedir=None, unicode=0):
|
def install(domain, localedir=None, unicode=0):
|
||||||
|
|
|
@ -26,7 +26,9 @@ Library
|
||||||
arbitrary shell code can't be executed because a bogus URL was
|
arbitrary shell code can't be executed because a bogus URL was
|
||||||
passed in.
|
passed in.
|
||||||
|
|
||||||
- gettext.translation has an optional fallback argument.
|
- gettext.translation has an optional fallback argument, and
|
||||||
|
gettext.find an optional all argument. Translations will now fallback
|
||||||
|
on a per-message basis.
|
||||||
|
|
||||||
Tools/Demos
|
Tools/Demos
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue