22
20
Usual usage in an openstack.common module:
24
from oslo.messaging.openstack.common.gettextutils import _
22
from oslo.messaging.openstack.common.gettextutils import _ # noqa
29
import logging.handlers
31
import UserString as _userString
33
import collections as _userString
35
from babel import localedata
36
38
_localedir = os.environ.get('oslo.messaging'.upper() + '_LOCALEDIR')
37
39
_t = gettext.translation('oslo.messaging', localedir=_localedir, fallback=True)
41
_AVAILABLE_LANGUAGES = {}
46
"""Convenience function for configuring _() to use lazy gettext
48
Call this at the start of execution to enable the gettextutils._
49
function to use lazy gettext functionality. This is useful if
50
your project is importing _ directly instead of using the
51
gettextutils.install() way of importing the _ function.
41
return _t.ugettext(msg)
59
return Message(msg, 'oslo.messaging')
62
return _t.gettext(msg)
63
return _t.ugettext(msg)
66
def install(domain, lazy=False):
45
67
"""Install a _() function using the given translation domain.
47
69
Given a translation domain, install a _() function using gettext's
51
73
overriding the default localedir (e.g. /usr/share/locale) using
52
74
a translation-domain-specific environment variable (e.g.
55
gettext.install(domain,
56
localedir=os.environ.get(domain.upper() + '_LOCALEDIR'),
61
Lazy gettext functionality.
63
The following is an attempt to introduce a deferred way
64
to do translations on messages in OpenStack. We attempt to
65
override the standard _() function and % (format string) operation
66
to build Message objects that can later be translated when we have
67
more information. Also included is an example LogHandler that
68
translates Messages to an associated locale, effectively allowing
69
many logs, each with their own locale.
73
def get_lazy_gettext(domain):
74
"""Assemble and return a lazy gettext function for a given domain.
76
Factory method for a project/module to get a lazy gettext function
77
for its own translation domain (i.e. nova, glance, cinder, etc.)
80
def _lazy_gettext(msg):
81
"""Create and return a Message object.
83
Message encapsulates a string so that we can translate it later when
86
return Message(msg, domain)
91
class Message(UserString.UserString, object):
77
:param domain: the translation domain
78
:param lazy: indicates whether or not to install the lazy _() function.
79
The lazy _() introduces a way to do deferred translation
80
of messages by installing a _ that builds Message objects,
81
instead of strings, which can then be lazily translated into
85
# NOTE(mrodden): Lazy gettext functionality.
87
# The following introduces a deferred way to do translations on
88
# messages in OpenStack. We override the standard _() function
89
# and % (format string) operation to build Message objects that can
90
# later be translated when we have more information.
92
# Also included below is an example LocaleHandler that translates
93
# Messages to an associated locale, effectively allowing many logs,
94
# each with their own locale.
96
def _lazy_gettext(msg):
97
"""Create and return a Message object.
99
Lazy gettext function for a given domain, it is a factory method
100
for a project/module to get a lazy gettext function for its own
101
translation domain (i.e. nova, glance, cinder, etc.)
103
Message encapsulates a string so that we can translate
104
it later when needed.
106
return Message(msg, domain)
108
from six import moves
109
moves.builtins.__dict__['_'] = _lazy_gettext
111
localedir = '%s_LOCALEDIR' % domain.upper()
113
gettext.install(domain,
114
localedir=os.environ.get(localedir))
116
gettext.install(domain,
117
localedir=os.environ.get(localedir),
121
class Message(_userString.UserString, object):
92
122
"""Class used to encapsulate translatable messages."""
93
123
def __init__(self, msg, domain):
94
124
# _msg is the gettext msgid and should never change
96
126
self._left_extra_msg = ''
97
127
self._right_extra_msg = ''
98
129
self.params = None
100
130
self.domain = domain
126
161
return six.text_type(full_msg)
168
def locale(self, value):
173
# This Message object may have been constructed with one or more
174
# Message objects as substitution parameters, given as a single
175
# Message, or a tuple or Map containing some, so when setting the
176
# locale for this Message we need to set it for those Messages too.
177
if isinstance(self.params, Message):
178
self.params.locale = value
180
if isinstance(self.params, tuple):
181
for param in self.params:
182
if isinstance(param, Message):
185
if isinstance(self.params, dict):
186
for param in self.params.values():
187
if isinstance(param, Message):
128
190
def _save_dictionary_parameter(self, dict_param):
129
191
full_msg = self.data
130
192
# look for %(blah) fields in string;
131
193
# ignore %% and deal with the
132
194
# case where % is first character on the line
133
keys = re.findall('(?:[^%]|^)%\((\w*)\)[a-z]', full_msg)
195
keys = re.findall('(?:[^%]|^)?%\((\w*)\)[a-z]', full_msg)
135
197
# if we don't find any %(blah) blocks but have a %s
136
198
if not keys and re.findall('(?:[^%]|^)%[a-z]', full_msg):
173
235
def __str__(self):
237
return self.__unicode__()
174
238
return self.data.encode('utf-8')
176
240
def __getstate__(self):
177
241
to_copy = ['_msg', '_right_extra_msg', '_left_extra_msg',
178
'domain', 'params', 'locale']
242
'domain', 'params', '_locale']
179
243
new_dict = self.__dict__.fromkeys(to_copy)
180
244
for attr in to_copy:
181
245
new_dict[attr] = copy.deepcopy(self.__dict__[attr])
230
294
return getattr(self.data, name)
232
return UserString.UserString.__getattribute__(self, name)
296
return _userString.UserString.__getattribute__(self, name)
299
def get_available_languages(domain):
300
"""Lists the available languages for the given translation domain.
302
:param domain: the domain to get languages for
304
if domain in _AVAILABLE_LANGUAGES:
305
return copy.copy(_AVAILABLE_LANGUAGES[domain])
307
localedir = '%s_LOCALEDIR' % domain.upper()
308
find = lambda x: gettext.find(domain,
309
localedir=os.environ.get(localedir),
312
# NOTE(mrodden): en_US should always be available (and first in case
313
# order matters) since our in-line message strings are en_US
314
language_list = ['en_US']
315
# NOTE(luisg): Babel <1.0 used a function called list(), which was
316
# renamed to locale_identifiers() in >=1.0, the requirements master list
317
# requires >=0.9.6, uncapped, so defensively work with both. We can remove
318
# this check when the master list updates to >=1.0, and update all projects
319
list_identifiers = (getattr(localedata, 'list', None) or
320
getattr(localedata, 'locale_identifiers'))
321
locale_identifiers = list_identifiers()
322
for i in locale_identifiers:
323
if find(i) is not None:
324
language_list.append(i)
325
_AVAILABLE_LANGUAGES[domain] = language_list
326
return copy.copy(language_list)
329
def get_localized_message(message, user_locale):
330
"""Gets a localized version of the given message in the given locale.
332
If the message is not a Message object the message is returned as-is.
333
If the locale is None the message is translated to the default locale.
335
:returns: the translated message in unicode, or the original message if
336
it could not be translated
339
if isinstance(message, Message):
340
original_locale = message.locale
341
message.locale = user_locale
342
translated = six.text_type(message)
343
message.locale = original_locale
235
347
class LocaleHandler(logging.Handler):