2019-06-07 11:16:07 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
|
|
|
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
|
|
|
|
# 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
|
2019-08-14 16:46:21 +00:00
|
|
|
from .logger import logger
|
2019-06-07 11:16:07 +00:00
|
|
|
|
|
|
|
# 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]
|
|
|
|
|
2019-08-14 16:46:21 +00:00
|
|
|
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
|
|
|
|
|
2019-06-07 11:16:07 +00:00
|
|
|
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()
|