Package elisa :: Package extern :: Module translation
[hide private]
[frames] | no frames]

Source Code for Module elisa.extern.translation

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