summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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