Package flumotion :: Package common :: Module messages
[hide private]

Source Code for Module flumotion.common.messages

  1  # -*- Mode: Python; test-case-name: flumotion.test.test_common_messages -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3  # 
  4  # Flumotion - a streaming media server 
  5  # Copyright (C) 2004,2005,2006,2007 Fluendo, S.L. (www.fluendo.com). 
  6  # All rights reserved. 
  7   
  8  # This file may be distributed and/or modified under the terms of 
  9  # the GNU General Public License version 2 as published by 
 10  # the Free Software Foundation. 
 11  # This file is distributed without any warranty; without even the implied 
 12  # warranty of merchantability or fitness for a particular purpose. 
 13  # See "LICENSE.GPL" in the source distribution for more information. 
 14   
 15  # Licensees having purchased or holding a valid Flumotion Advanced 
 16  # Streaming Server license may use this file in accordance with the 
 17  # Flumotion Advanced Streaming Server Commercial License Agreement. 
 18  # See "LICENSE.Flumotion" in the source distribution for more information. 
 19   
 20  # Headers in this file shall remain intact. 
 21   
 22  """ 
 23  support for serializable translatable messages from component/manager to admin 
 24  """ 
 25   
 26  import time 
 27  import gettext 
 28   
 29  from flumotion.common import log 
 30  from twisted.spread import pb 
 31  from twisted.python import util 
 32   
 33  ERROR = 1 
 34  WARNING = 2 
 35  INFO = 3 
 36   
37 -def N_(format):
38 """ 39 Mark a singular string for translation, without translating it. 40 """ 41 return format
42
43 -def ngettext(singular, plural, count):
44 """ 45 Mark a plural string for translation, without translating it. 46 """ 47 return (singular, plural, count)
48
49 -def gettexter(domain):
50 """ 51 Return a function that takes a format string or tuple, and additional 52 format args, 53 and creates a L{Translatable} from it. 54 55 Example:: 56 57 T_ = messages.gettexter('flumotion') 58 t = T_(N_("Could not find '%s'."), file) 59 60 @param domain: the gettext domain to create translatables for. 61 """ 62 def create(format, *args): 63 if isinstance(format, str): 64 return TranslatableSingular(domain, format, *args) 65 else: 66 return TranslatablePlural(domain, format, *args)
67 68 return lambda *args: create(*args) 69
70 -class Translatable(pb.Copyable, pb.RemoteCopy):
71 """ 72 I represent a serializable translatable gettext msg. 73 """ 74 domain = None
75 76 # NOTE: subclassing FancyEqMixin allows us to compare two 77 # RemoteCopy instances gotten from the same Copyable; this allows 78 # state _append and _remove to work correctly 79 # Take note however that this also means that two RemoteCopy objects 80 # of two different Copyable objects, but with the same args, will 81 # also pass equality 82 # For our purposes, this is fine. 83
84 -class TranslatableSingular(Translatable, util.FancyEqMixin):
85 """ 86 I represent a translatable gettext msg in the singular form. 87 """ 88 89 compareAttributes = ["domain", "format", "args"] 90
91 - def __init__(self, domain, format, *args):
92 """ 93 @param domain: the text domain for translations of this message 94 @param format: a format string 95 @param args: any arguments to the format string 96 """ 97 self.domain = domain 98 self.format = format 99 self.args = args
100 pb.setUnjellyableForClass(TranslatableSingular, TranslatableSingular) 101
102 -class TranslatablePlural(Translatable, util.FancyEqMixin):
103 """ 104 I represent a translatable gettext msg in the plural form. 105 """ 106 107 compareAttributes = ["domain", "singular", "plural", "count", "args"] 108
109 - def __init__(self, domain, format, *args):
110 """ 111 @param domain: the text domain for translations of this message 112 @param format: a (singular, plural, count) tuple 113 @param args: any arguments to the format string 114 """ 115 singular, plural, count = format 116 self.domain = domain 117 self.singular = singular 118 self.plural = plural 119 self.count = count 120 self.args = args
121 pb.setUnjellyableForClass(TranslatablePlural, TranslatablePlural) 122
123 -class Translator(log.Loggable):
124 """ 125 I translate translatables and messages. 126 I need to be told where locale directories can be found for all domains 127 I need to translate for. 128 """ 129 130 logCategory = "translator" 131
132 - def __init__(self):
133 self._localedirs = {} # domain name -> list of locale dirs
134
135 - def addLocaleDir(self, domain, dir):
136 """ 137 Add a locale directory for the given text domain. 138 """ 139 if not domain in self._localedirs.keys(): 140 self._localedirs[domain] = [] 141 142 if not dir in self._localedirs[domain]: 143 self.debug('Adding localedir %s for domain %s' % (dir, domain)) 144 self._localedirs[domain].append(dir)
145
146 - def translateTranslatable(self, translatable, lang=None):
147 """ 148 Translate a translatable object, in the given language. 149 150 @param lang: language code (or the current locale if None) 151 """ 152 # gettext.translation objects are rumoured to be cached (API docs) 153 domain = translatable.domain 154 t = None 155 if domain in self._localedirs.keys(): 156 # FIXME: possibly trap IOError and handle nicely ? 157 for localedir in self._localedirs[domain]: 158 try: 159 t = gettext.translation(domain, localedir, lang) 160 except IOError: 161 pass 162 else: 163 self.debug('no locales for domain %s' % domain) 164 165 format = None 166 if not t: 167 # if no translation object found, fall back to C 168 self.debug('no translation found, falling back to C') 169 if isinstance(translatable, TranslatableSingular): 170 format = translatable.format 171 elif isinstance(translatable, TranslatablePlural): 172 if translatable.count == 1: 173 format = translatable.singular 174 else: 175 format = translatable.plural 176 else: 177 raise NotImplementedError('Cannot translate translatable %r' % 178 translatable) 179 else: 180 # translation object found, translate 181 if isinstance(translatable, TranslatableSingular): 182 format = t.gettext(translatable.format) 183 elif isinstance(translatable, TranslatablePlural): 184 format = t.ngettext(translatable.singular, translatable.plural, 185 translatable.count) 186 else: 187 raise NotImplementedError('Cannot translate translatable %r' % 188 translatable) 189 190 if translatable.args: 191 return format % translatable.args 192 else: 193 return format
194
195 - def translate(self, message, lang=None):
196 """ 197 Translate a message, in the given language. 198 """ 199 strings = [] 200 for t in message.translatables: 201 strings.append(self.translateTranslatable(t, lang)) 202 return "".join(strings)
203 204 # NOTE: same caveats apply for FancyEqMixin as above 205 # this might be a little heavy; we could consider only comparing 206 # on id, once we verify that all id's are unique 207
208 -class Message(pb.Copyable, pb.RemoteCopy, util.FancyEqMixin):
209 """ 210 I am a message to be shown in a UI. 211 """ 212 213 compareAttributes = ["level", "translatables", "debug", "id", "priority", 214 "timestamp"] 215
216 - def __init__(self, level, translatable, debug=None, id=None, priority=50, 217 timestamp=None):
218 """ 219 @param level: ERROR, WARNING or INFO 220 @param translatable: a translatable possibly with markup for 221 linking to documentation or running commands. 222 @param debug: further, untranslated, debug information, not 223 always shown 224 @param priority: priority compared to other messages of the same 225 level 226 @param timestamp: time since epoch at which the message was 227 generated, in seconds. 228 """ 229 self.level = level 230 self.translatables = [] 231 self.debug = debug 232 self.id = id 233 self.priority = priority 234 self.timestamp = timestamp or time.time() 235 236 self.add(translatable)
237
238 - def __repr__(self):
239 return '<Message %r at %r>' % (self.id, id(self))
240
241 - def add(self, translatable):
242 if not isinstance(translatable, Translatable): 243 raise ValueError('%r is not Translatable' % translatable) 244 self.translatables.append(translatable)
245 pb.setUnjellyableForClass(Message, Message) 246 247 # these are implemented as factory functions instead of classes because 248 # properly proxying to the correct subclass is hard with Copyable/RemoteCopy
249 -def Error(*args, **kwargs):
250 """ 251 Create a L{Message} at ERROR level, indicating a failure that needs 252 intervention to be resolved. 253 """ 254 return Message(ERROR, *args, **kwargs)
255
256 -def Warning(*args, **kwargs):
257 """ 258 Create a L{Message} at WARNING level, indicating a potential problem. 259 """ 260 return Message(WARNING, *args, **kwargs)
261
262 -def Info(*args, **kwargs):
263 """ 264 Create a L{Message} at INFO level. 265 """ 266 return Message(INFO, *args, **kwargs)
267
268 -class Result(pb.Copyable, pb.RemoteCopy):
269 """ 270 I am used in worker checks to return a result. 271 272 @ivar value: the result value of the check 273 @ivar failed: whether or not the check failed. Typically triggered 274 by adding an ERROR message to the result. 275 @ivar messages: list of messages 276 @type messages: list of L{Message} 277 """
278 - def __init__(self):
279 self.messages = [] 280 self.value = None 281 self.failed = False
282
283 - def succeed(self, value):
284 """ 285 Make the result be successful, setting the given result value. 286 """ 287 self.value = value
288
289 - def add(self, message):
290 """ 291 Add a message to the result. 292 293 @type message: L{Message} 294 """ 295 self.messages.append(message) 296 if message.level == ERROR: 297 self.failed = True 298 self.value = None
299 pb.setUnjellyableForClass(Result, Result) 300