summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDoug Hellmann <doug.hellmann@dreamhost.com>2014-06-04 13:56:55 -0700
committerDoug Hellmann <doug.hellmann@dreamhost.com>2014-06-04 13:56:55 -0700
commit3779302aa1a0ebb0e6ad68852281fd9f2fba975c (patch)
tree4b9f1c470010b5f88b8ccf1b9bd368ce46f9ae94
parent67ac9babdfd3f9350f6dd2ab043519000a853ae0 (diff)
downloadoslo-i18n-3779302aa1a0ebb0e6ad68852281fd9f2fba975c.tar.gz
Update the public API of the library
Move code around to hide parts that we want to protect, keeping the public API exposed through the gettextutils and log modules. There shouldn't be any changes to the code other than what is required to make the moves work. Change-Id: I135c535983b74a2e72d9ed133d3f3062752187ef
-rw-r--r--oslo/i18n/_factory.py109
-rw-r--r--oslo/i18n/_i18n.py25
-rw-r--r--oslo/i18n/_lazy.py34
-rw-r--r--oslo/i18n/_locale.py22
-rw-r--r--oslo/i18n/_message.py170
-rw-r--r--oslo/i18n/_translate.py71
-rw-r--r--oslo/i18n/gettextutils.py395
-rw-r--r--oslo/i18n/log.py89
-rw-r--r--tests/test_factory.py14
-rw-r--r--tests/test_gettextutils.py45
-rw-r--r--tests/test_handler.py17
-rw-r--r--tests/test_lazy.py41
-rw-r--r--tests/test_locale_dir_variable.py4
-rw-r--r--tests/test_message.py93
-rw-r--r--tests/test_translate.py46
-rw-r--r--tox.ini6
16 files changed, 692 insertions, 489 deletions
diff --git a/oslo/i18n/_factory.py b/oslo/i18n/_factory.py
new file mode 100644
index 0000000..acd0367
--- /dev/null
+++ b/oslo/i18n/_factory.py
@@ -0,0 +1,109 @@
+# Copyright 2012 Red Hat, Inc.
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""Translation function factory
+"""
+
+import gettext
+import os
+
+import six
+
+from oslo.i18n import _lazy
+from oslo.i18n import _locale
+from oslo.i18n import _message
+
+
+class TranslatorFactory(object):
+ """Create translator functions
+ """
+
+ def __init__(self, domain, localedir=None):
+ """Establish a set of translation functions for the domain.
+
+ :param domain: Name of translation domain,
+ specifying a message catalog.
+ :type domain: str
+ :param localedir: Directory with translation catalogs.
+ :type localedir: str
+ """
+ self.domain = domain
+ if localedir is None:
+ localedir = os.environ.get(_locale.get_locale_dir_variable_name(
+ domain
+ ))
+ self.localedir = localedir
+
+ def _make_translation_func(self, domain=None):
+ """Return a translation function ready for use with messages.
+
+ The returned function takes a single value, the unicode string
+ to be translated. The return type varies depending on whether
+ lazy translation is being done. When lazy translation is
+ enabled, :class:`Message` objects are returned instead of
+ regular :class:`unicode` strings.
+
+ The domain argument can be specified to override the default
+ from the factory, but the localedir from the factory is always
+ used because we assume the log-level translation catalogs are
+ installed in the same directory as the main application
+ catalog.
+
+ """
+ if domain is None:
+ domain = self.domain
+ t = gettext.translation(
+ domain,
+ localedir=self.localedir,
+ fallback=True,
+ )
+ # Use the appropriate method of the translation object based
+ # on the python version.
+ m = t.gettext if six.PY3 else t.ugettext
+
+ def f(msg):
+ """oslo.i18n.gettextutils translation function."""
+ if _lazy.USE_LAZY:
+ return _message.Message(msg, domain=domain)
+ return m(msg)
+ return f
+
+ @property
+ def primary(self):
+ "The default translation function."
+ return self._make_translation_func()
+
+ def _make_log_translation_func(self, level):
+ return self._make_translation_func(self.domain + '-log-' + level)
+
+ @property
+ def log_info(self):
+ "Translate info-level log messages."
+ return self._make_log_translation_func('info')
+
+ @property
+ def log_warning(self):
+ "Translate warning-level log messages."
+ return self._make_log_translation_func('warning')
+
+ @property
+ def log_error(self):
+ "Translate error-level log messages."
+ return self._make_log_translation_func('error')
+
+ @property
+ def log_critical(self):
+ "Translate critical-level log messages."
+ return self._make_log_translation_func('critical')
diff --git a/oslo/i18n/_i18n.py b/oslo/i18n/_i18n.py
new file mode 100644
index 0000000..2dd2e4f
--- /dev/null
+++ b/oslo/i18n/_i18n.py
@@ -0,0 +1,25 @@
+# Copyright 2012 Red Hat, Inc.
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""Translation support for messages in this library.
+"""
+
+from oslo.i18n import _factory
+
+# Create the global translation functions.
+_translators = _factory.TranslatorFactory('oslo.i18n')
+
+# The primary translation function using the well-known name "_"
+_ = _translators.primary
diff --git a/oslo/i18n/_lazy.py b/oslo/i18n/_lazy.py
new file mode 100644
index 0000000..f7e0398
--- /dev/null
+++ b/oslo/i18n/_lazy.py
@@ -0,0 +1,34 @@
+# Copyright 2012 Red Hat, Inc.
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+USE_LAZY = False
+
+
+def enable_lazy(enable=True):
+ """Convenience function for configuring _() to use lazy gettext
+
+ Call this at the start of execution to enable the gettextutils._
+ function to use lazy gettext functionality. This is useful if
+ your project is importing _ directly instead of using the
+ gettextutils.install() way of importing the _ function.
+
+ :param enable: Flag indicating whether lazy translation should be
+ turned on or off. Defaults to True.
+ :type enable: bool
+
+ """
+ global USE_LAZY
+ USE_LAZY = enable
diff --git a/oslo/i18n/_locale.py b/oslo/i18n/_locale.py
new file mode 100644
index 0000000..79d12fb
--- /dev/null
+++ b/oslo/i18n/_locale.py
@@ -0,0 +1,22 @@
+# Copyright 2012 Red Hat, Inc.
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+def get_locale_dir_variable_name(domain):
+ """Convert a translation domain name to a variable for specifying
+ a separate locale dir.
+ """
+ return domain.upper().replace('.', '_').replace('-', '_') + '_LOCALEDIR'
diff --git a/oslo/i18n/_message.py b/oslo/i18n/_message.py
new file mode 100644
index 0000000..73cb7bf
--- /dev/null
+++ b/oslo/i18n/_message.py
@@ -0,0 +1,170 @@
+# Copyright 2012 Red Hat, Inc.
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""Private Message class for lazy translation support.
+"""
+
+import copy
+import gettext
+import locale
+import os
+
+import six
+
+from oslo.i18n import _translate
+
+
+class Message(six.text_type):
+ """A Message object is a unicode object that can be translated.
+
+ Translation of Message is done explicitly using the translate() method.
+ For all non-translation intents and purposes, a Message is simply unicode,
+ and can be treated as such.
+ """
+
+ def __new__(cls, msgid, msgtext=None, params=None,
+ domain='oslo', *args):
+ """Create a new Message object.
+
+ In order for translation to work gettext requires a message ID, this
+ msgid will be used as the base unicode text. It is also possible
+ for the msgid and the base unicode text to be different by passing
+ the msgtext parameter.
+ """
+ # If the base msgtext is not given, we use the default translation
+ # of the msgid (which is in English) just in case the system locale is
+ # not English, so that the base text will be in that locale by default.
+ if not msgtext:
+ msgtext = Message._translate_msgid(msgid, domain)
+ # We want to initialize the parent unicode with the actual object that
+ # would have been plain unicode if 'Message' was not enabled.
+ msg = super(Message, cls).__new__(cls, msgtext)
+ msg.msgid = msgid
+ msg.domain = domain
+ msg.params = params
+ return msg
+
+ def translate(self, desired_locale=None):
+ """Translate this message to the desired locale.
+
+ :param desired_locale: The desired locale to translate the message to,
+ if no locale is provided the message will be
+ translated to the system's default locale.
+
+ :returns: the translated message in unicode
+ """
+
+ translated_message = Message._translate_msgid(self.msgid,
+ self.domain,
+ desired_locale)
+ if self.params is None:
+ # No need for more translation
+ return translated_message
+
+ # This Message object may have been formatted with one or more
+ # Message objects as substitution arguments, given either as a single
+ # argument, part of a tuple, or as one or more values in a dictionary.
+ # When translating this Message we need to translate those Messages too
+ translated_params = _translate.translate_args(self.params,
+ desired_locale)
+
+ translated_message = translated_message % translated_params
+
+ return translated_message
+
+ @staticmethod
+ def _translate_msgid(msgid, domain, desired_locale=None):
+ if not desired_locale:
+ system_locale = locale.getdefaultlocale()
+ # If the system locale is not available to the runtime use English
+ if not system_locale[0]:
+ desired_locale = 'en_US'
+ else:
+ desired_locale = system_locale[0]
+
+ locale_dir = os.environ.get(domain.upper() + '_LOCALEDIR')
+ lang = gettext.translation(domain,
+ localedir=locale_dir,
+ languages=[desired_locale],
+ fallback=True)
+ if six.PY3:
+ translator = lang.gettext
+ else:
+ translator = lang.ugettext
+
+ translated_message = translator(msgid)
+ return translated_message
+
+ def __mod__(self, other):
+ # When we mod a Message we want the actual operation to be performed
+ # by the parent class (i.e. unicode()), the only thing we do here is
+ # save the original msgid and the parameters in case of a translation
+ params = self._sanitize_mod_params(other)
+ unicode_mod = super(Message, self).__mod__(params)
+ modded = Message(self.msgid,
+ msgtext=unicode_mod,
+ params=params,
+ domain=self.domain)
+ return modded
+
+ def _sanitize_mod_params(self, other):
+ """Sanitize the object being modded with this Message.
+
+ - Add support for modding 'None' so translation supports it
+ - Trim the modded object, which can be a large dictionary, to only
+ those keys that would actually be used in a translation
+ - Snapshot the object being modded, in case the message is
+ translated, it will be used as it was when the Message was created
+ """
+ if other is None:
+ params = (other,)
+ elif isinstance(other, dict):
+ # Merge the dictionaries
+ # Copy each item in case one does not support deep copy.
+ params = {}
+ if isinstance(self.params, dict):
+ for key, val in self.params.items():
+ params[key] = self._copy_param(val)
+ for key, val in other.items():
+ params[key] = self._copy_param(val)
+ else:
+ params = self._copy_param(other)
+ return params
+
+ def _copy_param(self, param):
+ try:
+ return copy.deepcopy(param)
+ except Exception:
+ # Fallback to casting to unicode this will handle the
+ # python code-like objects that can't be deep-copied
+ return six.text_type(param)
+
+ def __add__(self, other):
+ from oslo.i18n._i18n import _
+ msg = _('Message objects do not support addition.')
+ raise TypeError(msg)
+
+ def __radd__(self, other):
+ return self.__add__(other)
+
+ if six.PY2:
+ def __str__(self):
+ # NOTE(luisg): Logging in python 2.6 tries to str() log records,
+ # and it expects specifically a UnicodeError in order to proceed.
+ from oslo.i18n._i18n import _
+ msg = _('Message objects do not support str() because they may '
+ 'contain non-ascii characters. '
+ 'Please use unicode() or translate() instead.')
+ raise UnicodeError(msg)
diff --git a/oslo/i18n/_translate.py b/oslo/i18n/_translate.py
new file mode 100644
index 0000000..bd81b85
--- /dev/null
+++ b/oslo/i18n/_translate.py
@@ -0,0 +1,71 @@
+# Copyright 2012 Red Hat, Inc.
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import six
+
+
+def translate(obj, desired_locale=None):
+ """Gets the translated unicode representation of the given object.
+
+ If the object is not translatable it is returned as-is.
+
+ If the desired_locale argument is None the object is translated to
+ the system locale.
+
+ :param obj: the object to translate
+ :param desired_locale: the locale to translate the message to, if None the
+ default system locale will be used
+ :returns: the translated object in unicode, or the original object if
+ it could not be translated
+
+ """
+ from oslo.i18n import _message # avoid circular dependency at module level
+ message = obj
+ if not isinstance(message, _message.Message):
+ # If the object to translate is not already translatable,
+ # let's first get its unicode representation
+ message = six.text_type(obj)
+ if isinstance(message, _message.Message):
+ # Even after unicoding() we still need to check if we are
+ # running with translatable unicode before translating
+ return message.translate(desired_locale)
+ return obj
+
+
+def translate_args(args, desired_locale=None):
+ """Translates all the translatable elements of the given arguments object.
+
+ This method is used for translating the translatable values in method
+ arguments which include values of tuples or dictionaries.
+ If the object is not a tuple or a dictionary the object itself is
+ translated if it is translatable.
+
+ If the locale is None the object is translated to the system locale.
+
+ :param args: the args to translate
+ :param desired_locale: the locale to translate the args to, if None the
+ default system locale will be used
+ :returns: a new args object with the translated contents of the original
+ """
+ if isinstance(args, tuple):
+ return tuple(translate(v, desired_locale) for v in args)
+ if isinstance(args, dict):
+ translated_dict = {}
+ for (k, v) in six.iteritems(args):
+ translated_v = translate(v, desired_locale)
+ translated_dict[k] = translated_v
+ return translated_dict
+ return translate(args, desired_locale)
diff --git a/oslo/i18n/gettextutils.py b/oslo/i18n/gettextutils.py
index a8134fc..a7285f5 100644
--- a/oslo/i18n/gettextutils.py
+++ b/oslo/i18n/gettextutils.py
@@ -19,145 +19,15 @@
import copy
import gettext
-import locale
-from logging import handlers
import os
from babel import localedata
import six
-_AVAILABLE_LANGUAGES = {}
-
-_USE_LAZY = False
-
-
-def _get_locale_dir_variable_name(domain):
- """Convert a translation domain name to a variable for specifying
- a separate locale dir.
- """
- return domain.upper().replace('.', '_').replace('-', '_') + '_LOCALEDIR'
-
-
-class TranslatorFactory(object):
- """Create translator functions
- """
-
- def __init__(self, domain, localedir=None):
- """Establish a set of translation functions for the domain.
-
- :param domain: Name of translation domain,
- specifying a message catalog.
- :type domain: str
- :param localedir: Directory with translation catalogs.
- :type localedir: str
- """
- self.domain = domain
- if localedir is None:
- localedir = os.environ.get(_get_locale_dir_variable_name(domain))
- self.localedir = localedir
-
- def _make_translation_func(self, domain=None):
- """Return a translation function ready for use with messages.
-
- The returned function takes a single value, the unicode string
- to be translated. The return type varies depending on whether
- lazy translation is being done. When lazy translation is
- enabled, :class:`Message` objects are returned instead of
- regular :class:`unicode` strings.
-
- The domain argument can be specified to override the default
- from the factory, but the localedir from the factory is always
- used because we assume the log-level translation catalogs are
- installed in the same directory as the main application
- catalog.
-
- """
- if domain is None:
- domain = self.domain
- t = gettext.translation(
- domain,
- localedir=self.localedir,
- fallback=True,
- )
- # Use the appropriate method of the translation object based
- # on the python version.
- m = t.gettext if six.PY3 else t.ugettext
-
- def f(msg):
- """oslo.i18n.gettextutils translation function."""
- if _USE_LAZY:
- return Message(msg, domain=domain)
- return m(msg)
- return f
-
- @property
- def primary(self):
- "The default translation function."
- return self._make_translation_func()
-
- def _make_log_translation_func(self, level):
- return self._make_translation_func(self.domain + '-log-' + level)
-
- @property
- def log_info(self):
- "Translate info-level log messages."
- return self._make_log_translation_func('info')
-
- @property
- def log_warning(self):
- "Translate warning-level log messages."
- return self._make_log_translation_func('warning')
-
- @property
- def log_error(self):
- "Translate error-level log messages."
- return self._make_log_translation_func('error')
-
- @property
- def log_critical(self):
- "Translate critical-level log messages."
- return self._make_log_translation_func('critical')
-
-
-# NOTE(dhellmann): When this module moves out of the incubator into
-# oslo.i18n, these global variables can be moved to an integration
-# module within each application.
-
-# Create the global translation functions.
-_translators = TranslatorFactory('oslo')
-
-# The primary translation function using the well-known name "_"
-_ = _translators.primary
-
-# Translators for log levels.
-#
-# The abbreviated names are meant to reflect the usual use of a short
-# name like '_'. The "L" is for "log" and the other letter comes from
-# the level.
-_LI = _translators.log_info
-_LW = _translators.log_warning
-_LE = _translators.log_error
-_LC = _translators.log_critical
-
-# NOTE(dhellmann): End of globals that will move to the application's
-# integration module.
-
-
-def enable_lazy(enable=True):
- """Convenience function for configuring _() to use lazy gettext
-
- Call this at the start of execution to enable the gettextutils._
- function to use lazy gettext functionality. This is useful if
- your project is importing _ directly instead of using the
- gettextutils.install() way of importing the _ function.
-
- :param enable: Flag indicating whether lazy translation should be
- turned on or off. Defaults to True.
- :type enable: bool
-
- """
- global _USE_LAZY
- _USE_LAZY = enable
+# Expose a few internal pieces as part of our public API.
+from oslo.i18n._factory import TranslatorFactory # noqa
+from oslo.i18n._lazy import enable_lazy # noqa
+from oslo.i18n._translate import translate # noqa
def install(domain):
@@ -183,145 +53,7 @@ def install(domain):
moves.builtins.__dict__['_'] = tf.primary
-class Message(six.text_type):
- """A Message object is a unicode object that can be translated.
-
- Translation of Message is done explicitly using the translate() method.
- For all non-translation intents and purposes, a Message is simply unicode,
- and can be treated as such.
- """
-
- def __new__(cls, msgid, msgtext=None, params=None,
- domain='oslo', *args):
- """Create a new Message object.
-
- In order for translation to work gettext requires a message ID, this
- msgid will be used as the base unicode text. It is also possible
- for the msgid and the base unicode text to be different by passing
- the msgtext parameter.
- """
- # If the base msgtext is not given, we use the default translation
- # of the msgid (which is in English) just in case the system locale is
- # not English, so that the base text will be in that locale by default.
- if not msgtext:
- msgtext = Message._translate_msgid(msgid, domain)
- # We want to initialize the parent unicode with the actual object that
- # would have been plain unicode if 'Message' was not enabled.
- msg = super(Message, cls).__new__(cls, msgtext)
- msg.msgid = msgid
- msg.domain = domain
- msg.params = params
- return msg
-
- def translate(self, desired_locale=None):
- """Translate this message to the desired locale.
-
- :param desired_locale: The desired locale to translate the message to,
- if no locale is provided the message will be
- translated to the system's default locale.
-
- :returns: the translated message in unicode
- """
-
- translated_message = Message._translate_msgid(self.msgid,
- self.domain,
- desired_locale)
- if self.params is None:
- # No need for more translation
- return translated_message
-
- # This Message object may have been formatted with one or more
- # Message objects as substitution arguments, given either as a single
- # argument, part of a tuple, or as one or more values in a dictionary.
- # When translating this Message we need to translate those Messages too
- translated_params = _translate_args(self.params, desired_locale)
-
- translated_message = translated_message % translated_params
-
- return translated_message
-
- @staticmethod
- def _translate_msgid(msgid, domain, desired_locale=None):
- if not desired_locale:
- system_locale = locale.getdefaultlocale()
- # If the system locale is not available to the runtime use English
- if not system_locale[0]:
- desired_locale = 'en_US'
- else:
- desired_locale = system_locale[0]
-
- locale_dir = os.environ.get(domain.upper() + '_LOCALEDIR')
- lang = gettext.translation(domain,
- localedir=locale_dir,
- languages=[desired_locale],
- fallback=True)
- if six.PY3:
- translator = lang.gettext
- else:
- translator = lang.ugettext
-
- translated_message = translator(msgid)
- return translated_message
-
- def __mod__(self, other):
- # When we mod a Message we want the actual operation to be performed
- # by the parent class (i.e. unicode()), the only thing we do here is
- # save the original msgid and the parameters in case of a translation
- params = self._sanitize_mod_params(other)
- unicode_mod = super(Message, self).__mod__(params)
- modded = Message(self.msgid,
- msgtext=unicode_mod,
- params=params,
- domain=self.domain)
- return modded
-
- def _sanitize_mod_params(self, other):
- """Sanitize the object being modded with this Message.
-
- - Add support for modding 'None' so translation supports it
- - Trim the modded object, which can be a large dictionary, to only
- those keys that would actually be used in a translation
- - Snapshot the object being modded, in case the message is
- translated, it will be used as it was when the Message was created
- """
- if other is None:
- params = (other,)
- elif isinstance(other, dict):
- # Merge the dictionaries
- # Copy each item in case one does not support deep copy.
- params = {}
- if isinstance(self.params, dict):
- for key, val in self.params.items():
- params[key] = self._copy_param(val)
- for key, val in other.items():
- params[key] = self._copy_param(val)
- else:
- params = self._copy_param(other)
- return params
-
- def _copy_param(self, param):
- try:
- return copy.deepcopy(param)
- except Exception:
- # Fallback to casting to unicode this will handle the
- # python code-like objects that can't be deep-copied
- return six.text_type(param)
-
- def __add__(self, other):
- msg = _('Message objects do not support addition.')
- raise TypeError(msg)
-
- def __radd__(self, other):
- return self.__add__(other)
-
- if six.PY2:
- def __str__(self):
- # NOTE(luisg): Logging in python 2.6 tries to str() log records,
- # and it expects specifically a UnicodeError in order to proceed.
- msg = _('Message objects do not support str() because they may '
- 'contain non-ascii characters. '
- 'Please use unicode() or translate() instead.')
- raise UnicodeError(msg)
+_AVAILABLE_LANGUAGES = {}
def get_available_languages(domain):
@@ -370,120 +102,3 @@ def get_available_languages(domain):
_AVAILABLE_LANGUAGES[domain] = language_list
return copy.copy(language_list)
-
-
-def translate(obj, desired_locale=None):
- """Gets the translated unicode representation of the given object.
-
- If the object is not translatable it is returned as-is.
- If the locale is None the object is translated to the system locale.
-
- :param obj: the object to translate
- :param desired_locale: the locale to translate the message to, if None the
- default system locale will be used
- :returns: the translated object in unicode, or the original object if
- it could not be translated
- """
- message = obj
- if not isinstance(message, Message):
- # If the object to translate is not already translatable,
- # let's first get its unicode representation
- message = six.text_type(obj)
- if isinstance(message, Message):
- # Even after unicoding() we still need to check if we are
- # running with translatable unicode before translating
- return message.translate(desired_locale)
- return obj
-
-
-def _translate_args(args, desired_locale=None):
- """Translates all the translatable elements of the given arguments object.
-
- This method is used for translating the translatable values in method
- arguments which include values of tuples or dictionaries.
- If the object is not a tuple or a dictionary the object itself is
- translated if it is translatable.
-
- If the locale is None the object is translated to the system locale.
-
- :param args: the args to translate
- :param desired_locale: the locale to translate the args to, if None the
- default system locale will be used
- :returns: a new args object with the translated contents of the original
- """
- if isinstance(args, tuple):
- return tuple(translate(v, desired_locale) for v in args)
- if isinstance(args, dict):
- translated_dict = {}
- for (k, v) in six.iteritems(args):
- translated_v = translate(v, desired_locale)
- translated_dict[k] = translated_v
- return translated_dict
- return translate(args, desired_locale)
-
-
-class TranslationHandler(handlers.MemoryHandler):
- """Handler that translates records before logging them.
-
- The TranslationHandler takes a locale and a target logging.Handler object
- to forward LogRecord objects to after translating them. This handler
- depends on Message objects being logged, instead of regular strings.
-
- The handler can be configured declaratively in the logging.conf as follows:
-
- [handlers]
- keys = translatedlog, translator
-
- [handler_translatedlog]
- class = handlers.WatchedFileHandler
- args = ('/var/log/api-localized.log',)
- formatter = context
-
- [handler_translator]
- class = openstack.common.log.TranslationHandler
- target = translatedlog
- args = ('zh_CN',)
-
- If the specified locale is not available in the system, the handler will
- log in the default locale.
- """
-
- def __init__(self, locale=None, target=None):
- """Initialize a TranslationHandler
-
- :param locale: locale to use for translating messages
- :param target: logging.Handler object to forward
- LogRecord objects to after translation
- """
- # NOTE(luisg): In order to allow this handler to be a wrapper for
- # other handlers, such as a FileHandler, and still be able to
- # configure it using logging.conf, this handler has to extend
- # MemoryHandler because only the MemoryHandlers' logging.conf
- # parsing is implemented such that it accepts a target handler.
- handlers.MemoryHandler.__init__(self, capacity=0, target=target)
- self.locale = locale
-
- def setFormatter(self, fmt):
- self.target.setFormatter(fmt)
-
- def emit(self, record):
- # We save the message from the original record to restore it
- # after translation, so other handlers are not affected by this
- original_msg = record.msg
- original_args = record.args
-
- try:
- self._translate_and_log_record(record)
- finally:
- record.msg = original_msg
- record.args = original_args
-
- def _translate_and_log_record(self, record):
- record.msg = translate(record.msg, self.locale)
-
- # In addition to translating the message, we also need to translate
- # arguments that were passed to the log method that were not part
- # of the main message e.g., log.info(_('Some message %s'), this_one))
- record.args = _translate_args(record.args, self.locale)
-
- self.target.emit(record)
diff --git a/oslo/i18n/log.py b/oslo/i18n/log.py
new file mode 100644
index 0000000..46d6c1e
--- /dev/null
+++ b/oslo/i18n/log.py
@@ -0,0 +1,89 @@
+# Copyright 2012 Red Hat, Inc.
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""logging utilities for translation
+"""
+
+from logging import handlers
+
+from oslo.i18n import _translate
+
+
+class TranslationHandler(handlers.MemoryHandler):
+ """Handler that translates records before logging them.
+
+ The TranslationHandler takes a locale and a target logging.Handler object
+ to forward LogRecord objects to after translating them. This handler
+ depends on Message objects being logged, instead of regular strings.
+
+ The handler can be configured declaratively in the logging.conf as follows:
+
+ [handlers]
+ keys = translatedlog, translator
+
+ [handler_translatedlog]
+ class = handlers.WatchedFileHandler
+ args = ('/var/log/api-localized.log',)
+ formatter = context
+
+ [handler_translator]
+ class = openstack.common.log.TranslationHandler
+ target = translatedlog
+ args = ('zh_CN',)
+
+ If the specified locale is not available in the system, the handler will
+ log in the default locale.
+ """
+
+ def __init__(self, locale=None, target=None):
+ """Initialize a TranslationHandler
+
+ :param locale: locale to use for translating messages
+ :param target: logging.Handler object to forward
+ LogRecord objects to after translation
+ """
+ # NOTE(luisg): In order to allow this handler to be a wrapper for
+ # other handlers, such as a FileHandler, and still be able to
+ # configure it using logging.conf, this handler has to extend
+ # MemoryHandler because only the MemoryHandlers' logging.conf
+ # parsing is implemented such that it accepts a target handler.
+ handlers.MemoryHandler.__init__(self, capacity=0, target=target)
+ self.locale = locale
+
+ def setFormatter(self, fmt):
+ self.target.setFormatter(fmt)
+
+ def emit(self, record):
+ # We save the message from the original record to restore it
+ # after translation, so other handlers are not affected by this
+ original_msg = record.msg
+ original_args = record.args
+
+ try:
+ self._translate_and_log_record(record)
+ finally:
+ record.msg = original_msg
+ record.args = original_args
+
+ def _translate_and_log_record(self, record):
+ record.msg = _translate.translate(record.msg, self.locale)
+
+ # In addition to translating the message, we also need to translate
+ # arguments that were passed to the log method that were not part
+ # of the main message e.g., log.info(_('Some message %s'), this_one))
+ record.args = _translate.translate_args(record.args, self.locale)
+
+ self.target.emit(record)
diff --git a/tests/test_factory.py b/tests/test_factory.py
index 58af03d..112960f 100644
--- a/tests/test_factory.py
+++ b/tests/test_factory.py
@@ -19,6 +19,8 @@ import six
from oslotest import base as test_base
+from oslo.i18n import _lazy
+from oslo.i18n import _message
from oslo.i18n import gettextutils
@@ -27,23 +29,23 @@ class TranslatorFactoryTest(test_base.BaseTestCase):
def setUp(self):
super(TranslatorFactoryTest, self).setUp()
# remember so we can reset to it later in case it changes
- self._USE_LAZY = gettextutils._USE_LAZY
+ self._USE_LAZY = _lazy.USE_LAZY
def tearDown(self):
# reset to value before test
- gettextutils._USE_LAZY = self._USE_LAZY
+ _lazy.USE_LAZY = self._USE_LAZY
super(TranslatorFactoryTest, self).tearDown()
def test_lazy(self):
gettextutils.enable_lazy(True)
- with mock.patch.object(gettextutils, 'Message') as msg:
+ with mock.patch.object(_message, 'Message') as msg:
tf = gettextutils.TranslatorFactory('domain')
tf.primary('some text')
msg.assert_called_with('some text', domain='domain')
def test_not_lazy(self):
gettextutils.enable_lazy(False)
- with mock.patch.object(gettextutils, 'Message') as msg:
+ with mock.patch.object(_message, 'Message') as msg:
msg.side_effect = AssertionError('should not use Message')
tf = gettextutils.TranslatorFactory('domain')
tf.primary('some text')
@@ -52,11 +54,11 @@ class TranslatorFactoryTest(test_base.BaseTestCase):
gettextutils.enable_lazy(True)
tf = gettextutils.TranslatorFactory('domain')
r = tf.primary('some text')
- self.assertIsInstance(r, gettextutils.Message)
+ self.assertIsInstance(r, _message.Message)
gettextutils.enable_lazy(False)
r = tf.primary('some text')
# Python 2.6 doesn't have assertNotIsInstance().
- self.assertFalse(isinstance(r, gettextutils.Message))
+ self.assertFalse(isinstance(r, _message.Message))
def test_py2(self):
gettextutils.enable_lazy(False)
diff --git a/tests/test_gettextutils.py b/tests/test_gettextutils.py
index 543e897..8797e1f 100644
--- a/tests/test_gettextutils.py
+++ b/tests/test_gettextutils.py
@@ -24,9 +24,8 @@ import six
from oslotest import base as test_base
from oslotest import moxstubout
-from tests import fakes
-from tests import utils
-
+from oslo.i18n import _lazy
+from oslo.i18n import _message
from oslo.i18n import gettextutils
@@ -41,35 +40,27 @@ class GettextTest(test_base.BaseTestCase):
self.stubs = moxfixture.stubs
self.mox = moxfixture.mox
# remember so we can reset to it later in case it changes
- self._USE_LAZY = gettextutils._USE_LAZY
+ self._USE_LAZY = _lazy.USE_LAZY
+ self.t = gettextutils.TranslatorFactory('oslo.i18n.test')
def tearDown(self):
# reset to value before test
- gettextutils._USE_LAZY = self._USE_LAZY
+ _lazy.USE_LAZY = self._USE_LAZY
super(GettextTest, self).tearDown()
- def test_enable_lazy(self):
- gettextutils._USE_LAZY = False
- gettextutils.enable_lazy()
- self.assertTrue(gettextutils._USE_LAZY)
-
- def test_disable_lazy(self):
- gettextutils._USE_LAZY = True
- gettextutils.enable_lazy(False)
- self.assertFalse(gettextutils._USE_LAZY)
-
def test_gettext_does_not_blow_up(self):
- LOG.info(gettextutils._('test'))
+ LOG.info(self.t.primary('test'))
def test_gettextutils_install(self):
gettextutils.install('blaa')
gettextutils.enable_lazy(False)
- self.assertTrue(isinstance(_('A String'), six.text_type)) # noqa
+ self.assertTrue(isinstance(self.t.primary('A String'),
+ six.text_type))
gettextutils.install('blaa')
gettextutils.enable_lazy(True)
- self.assertTrue(isinstance(_('A Message'), # noqa
- gettextutils.Message))
+ self.assertTrue(isinstance(self.t.primary('A Message'),
+ _message.Message))
def test_gettext_install_looks_up_localedir(self):
with mock.patch('os.environ.get') as environ_get:
@@ -133,19 +124,3 @@ class GettextTest(test_base.BaseTestCase):
unknown_domain_languages = gettextutils.get_available_languages('huh')
self.assertEqual(1, len(unknown_domain_languages))
self.assertIn('en_US', unknown_domain_languages)
-
- @mock.patch('gettext.translation')
- def test_translate(self, mock_translation):
- en_message = 'A message in the default locale'
- es_translation = 'A message in Spanish'
- message = gettextutils.Message(en_message)
-
- es_translations = {en_message: es_translation}
- translations_map = {'es': es_translations}
- translator = fakes.FakeTranslations.translator(translations_map)
- mock_translation.side_effect = translator
-
- # translate() works on msgs and on objects whose unicode reps are msgs
- obj = utils.SomeObject(message)
- self.assertEqual(es_translation, gettextutils.translate(message, 'es'))
- self.assertEqual(es_translation, gettextutils.translate(obj, 'es'))
diff --git a/tests/test_handler.py b/tests/test_handler.py
index 6b634da..9814f38 100644
--- a/tests/test_handler.py
+++ b/tests/test_handler.py
@@ -23,7 +23,8 @@ from oslotest import base as test_base
from tests import fakes
-from oslo.i18n import gettextutils
+from oslo.i18n import _message
+from oslo.i18n import log as i18n_log
LOG = logging.getLogger(__name__)
@@ -35,7 +36,7 @@ class TranslationHandlerTestCase(test_base.BaseTestCase):
self.stream = six.StringIO()
self.destination_handler = logging.StreamHandler(self.stream)
- self.translation_handler = gettextutils.TranslationHandler('zh_CN')
+ self.translation_handler = i18n_log.TranslationHandler('zh_CN')
self.translation_handler.setTarget(self.destination_handler)
self.logger = logging.getLogger('localehander_logger')
@@ -56,7 +57,7 @@ class TranslationHandlerTestCase(test_base.BaseTestCase):
translator = fakes.FakeTranslations.translator(translations_map)
mock_translation.side_effect = translator
- msg = gettextutils.Message(log_message)
+ msg = _message.Message(log_message)
self.logger.info(msg)
self.assertIn(log_message_translation, self.stream.getvalue())
@@ -74,8 +75,8 @@ class TranslationHandlerTestCase(test_base.BaseTestCase):
translator = fakes.FakeTranslations.translator(translations_map)
mock_translation.side_effect = translator
- msg = gettextutils.Message(log_message)
- arg = gettextutils.Message(log_arg)
+ msg = _message.Message(log_message)
+ arg = _message.Message(log_arg)
self.logger.info(msg, arg)
self.assertIn(log_message_translation % log_arg_translation,
@@ -97,9 +98,9 @@ class TranslationHandlerTestCase(test_base.BaseTestCase):
translator = fakes.FakeTranslations.translator(translations_map)
mock_translation.side_effect = translator
- msg = gettextutils.Message(log_message)
- arg_1 = gettextutils.Message(log_arg_1)
- arg_2 = gettextutils.Message(log_arg_2)
+ msg = _message.Message(log_message)
+ arg_1 = _message.Message(log_arg_1)
+ arg_2 = _message.Message(log_arg_2)
self.logger.info(msg, {'arg1': arg_1, 'arg2': arg_2})
translation = log_message_translation % {'arg1': log_arg_1_translation,
diff --git a/tests/test_lazy.py b/tests/test_lazy.py
new file mode 100644
index 0000000..1183b8f
--- /dev/null
+++ b/tests/test_lazy.py
@@ -0,0 +1,41 @@
+# Copyright 2012 Red Hat, Inc.
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslotest import base as test_base
+
+from oslo.i18n import _lazy
+from oslo.i18n import gettextutils
+
+
+class LazyTest(test_base.BaseTestCase):
+
+ def setUp(self):
+ super(LazyTest, self).setUp()
+ self._USE_LAZY = _lazy.USE_LAZY
+
+ def tearDown(self):
+ _lazy.USE_LAZY = self._USE_LAZY
+ super(LazyTest, self).tearDown()
+
+ def test_enable_lazy(self):
+ _lazy.USE_LAZY = False
+ gettextutils.enable_lazy()
+ self.assertTrue(_lazy.USE_LAZY)
+
+ def test_disable_lazy(self):
+ _lazy.USE_LAZY = True
+ gettextutils.enable_lazy(False)
+ self.assertFalse(_lazy.USE_LAZY)
diff --git a/tests/test_locale_dir_variable.py b/tests/test_locale_dir_variable.py
index 59daab9..ca87482 100644
--- a/tests/test_locale_dir_variable.py
+++ b/tests/test_locale_dir_variable.py
@@ -15,7 +15,7 @@
from oslotest import base as test_base
import testscenarios.testcase
-from oslo.i18n import gettextutils
+from oslo.i18n import _locale
class LocaleDirVariableTest(testscenarios.testcase.WithScenarios,
@@ -28,5 +28,5 @@ class LocaleDirVariableTest(testscenarios.testcase.WithScenarios,
]
def test_make_variable_name(self):
- var = gettextutils._get_locale_dir_variable_name(self.domain)
+ var = _locale.get_locale_dir_variable_name(self.domain)
self.assertEqual(self.expected, var)
diff --git a/tests/test_message.py b/tests/test_message.py
index f324472..0e96ae1 100644
--- a/tests/test_message.py
+++ b/tests/test_message.py
@@ -14,6 +14,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+from __future__ import unicode_literals
+
import logging
import mock
@@ -25,7 +27,8 @@ from oslotest import base as test_base
from tests import fakes
from tests import utils
-from oslo.i18n import gettextutils
+from oslo.i18n import _message
+
LOG = logging.getLogger(__name__)
@@ -33,20 +36,16 @@ LOG = logging.getLogger(__name__)
class MessageTestCase(test_base.BaseTestCase):
"""Unit tests for locale Message class."""
- @staticmethod
- def message(msg):
- return gettextutils.Message(msg)
-
def test_message_id_and_message_text(self):
- message = gettextutils.Message('1')
+ message = _message.Message('1')
self.assertEqual('1', message.msgid)
self.assertEqual('1', message)
- message = gettextutils.Message('1', msgtext='A')
+ message = _message.Message('1', msgtext='A')
self.assertEqual('1', message.msgid)
self.assertEqual('A', message)
def test_message_is_unicode(self):
- message = self.message('some %s') % 'message'
+ message = _message.Message('some %s') % 'message'
self.assertIsInstance(message, six.text_type)
@mock.patch('locale.getdefaultlocale')
@@ -63,7 +62,7 @@ class MessageTestCase(test_base.BaseTestCase):
mock_translation.side_effect = translator
mock_getdefaultlocale.return_value = ('es',)
- message = gettextutils.Message(msgid)
+ message = _message.Message(msgid)
# The base representation of the message is in Spanish, as well as
# the default translation, since the default locale was Spanish.
@@ -71,7 +70,7 @@ class MessageTestCase(test_base.BaseTestCase):
self.assertEqual(es_translation, message.translate())
def test_translate_returns_unicode(self):
- message = self.message('some %s') % 'message'
+ message = _message.Message('some %s') % 'message'
self.assertIsInstance(message.translate(), six.text_type)
def test_mod_with_named_parameters(self):
@@ -85,7 +84,7 @@ class MessageTestCase(test_base.BaseTestCase):
'stderr': 'test5',
'something': 'trimmed'}
- result = self.message(msgid) % params
+ result = _message.Message(msgid) % params
expected = msgid % params
self.assertEqual(result, expected)
@@ -102,7 +101,7 @@ class MessageTestCase(test_base.BaseTestCase):
'stderr': 'test5'}
# Run string interpolation the first time to make a new Message
- first = self.message(msgid) % params
+ first = _message.Message(msgid) % params
# Run string interpolation on the new Message, to replicate
# one of the error paths with some Exception classes we've
@@ -145,7 +144,7 @@ class MessageTestCase(test_base.BaseTestCase):
'url': 'test2',
'headers': {'h1': 'val1'}}
- result = self.message(msgid) % params
+ result = _message.Message(msgid) % params
expected = msgid % params
self.assertEqual(result, expected)
@@ -155,11 +154,11 @@ class MessageTestCase(test_base.BaseTestCase):
msgid = "Test that we can inject a dictionary %s"
params = {'description': 'test1'}
- result = self.message(msgid) % params
+ result = _message.Message(msgid) % params
expected = msgid % params
- self.assertEqual(result, expected)
- self.assertEqual(result.translate(), expected)
+ self.assertEqual(expected, result)
+ self.assertEqual(expected, result.translate())
def test_mod_with_integer_parameters(self):
msgid = "Some string with params: %d"
@@ -169,10 +168,10 @@ class MessageTestCase(test_base.BaseTestCase):
results = []
for param in params:
messages.append(msgid % param)
- results.append(self.message(msgid) % param)
+ results.append(_message.Message(msgid) % param)
for message, result in zip(messages, results):
- self.assertEqual(type(result), gettextutils.Message)
+ self.assertEqual(type(result), _message.Message)
self.assertEqual(result.translate(), message)
# simulate writing out as string
@@ -184,7 +183,7 @@ class MessageTestCase(test_base.BaseTestCase):
msgid = "Found object: %(current_value)s"
changing_dict = {'current_value': 1}
# A message created with some params
- result = self.message(msgid) % changing_dict
+ result = _message.Message(msgid) % changing_dict
# The parameters may change
changing_dict['current_value'] = 2
# Even if the param changes when the message is
@@ -196,7 +195,7 @@ class MessageTestCase(test_base.BaseTestCase):
changing_list = list([1, 2, 3])
params = {'current_list': changing_list}
# Apply the params
- result = self.message(msgid) % params
+ result = _message.Message(msgid) % params
# Change the list
changing_list.append(4)
# Even though the list changed the message
@@ -207,24 +206,24 @@ class MessageTestCase(test_base.BaseTestCase):
msgid = "Value: %s"
params = utils.NoDeepCopyObject(5)
# Apply the params
- result = self.message(msgid) % params
+ result = _message.Message(msgid) % params
self.assertEqual(result.translate(), "Value: 5")
def test_mod_deep_copies_param_nodeep_dict(self):
msgid = "Values: %(val1)s %(val2)s"
params = {'val1': 1, 'val2': utils.NoDeepCopyObject(2)}
# Apply the params
- result = self.message(msgid) % params
+ result = _message.Message(msgid) % params
self.assertEqual(result.translate(), "Values: 1 2")
# Apply again to make sure other path works as well
params = {'val1': 3, 'val2': utils.NoDeepCopyObject(4)}
- result = self.message(msgid) % params
+ result = _message.Message(msgid) % params
self.assertEqual(result.translate(), "Values: 3 4")
def test_mod_returns_a_copy(self):
msgid = "Some msgid string: %(test1)s %(test2)s"
- message = self.message(msgid)
+ message = _message.Message(msgid)
m1 = message % {'test1': 'foo', 'test2': 'bar'}
m2 = message % {'test1': 'foo2', 'test2': 'bar2'}
@@ -237,13 +236,13 @@ class MessageTestCase(test_base.BaseTestCase):
def test_mod_with_none_parameter(self):
msgid = "Some string with params: %s"
- message = self.message(msgid) % None
+ message = _message.Message(msgid) % None
self.assertEqual(msgid % None, message)
self.assertEqual(msgid % None, message.translate())
def test_mod_with_missing_parameters(self):
msgid = "Some string with params: %s %s"
- test_me = lambda: self.message(msgid) % 'just one'
+ test_me = lambda: _message.Message(msgid) % 'just one'
# Just like with strings missing parameters raise TypeError
self.assertRaises(TypeError, test_me)
@@ -253,7 +252,7 @@ class MessageTestCase(test_base.BaseTestCase):
'param2': 'test2',
'param3': 'notinstring'}
- result = self.message(msgid) % params
+ result = _message.Message(msgid) % params
expected = msgid % params
self.assertEqual(result, expected)
@@ -268,31 +267,31 @@ class MessageTestCase(test_base.BaseTestCase):
params = {'param1': 'test',
'param2': 'test2'}
- test_me = lambda: self.message(msgid) % params
+ test_me = lambda: _message.Message(msgid) % params
# Just like with strings missing named parameters raise KeyError
self.assertRaises(KeyError, test_me)
def test_add_disabled(self):
msgid = "A message"
- test_me = lambda: self.message(msgid) + ' some string'
+ test_me = lambda: _message.Message(msgid) + ' some string'
self.assertRaises(TypeError, test_me)
def test_radd_disabled(self):
msgid = "A message"
- test_me = lambda: utils.SomeObject('test') + self.message(msgid)
+ test_me = lambda: utils.SomeObject('test') + _message.Message(msgid)
self.assertRaises(TypeError, test_me)
@testtools.skipIf(six.PY3, 'test specific to Python 2')
def test_str_disabled(self):
msgid = "A message"
- test_me = lambda: str(self.message(msgid))
+ test_me = lambda: str(_message.Message(msgid))
self.assertRaises(UnicodeError, test_me)
@mock.patch('gettext.translation')
def test_translate(self, mock_translation):
en_message = 'A message in the default locale'
es_translation = 'A message in Spanish'
- message = gettextutils.Message(en_message)
+ message = _message.Message(en_message)
es_translations = {en_message: es_translation}
translations_map = {'es': es_translations}
@@ -305,7 +304,7 @@ class MessageTestCase(test_base.BaseTestCase):
def test_translate_message_from_unicoded_object(self, mock_translation):
en_message = 'A message in the default locale'
es_translation = 'A message in Spanish'
- message = gettextutils.Message(en_message)
+ message = _message.Message(en_message)
es_translations = {en_message: es_translation}
translations_map = {'es': es_translations}
translator = fakes.FakeTranslations.translator(translations_map)
@@ -323,7 +322,7 @@ class MessageTestCase(test_base.BaseTestCase):
en_message = 'A message in the default locale'
es_translation = 'A message in Spanish'
zh_translation = 'A message in Chinese'
- message = gettextutils.Message(en_message)
+ message = _message.Message(en_message)
es_translations = {en_message: es_translation}
zh_translations = {en_message: zh_translation}
@@ -348,7 +347,7 @@ class MessageTestCase(test_base.BaseTestCase):
translator = fakes.FakeTranslations.translator({'es': translations})
mock_translation.side_effect = translator
- msg = gettextutils.Message(message_with_params)
+ msg = _message.Message(message_with_params)
msg = msg % param
default_translation = message_with_params % param
@@ -368,8 +367,8 @@ class MessageTestCase(test_base.BaseTestCase):
translator = fakes.FakeTranslations.translator({'es': translations})
mock_translation.side_effect = translator
- msg = gettextutils.Message(message_with_params)
- param_msg = gettextutils.Message(param)
+ msg = _message.Message(message_with_params)
+ param_msg = _message.Message(param)
# Here we are testing translation of a Message with another object
# that can be translated via its unicode() representation, this is
@@ -394,7 +393,7 @@ class MessageTestCase(test_base.BaseTestCase):
translator = fakes.FakeTranslations.translator({'es': translations})
mock_translation.side_effect = translator
- msg = gettextutils.Message(message_with_params)
+ msg = _message.Message(message_with_params)
msg = msg % param
default_translation = message_with_params % param
@@ -418,8 +417,8 @@ class MessageTestCase(test_base.BaseTestCase):
translator = fakes.FakeTranslations.translator({'es': translations})
mock_translation.side_effect = translator
- msg = gettextutils.Message(message_with_params)
- msg_param = gettextutils.Message(message_param)
+ msg = _message.Message(message_with_params)
+ msg_param = _message.Message(message_param)
msg = msg % msg_param
default_translation = message_with_params % message_param
@@ -442,9 +441,9 @@ class MessageTestCase(test_base.BaseTestCase):
translator = fakes.FakeTranslations.translator({'es': translations})
mock_translation.side_effect = translator
- msg = gettextutils.Message(message_with_params)
- param_1 = gettextutils.Message(message_param)
- param_2 = gettextutils.Message(another_message_param)
+ msg = _message.Message(message_with_params)
+ param_1 = _message.Message(message_param)
+ param_2 = _message.Message(another_message_param)
msg = msg % (param_1, param_2)
default_translation = message_with_params % (message_param,
@@ -466,8 +465,8 @@ class MessageTestCase(test_base.BaseTestCase):
translator = fakes.FakeTranslations.translator({'es': translations})
mock_translation.side_effect = translator
- msg = gettextutils.Message(message_with_params)
- msg_param = gettextutils.Message(message_param)
+ msg = _message.Message(message_with_params)
+ msg_param = _message.Message(message_param)
msg = msg % {'param': msg_param}
default_translation = message_with_params % {'param': message_param}
@@ -503,8 +502,8 @@ class MessageTestCase(test_base.BaseTestCase):
mock_translation.side_effect = translator
mock_getdefaultlocale.return_value = ('es',)
- msg = gettextutils.Message(message_with_params)
- msg_param = gettextutils.Message(message_param)
+ msg = _message.Message(message_with_params)
+ msg_param = _message.Message(message_param)
msg = msg % {'param': msg_param}
es_translation = es_translation % {'param': es_param_translation}
diff --git a/tests/test_translate.py b/tests/test_translate.py
new file mode 100644
index 0000000..2fad7c3
--- /dev/null
+++ b/tests/test_translate.py
@@ -0,0 +1,46 @@
+# Copyright 2012 Red Hat, Inc.
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from __future__ import unicode_literals
+
+import mock
+
+from oslotest import base as test_base
+
+from tests import fakes
+from tests import utils
+
+from oslo.i18n import _message
+from oslo.i18n import _translate
+
+
+class TranslateTest(test_base.BaseTestCase):
+
+ @mock.patch('gettext.translation')
+ def test_translate(self, mock_translation):
+ en_message = 'A message in the default locale'
+ es_translation = 'A message in Spanish'
+ message = _message.Message(en_message)
+
+ es_translations = {en_message: es_translation}
+ translations_map = {'es': es_translations}
+ translator = fakes.FakeTranslations.translator(translations_map)
+ mock_translation.side_effect = translator
+
+ # translate() works on msgs and on objects whose unicode reps are msgs
+ obj = utils.SomeObject(message)
+ self.assertEqual(es_translation, _translate.translate(message, 'es'))
+ self.assertEqual(es_translation, _translate.translate(obj, 'es'))
diff --git a/tox.ini b/tox.ini
index b5a814a..5d9ffa4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -32,4 +32,8 @@ commands = python setup.py testr --coverage --testr-args='{posargs}'
show-source = True
ignore = E123,E125,H803
builtins = _
-exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build
+exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,
+
+[hacking]
+import_exceptions =
+ oslo.i18n._i18n._ \ No newline at end of file