2828import locale
2929from logging import handlers
3030import os
31- import re
3231
3332from babel import localedata
3433import six
3534
36- _localedir = os .environ .get ('openstackclient' .upper () + '_LOCALEDIR' )
37- _t = gettext .translation ('openstackclient' , localedir = _localedir , fallback = True )
38-
39- # We use separate translation catalogs for each log level, so set up a
40- # mapping between the log level name and the translator. The domain
41- # for the log level is project_name + "-log-" + log_level so messages
42- # for each level end up in their own catalog.
43- _t_log_levels = dict (
44- (level , gettext .translation ('openstackclient' + '-log-' + level ,
45- localedir = _localedir ,
46- fallback = True ))
47- for level in ['info' , 'warning' , 'error' , 'critical' ]
48- )
49-
5035_AVAILABLE_LANGUAGES = {}
36+
37+ # FIXME(dhellmann): Remove this when moving to oslo.i18n.
5138USE_LAZY = False
5239
5340
54- def enable_lazy ():
55- """Convenience function for configuring _() to use lazy gettext
56-
57- Call this at the start of execution to enable the gettextutils._
58- function to use lazy gettext functionality. This is useful if
59- your project is importing _ directly instead of using the
60- gettextutils.install() way of importing the _ function.
41+ class TranslatorFactory (object ):
42+ """Create translator functions
6143 """
62- global USE_LAZY
63- USE_LAZY = True
6444
45+ def __init__ (self , domain , lazy = False , localedir = None ):
46+ """Establish a set of translation functions for the domain.
47+
48+ :param domain: Name of translation domain,
49+ specifying a message catalog.
50+ :type domain: str
51+ :param lazy: Delays translation until a message is emitted.
52+ Defaults to False.
53+ :type lazy: Boolean
54+ :param localedir: Directory with translation catalogs.
55+ :type localedir: str
56+ """
57+ self .domain = domain
58+ self .lazy = lazy
59+ if localedir is None :
60+ localedir = os .environ .get (domain .upper () + '_LOCALEDIR' )
61+ self .localedir = localedir
6562
66- def _ (msg ):
67- if USE_LAZY :
68- return Message (msg , domain = 'openstackclient' )
69- else :
70- if six .PY3 :
71- return _t .gettext (msg )
72- return _t .ugettext (msg )
63+ def _make_translation_func (self , domain = None ):
64+ """Return a new translation function ready for use.
7365
66+ Takes into account whether or not lazy translation is being
67+ done.
7468
75- def _log_translation (msg , level ):
76- """Build a single translation of a log message
77- """
78- if USE_LAZY :
79- return Message (msg , domain = 'openstackclient' + '-log-' + level )
80- else :
81- translator = _t_log_levels [level ]
69+ The domain can be specified to override the default from the
70+ factory, but the localedir from the factory is always used
71+ because we assume the log-level translation catalogs are
72+ installed in the same directory as the main application
73+ catalog.
74+
75+ """
76+ if domain is None :
77+ domain = self .domain
78+ if self .lazy :
79+ return functools .partial (Message , domain = domain )
80+ t = gettext .translation (
81+ domain ,
82+ localedir = self .localedir ,
83+ fallback = True ,
84+ )
8285 if six .PY3 :
83- return translator .gettext (msg )
84- return translator .ugettext (msg )
86+ return t .gettext
87+ return t .ugettext
88+
89+ @property
90+ def primary (self ):
91+ "The default translation function."
92+ return self ._make_translation_func ()
93+
94+ def _make_log_translation_func (self , level ):
95+ return self ._make_translation_func (self .domain + '-log-' + level )
96+
97+ @property
98+ def log_info (self ):
99+ "Translate info-level log messages."
100+ return self ._make_log_translation_func ('info' )
101+
102+ @property
103+ def log_warning (self ):
104+ "Translate warning-level log messages."
105+ return self ._make_log_translation_func ('warning' )
106+
107+ @property
108+ def log_error (self ):
109+ "Translate error-level log messages."
110+ return self ._make_log_translation_func ('error' )
111+
112+ @property
113+ def log_critical (self ):
114+ "Translate critical-level log messages."
115+ return self ._make_log_translation_func ('critical' )
116+
117+
118+ # NOTE(dhellmann): When this module moves out of the incubator into
119+ # oslo.i18n, these global variables can be moved to an integration
120+ # module within each application.
121+
122+ # Create the global translation functions.
123+ _translators = TranslatorFactory ('openstackclient' )
124+
125+ # The primary translation function using the well-known name "_"
126+ _ = _translators .primary
85127
86128# Translators for log levels.
87129#
88130# The abbreviated names are meant to reflect the usual use of a short
89131# name like '_'. The "L" is for "log" and the other letter comes from
90132# the level.
91- _LI = functools .partial (_log_translation , level = 'info' )
92- _LW = functools .partial (_log_translation , level = 'warning' )
93- _LE = functools .partial (_log_translation , level = 'error' )
94- _LC = functools .partial (_log_translation , level = 'critical' )
133+ _LI = _translators .log_info
134+ _LW = _translators .log_warning
135+ _LE = _translators .log_error
136+ _LC = _translators .log_critical
137+
138+ # NOTE(dhellmann): End of globals that will move to the application's
139+ # integration module.
140+
141+
142+ def enable_lazy ():
143+ """Convenience function for configuring _() to use lazy gettext
144+
145+ Call this at the start of execution to enable the gettextutils._
146+ function to use lazy gettext functionality. This is useful if
147+ your project is importing _ directly instead of using the
148+ gettextutils.install() way of importing the _ function.
149+ """
150+ # FIXME(dhellmann): This function will be removed in oslo.i18n,
151+ # because the TranslatorFactory makes it superfluous.
152+ global _ , _LI , _LW , _LE , _LC , USE_LAZY
153+ tf = TranslatorFactory ('openstackclient' , lazy = True )
154+ _ = tf .primary
155+ _LI = tf .log_info
156+ _LW = tf .log_warning
157+ _LE = tf .log_error
158+ _LC = tf .log_critical
159+ USE_LAZY = True
95160
96161
97162def install (domain , lazy = False ):
@@ -113,26 +178,9 @@ def install(domain, lazy=False):
113178 any available locale.
114179 """
115180 if lazy :
116- # NOTE(mrodden): Lazy gettext functionality.
117- #
118- # The following introduces a deferred way to do translations on
119- # messages in OpenStack. We override the standard _() function
120- # and % (format string) operation to build Message objects that can
121- # later be translated when we have more information.
122- def _lazy_gettext (msg ):
123- """Create and return a Message object.
124-
125- Lazy gettext function for a given domain, it is a factory method
126- for a project/module to get a lazy gettext function for its own
127- translation domain (i.e. nova, glance, cinder, etc.)
128-
129- Message encapsulates a string so that we can translate
130- it later when needed.
131- """
132- return Message (msg , domain = domain )
133-
134181 from six import moves
135- moves .builtins .__dict__ ['_' ] = _lazy_gettext
182+ tf = TranslatorFactory (domain , lazy = True )
183+ moves .builtins .__dict__ ['_' ] = tf .primary
136184 else :
137185 localedir = '%s_LOCALEDIR' % domain .upper ()
138186 if six .PY3 :
@@ -248,47 +296,22 @@ def _sanitize_mod_params(self, other):
248296 if other is None :
249297 params = (other ,)
250298 elif isinstance (other , dict ):
251- params = self ._trim_dictionary_parameters (other )
252- else :
253- params = self ._copy_param (other )
254- return params
255-
256- def _trim_dictionary_parameters (self , dict_param ):
257- """Return a dict that only has matching entries in the msgid."""
258- # NOTE(luisg): Here we trim down the dictionary passed as parameters
259- # to avoid carrying a lot of unnecessary weight around in the message
260- # object, for example if someone passes in Message() % locals() but
261- # only some params are used, and additionally we prevent errors for
262- # non-deepcopyable objects by unicoding() them.
263-
264- # Look for %(param) keys in msgid;
265- # Skip %% and deal with the case where % is first character on the line
266- keys = re .findall ('(?:[^%]|^)?%\((\w*)\)[a-z]' , self .msgid )
267-
268- # If we don't find any %(param) keys but have a %s
269- if not keys and re .findall ('(?:[^%]|^)%[a-z]' , self .msgid ):
270- # Apparently the full dictionary is the parameter
271- params = self ._copy_param (dict_param )
272- else :
299+ # Merge the dictionaries
300+ # Copy each item in case one does not support deep copy.
273301 params = {}
274- # Save our existing parameters as defaults to protect
275- # ourselves from losing values if we are called through an
276- # (erroneous) chain that builds a valid Message with
277- # arguments, and then does something like "msg % kwds"
278- # where kwds is an empty dictionary.
279- src = {}
280302 if isinstance (self .params , dict ):
281- src .update (self .params )
282- src .update (dict_param )
283- for key in keys :
284- params [key ] = self ._copy_param (src [key ])
285-
303+ for key , val in self .params .items ():
304+ params [key ] = self ._copy_param (val )
305+ for key , val in other .items ():
306+ params [key ] = self ._copy_param (val )
307+ else :
308+ params = self ._copy_param (other )
286309 return params
287310
288311 def _copy_param (self , param ):
289312 try :
290313 return copy .deepcopy (param )
291- except TypeError :
314+ except Exception :
292315 # Fallback to casting to unicode this will handle the
293316 # python code-like objects that can't be deep-copied
294317 return six .text_type (param )
@@ -300,13 +323,14 @@ def __add__(self, other):
300323 def __radd__ (self , other ):
301324 return self .__add__ (other )
302325
303- def __str__ (self ):
304- # NOTE(luisg): Logging in python 2.6 tries to str() log records,
305- # and it expects specifically a UnicodeError in order to proceed.
306- msg = _ ('Message objects do not support str() because they may '
307- 'contain non-ascii characters. '
308- 'Please use unicode() or translate() instead.' )
309- raise UnicodeError (msg )
326+ if six .PY2 :
327+ def __str__ (self ):
328+ # NOTE(luisg): Logging in python 2.6 tries to str() log records,
329+ # and it expects specifically a UnicodeError in order to proceed.
330+ msg = _ ('Message objects do not support str() because they may '
331+ 'contain non-ascii characters. '
332+ 'Please use unicode() or translate() instead.' )
333+ raise UnicodeError (msg )
310334
311335
312336def get_available_languages (domain ):
0 commit comments