From a55ffaeee9eb2fba62f280c364b3460fafd9efc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20v=2E=20L=C3=B6wis?= Date: Fri, 11 Jan 2002 06:58:49 +0000 Subject: [PATCH] Add a per-message fallback mechanism for translations. --- Doc/lib/libgettext.tex | 43 ++++++++++++++++++++-------- Lib/gettext.py | 63 +++++++++++++++++++++++++++++++++--------- Misc/NEWS | 4 ++- 3 files changed, 84 insertions(+), 26 deletions(-) diff --git a/Doc/lib/libgettext.tex b/Doc/lib/libgettext.tex index be2234000df..c19d9b9b4ab 100644 --- a/Doc/lib/libgettext.tex +++ b/Doc/lib/libgettext.tex @@ -95,7 +95,8 @@ for returning either standard 8-bit strings or Unicode strings. Translations instances can also install themselves in the built-in 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 algorithm. It takes a \var{domain}, identical to what \function{textdomain()} takes. Optional \var{localedir} is as in @@ -119,7 +120,9 @@ components: \file{\var{localedir}/\var{language}/LC_MESSAGES/\var{domain}.mo} 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} \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}}}}} Return a \class{Translations} instance based on the \var{domain}, \var{localedir}, and \var{languages}, which are first passed to -\function{find()} to get the -associated \file{.mo} file path. Instances with +\function{find()} to get a list of the +associated \file{.mo} file paths. Instances with identical \file{.mo} file names are cached. The actual class instantiated is either \var{class_} if provided, otherwise \class{GNUTranslations}. The class's constructor must take a single -file object argument. 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. +file object argument. + +If multiple files are found, later files are used as fallbacks for +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} \begin{funcdesc}{install}{domain\optional{, localedir\optional{, unicode}}} @@ -168,7 +178,8 @@ methods of \class{NullTranslations}: \begin{methoddesc}[NullTranslations]{__init__}{\optional{fp}} Takes an optional file object \var{fp}, which is ignored by the base 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}. \end{methoddesc} @@ -179,13 +190,21 @@ you have an unsupported message catalog file format, you should override this method to parse your format. \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} -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} \begin{methoddesc}[NullTranslations]{ugettext}{message} -Return the translated message as a Unicode string. Overridden in -derived classes. +If a fallback has been set, forward \method{ugettext} to the fallback. +Otherwise, return the translated message as a Unicode string. +Overridden in derived classes. \end{methoddesc} \begin{methoddesc}[NullTranslations]{info}{} diff --git a/Lib/gettext.py b/Lib/gettext.py index 6795ee6528f..0bea9ed0de1 100644 --- a/Lib/gettext.py +++ b/Lib/gettext.py @@ -46,6 +46,7 @@ internationalized, to the local language and cultural habits. import os import sys import struct +import copy from errno import ENOENT __all__ = ["bindtextdomain","textdomain","gettext","dgettext", @@ -102,16 +103,27 @@ class NullTranslations: def __init__(self, fp=None): self._info = {} self._charset = None + self._fallback = None if fp: self._parse(fp) def _parse(self, fp): pass + def add_fallback(self, fallback): + if self._fallback: + self._fallback.add_fallback(fallback) + else: + self._fallback = fallback + def gettext(self, message): + if self._fallback: + return self._fallback.gettext(message) return message def ugettext(self, message): + if self._fallback: + return self._fallback.ugettext(message) return unicode(message) def info(self): @@ -188,16 +200,26 @@ class GNUTranslations(NullTranslations): transidx += 8 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): - 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) # 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 if localedir is None: localedir = _default_localedir @@ -217,13 +239,20 @@ def find(domain, localedir=None, languages=None): if nelang not in nelangs: nelangs.append(nelang) # select a language + if all: + result = [] + else: + result = None for lang in nelangs: if lang == 'C': break mofile = os.path.join(localedir, lang, 'LC_MESSAGES', '%s.mo' % domain) if os.path.exists(mofile): - return mofile - return None + if all: + result.append(mofile) + else: + return mofile + return result @@ -234,20 +263,28 @@ def translation(domain, localedir=None, languages=None, class_=None, fallback=0): if class_ is None: class_ = GNUTranslations - mofile = find(domain, localedir, languages) - if mofile is None: + mofiles = find(domain, localedir, languages, all=1) + if len(mofiles)==0: if fallback: return NullTranslations() 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? # Avoid opening, reading, and parsing the .mo file after it's been done # once. - t = _translations.get(key) - if t is None: - t = _translations.setdefault(key, class_(open(mofile, 'rb'))) - return t - + result = None + for mofile in mofiles: + key = os.path.abspath(mofile) + t = _translations.get(key) + if t is None: + t = _translations.setdefault(key, class_(open(mofile, 'rb'))) + # 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): diff --git a/Misc/NEWS b/Misc/NEWS index cc8115062ab..355c926a3a0 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -26,7 +26,9 @@ Library arbitrary shell code can't be executed because a bogus URL was 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