Add API for creating translation functions
After gettextutils moves to a library, we will not be able to rely on a single set of global translation functions. Each library will need its own functions, using a different translation domain, so the right message catalogs can be loaded. Since we know when the factory is created which version of Python is used and whether or not lazy translation should be enabled, return functions that do the translation without checking those settings at runtime for each message. bp graduate-oslo-i18n Change-Id: I0a177f70e71f1a5708d9e50ff0399eba6ec02c1c
This commit is contained in:
parent
a31d3f01dc
commit
574adfa16d
@ -32,24 +32,113 @@ import os
|
||||
from babel import localedata
|
||||
import six
|
||||
|
||||
_localedir = os.environ.get('oslo'.upper() + '_LOCALEDIR')
|
||||
_t = gettext.translation('oslo', localedir=_localedir, fallback=True)
|
||||
|
||||
# We use separate translation catalogs for each log level, so set up a
|
||||
# mapping between the log level name and the translator. The domain
|
||||
# for the log level is project_name + "-log-" + log_level so messages
|
||||
# for each level end up in their own catalog.
|
||||
_t_log_levels = dict(
|
||||
(level, gettext.translation('oslo' + '-log-' + level,
|
||||
localedir=_localedir,
|
||||
fallback=True))
|
||||
for level in ['info', 'warning', 'error', 'critical']
|
||||
)
|
||||
|
||||
_AVAILABLE_LANGUAGES = {}
|
||||
|
||||
# FIXME(dhellmann): Remove this when moving to oslo.i18n.
|
||||
USE_LAZY = False
|
||||
|
||||
|
||||
class TranslatorFactory(object):
|
||||
"""Create translator functions
|
||||
"""
|
||||
|
||||
def __init__(self, domain, lazy=False, 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 lazy: Delays translation until a message is emitted.
|
||||
Defaults to False.
|
||||
:type lazy: Boolean
|
||||
:param localedir: Directory with translation catalogs.
|
||||
:type localedir: str
|
||||
"""
|
||||
self.domain = domain
|
||||
self.lazy = lazy
|
||||
if localedir is None:
|
||||
localedir = os.environ.get(domain.upper() + '_LOCALEDIR')
|
||||
self.localedir = localedir
|
||||
|
||||
def _make_translation_func(self, domain=None):
|
||||
"""Return a new translation function ready for use.
|
||||
|
||||
Takes into account whether or not lazy translation is being
|
||||
done.
|
||||
|
||||
The domain 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
|
||||
if self.lazy:
|
||||
return functools.partial(Message, domain=domain)
|
||||
t = gettext.translation(
|
||||
domain,
|
||||
localedir=self.localedir,
|
||||
fallback=True,
|
||||
)
|
||||
if six.PY3:
|
||||
return t.gettext
|
||||
return t.ugettext
|
||||
|
||||
@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():
|
||||
"""Convenience function for configuring _() to use lazy gettext
|
||||
|
||||
@ -58,41 +147,18 @@ def enable_lazy():
|
||||
your project is importing _ directly instead of using the
|
||||
gettextutils.install() way of importing the _ function.
|
||||
"""
|
||||
global USE_LAZY
|
||||
# FIXME(dhellmann): This function will be removed in oslo.i18n,
|
||||
# because the TranslatorFactory makes it superfluous.
|
||||
global _, _LI, _LW, _LE, _LC, USE_LAZY
|
||||
tf = TranslatorFactory('oslo', lazy=True)
|
||||
_ = tf.primary
|
||||
_LI = tf.log_info
|
||||
_LW = tf.log_warning
|
||||
_LE = tf.log_error
|
||||
_LC = tf.log_critical
|
||||
USE_LAZY = True
|
||||
|
||||
|
||||
def _(msg):
|
||||
if USE_LAZY:
|
||||
return Message(msg, domain='oslo')
|
||||
else:
|
||||
if six.PY3:
|
||||
return _t.gettext(msg)
|
||||
return _t.ugettext(msg)
|
||||
|
||||
|
||||
def _log_translation(msg, level):
|
||||
"""Build a single translation of a log message
|
||||
"""
|
||||
if USE_LAZY:
|
||||
return Message(msg, domain='oslo' + '-log-' + level)
|
||||
else:
|
||||
translator = _t_log_levels[level]
|
||||
if six.PY3:
|
||||
return translator.gettext(msg)
|
||||
return translator.ugettext(msg)
|
||||
|
||||
# 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 = functools.partial(_log_translation, level='info')
|
||||
_LW = functools.partial(_log_translation, level='warning')
|
||||
_LE = functools.partial(_log_translation, level='error')
|
||||
_LC = functools.partial(_log_translation, level='critical')
|
||||
|
||||
|
||||
def install(domain, lazy=False):
|
||||
"""Install a _() function using the given translation domain.
|
||||
|
||||
@ -112,26 +178,9 @@ def install(domain, lazy=False):
|
||||
any available locale.
|
||||
"""
|
||||
if lazy:
|
||||
# NOTE(mrodden): Lazy gettext functionality.
|
||||
#
|
||||
# The following introduces a deferred way to do translations on
|
||||
# messages in OpenStack. We override the standard _() function
|
||||
# and % (format string) operation to build Message objects that can
|
||||
# later be translated when we have more information.
|
||||
def _lazy_gettext(msg):
|
||||
"""Create and return a Message object.
|
||||
|
||||
Lazy gettext function for a given domain, it is a factory method
|
||||
for a project/module to get a lazy gettext function for its own
|
||||
translation domain (i.e. nova, glance, cinder, etc.)
|
||||
|
||||
Message encapsulates a string so that we can translate
|
||||
it later when needed.
|
||||
"""
|
||||
return Message(msg, domain=domain)
|
||||
|
||||
from six import moves
|
||||
moves.builtins.__dict__['_'] = _lazy_gettext
|
||||
tf = TranslatorFactory(domain, lazy=True)
|
||||
moves.builtins.__dict__['_'] = tf.primary
|
||||
else:
|
||||
localedir = '%s_LOCALEDIR' % domain.upper()
|
||||
if six.PY3:
|
||||
|
@ -52,30 +52,6 @@ class GettextTest(test_base.BaseTestCase):
|
||||
# assert now enabled
|
||||
self.assertTrue(gettextutils.USE_LAZY)
|
||||
|
||||
def test_underscore_non_lazy(self):
|
||||
# set lazy off
|
||||
gettextutils.USE_LAZY = False
|
||||
|
||||
if six.PY3:
|
||||
self.mox.StubOutWithMock(gettextutils._t, 'gettext')
|
||||
gettextutils._t.gettext('blah').AndReturn('translated blah')
|
||||
else:
|
||||
self.mox.StubOutWithMock(gettextutils._t, 'ugettext')
|
||||
gettextutils._t.ugettext('blah').AndReturn('translated blah')
|
||||
self.mox.ReplayAll()
|
||||
|
||||
result = gettextutils._('blah')
|
||||
self.assertEqual('translated blah', result)
|
||||
|
||||
def test_underscore_lazy(self):
|
||||
# set lazy off
|
||||
gettextutils.USE_LAZY = False
|
||||
|
||||
gettextutils.enable_lazy()
|
||||
result = gettextutils._('blah')
|
||||
self.assertIsInstance(result, gettextutils.Message)
|
||||
self.assertEqual('oslo', result.domain)
|
||||
|
||||
def test_gettext_does_not_blow_up(self):
|
||||
LOG.info(gettextutils._('test'))
|
||||
|
||||
@ -740,6 +716,44 @@ class TranslationHandlerTestCase(test_base.BaseTestCase):
|
||||
self.assertIn(translation, self.stream.getvalue())
|
||||
|
||||
|
||||
class TranslatorFactoryTest(test_base.BaseTestCase):
|
||||
|
||||
def test_lazy(self):
|
||||
with mock.patch.object(gettextutils, 'Message') as msg:
|
||||
tf = gettextutils.TranslatorFactory('domain', lazy=True)
|
||||
tf.primary('some text')
|
||||
msg.assert_called_with('some text', domain='domain')
|
||||
|
||||
def test_py2(self):
|
||||
with mock.patch.object(six, 'PY3', False):
|
||||
with mock.patch('gettext.translation') as translation:
|
||||
trans = mock.Mock()
|
||||
translation.return_value = trans
|
||||
trans.gettext.side_effect = AssertionError(
|
||||
'should have called ugettext')
|
||||
tf = gettextutils.TranslatorFactory('domain', lazy=False)
|
||||
tf.primary('some text')
|
||||
trans.ugettext.assert_called_with('some text')
|
||||
|
||||
def test_py3(self):
|
||||
with mock.patch.object(six, 'PY3', True):
|
||||
with mock.patch('gettext.translation') as translation:
|
||||
trans = mock.Mock()
|
||||
translation.return_value = trans
|
||||
trans.ugettext.side_effect = AssertionError(
|
||||
'should have called gettext')
|
||||
tf = gettextutils.TranslatorFactory('domain', lazy=False)
|
||||
tf.primary('some text')
|
||||
trans.gettext.assert_called_with('some text')
|
||||
|
||||
def test_log_level_domain_name(self):
|
||||
with mock.patch.object(gettextutils.TranslatorFactory,
|
||||
'_make_translation_func') as mtf:
|
||||
tf = gettextutils.TranslatorFactory('domain', lazy=False)
|
||||
tf._make_log_translation_func('mylevel')
|
||||
mtf.assert_called_with('domain-log-mylevel')
|
||||
|
||||
|
||||
class LogLevelTranslationsTest(test_base.BaseTestCase):
|
||||
|
||||
scenarios = [
|
||||
@ -748,47 +762,12 @@ class LogLevelTranslationsTest(test_base.BaseTestCase):
|
||||
['info', 'warning', 'error', 'critical']
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
super(LogLevelTranslationsTest, self).setUp()
|
||||
moxfixture = self.useFixture(moxstubout.MoxStubout())
|
||||
self.stubs = moxfixture.stubs
|
||||
self.mox = moxfixture.mox
|
||||
# remember so we can reset to it later
|
||||
self._USE_LAZY = gettextutils.USE_LAZY
|
||||
# Stub out the appropriate translation logger
|
||||
if six.PY3:
|
||||
self.mox.StubOutWithMock(gettextutils._t_log_levels[self.level],
|
||||
'gettext')
|
||||
self.trans_func = gettextutils._t_log_levels[self.level].gettext
|
||||
else:
|
||||
self.mox.StubOutWithMock(gettextutils._t_log_levels[self.level],
|
||||
'ugettext')
|
||||
self.trans_func = gettextutils._t_log_levels[self.level].ugettext
|
||||
# Look up the translator function for the log level we are testing
|
||||
translator_name = '_L' + self.level[0].upper()
|
||||
self.translator = getattr(gettextutils, translator_name)
|
||||
|
||||
def tearDown(self):
|
||||
# reset to value before test
|
||||
gettextutils.USE_LAZY = self._USE_LAZY
|
||||
super(LogLevelTranslationsTest, self).tearDown()
|
||||
|
||||
def test_non_lazy(self):
|
||||
# set lazy off
|
||||
gettextutils.USE_LAZY = False
|
||||
|
||||
self.trans_func('blah').AndReturn('translated blah ' + self.level)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
result = self.translator('blah')
|
||||
self.assertEqual('translated blah ' + self.level, result)
|
||||
|
||||
def test_lazy(self):
|
||||
# set lazy on
|
||||
gettextutils.enable_lazy()
|
||||
result = self.translator('blah')
|
||||
self.assertIsInstance(result, gettextutils.Message)
|
||||
self.assertEqual('oslo-log-' + self.level, result.domain)
|
||||
def test(self):
|
||||
with mock.patch.object(gettextutils.TranslatorFactory,
|
||||
'_make_translation_func') as mtf:
|
||||
tf = gettextutils.TranslatorFactory('domain', lazy=False)
|
||||
getattr(tf, 'log_%s' % self.level)
|
||||
mtf.assert_called_with('domain-log-%s' % self.level)
|
||||
|
||||
|
||||
class SomeObject(object):
|
||||
|
Loading…
Reference in New Issue
Block a user