#
# Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
# This module is free software, and you may redistribute it and/or modify
# under the same terms as Python, so long as this copyright message and
# disclaimer are retained in their original form.
#
# IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
# OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# 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.15 2005-06-14 05:33:32 a1s Exp $

"""
RoundUp Internationalization (I18N)

To use this module, the following code should be used::

    from roundup.i18n import _
    ...
    print _("Some text that can be translated")

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::

    ... _('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"

if hasattr(gettext_module.GNUTranslations, "ngettext"):
    # gettext_module has everything needed
    RoundupNullTranslations = gettext_module.NullTranslations
    RoundupTranslations = gettext_module.GNUTranslations
else:
    # 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 :
