~xianghui/ubuntu/trusty/oslo.messaging/icehouse-lp1521958

« back to all changes in this revision

Viewing changes to oslo/messaging/openstack/common/gettextutils.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2014-02-07 14:32:19 UTC
  • mfrom: (1.1.2)
  • Revision ID: package-import@ubuntu.com-20140207143219-faeitvpdrypb1c9x
Tags: 1.3.0~a7-0ubuntu1
* New upstream release.
* debian/control:
  - Add python-yaml, python-babel, python-six, python-mox3,
    python-mock as build dependency.
  - Dropped python-d2to1 as a dependency.
* wrap and sort.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
 
 
3
1
# Copyright 2012 Red Hat, Inc.
 
2
# Copyright 2013 IBM Corp.
4
3
# All Rights Reserved.
5
 
# Copyright 2013 IBM Corp.
6
4
#
7
5
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
8
6
#    not use this file except in compliance with the License. You may obtain
21
19
 
22
20
Usual usage in an openstack.common module:
23
21
 
24
 
    from oslo.messaging.openstack.common.gettextutils import _
 
22
    from oslo.messaging.openstack.common.gettextutils import _  # noqa
25
23
"""
26
24
 
27
25
import copy
28
26
import gettext
29
 
import logging.handlers
 
27
import logging
30
28
import os
31
29
import re
32
 
import UserString
 
30
try:
 
31
    import UserString as _userString
 
32
except ImportError:
 
33
    import collections as _userString
33
34
 
 
35
from babel import localedata
34
36
import six
35
37
 
36
38
_localedir = os.environ.get('oslo.messaging'.upper() + '_LOCALEDIR')
37
39
_t = gettext.translation('oslo.messaging', localedir=_localedir, fallback=True)
38
40
 
 
41
_AVAILABLE_LANGUAGES = {}
 
42
USE_LAZY = False
 
43
 
 
44
 
 
45
def enable_lazy():
 
46
    """Convenience function for configuring _() to use lazy gettext
 
47
 
 
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.
 
52
    """
 
53
    global USE_LAZY
 
54
    USE_LAZY = True
 
55
 
39
56
 
40
57
def _(msg):
41
 
    return _t.ugettext(msg)
42
 
 
43
 
 
44
 
def install(domain):
 
58
    if USE_LAZY:
 
59
        return Message(msg, 'oslo.messaging')
 
60
    else:
 
61
        if six.PY3:
 
62
            return _t.gettext(msg)
 
63
        return _t.ugettext(msg)
 
64
 
 
65
 
 
66
def install(domain, lazy=False):
45
67
    """Install a _() function using the given translation domain.
46
68
 
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.
53
75
    NOVA_LOCALEDIR).
54
 
    """
55
 
    gettext.install(domain,
56
 
                    localedir=os.environ.get(domain.upper() + '_LOCALEDIR'),
57
 
                    unicode=True)
58
 
 
59
 
 
60
 
"""
61
 
Lazy gettext functionality.
62
 
 
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.
70
 
"""
71
 
 
72
 
 
73
 
def get_lazy_gettext(domain):
74
 
    """Assemble and return a lazy gettext function for a given domain.
75
 
 
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.)
78
 
    """
79
 
 
80
 
    def _lazy_gettext(msg):
81
 
        """Create and return a Message object.
82
 
 
83
 
        Message encapsulates a string so that we can translate it later when
84
 
        needed.
85
 
        """
86
 
        return Message(msg, domain)
87
 
 
88
 
    return _lazy_gettext
89
 
 
90
 
 
91
 
class Message(UserString.UserString, object):
 
76
 
 
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
 
82
                 any available locale.
 
83
    """
 
84
    if lazy:
 
85
        # NOTE(mrodden): Lazy gettext functionality.
 
86
        #
 
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.
 
91
        #
 
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.
 
95
 
 
96
        def _lazy_gettext(msg):
 
97
            """Create and return a Message object.
 
98
 
 
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.)
 
102
 
 
103
            Message encapsulates a string so that we can translate
 
104
            it later when needed.
 
105
            """
 
106
            return Message(msg, domain)
 
107
 
 
108
        from six import moves
 
109
        moves.builtins.__dict__['_'] = _lazy_gettext
 
110
    else:
 
111
        localedir = '%s_LOCALEDIR' % domain.upper()
 
112
        if six.PY3:
 
113
            gettext.install(domain,
 
114
                            localedir=os.environ.get(localedir))
 
115
        else:
 
116
            gettext.install(domain,
 
117
                            localedir=os.environ.get(localedir),
 
118
                            unicode=True)
 
119
 
 
120
 
 
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
95
125
        self._msg = msg
96
126
        self._left_extra_msg = ''
97
127
        self._right_extra_msg = ''
 
128
        self._locale = None
98
129
        self.params = None
99
 
        self.locale = None
100
130
        self.domain = domain
101
131
 
102
132
    @property
116
146
                                       localedir=localedir,
117
147
                                       fallback=True)
118
148
 
 
149
        if six.PY3:
 
150
            ugettext = lang.gettext
 
151
        else:
 
152
            ugettext = lang.ugettext
 
153
 
119
154
        full_msg = (self._left_extra_msg +
120
 
                    lang.ugettext(self._msg) +
 
155
                    ugettext(self._msg) +
121
156
                    self._right_extra_msg)
122
157
 
123
158
        if self.params is not None:
125
160
 
126
161
        return six.text_type(full_msg)
127
162
 
 
163
    @property
 
164
    def locale(self):
 
165
        return self._locale
 
166
 
 
167
    @locale.setter
 
168
    def locale(self, value):
 
169
        self._locale = value
 
170
        if not self.params:
 
171
            return
 
172
 
 
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
 
179
            return
 
180
        if isinstance(self.params, tuple):
 
181
            for param in self.params:
 
182
                if isinstance(param, Message):
 
183
                    param.locale = value
 
184
            return
 
185
        if isinstance(self.params, dict):
 
186
            for param in self.params.values():
 
187
                if isinstance(param, Message):
 
188
                    param.locale = value
 
189
 
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)
134
196
 
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):
143
205
                    params[key] = copy.deepcopy(dict_param[key])
144
206
                except TypeError:
145
207
                    # cast uncopyable thing to unicode string
146
 
                    params[key] = unicode(dict_param[key])
 
208
                    params[key] = six.text_type(dict_param[key])
147
209
 
148
210
        return params
149
211
 
162
224
            try:
163
225
                self.params = copy.deepcopy(other)
164
226
            except TypeError:
165
 
                self.params = unicode(other)
 
227
                self.params = six.text_type(other)
166
228
 
167
229
        return self
168
230
 
171
233
        return self.data
172
234
 
173
235
    def __str__(self):
 
236
        if six.PY3:
 
237
            return self.__unicode__()
174
238
        return self.data.encode('utf-8')
175
239
 
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])
229
293
        if name in ops:
230
294
            return getattr(self.data, name)
231
295
        else:
232
 
            return UserString.UserString.__getattribute__(self, name)
 
296
            return _userString.UserString.__getattribute__(self, name)
 
297
 
 
298
 
 
299
def get_available_languages(domain):
 
300
    """Lists the available languages for the given translation domain.
 
301
 
 
302
    :param domain: the domain to get languages for
 
303
    """
 
304
    if domain in _AVAILABLE_LANGUAGES:
 
305
        return copy.copy(_AVAILABLE_LANGUAGES[domain])
 
306
 
 
307
    localedir = '%s_LOCALEDIR' % domain.upper()
 
308
    find = lambda x: gettext.find(domain,
 
309
                                  localedir=os.environ.get(localedir),
 
310
                                  languages=[x])
 
311
 
 
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)
 
327
 
 
328
 
 
329
def get_localized_message(message, user_locale):
 
330
    """Gets a localized version of the given message in the given locale.
 
331
 
 
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.
 
334
 
 
335
    :returns: the translated message in unicode, or the original message if
 
336
              it could not be translated
 
337
    """
 
338
    translated = message
 
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
 
344
    return translated
233
345
 
234
346
 
235
347
class LocaleHandler(logging.Handler):