11# vim: tabstop=4 shiftwidth=4 softtabstop=4
22
33# Copyright 2012 Red Hat, Inc.
4- # All Rights Reserved.
54# Copyright 2013 IBM Corp.
5+ # All Rights Reserved.
66#
77# Licensed under the Apache License, Version 2.0 (the "License"); you may
88# not use this file except in compliance with the License. You may obtain
2626
2727import copy
2828import gettext
29- import logging . handlers
29+ import logging
3030import os
3131import re
32- import UserString
32+ try :
33+ import UserString as _userString
34+ except ImportError :
35+ import collections as _userString
3336
37+ from babel import localedata
3438import six
3539
3640_localedir = os .environ .get ('openstackclient' .upper () + '_LOCALEDIR' )
3741_t = gettext .translation ('openstackclient' , localedir = _localedir , fallback = True )
3842
43+ _AVAILABLE_LANGUAGES = {}
44+ USE_LAZY = False
45+
46+
47+ def enable_lazy ():
48+ """Convenience function for configuring _() to use lazy gettext
49+
50+ Call this at the start of execution to enable the gettextutils._
51+ function to use lazy gettext functionality. This is useful if
52+ your project is importing _ directly instead of using the
53+ gettextutils.install() way of importing the _ function.
54+ """
55+ global USE_LAZY
56+ USE_LAZY = True
57+
3958
4059def _ (msg ):
41- return _t .ugettext (msg )
60+ if USE_LAZY :
61+ return Message (msg , 'openstackclient' )
62+ else :
63+ if six .PY3 :
64+ return _t .gettext (msg )
65+ return _t .ugettext (msg )
4266
4367
44- def install (domain ):
68+ def install (domain , lazy = False ):
4569 """Install a _() function using the given translation domain.
4670
4771 Given a translation domain, install a _() function using gettext's
@@ -51,52 +75,60 @@ def install(domain):
5175 overriding the default localedir (e.g. /usr/share/locale) using
5276 a translation-domain-specific environment variable (e.g.
5377 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.
7578
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.)
79+ :param domain: the translation domain
80+ :param lazy: indicates whether or not to install the lazy _() function.
81+ The lazy _() introduces a way to do deferred translation
82+ of messages by installing a _ that builds Message objects,
83+ instead of strings, which can then be lazily translated into
84+ any available locale.
7885 """
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
86+ if lazy :
87+ # NOTE(mrodden): Lazy gettext functionality.
88+ #
89+ # The following introduces a deferred way to do translations on
90+ # messages in OpenStack. We override the standard _() function
91+ # and % (format string) operation to build Message objects that can
92+ # later be translated when we have more information.
93+ #
94+ # Also included below is an example LocaleHandler that translates
95+ # Messages to an associated locale, effectively allowing many logs,
96+ # each with their own locale.
97+
98+ def _lazy_gettext (msg ):
99+ """Create and return a Message object.
100+
101+ Lazy gettext function for a given domain, it is a factory method
102+ for a project/module to get a lazy gettext function for its own
103+ translation domain (i.e. nova, glance, cinder, etc.)
104+
105+ Message encapsulates a string so that we can translate
106+ it later when needed.
107+ """
108+ return Message (msg , domain )
109+
110+ from six import moves
111+ moves .builtins .__dict__ ['_' ] = _lazy_gettext
112+ else :
113+ localedir = '%s_LOCALEDIR' % domain .upper ()
114+ if six .PY3 :
115+ gettext .install (domain ,
116+ localedir = os .environ .get (localedir ))
117+ else :
118+ gettext .install (domain ,
119+ localedir = os .environ .get (localedir ),
120+ unicode = True )
89121
90122
91- class Message (UserString .UserString , object ):
123+ class Message (_userString .UserString , object ):
92124 """Class used to encapsulate translatable messages."""
93125 def __init__ (self , msg , domain ):
94126 # _msg is the gettext msgid and should never change
95127 self ._msg = msg
96128 self ._left_extra_msg = ''
97129 self ._right_extra_msg = ''
130+ self ._locale = None
98131 self .params = None
99- self .locale = None
100132 self .domain = domain
101133
102134 @property
@@ -116,21 +148,53 @@ def data(self):
116148 localedir = localedir ,
117149 fallback = True )
118150
151+ if six .PY3 :
152+ ugettext = lang .gettext
153+ else :
154+ ugettext = lang .ugettext
155+
119156 full_msg = (self ._left_extra_msg +
120- lang . ugettext (self ._msg ) +
157+ ugettext (self ._msg ) +
121158 self ._right_extra_msg )
122159
123160 if self .params is not None :
124161 full_msg = full_msg % self .params
125162
126163 return six .text_type (full_msg )
127164
165+ @property
166+ def locale (self ):
167+ return self ._locale
168+
169+ @locale .setter
170+ def locale (self , value ):
171+ self ._locale = value
172+ if not self .params :
173+ return
174+
175+ # This Message object may have been constructed with one or more
176+ # Message objects as substitution parameters, given as a single
177+ # Message, or a tuple or Map containing some, so when setting the
178+ # locale for this Message we need to set it for those Messages too.
179+ if isinstance (self .params , Message ):
180+ self .params .locale = value
181+ return
182+ if isinstance (self .params , tuple ):
183+ for param in self .params :
184+ if isinstance (param , Message ):
185+ param .locale = value
186+ return
187+ if isinstance (self .params , dict ):
188+ for param in self .params .values ():
189+ if isinstance (param , Message ):
190+ param .locale = value
191+
128192 def _save_dictionary_parameter (self , dict_param ):
129193 full_msg = self .data
130194 # look for %(blah) fields in string;
131195 # ignore %% and deal with the
132196 # case where % is first character on the line
133- keys = re .findall ('(?:[^%]|^)%\((\w*)\)[a-z]' , full_msg )
197+ keys = re .findall ('(?:[^%]|^)? %\((\w*)\)[a-z]' , full_msg )
134198
135199 # if we don't find any %(blah) blocks but have a %s
136200 if not keys and re .findall ('(?:[^%]|^)%[a-z]' , full_msg ):
@@ -143,7 +207,7 @@ def _save_dictionary_parameter(self, dict_param):
143207 params [key ] = copy .deepcopy (dict_param [key ])
144208 except TypeError :
145209 # cast uncopyable thing to unicode string
146- params [key ] = unicode (dict_param [key ])
210+ params [key ] = six . text_type (dict_param [key ])
147211
148212 return params
149213
@@ -162,7 +226,7 @@ def _save_parameters(self, other):
162226 try :
163227 self .params = copy .deepcopy (other )
164228 except TypeError :
165- self .params = unicode (other )
229+ self .params = six . text_type (other )
166230
167231 return self
168232
@@ -171,11 +235,13 @@ def __unicode__(self):
171235 return self .data
172236
173237 def __str__ (self ):
238+ if six .PY3 :
239+ return self .__unicode__ ()
174240 return self .data .encode ('utf-8' )
175241
176242 def __getstate__ (self ):
177243 to_copy = ['_msg' , '_right_extra_msg' , '_left_extra_msg' ,
178- 'domain' , 'params' , 'locale ' ]
244+ 'domain' , 'params' , '_locale ' ]
179245 new_dict = self .__dict__ .fromkeys (to_copy )
180246 for attr in to_copy :
181247 new_dict [attr ] = copy .deepcopy (self .__dict__ [attr ])
@@ -229,7 +295,47 @@ def __getattribute__(self, name):
229295 if name in ops :
230296 return getattr (self .data , name )
231297 else :
232- return UserString .UserString .__getattribute__ (self , name )
298+ return _userString .UserString .__getattribute__ (self , name )
299+
300+
301+ def get_available_languages (domain ):
302+ """Lists the available languages for the given translation domain.
303+
304+ :param domain: the domain to get languages for
305+ """
306+ if domain in _AVAILABLE_LANGUAGES :
307+ return copy .copy (_AVAILABLE_LANGUAGES [domain ])
308+
309+ localedir = '%s_LOCALEDIR' % domain .upper ()
310+ find = lambda x : gettext .find (domain ,
311+ localedir = os .environ .get (localedir ),
312+ languages = [x ])
313+
314+ # NOTE(mrodden): en_US should always be available (and first in case
315+ # order matters) since our in-line message strings are en_US
316+ language_list = ['en_US' ]
317+ # NOTE(luisg): Babel <1.0 used a function called list(), which was
318+ # renamed to locale_identifiers() in >=1.0, the requirements master list
319+ # requires >=0.9.6, uncapped, so defensively work with both. We can remove
320+ # this check when the master list updates to >=1.0, and all projects udpate
321+ list_identifiers = (getattr (localedata , 'list' , None ) or
322+ getattr (localedata , 'locale_identifiers' ))
323+ locale_identifiers = list_identifiers ()
324+ for i in locale_identifiers :
325+ if find (i ) is not None :
326+ language_list .append (i )
327+ _AVAILABLE_LANGUAGES [domain ] = language_list
328+ return copy .copy (language_list )
329+
330+
331+ def get_localized_message (message , user_locale ):
332+ """Gets a localized version of the given message in the given locale."""
333+ if isinstance (message , Message ):
334+ if user_locale :
335+ message .locale = user_locale
336+ return six .text_type (message )
337+ else :
338+ return message
233339
234340
235341class LocaleHandler (logging .Handler ):
0 commit comments