# -*- coding: utf-8 -*- # # Copyright (C) 2019 Chris Caron # All rights reserved. # # This code is licensed under the MIT License. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files(the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and / or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions : # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import six import ctypes import locale import contextlib from os.path import join from os.path import dirname from os.path import abspath from .logger import logger # Define our translation domain DOMAIN = 'apprise' LOCALE_DIR = abspath(join(dirname(__file__), 'i18n')) # This gets toggled to True if we succeed GETTEXT_LOADED = False try: # Initialize gettext import gettext # install() creates a _() in our builtins gettext.install(DOMAIN, localedir=LOCALE_DIR) # Toggle our flag GETTEXT_LOADED = True except ImportError: # gettext isn't available; no problem, just fall back to using # the library features without multi-language support. try: # Python v2.7 import __builtin__ __builtin__.__dict__['_'] = lambda x: x # pragma: no branch except ImportError: # Python v3.4+ import builtins builtins.__dict__['_'] = lambda x: x # pragma: no branch class LazyTranslation(object): """ Doesn't translate anything until str() or unicode() references are made. """ def __init__(self, text, *args, **kwargs): """ Store our text """ self.text = text super(LazyTranslation, self).__init__(*args, **kwargs) def __str__(self): return gettext.gettext(self.text) # Lazy translation handling def gettext_lazy(text): """ A dummy function that can be referenced """ return LazyTranslation(text=text) class AppriseLocale(object): """ A wrapper class to gettext so that we can manipulate multiple lanaguages on the fly if required. """ def __init__(self, language=None): """ Initializes our object, if a language is specified, then we initialize ourselves to that, otherwise we use whatever we detect from the local operating system. If all else fails, we resort to the defined default_language. """ # Cache previously loaded translations self._gtobjs = {} # Get our language self.lang = AppriseLocale.detect_language(language) if GETTEXT_LOADED is False: # We're done return if self.lang: # Load our gettext object and install our language try: self._gtobjs[self.lang] = gettext.translation( DOMAIN, localedir=LOCALE_DIR, languages=[self.lang]) # Install our language self._gtobjs[self.lang].install() except IOError: # This occurs if we can't access/load our translations pass @contextlib.contextmanager def lang_at(self, lang): """ The syntax works as: with at.lang_at('fr'): # apprise works as though the french language has been # defined. afterwards, the language falls back to whatever # it was. """ if GETTEXT_LOADED is False: # yield yield # we're done return # Tidy the language lang = AppriseLocale.detect_language(lang, detect_fallback=False) # Now attempt to load it try: if lang in self._gtobjs: if lang != self.lang: # Install our language only if we aren't using it # already self._gtobjs[lang].install() else: self._gtobjs[lang] = gettext.translation( DOMAIN, localedir=LOCALE_DIR, languages=[self.lang]) # Install our language self._gtobjs[lang].install() # Yield yield except (IOError, KeyError): # This occurs if we can't access/load our translations # Yield reguardless yield finally: # Fall back to our previous language if lang != self.lang and lang in self._gtobjs: # Install our language self._gtobjs[self.lang].install() return @staticmethod def detect_language(lang=None, detect_fallback=True): """ returns the language (if it's retrievable) """ # We want to only use the 2 character version of this language # hence en_CA becomes en, en_US becomes en. if not isinstance(lang, six.string_types): if detect_fallback is False: # no detection enabled; we're done return None if hasattr(ctypes, 'windll'): windll = ctypes.windll.kernel32 try: lang = locale.windows_locale[ windll.GetUserDefaultUILanguage()] # Our detected windows language return lang[0:2].lower() except (TypeError, KeyError): # Fallback to posix detection pass try: # Detect language lang = locale.getdefaultlocale()[0] except ValueError as e: # This occurs when an invalid locale was parsed from the # environment variable. While we still return None in this # case, we want to better notify the end user of this. Users # receiving this error should check their environment # variables. logger.warning( 'Language detection failure / {}'.format(str(e))) return None except TypeError: # None is returned if the default can't be determined # we're done in this case return None return None if not lang else lang[0:2].lower()