Code

Yet another fix to the mail gateway, messages got *all* files of
[roundup.git] / roundup / i18n.py
index 81edf78477a6d08366bbc3c6f4d75e9f242f5fc2..ecebc9ece8f18edfe82c49caa03df1ed3e359858 100644 (file)
 # FOR A PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
-# 
-# $Id: i18n.py,v 1.1 2001-11-21 22:57:29 jhermann Exp $
+#
+# $Id: i18n.py,v 1.15 2005-06-14 05:33:32 a1s Exp $
 
 """
 RoundUp Internationalization (I18N)
 
-To use this module, the following code should be used:
+To use this module, the following code should be used::
 
     from roundup.i18n import _
     ...
@@ -28,23 +28,202 @@ To use this module, the following code should be used:
 
 Note that to enable re-ordering of inserted texts in formatting strings
 (which can easily happen if a sentence has to be re-ordered due to
-grammatical changes), translatable formats should use named format specs:
+grammatical changes), translatable formats should use named format specs::
 
     ... _('Index of %(classname)s') % {'classname': cn} ...
 
 Also, this eases the job of translators since they have some context what
 the dynamic portion of a message really means.
-
 """
+__docformat__ = 'restructuredtext'
+
+import errno
+import gettext as gettext_module
+import os
+
+from roundup import msgfmt
+
+# List of directories for mo file search (see SF bug 1219689)
+LOCALE_DIRS = [
+    gettext_module._default_localedir,
+]
+# compute mo location relative to roundup installation directory
+# (prefix/lib/python/site-packages/roundup/msgfmt.py on posix systems,
+# prefix/lib/site-packages/roundup/msgfmt.py on windows).
+# locale root is prefix/share/locale.
+if os.name == "nt":
+    _mo_path = [".."] * 4 + ["share", "locale"]
+else:
+    _mo_path = [".."] * 5 + ["share", "locale"]
+_mo_path = os.path.normpath(os.path.join(msgfmt.__file__, *_mo_path))
+if _mo_path not in LOCALE_DIRS:
+    LOCALE_DIRS.append(_mo_path)
+del _mo_path
+
+# Roundup text domain
+DOMAIN = "roundup"
 
-# first, we try to import gettext; this probably never fails, but we make
-# sure we survive this anyway
-try:
-    import gettext
-except ImportError:
-    # fall-back to dummy on errors (returning the english text)
-    _ = lambda text: text
+if hasattr(gettext_module.GNUTranslations, "ngettext"):
+    # gettext_module has everything needed
+    RoundupNullTranslations = gettext_module.NullTranslations
+    RoundupTranslations = gettext_module.GNUTranslations
 else:
-    # and for now, we JUST implement the dummy in any case
-    _ = lambda text: text
+    # prior to 2.3, there was no plural forms.  mix simple emulation in
+    class PluralFormsMixIn:
+        def ngettext(self, singular, plural, count):
+            if count == 1:
+                _msg = singular
+            else:
+                _msg = plural
+            return self.gettext(_msg)
+        def ungettext(self, singular, plural, count):
+            if count == 1:
+                _msg = singular
+            else:
+                _msg = plural
+            return self.ugettext(_msg)
+    class RoundupNullTranslations(
+        gettext_module.NullTranslations, PluralFormsMixIn
+    ):
+        pass
+    class RoundupTranslations(
+        gettext_module.GNUTranslations, PluralFormsMixIn
+    ):
+        pass
+
+def find_locales(language=None):
+    """Return normalized list of locale names to try for given language
+
+    Argument 'language' may be a single language code or a list of codes.
+    If 'language' is omitted or None, use locale settings in OS environment.
+
+    """
+    # body of this function is borrowed from gettext_module.find()
+    if language is None:
+        languages = []
+        for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'):
+            val = os.environ.get(envar)
+            if val:
+                languages = val.split(':')
+                break
+    elif isinstance(language, str) or  isinstance(language, unicode):
+        languages = [language]
+    else:
+        # 'language' must be iterable
+        languages = language
+    # now normalize and expand the languages
+    nelangs = []
+    for lang in languages:
+        for nelang in gettext_module._expand_lang(lang):
+            if nelang not in nelangs:
+                nelangs.append(nelang)
+    return nelangs
+
+def get_mofile(languages, localedir, domain=None):
+    """Return the first of .mo files found in localedir for languages
+
+    Parameters:
+        languages:
+            list of locale names to try
+        localedir:
+            path to directory containing locale files.
+            Usually this is either gettext_module._default_localedir
+            or 'locale' subdirectory in the tracker home.
+        domain:
+            optional name of messages domain.
+            If omitted or None, work with simplified
+            locale directory, as used in tracker homes:
+            message catalogs are kept in files locale.po
+            instead of locale/LC_MESSAGES/domain.po
+
+    Return the path of the first .mo file found.
+    If nothing found, return None.
+
+    Automatically compile .po files if necessary.
+
+    """
+    for locale in languages:
+        if locale == "C":
+            break
+        if domain:
+            basename = os.path.join(localedir, locale, "LC_MESSAGES", domain)
+        else:
+            basename = os.path.join(localedir, locale)
+        # look for message catalog files, check timestamps
+        mofile = basename + ".mo"
+        if os.path.isfile(mofile):
+            motime = os.path.getmtime(mofile)
+        else:
+            motime = 0
+        pofile = basename + ".po"
+        if os.path.isfile(pofile):
+            potime = os.path.getmtime(pofile)
+        else:
+            potime = 0
+        # see what we've found
+        if motime < potime:
+            # compile
+            msgfmt.make(pofile, mofile)
+        elif motime == 0:
+            # no files found - proceed to the next locale name
+            continue
+        # .mo file found or made
+        return mofile
+    return None
+
+def get_translation(language=None, tracker_home=None,
+    translation_class=RoundupTranslations,
+    null_translation_class=RoundupNullTranslations
+):
+    """Return Translation object for given language and domain
+
+    Argument 'language' may be a single language code or a list of codes.
+    If 'language' is omitted or None, use locale settings in OS environment.
+
+    Arguments 'translation_class' and 'null_translation_class'
+    specify the classes that are instantiated for existing
+    and non-existing translations, respectively.
+
+    """
+    mofiles = []
+    # locale directory paths
+    if tracker_home is None:
+        tracker_locale = None
+    else:
+        tracker_locale = os.path.join(tracker_home, "locale")
+    # get the list of locales
+    locales = find_locales(language)
+    # add mofiles found in the tracker, then in the system locale directory
+    if tracker_locale:
+        mofiles.append(get_mofile(locales, tracker_locale))
+    for system_locale in LOCALE_DIRS:
+        mofiles.append(get_mofile(locales, system_locale, DOMAIN))
+    # we want to fall back to english unless english is selected language
+    if "en" not in locales:
+        locales = find_locales("en")
+        # add mofiles found in the tracker, then in the system locale directory
+        if tracker_locale:
+            mofiles.append(get_mofile(locales, tracker_locale))
+        for system_locale in LOCALE_DIRS:
+            mofiles.append(get_mofile(locales, system_locale, DOMAIN))
+    # filter out elements that are not found
+    mofiles = filter(None, mofiles)
+    if mofiles:
+        translator = translation_class(open(mofiles[0], "rb"))
+        for mofile in mofiles[1:]:
+            # note: current implementation of gettext_module
+            #   always adds fallback to the end of the fallback chain.
+            translator.add_fallback(translation_class(open(mofile, "rb")))
+    else:
+        translator = null_translation_class()
+    return translator
+
+# static translations object
+translation = get_translation()
+# static translation functions
+_ = gettext = translation.gettext
+ugettext = translation.ugettext
+ngettext = translation.ngettext
+ungettext = translation.ungettext
 
+# vim: set filetype=python sts=4 sw=4 et si :