mirror of
https://github.com/caronc/apprise.git
synced 2024-12-26 00:29:45 +01:00
Prevent gettext() from installing to global _ namespace (#821)
This commit is contained in:
parent
31caff1ac9
commit
f82934a815
1
.gitignore
vendored
1
.gitignore
vendored
@ -26,6 +26,7 @@ sdist/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
.local
|
||||
|
||||
# Generated from Docker Instance
|
||||
.bash_history
|
||||
|
@ -40,9 +40,6 @@ 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
|
||||
@ -51,43 +48,13 @@ 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.
|
||||
import builtins
|
||||
builtins.__dict__['_'] = lambda x: x # pragma: no branch
|
||||
|
||||
|
||||
class LazyTranslation:
|
||||
"""
|
||||
Doesn't translate anything until str() or unicode() references
|
||||
are made.
|
||||
|
||||
"""
|
||||
def __init__(self, text, *args, **kwargs):
|
||||
"""
|
||||
Store our text
|
||||
"""
|
||||
self.text = text
|
||||
|
||||
super().__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)
|
||||
# gettext isn't available; no problem; Use the library features without
|
||||
# multi-language support.
|
||||
pass
|
||||
|
||||
|
||||
class AppriseLocale:
|
||||
@ -97,15 +64,24 @@ class AppriseLocale:
|
||||
|
||||
"""
|
||||
|
||||
# Define our translation domain
|
||||
_domain = 'apprise'
|
||||
|
||||
# The path to our translations
|
||||
_locale_dir = abspath(join(dirname(__file__), 'i18n'))
|
||||
|
||||
# Locale regular expression
|
||||
_local_re = re.compile(
|
||||
r'^\s*(?P<lang>[a-z]{2})([_:]((?P<country>[a-z]{2}))?'
|
||||
r'(\.(?P<enc>[a-z0-9]+))?|.+)?', re.IGNORECASE)
|
||||
r'^((?P<ansii>C)|(?P<lang>([a-z]{2}))([_:](?P<country>[a-z]{2}))?)'
|
||||
r'(\.(?P<enc>[a-z0-9-]+))?$', re.IGNORECASE)
|
||||
|
||||
# Define our default encoding
|
||||
_default_encoding = 'utf-8'
|
||||
|
||||
# Define our default language
|
||||
# The function to assign `_` by default
|
||||
_fn = 'gettext'
|
||||
|
||||
# The language we should fall back to if all else fails
|
||||
_default_language = 'en'
|
||||
|
||||
def __init__(self, language=None):
|
||||
@ -123,25 +99,55 @@ class AppriseLocale:
|
||||
# Get our language
|
||||
self.lang = AppriseLocale.detect_language(language)
|
||||
|
||||
# Our mapping to our _fn
|
||||
self.__fn_map = None
|
||||
|
||||
if GETTEXT_LOADED is False:
|
||||
# We're done
|
||||
return
|
||||
|
||||
if self.lang:
|
||||
# Add language
|
||||
self.add(self.lang)
|
||||
|
||||
def add(self, lang=None, set_default=True):
|
||||
"""
|
||||
Add a language to our list
|
||||
"""
|
||||
lang = lang if lang else self._default_language
|
||||
if lang not in self._gtobjs:
|
||||
# Load our gettext object and install our language
|
||||
try:
|
||||
self._gtobjs[self.lang] = gettext.translation(
|
||||
DOMAIN, localedir=LOCALE_DIR, languages=[self.lang])
|
||||
self._gtobjs[lang] = gettext.translation(
|
||||
self._domain, localedir=self._locale_dir, languages=[lang],
|
||||
fallback=False)
|
||||
|
||||
# Install our language
|
||||
self._gtobjs[self.lang].install()
|
||||
# The non-intrusive method of applying the gettext change to
|
||||
# the global namespace only
|
||||
self.__fn_map = getattr(self._gtobjs[lang], self._fn)
|
||||
|
||||
except IOError:
|
||||
# This occurs if we can't access/load our translations
|
||||
pass
|
||||
except FileNotFoundError:
|
||||
# The translation directory does not exist
|
||||
logger.debug(
|
||||
'Could not load translation path: %s',
|
||||
join(self._locale_dir, lang))
|
||||
|
||||
# Fallback (handle case where self.lang does not exist)
|
||||
if self.lang not in self._gtobjs:
|
||||
self._gtobjs[self.lang] = gettext
|
||||
self.__fn_map = getattr(self._gtobjs[self.lang], self._fn)
|
||||
|
||||
return False
|
||||
|
||||
logger.trace('Loaded language %s', lang)
|
||||
|
||||
if set_default:
|
||||
logger.debug('Language set to %s', lang)
|
||||
self.lang = lang
|
||||
|
||||
return True
|
||||
|
||||
@contextlib.contextmanager
|
||||
def lang_at(self, lang):
|
||||
def lang_at(self, lang, mapto=_fn):
|
||||
"""
|
||||
The syntax works as:
|
||||
with at.lang_at('fr'):
|
||||
@ -151,46 +157,32 @@ class AppriseLocale:
|
||||
"""
|
||||
|
||||
if GETTEXT_LOADED is False:
|
||||
# yield
|
||||
yield
|
||||
# Do nothing
|
||||
yield None
|
||||
|
||||
# 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()
|
||||
|
||||
if lang not in self._gtobjs and not self.add(lang, set_default=False):
|
||||
# Do Nothing
|
||||
yield getattr(self._gtobjs[self.lang], mapto)
|
||||
else:
|
||||
# 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()
|
||||
yield getattr(self._gtobjs[lang], mapto)
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def gettext(self):
|
||||
"""
|
||||
Return the current language gettext() function
|
||||
|
||||
Useful for assigning to `_`
|
||||
"""
|
||||
return self._gtobjs[self.lang].gettext
|
||||
|
||||
@staticmethod
|
||||
def detect_language(lang=None, detect_fallback=True):
|
||||
"""
|
||||
@ -227,12 +219,12 @@ class AppriseLocale:
|
||||
# Fallback to posix detection
|
||||
pass
|
||||
|
||||
# Linux Handling
|
||||
# Built in locale library check
|
||||
try:
|
||||
# Acquire our locale
|
||||
lang = locale.getlocale()[0]
|
||||
|
||||
except TypeError as e:
|
||||
except (ValueError, TypeError) 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
|
||||
@ -249,8 +241,10 @@ class AppriseLocale:
|
||||
Pickle Support dumps()
|
||||
"""
|
||||
state = self.__dict__.copy()
|
||||
|
||||
# Remove the unpicklable entries.
|
||||
del state['_gtobjs']
|
||||
del state['_AppriseLocale__fn_map']
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
@ -258,4 +252,39 @@ class AppriseLocale:
|
||||
Pickle Support loads()
|
||||
"""
|
||||
self.__dict__.update(state)
|
||||
# Our mapping to our _fn
|
||||
self.__fn_map = None
|
||||
self._gtobjs = {}
|
||||
self.add(state['lang'], set_default=True)
|
||||
|
||||
|
||||
#
|
||||
# Prepare our default LOCALE Singleton
|
||||
#
|
||||
LOCALE = AppriseLocale()
|
||||
|
||||
|
||||
class LazyTranslation:
|
||||
"""
|
||||
Doesn't translate anything until str() or unicode() references
|
||||
are made.
|
||||
|
||||
"""
|
||||
def __init__(self, text, *args, **kwargs):
|
||||
"""
|
||||
Store our text
|
||||
"""
|
||||
self.text = text
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return LOCALE.gettext(self.text) if GETTEXT_LOADED else self.text
|
||||
|
||||
|
||||
# Lazy translation handling
|
||||
def gettext_lazy(text):
|
||||
"""
|
||||
A dummy function that can be referenced
|
||||
"""
|
||||
return LazyTranslation(text=text)
|
||||
|
@ -3,9 +3,10 @@
|
||||
# This file is distributed under the same license as the apprise project.
|
||||
# Chris Caron <lead2gold@gmail.com>, 2019.
|
||||
#
|
||||
msgid ""
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: apprise 0.7.6\n"
|
||||
|
||||
"Project-Id-Version: apprise 1.4.5\n"
|
||||
"Report-Msgid-Bugs-To: lead2gold@gmail.com\n"
|
||||
"POT-Creation-Date: 2019-05-28 16:56-0400\n"
|
||||
"PO-Revision-Date: 2019-05-24 20:00-0400\n"
|
||||
@ -18,276 +19,272 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.6.0\n"
|
||||
|
||||
msgid "API Key"
|
||||
msgstr ""
|
||||
msgid "API Key"
|
||||
msgstr "API Key"
|
||||
|
||||
msgid "Access Key"
|
||||
msgstr ""
|
||||
msgid "Access Key"
|
||||
msgstr "Access Key"
|
||||
|
||||
msgid "Access Key ID"
|
||||
msgstr ""
|
||||
msgid "Access Key ID"
|
||||
msgstr "Access Key ID"
|
||||
|
||||
msgid "Access Secret"
|
||||
msgstr ""
|
||||
msgid "Access Secret"
|
||||
msgstr "Access Secret"
|
||||
|
||||
msgid "Access Token"
|
||||
msgstr ""
|
||||
msgid "Access Token"
|
||||
msgstr "Access Token"
|
||||
|
||||
msgid "Account SID"
|
||||
msgstr ""
|
||||
msgid "Account SID"
|
||||
msgstr "Account SID"
|
||||
|
||||
msgid "Add Tokens"
|
||||
msgstr ""
|
||||
msgid "Add Tokens"
|
||||
msgstr "Add Tokens"
|
||||
|
||||
msgid "Application Key"
|
||||
msgstr ""
|
||||
msgid "Application Key"
|
||||
msgstr "Application Key"
|
||||
|
||||
msgid "Application Secret"
|
||||
msgstr ""
|
||||
msgid "Application Secret"
|
||||
msgstr "Application Secret"
|
||||
|
||||
msgid "Auth Token"
|
||||
msgstr ""
|
||||
msgid "Auth Token"
|
||||
msgstr "Auth Token"
|
||||
|
||||
msgid "Authorization Token"
|
||||
msgstr ""
|
||||
msgid "Authorization Token"
|
||||
msgstr "Authorization Token"
|
||||
|
||||
msgid "Avatar Image"
|
||||
msgstr ""
|
||||
msgid "Avatar Image"
|
||||
msgstr "Avatar Image"
|
||||
|
||||
msgid "Bot Name"
|
||||
msgstr ""
|
||||
msgid "Bot Name"
|
||||
msgstr "Bot Name"
|
||||
|
||||
msgid "Bot Token"
|
||||
msgstr ""
|
||||
msgid "Bot Token"
|
||||
msgstr "Bot Token"
|
||||
|
||||
msgid "Channels"
|
||||
msgstr ""
|
||||
msgid "Channels"
|
||||
msgstr "Channels"
|
||||
|
||||
msgid "Consumer Key"
|
||||
msgstr ""
|
||||
msgid "Consumer Key"
|
||||
msgstr "Consumer Key"
|
||||
|
||||
msgid "Consumer Secret"
|
||||
msgstr ""
|
||||
msgid "Consumer Secret"
|
||||
msgstr "Consumer Secret"
|
||||
|
||||
msgid "Detect Bot Owner"
|
||||
msgstr ""
|
||||
msgid "Detect Bot Owner"
|
||||
msgstr "Detect Bot Owner"
|
||||
|
||||
msgid "Device ID"
|
||||
msgstr ""
|
||||
msgid "Device ID"
|
||||
msgstr "Device ID"
|
||||
|
||||
msgid "Display Footer"
|
||||
msgstr ""
|
||||
msgid "Display Footer"
|
||||
msgstr "Display Footer"
|
||||
|
||||
msgid "Domain"
|
||||
msgstr ""
|
||||
msgid "Domain"
|
||||
msgstr "Domain"
|
||||
|
||||
msgid "Duration"
|
||||
msgstr ""
|
||||
msgid "Duration"
|
||||
msgstr "Duration"
|
||||
|
||||
msgid "Events"
|
||||
msgstr ""
|
||||
msgid "Events"
|
||||
msgstr "Events"
|
||||
|
||||
msgid "Footer Logo"
|
||||
msgstr ""
|
||||
msgid "Footer Logo"
|
||||
msgstr "Footer Logo"
|
||||
|
||||
msgid "From Email"
|
||||
msgstr ""
|
||||
msgid "From Email"
|
||||
msgstr "From Email"
|
||||
|
||||
msgid "From Name"
|
||||
msgstr ""
|
||||
msgid "From Name"
|
||||
msgstr "From Name"
|
||||
|
||||
msgid "From Phone No"
|
||||
msgstr ""
|
||||
msgid "From Phone No"
|
||||
msgstr "From Phone No"
|
||||
|
||||
msgid "Group"
|
||||
msgstr ""
|
||||
msgid "Group"
|
||||
msgstr "Group"
|
||||
|
||||
msgid "HTTP Header"
|
||||
msgstr ""
|
||||
msgid "HTTP Header"
|
||||
msgstr "HTTP Header"
|
||||
|
||||
msgid "Hostname"
|
||||
msgstr ""
|
||||
msgid "Hostname"
|
||||
msgstr "Hostname"
|
||||
|
||||
msgid "Include Image"
|
||||
msgstr ""
|
||||
msgid "Include Image"
|
||||
msgstr "Include Image"
|
||||
|
||||
msgid "Modal"
|
||||
msgstr ""
|
||||
msgid "Modal"
|
||||
msgstr "Modal"
|
||||
|
||||
msgid "Notify Format"
|
||||
msgstr ""
|
||||
msgid "Notify Format"
|
||||
msgstr "Notify Format"
|
||||
|
||||
msgid "Organization"
|
||||
msgstr ""
|
||||
msgid "Organization"
|
||||
msgstr "Organization"
|
||||
|
||||
msgid "Overflow Mode"
|
||||
msgstr ""
|
||||
msgid "Overflow Mode"
|
||||
msgstr "Overflow Mode"
|
||||
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
|
||||
msgid "Port"
|
||||
msgstr ""
|
||||
msgid "Port"
|
||||
msgstr "Port"
|
||||
|
||||
msgid "Priority"
|
||||
msgstr ""
|
||||
msgid "Priority"
|
||||
msgstr "Priority"
|
||||
|
||||
msgid "Provider Key"
|
||||
msgstr ""
|
||||
msgid "Provider Key"
|
||||
msgstr "Provider Key"
|
||||
|
||||
msgid "Region"
|
||||
msgstr ""
|
||||
msgid "Region"
|
||||
msgstr "Region"
|
||||
|
||||
msgid "Region Name"
|
||||
msgstr ""
|
||||
msgid "Region Name"
|
||||
msgstr "Region Name"
|
||||
|
||||
msgid "Remove Tokens"
|
||||
msgstr ""
|
||||
msgid "Remove Tokens"
|
||||
msgstr "Remove Tokens"
|
||||
|
||||
msgid "Rooms"
|
||||
msgstr ""
|
||||
msgid "Rooms"
|
||||
msgstr "Rooms"
|
||||
|
||||
msgid "SMTP Server"
|
||||
msgstr ""
|
||||
msgid "SMTP Server"
|
||||
msgstr "SMTP Server"
|
||||
|
||||
msgid "Schema"
|
||||
msgstr ""
|
||||
msgid "Schema"
|
||||
msgstr "Schema"
|
||||
|
||||
msgid "Secret Access Key"
|
||||
msgstr ""
|
||||
msgid "Secret Access Key"
|
||||
msgstr "Secret Access Key"
|
||||
|
||||
msgid "Secret Key"
|
||||
msgstr ""
|
||||
msgid "Secret Key"
|
||||
msgstr "Secret Key"
|
||||
|
||||
msgid "Secure Mode"
|
||||
msgstr ""
|
||||
msgid "Secure Mode"
|
||||
msgstr "Secure Mode"
|
||||
|
||||
msgid "Server Timeout"
|
||||
msgstr ""
|
||||
msgid "Server Timeout"
|
||||
msgstr "Server Timeout"
|
||||
|
||||
msgid "Sound"
|
||||
msgstr ""
|
||||
msgid "Sound"
|
||||
msgstr "Sound"
|
||||
|
||||
msgid "Source JID"
|
||||
msgstr ""
|
||||
msgid "Source JID"
|
||||
msgstr "Source JID"
|
||||
|
||||
msgid "Target Channel"
|
||||
msgstr ""
|
||||
msgid "Target Channel"
|
||||
msgstr "Target Channel"
|
||||
|
||||
msgid "Target Chat ID"
|
||||
msgstr ""
|
||||
msgid "Target Chat ID"
|
||||
msgstr "Target Chat ID"
|
||||
|
||||
msgid "Target Device"
|
||||
msgstr ""
|
||||
msgid "Target Device"
|
||||
msgstr "Target Device"
|
||||
|
||||
msgid "Target Device ID"
|
||||
msgstr ""
|
||||
msgid "Target Device ID"
|
||||
msgstr "Target Device ID"
|
||||
|
||||
msgid "Target Email"
|
||||
msgstr ""
|
||||
msgid "Target Email"
|
||||
msgstr "Target Email"
|
||||
|
||||
msgid "Target Emails"
|
||||
msgstr ""
|
||||
msgid "Target Emails"
|
||||
msgstr "Target Emails"
|
||||
|
||||
msgid "Target Encoded ID"
|
||||
msgstr ""
|
||||
msgid "Target Encoded ID"
|
||||
msgstr "Target Encoded ID"
|
||||
|
||||
msgid "Target JID"
|
||||
msgstr ""
|
||||
msgid "Target JID"
|
||||
msgstr "Target JID"
|
||||
|
||||
msgid "Target Phone No"
|
||||
msgstr ""
|
||||
msgid "Target Phone No"
|
||||
msgstr "Target Phone No"
|
||||
|
||||
msgid "Target Room Alias"
|
||||
msgstr ""
|
||||
msgid "Target Room Alias"
|
||||
msgstr "Target Room Alias"
|
||||
|
||||
msgid "Target Room ID"
|
||||
msgstr ""
|
||||
msgid "Target Room ID"
|
||||
msgstr "Target Room ID"
|
||||
|
||||
msgid "Target Short Code"
|
||||
msgstr ""
|
||||
msgid "Target Short Code"
|
||||
msgstr "Target Short Code"
|
||||
|
||||
msgid "Target Tag ID"
|
||||
msgstr ""
|
||||
msgid "Target Tag ID"
|
||||
msgstr "Target Tag ID"
|
||||
|
||||
msgid "Target Topic"
|
||||
msgstr ""
|
||||
msgid "Target Topic"
|
||||
msgstr "Target Topic"
|
||||
|
||||
msgid "Target User"
|
||||
msgstr ""
|
||||
msgid "Target User"
|
||||
msgstr "Target User"
|
||||
|
||||
msgid "Targets"
|
||||
msgstr ""
|
||||
msgid "Targets"
|
||||
msgstr "Targets"
|
||||
|
||||
msgid "Text To Speech"
|
||||
msgstr ""
|
||||
msgid "Text To Speech"
|
||||
msgstr "Text To Speech"
|
||||
|
||||
msgid "To Channel ID"
|
||||
msgstr ""
|
||||
msgid "To Channel ID"
|
||||
msgstr "To Channel ID"
|
||||
|
||||
msgid "To Email"
|
||||
msgstr ""
|
||||
msgid "To Email"
|
||||
msgstr "To Email"
|
||||
|
||||
msgid "To User ID"
|
||||
msgstr ""
|
||||
msgid "To User ID"
|
||||
msgstr "To User ID"
|
||||
|
||||
msgid "Token"
|
||||
msgstr ""
|
||||
msgid "Token"
|
||||
msgstr "Token"
|
||||
|
||||
msgid "Token A"
|
||||
msgstr ""
|
||||
msgid "Token A"
|
||||
msgstr "Token A"
|
||||
|
||||
msgid "Token B"
|
||||
msgstr ""
|
||||
msgid "Token B"
|
||||
msgstr "Token B"
|
||||
|
||||
msgid "Token C"
|
||||
msgstr ""
|
||||
msgid "Token C"
|
||||
msgstr "Token C"
|
||||
|
||||
msgid "Urgency"
|
||||
msgstr ""
|
||||
msgid "Urgency"
|
||||
msgstr "Urgency"
|
||||
|
||||
msgid "Use Avatar"
|
||||
msgstr ""
|
||||
msgid "Use Avatar"
|
||||
msgstr "Use Avatar"
|
||||
|
||||
msgid "User"
|
||||
msgstr ""
|
||||
msgid "User"
|
||||
msgstr "User"
|
||||
|
||||
msgid "User Key"
|
||||
msgstr ""
|
||||
msgid "User Key"
|
||||
msgstr "User Key"
|
||||
|
||||
msgid "User Name"
|
||||
msgstr ""
|
||||
msgid "User Name"
|
||||
msgstr "User Name"
|
||||
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
msgid "Username"
|
||||
msgstr "Username"
|
||||
|
||||
msgid "Verify SSL"
|
||||
msgstr ""
|
||||
msgid "Verify SSL"
|
||||
msgstr "Verify SSL"
|
||||
|
||||
msgid "Version"
|
||||
msgstr ""
|
||||
msgid "Version"
|
||||
msgstr "Version"
|
||||
|
||||
msgid "Webhook"
|
||||
msgstr ""
|
||||
msgid "Webhook"
|
||||
msgstr "Webhook"
|
||||
|
||||
msgid "Webhook ID"
|
||||
msgstr ""
|
||||
msgid "Webhook ID"
|
||||
msgstr "Webhook ID"
|
||||
|
||||
msgid "Webhook Mode"
|
||||
msgstr ""
|
||||
msgid "Webhook Mode"
|
||||
msgstr "Webhook Mode"
|
||||
|
||||
msgid "Webhook Token"
|
||||
msgstr ""
|
||||
msgid "Webhook Token"
|
||||
msgstr "Webhook Token"
|
||||
|
||||
msgid "X-Axis"
|
||||
msgstr ""
|
||||
msgid "X-Axis"
|
||||
msgstr "X-Axis"
|
||||
|
||||
msgid "XEP"
|
||||
msgstr ""
|
||||
|
||||
msgid "Y-Axis"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Access Key Secret"
|
||||
#~ msgstr ""
|
||||
msgid "XEP"
|
||||
msgstr "XEP"
|
||||
|
||||
msgid "Y-Axis"
|
||||
msgstr "Y-Axis"
|
||||
|
@ -1,7 +1,7 @@
|
||||
diff -Naur apprise-1.0.0/test/test_cli.py apprise-1.0.0.patched/test/test_cli.py
|
||||
--- apprise-1.0.0/test/test_cli.py 2022-07-15 14:52:13.000000000 -0400
|
||||
+++ apprise-1.0.0.patched/test/test_cli.py 2022-08-06 13:32:50.796935607 -0400
|
||||
@@ -1022,9 +1022,6 @@
|
||||
diff -Naur apprise-1.4.5/test/test_apprise_cli.py apprise-1.4.5-patched/test/test_apprise_cli.py
|
||||
--- apprise-1.4.5/test/test_apprise_cli.py 2023-08-20 11:26:43.000000000 -0400
|
||||
+++ apprise-1.4.5-patched/test/test_apprise_cli.py 2023-08-20 16:37:42.922342103 -0400
|
||||
@@ -1027,9 +1027,6 @@
|
||||
# Absolute path to __init__.py is okay
|
||||
assert result.exit_code == 0
|
||||
|
||||
@ -11,7 +11,7 @@ diff -Naur apprise-1.0.0/test/test_cli.py apprise-1.0.0.patched/test/test_cli.py
|
||||
# Clear our working variables so they don't obstruct the next test
|
||||
# This simulates an actual call from the CLI. Unfortunately through
|
||||
# testing were occupying the same memory space so our singleton's
|
||||
@@ -1044,9 +1041,6 @@
|
||||
@@ -1049,9 +1046,6 @@
|
||||
# an __init__.py is found on the inside of it
|
||||
assert result.exit_code == 0
|
||||
|
||||
@ -21,7 +21,7 @@ diff -Naur apprise-1.0.0/test/test_cli.py apprise-1.0.0.patched/test/test_cli.py
|
||||
# Test double paths that are the same; this ensures we only
|
||||
# load the plugin once
|
||||
result = runner.invoke(cli.main, [
|
||||
@@ -1179,15 +1173,6 @@
|
||||
@@ -1183,15 +1177,6 @@
|
||||
# Print our custom details to the screen
|
||||
'--details',
|
||||
])
|
||||
|
@ -3,11 +3,11 @@ universal = 0
|
||||
|
||||
[metadata]
|
||||
# ensure LICENSE is included in wheel metadata
|
||||
license_file = LICENSE
|
||||
license_files = LICENSE
|
||||
|
||||
[flake8]
|
||||
# We exclude packages we don't maintain
|
||||
exclude = .eggs,.tox
|
||||
exclude = .eggs,.tox,.local
|
||||
ignore = E741,E722,W503,W504,W605
|
||||
statistics = true
|
||||
builtins = _
|
||||
|
4
setup.py
4
setup.py
@ -90,7 +90,7 @@ setup(
|
||||
],
|
||||
},
|
||||
install_requires=install_requires,
|
||||
classifiers=(
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: System Administrators',
|
||||
@ -109,7 +109,7 @@ setup(
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
'Topic :: Software Development :: Libraries :: Application Frameworks',
|
||||
),
|
||||
],
|
||||
entry_points={'console_scripts': console_scripts},
|
||||
python_requires='>=3.6',
|
||||
setup_requires=['babel', ],
|
||||
|
@ -53,6 +53,7 @@ from apprise import __version__
|
||||
from apprise import URLBase
|
||||
from apprise import PrivacyMode
|
||||
from apprise.AppriseLocale import LazyTranslation
|
||||
from apprise.AppriseLocale import gettext_lazy as _
|
||||
|
||||
from apprise import common
|
||||
from apprise.plugins import __load_matrix
|
||||
@ -1379,7 +1380,8 @@ def test_apprise_details():
|
||||
assert 'details' in entry['requirements']
|
||||
assert 'packages_required' in entry['requirements']
|
||||
assert 'packages_recommended' in entry['requirements']
|
||||
assert isinstance(entry['requirements']['details'], str)
|
||||
assert isinstance(entry['requirements']['details'], (
|
||||
str, LazyTranslation))
|
||||
assert isinstance(entry['requirements']['packages_required'], list)
|
||||
assert isinstance(entry['requirements']['packages_recommended'], list)
|
||||
|
||||
@ -1406,7 +1408,8 @@ def test_apprise_details():
|
||||
assert 'details' in entry['requirements']
|
||||
assert 'packages_required' in entry['requirements']
|
||||
assert 'packages_recommended' in entry['requirements']
|
||||
assert isinstance(entry['requirements']['details'], str)
|
||||
assert isinstance(entry['requirements']['details'], (
|
||||
str, LazyTranslation))
|
||||
assert isinstance(entry['requirements']['packages_required'], list)
|
||||
assert isinstance(entry['requirements']['packages_recommended'], list)
|
||||
|
||||
|
@ -49,6 +49,8 @@ from apprise.utils import environ
|
||||
from apprise.plugins import __load_matrix
|
||||
from apprise.plugins import __reset_matrix
|
||||
|
||||
from apprise.AppriseLocale import gettext_lazy as _
|
||||
|
||||
from importlib import reload
|
||||
|
||||
|
398
test/test_apprise_translations.py
Normal file
398
test/test_apprise_translations.py
Normal file
@ -0,0 +1,398 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# BSD 3-Clause License
|
||||
#
|
||||
# Apprise - Push Notification Library.
|
||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# 3. Neither the name of the copyright holder nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import os
|
||||
import sys
|
||||
from unittest import mock
|
||||
|
||||
import ctypes
|
||||
import pytest
|
||||
|
||||
from apprise import AppriseLocale
|
||||
from apprise.utils import environ
|
||||
from importlib import reload
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
|
||||
def test_apprise_trans():
|
||||
"""
|
||||
API: Test apprise locale object
|
||||
"""
|
||||
lazytrans = AppriseLocale.LazyTranslation('Token')
|
||||
assert str(lazytrans) == 'Token'
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
'gettext' not in sys.modules, reason="Requires gettext")
|
||||
def test_apprise_trans_gettext_init():
|
||||
"""
|
||||
API: Handle gettext
|
||||
"""
|
||||
# Toggle
|
||||
AppriseLocale.GETTEXT_LOADED = False
|
||||
|
||||
# Objects can still be created
|
||||
al = AppriseLocale.AppriseLocale()
|
||||
|
||||
with al.lang_at('en') as _:
|
||||
# functions still behave as normal
|
||||
assert _ is None
|
||||
|
||||
# Restore the object
|
||||
AppriseLocale.GETTEXT_LOADED = True
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
'gettext' not in sys.modules, reason="Requires gettext")
|
||||
@mock.patch('gettext.translation')
|
||||
@mock.patch('locale.getlocale')
|
||||
def test_apprise_trans_gettext_translations(
|
||||
mock_getlocale, mock_gettext_trans):
|
||||
"""
|
||||
API: Apprise() Gettext translations
|
||||
|
||||
"""
|
||||
|
||||
# Set- our gettext.locale() return value
|
||||
mock_getlocale.return_value = ('en_US', 'UTF-8')
|
||||
|
||||
mock_gettext_trans.side_effect = FileNotFoundError()
|
||||
|
||||
# This throws internally but we handle it gracefully
|
||||
al = AppriseLocale.AppriseLocale()
|
||||
|
||||
with al.lang_at('en'):
|
||||
# functions still behave as normal
|
||||
pass
|
||||
|
||||
# This throws internally but we handle it gracefully
|
||||
AppriseLocale.AppriseLocale(language="fr")
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
hasattr(ctypes, 'windll'), reason="Unique Nux test cases")
|
||||
@pytest.mark.skipif(
|
||||
'gettext' not in sys.modules, reason="Requires gettext")
|
||||
@mock.patch('locale.getlocale')
|
||||
def test_apprise_trans_gettext_lang_at(mock_getlocale):
|
||||
"""
|
||||
API: Apprise() Gettext lang_at
|
||||
|
||||
"""
|
||||
|
||||
# Set- our gettext.locale() return value
|
||||
mock_getlocale.return_value = ('en_CA', 'UTF-8')
|
||||
|
||||
# This throws internally but we handle it gracefully
|
||||
al = AppriseLocale.AppriseLocale()
|
||||
|
||||
# Edge Cases
|
||||
assert al.add('en', set_default=False) is True
|
||||
assert al.add('en', set_default=True) is True
|
||||
|
||||
with al.lang_at('en'):
|
||||
# functions still behave as normal
|
||||
pass
|
||||
|
||||
# This throws internally but we handle it gracefully
|
||||
AppriseLocale.AppriseLocale(language="fr")
|
||||
|
||||
with al.lang_at('en') as _:
|
||||
# functions still behave as normal
|
||||
assert callable(_)
|
||||
|
||||
with al.lang_at('es') as _:
|
||||
# functions still behave as normal
|
||||
assert callable(_)
|
||||
|
||||
with al.lang_at('fr') as _:
|
||||
# functions still behave as normal
|
||||
assert callable(_)
|
||||
|
||||
# Test our initialization when our fallback is a language we do
|
||||
# not have. This is only done to test edge cases when for whatever
|
||||
# reason the person who set up apprise does not have the languages
|
||||
# installed.
|
||||
fallback = AppriseLocale.AppriseLocale._default_language
|
||||
mock_getlocale.return_value = None
|
||||
|
||||
with environ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG'):
|
||||
# Our default language
|
||||
AppriseLocale.AppriseLocale._default_language = 'zz'
|
||||
|
||||
# We will detect the zz since there were no environment variables to
|
||||
# help us otherwise
|
||||
assert AppriseLocale.AppriseLocale.detect_language() is None
|
||||
al = AppriseLocale.AppriseLocale()
|
||||
|
||||
# No Language could be set becuause no locale directory exists for this
|
||||
assert al.lang is None
|
||||
|
||||
with al.lang_at(None) as _:
|
||||
# functions still behave as normal
|
||||
assert callable(_)
|
||||
|
||||
with al.lang_at('en') as _:
|
||||
# functions still behave as normal
|
||||
assert callable(_)
|
||||
|
||||
with al.lang_at('es') as _:
|
||||
# functions still behave as normal
|
||||
assert callable(_)
|
||||
|
||||
with al.lang_at('fr') as _:
|
||||
# functions still behave as normal
|
||||
assert callable(_)
|
||||
|
||||
# We can still perform simple lookups; they access a dummy wrapper:
|
||||
assert al.gettext('test') == 'test'
|
||||
|
||||
with environ('LANGUAGE', 'LC_CTYPE', LC_ALL='C.UTF-8', LANG="en_CA"):
|
||||
# the UTF-8 entry is skipped over
|
||||
AppriseLocale.AppriseLocale._default_language = 'fr'
|
||||
|
||||
# We will detect the english language (found in the LANG= environment
|
||||
# variable which over-rides the _default
|
||||
assert AppriseLocale.AppriseLocale.detect_language() == "en"
|
||||
al = AppriseLocale.AppriseLocale()
|
||||
assert al.lang == "en"
|
||||
assert al.gettext('test') == 'test'
|
||||
|
||||
# Test case with set_default set to False (so we're still set to 'fr')
|
||||
assert al.add('zy', set_default=False) is False
|
||||
assert al.gettext('test') == 'test'
|
||||
|
||||
al.add('ab', set_default=True)
|
||||
assert al.gettext('test') == 'test'
|
||||
|
||||
assert al.add('zy', set_default=False) is False
|
||||
AppriseLocale.AppriseLocale._default_language = fallback
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
'gettext' not in sys.modules, reason="Requires gettext")
|
||||
def test_apprise_trans_add():
|
||||
"""
|
||||
API: Apprise() Gettext add
|
||||
|
||||
"""
|
||||
|
||||
# This throws internally but we handle it gracefully
|
||||
al = AppriseLocale.AppriseLocale()
|
||||
|
||||
assert al.add('en') is True
|
||||
|
||||
# Double add (copy of above) to access logic that prevents adding it again
|
||||
assert al.add('en') is True
|
||||
|
||||
# Invalid Language
|
||||
assert al.add('bad') is False
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not hasattr(ctypes, 'windll'), reason="Unique Windows test cases")
|
||||
@pytest.mark.skipif(
|
||||
'gettext' not in sys.modules, reason="Requires gettext")
|
||||
@mock.patch('locale.getlocale')
|
||||
def test_apprise_trans_windows_users_win(mock_getlocale):
|
||||
"""
|
||||
API: Apprise() Windows Locale Testing (Win version)
|
||||
|
||||
"""
|
||||
|
||||
# Set- our gettext.locale() return value
|
||||
mock_getlocale.return_value = ('fr_CA', 'UTF-8')
|
||||
|
||||
with mock.patch(
|
||||
'ctypes.windll.kernel32.GetUserDefaultUILanguage') as ui_lang:
|
||||
|
||||
# 4105 = en_CA
|
||||
ui_lang.return_value = 4105
|
||||
|
||||
with environ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG'):
|
||||
# Our default language
|
||||
AppriseLocale.AppriseLocale._default_language = 'zz'
|
||||
|
||||
# We will pick up the windll module and detect english
|
||||
assert AppriseLocale.AppriseLocale.detect_language() == 'en'
|
||||
|
||||
# The below accesses the windows fallback code
|
||||
with environ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', LANG="es_AR"):
|
||||
# Environment Variable Trumps
|
||||
assert AppriseLocale.AppriseLocale.detect_language() == 'es'
|
||||
|
||||
# No environment variable, then the Windows environment is used
|
||||
with environ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG'):
|
||||
# Windows Environment
|
||||
assert AppriseLocale.AppriseLocale.detect_language() == 'en'
|
||||
|
||||
assert AppriseLocale.AppriseLocale\
|
||||
.detect_language(detect_fallback=False) is None
|
||||
|
||||
# 0 = IndexError
|
||||
ui_lang.return_value = 0
|
||||
with environ('LANGUAGE', 'LANG', 'LC_ALL', 'LC_CTYPE'):
|
||||
# We fall back to posix locale
|
||||
assert AppriseLocale.AppriseLocale.detect_language() == 'fr'
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
hasattr(ctypes, 'windll'), reason="Unique Nux test cases")
|
||||
@pytest.mark.skipif(
|
||||
'gettext' not in sys.modules, reason="Requires gettext")
|
||||
@mock.patch('locale.getlocale')
|
||||
def test_apprise_trans_windows_users_nux(mock_getlocale):
|
||||
"""
|
||||
API: Apprise() Windows Locale Testing (Nux version)
|
||||
|
||||
"""
|
||||
|
||||
# Set- our gettext.locale() return value
|
||||
mock_getlocale.return_value = ('fr_CA', 'UTF-8')
|
||||
|
||||
# Emulate a windows environment
|
||||
windll = mock.Mock()
|
||||
setattr(ctypes, 'windll', windll)
|
||||
|
||||
# 4105 = en_CA
|
||||
windll.kernel32.GetUserDefaultUILanguage.return_value = 4105
|
||||
|
||||
with environ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG'):
|
||||
# Our default language
|
||||
AppriseLocale.AppriseLocale._default_language = 'zz'
|
||||
|
||||
# We will pick up the windll module and detect english
|
||||
assert AppriseLocale.AppriseLocale.detect_language() == 'en'
|
||||
|
||||
# The below accesses the windows fallback code
|
||||
with environ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', LANG="es_AR"):
|
||||
# Environment Variable Trumps
|
||||
assert AppriseLocale.AppriseLocale.detect_language() == 'es'
|
||||
|
||||
# No environment variable, then the Windows environment is used
|
||||
with environ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG'):
|
||||
# Windows Environment
|
||||
assert AppriseLocale.AppriseLocale.detect_language() == 'en'
|
||||
|
||||
assert AppriseLocale.AppriseLocale\
|
||||
.detect_language(detect_fallback=False) is None
|
||||
|
||||
# 0 = IndexError
|
||||
windll.kernel32.GetUserDefaultUILanguage.return_value = 0
|
||||
with environ('LANGUAGE', 'LANG', 'LC_ALL', 'LC_CTYPE'):
|
||||
# We fall back to posix locale
|
||||
assert AppriseLocale.AppriseLocale.detect_language() == 'fr'
|
||||
|
||||
delattr(ctypes, 'windll')
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform == "win32", reason="Unique Nux test cases")
|
||||
@mock.patch('locale.getlocale')
|
||||
def test_detect_language_using_env(mock_getlocale):
|
||||
"""
|
||||
Test the reading of information from an environment variable
|
||||
"""
|
||||
|
||||
# Set- our gettext.locale() return value
|
||||
mock_getlocale.return_value = ('en_CA', 'UTF-8')
|
||||
|
||||
# The below accesses the windows fallback code and fail
|
||||
# then it will resort to the environment variables.
|
||||
with environ('LANG', 'LANGUAGE', 'LC_ALL', 'LC_CTYPE'):
|
||||
# Language can now be detected in this case
|
||||
assert isinstance(
|
||||
AppriseLocale.AppriseLocale.detect_language(), str)
|
||||
|
||||
# Detect French language.
|
||||
with environ('LANGUAGE', 'LC_ALL', LC_CTYPE="garbage", LANG="fr_CA"):
|
||||
assert AppriseLocale.AppriseLocale.detect_language() == 'fr'
|
||||
|
||||
# The following unsets all environment variables and sets LC_CTYPE
|
||||
# This was causing Python 2.7 to internally parse UTF-8 as an invalid
|
||||
# locale and throw an uncaught ValueError; Python v2 support has been
|
||||
# dropped, but just to ensure this issue does not come back, we keep
|
||||
# this test:
|
||||
with environ(*list(os.environ.keys()), LC_CTYPE="UTF-8"):
|
||||
assert isinstance(AppriseLocale.AppriseLocale.detect_language(), str)
|
||||
|
||||
# Test with absolutely no environment variables what-so-ever
|
||||
with environ(*list(os.environ.keys())):
|
||||
assert isinstance(AppriseLocale.AppriseLocale.detect_language(), str)
|
||||
|
||||
# Handle case where getlocale() can't be detected
|
||||
mock_getlocale.return_value = None
|
||||
with environ('LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE'):
|
||||
assert AppriseLocale.AppriseLocale.detect_language() is None
|
||||
|
||||
mock_getlocale.return_value = (None, None)
|
||||
with environ('LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE'):
|
||||
assert AppriseLocale.AppriseLocale.detect_language() is None
|
||||
|
||||
# if detect_language and windows env fail us, then we don't
|
||||
# set up a default language on first load
|
||||
AppriseLocale.AppriseLocale()
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
'gettext' not in sys.modules, reason="Requires gettext")
|
||||
def test_apprise_trans_gettext_missing(tmpdir):
|
||||
"""
|
||||
Verify we can still operate without the gettext library
|
||||
"""
|
||||
|
||||
# remove gettext from our system enviroment
|
||||
del sys.modules["gettext"]
|
||||
|
||||
# Make our new path to a fake gettext (used to over-ride real one)
|
||||
# have it fail right out of the gate
|
||||
gettext_dir = tmpdir.mkdir("gettext")
|
||||
gettext_dir.join("__init__.py").write("")
|
||||
gettext_dir.join("gettext.py").write("""raise ImportError()""")
|
||||
|
||||
# Update our path to point path to head
|
||||
sys.path.insert(0, str(gettext_dir))
|
||||
|
||||
# reload our module (forcing the import error when it tries to load gettext
|
||||
reload(sys.modules['apprise.AppriseLocale'])
|
||||
from apprise import AppriseLocale
|
||||
assert AppriseLocale.GETTEXT_LOADED is False
|
||||
|
||||
# Now roll our changes back
|
||||
sys.path.pop(0)
|
||||
|
||||
# Reload again (reverting back)
|
||||
reload(sys.modules['apprise.AppriseLocale'])
|
||||
from apprise import AppriseLocale
|
||||
assert AppriseLocale.GETTEXT_LOADED is True
|
@ -1,215 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# BSD 3-Clause License
|
||||
#
|
||||
# Apprise - Push Notification Library.
|
||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# 3. Neither the name of the copyright holder nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import os
|
||||
import sys
|
||||
from unittest import mock
|
||||
|
||||
import ctypes
|
||||
import pytest
|
||||
|
||||
from apprise import AppriseLocale
|
||||
from apprise.utils import environ
|
||||
from importlib import reload
|
||||
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
|
||||
@mock.patch('gettext.install')
|
||||
def test_apprise_locale(mock_gettext_install):
|
||||
"""
|
||||
API: Test apprise locale object
|
||||
"""
|
||||
lazytrans = AppriseLocale.LazyTranslation('Token')
|
||||
assert str(lazytrans) == 'Token'
|
||||
|
||||
|
||||
@mock.patch('gettext.install')
|
||||
def test_gettext_init(mock_gettext_install):
|
||||
"""
|
||||
API: Mock Gettext init
|
||||
"""
|
||||
mock_gettext_install.side_effect = ImportError()
|
||||
# Test our fall back to not supporting translations
|
||||
reload(AppriseLocale)
|
||||
|
||||
# Objects can still be created
|
||||
al = AppriseLocale.AppriseLocale()
|
||||
|
||||
with al.lang_at('en'):
|
||||
# functions still behave as normal
|
||||
pass
|
||||
|
||||
# restore the object
|
||||
mock_gettext_install.side_effect = None
|
||||
reload(AppriseLocale)
|
||||
|
||||
|
||||
@mock.patch('gettext.translation')
|
||||
def test_gettext_translations(mock_gettext_trans):
|
||||
"""
|
||||
API: Apprise() Gettext translations
|
||||
|
||||
"""
|
||||
|
||||
mock_gettext_trans.side_effect = IOError()
|
||||
|
||||
# This throws internally but we handle it gracefully
|
||||
al = AppriseLocale.AppriseLocale()
|
||||
|
||||
with al.lang_at('en'):
|
||||
# functions still behave as normal
|
||||
pass
|
||||
|
||||
# This throws internally but we handle it gracefully
|
||||
AppriseLocale.AppriseLocale(language="fr")
|
||||
|
||||
|
||||
@mock.patch('gettext.translation')
|
||||
def test_gettext_installs(mock_gettext_trans):
|
||||
"""
|
||||
API: Apprise() Gettext install
|
||||
|
||||
"""
|
||||
|
||||
mock_lang = mock.Mock()
|
||||
mock_lang.install.return_value = True
|
||||
mock_gettext_trans.return_value = mock_lang
|
||||
|
||||
# This throws internally but we handle it gracefully
|
||||
al = AppriseLocale.AppriseLocale()
|
||||
|
||||
with al.lang_at('en'):
|
||||
# functions still behave as normal
|
||||
pass
|
||||
|
||||
# This throws internally but we handle it gracefully
|
||||
AppriseLocale.AppriseLocale(language="fr")
|
||||
|
||||
# Force a few different languages
|
||||
al._gtobjs['en'] = mock_lang
|
||||
al._gtobjs['es'] = mock_lang
|
||||
al.lang = 'en'
|
||||
|
||||
with al.lang_at('en'):
|
||||
# functions still behave as normal
|
||||
pass
|
||||
|
||||
with al.lang_at('es'):
|
||||
# functions still behave as normal
|
||||
pass
|
||||
|
||||
with al.lang_at('fr'):
|
||||
# functions still behave as normal
|
||||
pass
|
||||
|
||||
|
||||
def test_detect_language_windows_users():
|
||||
"""
|
||||
API: Apprise() Detect language
|
||||
|
||||
"""
|
||||
|
||||
if hasattr(ctypes, 'windll'):
|
||||
from ctypes import windll
|
||||
|
||||
else:
|
||||
windll = mock.Mock()
|
||||
# 4105 = en_CA
|
||||
windll.kernel32.GetUserDefaultUILanguage.return_value = 4105
|
||||
setattr(ctypes, 'windll', windll)
|
||||
|
||||
# The below accesses the windows fallback code
|
||||
with environ('LANG', 'LANGUAGE', 'LC_ALL', 'LC_CTYPE', LANG="en_CA"):
|
||||
assert AppriseLocale.AppriseLocale.detect_language() == 'en'
|
||||
|
||||
assert AppriseLocale.AppriseLocale\
|
||||
.detect_language(detect_fallback=False) is None
|
||||
|
||||
# 0 = IndexError
|
||||
windll.kernel32.GetUserDefaultUILanguage.return_value = 0
|
||||
setattr(ctypes, 'windll', windll)
|
||||
with environ('LANG', 'LC_ALL', 'LC_CTYPE', LANGUAGE="en_CA"):
|
||||
assert AppriseLocale.AppriseLocale.detect_language() == 'en'
|
||||
|
||||
|
||||
def test_detect_language_using_env():
|
||||
"""
|
||||
Test the reading of information from an environment variable
|
||||
"""
|
||||
|
||||
# The below accesses the windows fallback code and fail
|
||||
# then it will resort to the environment variables.
|
||||
with environ('LANG', 'LANGUAGE', 'LC_ALL', 'LC_CTYPE'):
|
||||
# Language can now be detected in this case
|
||||
assert isinstance(
|
||||
AppriseLocale.AppriseLocale.detect_language(), str)
|
||||
|
||||
# Detect French language.
|
||||
with environ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', LANG="fr_CA"):
|
||||
assert AppriseLocale.AppriseLocale.detect_language() == 'fr'
|
||||
|
||||
# The following unsets all environment variables and sets LC_CTYPE
|
||||
# This was causing Python 2.7 to internally parse UTF-8 as an invalid
|
||||
# locale and throw an uncaught ValueError; Python v2 support has been
|
||||
# dropped, but just to ensure this issue does not come back, we keep
|
||||
# this test:
|
||||
with environ(*list(os.environ.keys()), LC_CTYPE="UTF-8"):
|
||||
assert isinstance(AppriseLocale.AppriseLocale.detect_language(), str)
|
||||
|
||||
# Test with absolutely no environment variables what-so-ever
|
||||
with environ(*list(os.environ.keys())):
|
||||
assert isinstance(AppriseLocale.AppriseLocale.detect_language(), str)
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform == "win32", reason="Does not work on Windows")
|
||||
@mock.patch('locale.getlocale')
|
||||
def test_detect_language_locale(mock_getlocale):
|
||||
"""
|
||||
API: Apprise() Default locale detection
|
||||
|
||||
"""
|
||||
# Handle case where getlocale() can't be detected
|
||||
mock_getlocale.return_value = None
|
||||
with environ('LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE'):
|
||||
assert AppriseLocale.AppriseLocale.detect_language() is None
|
||||
|
||||
mock_getlocale.return_value = (None, None)
|
||||
with environ('LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE'):
|
||||
assert AppriseLocale.AppriseLocale.detect_language() is None
|
||||
|
||||
# if detect_language and windows env fail us, then we don't
|
||||
# set up a default language on first load
|
||||
AppriseLocale.AppriseLocale()
|
Loading…
Reference in New Issue
Block a user