mirror of
https://github.com/caronc/apprise.git
synced 2024-11-25 17:44:00 +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/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
|
.local
|
||||||
|
|
||||||
# Generated from Docker Instance
|
# Generated from Docker Instance
|
||||||
.bash_history
|
.bash_history
|
||||||
|
@ -40,9 +40,6 @@ from os.path import dirname
|
|||||||
from os.path import abspath
|
from os.path import abspath
|
||||||
from .logger import logger
|
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
|
# This gets toggled to True if we succeed
|
||||||
GETTEXT_LOADED = False
|
GETTEXT_LOADED = False
|
||||||
@ -51,43 +48,13 @@ try:
|
|||||||
# Initialize gettext
|
# Initialize gettext
|
||||||
import gettext
|
import gettext
|
||||||
|
|
||||||
# install() creates a _() in our builtins
|
|
||||||
gettext.install(DOMAIN, localedir=LOCALE_DIR)
|
|
||||||
|
|
||||||
# Toggle our flag
|
# Toggle our flag
|
||||||
GETTEXT_LOADED = True
|
GETTEXT_LOADED = True
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# gettext isn't available; no problem, just fall back to using
|
# gettext isn't available; no problem; Use the library features without
|
||||||
# the library features without multi-language support.
|
# multi-language support.
|
||||||
import builtins
|
pass
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
class AppriseLocale:
|
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
|
# Locale regular expression
|
||||||
_local_re = re.compile(
|
_local_re = re.compile(
|
||||||
r'^\s*(?P<lang>[a-z]{2})([_:]((?P<country>[a-z]{2}))?'
|
r'^((?P<ansii>C)|(?P<lang>([a-z]{2}))([_:](?P<country>[a-z]{2}))?)'
|
||||||
r'(\.(?P<enc>[a-z0-9]+))?|.+)?', re.IGNORECASE)
|
r'(\.(?P<enc>[a-z0-9-]+))?$', re.IGNORECASE)
|
||||||
|
|
||||||
# Define our default encoding
|
# Define our default encoding
|
||||||
_default_encoding = 'utf-8'
|
_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'
|
_default_language = 'en'
|
||||||
|
|
||||||
def __init__(self, language=None):
|
def __init__(self, language=None):
|
||||||
@ -123,25 +99,55 @@ class AppriseLocale:
|
|||||||
# Get our language
|
# Get our language
|
||||||
self.lang = AppriseLocale.detect_language(language)
|
self.lang = AppriseLocale.detect_language(language)
|
||||||
|
|
||||||
|
# Our mapping to our _fn
|
||||||
|
self.__fn_map = None
|
||||||
|
|
||||||
if GETTEXT_LOADED is False:
|
if GETTEXT_LOADED is False:
|
||||||
# We're done
|
# We're done
|
||||||
return
|
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
|
# Load our gettext object and install our language
|
||||||
try:
|
try:
|
||||||
self._gtobjs[self.lang] = gettext.translation(
|
self._gtobjs[lang] = gettext.translation(
|
||||||
DOMAIN, localedir=LOCALE_DIR, languages=[self.lang])
|
self._domain, localedir=self._locale_dir, languages=[lang],
|
||||||
|
fallback=False)
|
||||||
|
|
||||||
# Install our language
|
# The non-intrusive method of applying the gettext change to
|
||||||
self._gtobjs[self.lang].install()
|
# the global namespace only
|
||||||
|
self.__fn_map = getattr(self._gtobjs[lang], self._fn)
|
||||||
|
|
||||||
except IOError:
|
except FileNotFoundError:
|
||||||
# This occurs if we can't access/load our translations
|
# The translation directory does not exist
|
||||||
pass
|
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
|
@contextlib.contextmanager
|
||||||
def lang_at(self, lang):
|
def lang_at(self, lang, mapto=_fn):
|
||||||
"""
|
"""
|
||||||
The syntax works as:
|
The syntax works as:
|
||||||
with at.lang_at('fr'):
|
with at.lang_at('fr'):
|
||||||
@ -151,46 +157,32 @@ class AppriseLocale:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if GETTEXT_LOADED is False:
|
if GETTEXT_LOADED is False:
|
||||||
# yield
|
# Do nothing
|
||||||
yield
|
yield None
|
||||||
|
|
||||||
# we're done
|
# we're done
|
||||||
return
|
return
|
||||||
|
|
||||||
# Tidy the language
|
# Tidy the language
|
||||||
lang = AppriseLocale.detect_language(lang, detect_fallback=False)
|
lang = AppriseLocale.detect_language(lang, detect_fallback=False)
|
||||||
|
if lang not in self._gtobjs and not self.add(lang, set_default=False):
|
||||||
# Now attempt to load it
|
# Do Nothing
|
||||||
try:
|
yield getattr(self._gtobjs[self.lang], mapto)
|
||||||
if lang in self._gtobjs:
|
else:
|
||||||
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
|
||||||
yield
|
yield getattr(self._gtobjs[lang], mapto)
|
||||||
|
|
||||||
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
|
return
|
||||||
|
|
||||||
|
@property
|
||||||
|
def gettext(self):
|
||||||
|
"""
|
||||||
|
Return the current language gettext() function
|
||||||
|
|
||||||
|
Useful for assigning to `_`
|
||||||
|
"""
|
||||||
|
return self._gtobjs[self.lang].gettext
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def detect_language(lang=None, detect_fallback=True):
|
def detect_language(lang=None, detect_fallback=True):
|
||||||
"""
|
"""
|
||||||
@ -227,12 +219,12 @@ class AppriseLocale:
|
|||||||
# Fallback to posix detection
|
# Fallback to posix detection
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Linux Handling
|
# Built in locale library check
|
||||||
try:
|
try:
|
||||||
# Acquire our locale
|
# Acquire our locale
|
||||||
lang = locale.getlocale()[0]
|
lang = locale.getlocale()[0]
|
||||||
|
|
||||||
except TypeError as e:
|
except (ValueError, TypeError) as e:
|
||||||
# This occurs when an invalid locale was parsed from the
|
# This occurs when an invalid locale was parsed from the
|
||||||
# environment variable. While we still return None in this
|
# environment variable. While we still return None in this
|
||||||
# case, we want to better notify the end user of this. Users
|
# case, we want to better notify the end user of this. Users
|
||||||
@ -249,8 +241,10 @@ class AppriseLocale:
|
|||||||
Pickle Support dumps()
|
Pickle Support dumps()
|
||||||
"""
|
"""
|
||||||
state = self.__dict__.copy()
|
state = self.__dict__.copy()
|
||||||
|
|
||||||
# Remove the unpicklable entries.
|
# Remove the unpicklable entries.
|
||||||
del state['_gtobjs']
|
del state['_gtobjs']
|
||||||
|
del state['_AppriseLocale__fn_map']
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
@ -258,4 +252,39 @@ class AppriseLocale:
|
|||||||
Pickle Support loads()
|
Pickle Support loads()
|
||||||
"""
|
"""
|
||||||
self.__dict__.update(state)
|
self.__dict__.update(state)
|
||||||
|
# Our mapping to our _fn
|
||||||
|
self.__fn_map = None
|
||||||
self._gtobjs = {}
|
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.
|
# This file is distributed under the same license as the apprise project.
|
||||||
# Chris Caron <lead2gold@gmail.com>, 2019.
|
# Chris Caron <lead2gold@gmail.com>, 2019.
|
||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
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"
|
"Report-Msgid-Bugs-To: lead2gold@gmail.com\n"
|
||||||
"POT-Creation-Date: 2019-05-28 16:56-0400\n"
|
"POT-Creation-Date: 2019-05-28 16:56-0400\n"
|
||||||
"PO-Revision-Date: 2019-05-24 20:00-0400\n"
|
"PO-Revision-Date: 2019-05-24 20:00-0400\n"
|
||||||
@ -18,276 +19,272 @@ msgstr ""
|
|||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Generated-By: Babel 2.6.0\n"
|
"Generated-By: Babel 2.6.0\n"
|
||||||
|
|
||||||
msgid "API Key"
|
msgid "API Key"
|
||||||
msgstr ""
|
msgstr "API Key"
|
||||||
|
|
||||||
msgid "Access Key"
|
msgid "Access Key"
|
||||||
msgstr ""
|
msgstr "Access Key"
|
||||||
|
|
||||||
msgid "Access Key ID"
|
msgid "Access Key ID"
|
||||||
msgstr ""
|
msgstr "Access Key ID"
|
||||||
|
|
||||||
msgid "Access Secret"
|
msgid "Access Secret"
|
||||||
msgstr ""
|
msgstr "Access Secret"
|
||||||
|
|
||||||
msgid "Access Token"
|
msgid "Access Token"
|
||||||
msgstr ""
|
msgstr "Access Token"
|
||||||
|
|
||||||
msgid "Account SID"
|
msgid "Account SID"
|
||||||
msgstr ""
|
msgstr "Account SID"
|
||||||
|
|
||||||
msgid "Add Tokens"
|
msgid "Add Tokens"
|
||||||
msgstr ""
|
msgstr "Add Tokens"
|
||||||
|
|
||||||
msgid "Application Key"
|
msgid "Application Key"
|
||||||
msgstr ""
|
msgstr "Application Key"
|
||||||
|
|
||||||
msgid "Application Secret"
|
msgid "Application Secret"
|
||||||
msgstr ""
|
msgstr "Application Secret"
|
||||||
|
|
||||||
msgid "Auth Token"
|
msgid "Auth Token"
|
||||||
msgstr ""
|
msgstr "Auth Token"
|
||||||
|
|
||||||
msgid "Authorization Token"
|
msgid "Authorization Token"
|
||||||
msgstr ""
|
msgstr "Authorization Token"
|
||||||
|
|
||||||
msgid "Avatar Image"
|
msgid "Avatar Image"
|
||||||
msgstr ""
|
msgstr "Avatar Image"
|
||||||
|
|
||||||
msgid "Bot Name"
|
msgid "Bot Name"
|
||||||
msgstr ""
|
msgstr "Bot Name"
|
||||||
|
|
||||||
msgid "Bot Token"
|
msgid "Bot Token"
|
||||||
msgstr ""
|
msgstr "Bot Token"
|
||||||
|
|
||||||
msgid "Channels"
|
msgid "Channels"
|
||||||
msgstr ""
|
msgstr "Channels"
|
||||||
|
|
||||||
msgid "Consumer Key"
|
msgid "Consumer Key"
|
||||||
msgstr ""
|
msgstr "Consumer Key"
|
||||||
|
|
||||||
msgid "Consumer Secret"
|
msgid "Consumer Secret"
|
||||||
msgstr ""
|
msgstr "Consumer Secret"
|
||||||
|
|
||||||
msgid "Detect Bot Owner"
|
msgid "Detect Bot Owner"
|
||||||
msgstr ""
|
msgstr "Detect Bot Owner"
|
||||||
|
|
||||||
msgid "Device ID"
|
msgid "Device ID"
|
||||||
msgstr ""
|
msgstr "Device ID"
|
||||||
|
|
||||||
msgid "Display Footer"
|
msgid "Display Footer"
|
||||||
msgstr ""
|
msgstr "Display Footer"
|
||||||
|
|
||||||
msgid "Domain"
|
msgid "Domain"
|
||||||
msgstr ""
|
msgstr "Domain"
|
||||||
|
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr ""
|
msgstr "Duration"
|
||||||
|
|
||||||
msgid "Events"
|
msgid "Events"
|
||||||
msgstr ""
|
msgstr "Events"
|
||||||
|
|
||||||
msgid "Footer Logo"
|
msgid "Footer Logo"
|
||||||
msgstr ""
|
msgstr "Footer Logo"
|
||||||
|
|
||||||
msgid "From Email"
|
msgid "From Email"
|
||||||
msgstr ""
|
msgstr "From Email"
|
||||||
|
|
||||||
msgid "From Name"
|
msgid "From Name"
|
||||||
msgstr ""
|
msgstr "From Name"
|
||||||
|
|
||||||
msgid "From Phone No"
|
msgid "From Phone No"
|
||||||
msgstr ""
|
msgstr "From Phone No"
|
||||||
|
|
||||||
msgid "Group"
|
msgid "Group"
|
||||||
msgstr ""
|
msgstr "Group"
|
||||||
|
|
||||||
msgid "HTTP Header"
|
msgid "HTTP Header"
|
||||||
msgstr ""
|
msgstr "HTTP Header"
|
||||||
|
|
||||||
msgid "Hostname"
|
msgid "Hostname"
|
||||||
msgstr ""
|
msgstr "Hostname"
|
||||||
|
|
||||||
msgid "Include Image"
|
msgid "Include Image"
|
||||||
msgstr ""
|
msgstr "Include Image"
|
||||||
|
|
||||||
msgid "Modal"
|
msgid "Modal"
|
||||||
msgstr ""
|
msgstr "Modal"
|
||||||
|
|
||||||
msgid "Notify Format"
|
msgid "Notify Format"
|
||||||
msgstr ""
|
msgstr "Notify Format"
|
||||||
|
|
||||||
msgid "Organization"
|
msgid "Organization"
|
||||||
msgstr ""
|
msgstr "Organization"
|
||||||
|
|
||||||
msgid "Overflow Mode"
|
msgid "Overflow Mode"
|
||||||
msgstr ""
|
msgstr "Overflow Mode"
|
||||||
|
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr ""
|
msgstr "Password"
|
||||||
|
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr ""
|
msgstr "Port"
|
||||||
|
|
||||||
msgid "Priority"
|
msgid "Priority"
|
||||||
msgstr ""
|
msgstr "Priority"
|
||||||
|
|
||||||
msgid "Provider Key"
|
msgid "Provider Key"
|
||||||
msgstr ""
|
msgstr "Provider Key"
|
||||||
|
|
||||||
msgid "Region"
|
msgid "Region"
|
||||||
msgstr ""
|
msgstr "Region"
|
||||||
|
|
||||||
msgid "Region Name"
|
msgid "Region Name"
|
||||||
msgstr ""
|
msgstr "Region Name"
|
||||||
|
|
||||||
msgid "Remove Tokens"
|
msgid "Remove Tokens"
|
||||||
msgstr ""
|
msgstr "Remove Tokens"
|
||||||
|
|
||||||
msgid "Rooms"
|
msgid "Rooms"
|
||||||
msgstr ""
|
msgstr "Rooms"
|
||||||
|
|
||||||
msgid "SMTP Server"
|
msgid "SMTP Server"
|
||||||
msgstr ""
|
msgstr "SMTP Server"
|
||||||
|
|
||||||
msgid "Schema"
|
msgid "Schema"
|
||||||
msgstr ""
|
msgstr "Schema"
|
||||||
|
|
||||||
msgid "Secret Access Key"
|
msgid "Secret Access Key"
|
||||||
msgstr ""
|
msgstr "Secret Access Key"
|
||||||
|
|
||||||
msgid "Secret Key"
|
msgid "Secret Key"
|
||||||
msgstr ""
|
msgstr "Secret Key"
|
||||||
|
|
||||||
msgid "Secure Mode"
|
msgid "Secure Mode"
|
||||||
msgstr ""
|
msgstr "Secure Mode"
|
||||||
|
|
||||||
msgid "Server Timeout"
|
msgid "Server Timeout"
|
||||||
msgstr ""
|
msgstr "Server Timeout"
|
||||||
|
|
||||||
msgid "Sound"
|
msgid "Sound"
|
||||||
msgstr ""
|
msgstr "Sound"
|
||||||
|
|
||||||
msgid "Source JID"
|
msgid "Source JID"
|
||||||
msgstr ""
|
msgstr "Source JID"
|
||||||
|
|
||||||
msgid "Target Channel"
|
msgid "Target Channel"
|
||||||
msgstr ""
|
msgstr "Target Channel"
|
||||||
|
|
||||||
msgid "Target Chat ID"
|
msgid "Target Chat ID"
|
||||||
msgstr ""
|
msgstr "Target Chat ID"
|
||||||
|
|
||||||
msgid "Target Device"
|
msgid "Target Device"
|
||||||
msgstr ""
|
msgstr "Target Device"
|
||||||
|
|
||||||
msgid "Target Device ID"
|
msgid "Target Device ID"
|
||||||
msgstr ""
|
msgstr "Target Device ID"
|
||||||
|
|
||||||
msgid "Target Email"
|
msgid "Target Email"
|
||||||
msgstr ""
|
msgstr "Target Email"
|
||||||
|
|
||||||
msgid "Target Emails"
|
msgid "Target Emails"
|
||||||
msgstr ""
|
msgstr "Target Emails"
|
||||||
|
|
||||||
msgid "Target Encoded ID"
|
msgid "Target Encoded ID"
|
||||||
msgstr ""
|
msgstr "Target Encoded ID"
|
||||||
|
|
||||||
msgid "Target JID"
|
msgid "Target JID"
|
||||||
msgstr ""
|
msgstr "Target JID"
|
||||||
|
|
||||||
msgid "Target Phone No"
|
msgid "Target Phone No"
|
||||||
msgstr ""
|
msgstr "Target Phone No"
|
||||||
|
|
||||||
msgid "Target Room Alias"
|
msgid "Target Room Alias"
|
||||||
msgstr ""
|
msgstr "Target Room Alias"
|
||||||
|
|
||||||
msgid "Target Room ID"
|
msgid "Target Room ID"
|
||||||
msgstr ""
|
msgstr "Target Room ID"
|
||||||
|
|
||||||
msgid "Target Short Code"
|
msgid "Target Short Code"
|
||||||
msgstr ""
|
msgstr "Target Short Code"
|
||||||
|
|
||||||
msgid "Target Tag ID"
|
msgid "Target Tag ID"
|
||||||
msgstr ""
|
msgstr "Target Tag ID"
|
||||||
|
|
||||||
msgid "Target Topic"
|
msgid "Target Topic"
|
||||||
msgstr ""
|
msgstr "Target Topic"
|
||||||
|
|
||||||
msgid "Target User"
|
msgid "Target User"
|
||||||
msgstr ""
|
msgstr "Target User"
|
||||||
|
|
||||||
msgid "Targets"
|
msgid "Targets"
|
||||||
msgstr ""
|
msgstr "Targets"
|
||||||
|
|
||||||
msgid "Text To Speech"
|
msgid "Text To Speech"
|
||||||
msgstr ""
|
msgstr "Text To Speech"
|
||||||
|
|
||||||
msgid "To Channel ID"
|
msgid "To Channel ID"
|
||||||
msgstr ""
|
msgstr "To Channel ID"
|
||||||
|
|
||||||
msgid "To Email"
|
msgid "To Email"
|
||||||
msgstr ""
|
msgstr "To Email"
|
||||||
|
|
||||||
msgid "To User ID"
|
msgid "To User ID"
|
||||||
msgstr ""
|
msgstr "To User ID"
|
||||||
|
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr "Token"
|
||||||
|
|
||||||
msgid "Token A"
|
msgid "Token A"
|
||||||
msgstr ""
|
msgstr "Token A"
|
||||||
|
|
||||||
msgid "Token B"
|
msgid "Token B"
|
||||||
msgstr ""
|
msgstr "Token B"
|
||||||
|
|
||||||
msgid "Token C"
|
msgid "Token C"
|
||||||
msgstr ""
|
msgstr "Token C"
|
||||||
|
|
||||||
msgid "Urgency"
|
msgid "Urgency"
|
||||||
msgstr ""
|
msgstr "Urgency"
|
||||||
|
|
||||||
msgid "Use Avatar"
|
msgid "Use Avatar"
|
||||||
msgstr ""
|
msgstr "Use Avatar"
|
||||||
|
|
||||||
msgid "User"
|
msgid "User"
|
||||||
msgstr ""
|
msgstr "User"
|
||||||
|
|
||||||
msgid "User Key"
|
msgid "User Key"
|
||||||
msgstr ""
|
msgstr "User Key"
|
||||||
|
|
||||||
msgid "User Name"
|
msgid "User Name"
|
||||||
msgstr ""
|
msgstr "User Name"
|
||||||
|
|
||||||
msgid "Username"
|
msgid "Username"
|
||||||
msgstr ""
|
msgstr "Username"
|
||||||
|
|
||||||
msgid "Verify SSL"
|
msgid "Verify SSL"
|
||||||
msgstr ""
|
msgstr "Verify SSL"
|
||||||
|
|
||||||
msgid "Version"
|
msgid "Version"
|
||||||
msgstr ""
|
msgstr "Version"
|
||||||
|
|
||||||
msgid "Webhook"
|
msgid "Webhook"
|
||||||
msgstr ""
|
msgstr "Webhook"
|
||||||
|
|
||||||
msgid "Webhook ID"
|
msgid "Webhook ID"
|
||||||
msgstr ""
|
msgstr "Webhook ID"
|
||||||
|
|
||||||
msgid "Webhook Mode"
|
msgid "Webhook Mode"
|
||||||
msgstr ""
|
msgstr "Webhook Mode"
|
||||||
|
|
||||||
msgid "Webhook Token"
|
msgid "Webhook Token"
|
||||||
msgstr ""
|
msgstr "Webhook Token"
|
||||||
|
|
||||||
msgid "X-Axis"
|
msgid "X-Axis"
|
||||||
msgstr ""
|
msgstr "X-Axis"
|
||||||
|
|
||||||
msgid "XEP"
|
msgid "XEP"
|
||||||
msgstr ""
|
msgstr "XEP"
|
||||||
|
|
||||||
msgid "Y-Axis"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#~ msgid "Access Key Secret"
|
|
||||||
#~ msgstr ""
|
|
||||||
|
|
||||||
|
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
|
diff -Naur apprise-1.4.5/test/test_apprise_cli.py apprise-1.4.5-patched/test/test_apprise_cli.py
|
||||||
--- apprise-1.0.0/test/test_cli.py 2022-07-15 14:52:13.000000000 -0400
|
--- apprise-1.4.5/test/test_apprise_cli.py 2023-08-20 11:26:43.000000000 -0400
|
||||||
+++ apprise-1.0.0.patched/test/test_cli.py 2022-08-06 13:32:50.796935607 -0400
|
+++ apprise-1.4.5-patched/test/test_apprise_cli.py 2023-08-20 16:37:42.922342103 -0400
|
||||||
@@ -1022,9 +1022,6 @@
|
@@ -1027,9 +1027,6 @@
|
||||||
# Absolute path to __init__.py is okay
|
# Absolute path to __init__.py is okay
|
||||||
assert result.exit_code == 0
|
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
|
# Clear our working variables so they don't obstruct the next test
|
||||||
# This simulates an actual call from the CLI. Unfortunately through
|
# This simulates an actual call from the CLI. Unfortunately through
|
||||||
# testing were occupying the same memory space so our singleton's
|
# 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
|
# an __init__.py is found on the inside of it
|
||||||
assert result.exit_code == 0
|
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
|
# Test double paths that are the same; this ensures we only
|
||||||
# load the plugin once
|
# load the plugin once
|
||||||
result = runner.invoke(cli.main, [
|
result = runner.invoke(cli.main, [
|
||||||
@@ -1179,15 +1173,6 @@
|
@@ -1183,15 +1177,6 @@
|
||||||
# Print our custom details to the screen
|
# Print our custom details to the screen
|
||||||
'--details',
|
'--details',
|
||||||
])
|
])
|
||||||
|
@ -3,11 +3,11 @@ universal = 0
|
|||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
# ensure LICENSE is included in wheel metadata
|
# ensure LICENSE is included in wheel metadata
|
||||||
license_file = LICENSE
|
license_files = LICENSE
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
# We exclude packages we don't maintain
|
# We exclude packages we don't maintain
|
||||||
exclude = .eggs,.tox
|
exclude = .eggs,.tox,.local
|
||||||
ignore = E741,E722,W503,W504,W605
|
ignore = E741,E722,W503,W504,W605
|
||||||
statistics = true
|
statistics = true
|
||||||
builtins = _
|
builtins = _
|
||||||
|
4
setup.py
4
setup.py
@ -90,7 +90,7 @@ setup(
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
install_requires=install_requires,
|
install_requires=install_requires,
|
||||||
classifiers=(
|
classifiers=[
|
||||||
'Development Status :: 5 - Production/Stable',
|
'Development Status :: 5 - Production/Stable',
|
||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
'Intended Audience :: System Administrators',
|
'Intended Audience :: System Administrators',
|
||||||
@ -109,7 +109,7 @@ setup(
|
|||||||
'License :: OSI Approved :: BSD License',
|
'License :: OSI Approved :: BSD License',
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||||
'Topic :: Software Development :: Libraries :: Application Frameworks',
|
'Topic :: Software Development :: Libraries :: Application Frameworks',
|
||||||
),
|
],
|
||||||
entry_points={'console_scripts': console_scripts},
|
entry_points={'console_scripts': console_scripts},
|
||||||
python_requires='>=3.6',
|
python_requires='>=3.6',
|
||||||
setup_requires=['babel', ],
|
setup_requires=['babel', ],
|
||||||
|
@ -53,6 +53,7 @@ from apprise import __version__
|
|||||||
from apprise import URLBase
|
from apprise import URLBase
|
||||||
from apprise import PrivacyMode
|
from apprise import PrivacyMode
|
||||||
from apprise.AppriseLocale import LazyTranslation
|
from apprise.AppriseLocale import LazyTranslation
|
||||||
|
from apprise.AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
from apprise import common
|
from apprise import common
|
||||||
from apprise.plugins import __load_matrix
|
from apprise.plugins import __load_matrix
|
||||||
@ -1379,7 +1380,8 @@ def test_apprise_details():
|
|||||||
assert 'details' in entry['requirements']
|
assert 'details' in entry['requirements']
|
||||||
assert 'packages_required' in entry['requirements']
|
assert 'packages_required' in entry['requirements']
|
||||||
assert 'packages_recommended' 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_required'], list)
|
||||||
assert isinstance(entry['requirements']['packages_recommended'], list)
|
assert isinstance(entry['requirements']['packages_recommended'], list)
|
||||||
|
|
||||||
@ -1406,7 +1408,8 @@ def test_apprise_details():
|
|||||||
assert 'details' in entry['requirements']
|
assert 'details' in entry['requirements']
|
||||||
assert 'packages_required' in entry['requirements']
|
assert 'packages_required' in entry['requirements']
|
||||||
assert 'packages_recommended' 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_required'], list)
|
||||||
assert isinstance(entry['requirements']['packages_recommended'], 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 __load_matrix
|
||||||
from apprise.plugins import __reset_matrix
|
from apprise.plugins import __reset_matrix
|
||||||
|
|
||||||
|
from apprise.AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
from importlib import reload
|
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