1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
32 (ERROR,
33 WARNING,
34 INFO) = range(1, 4)
35
37 """
38 Mark a singular string for translation, without translating it.
39 """
40 return format
41
42 -def ngettext(singular, plural, count):
43 """
44 Mark a plural string for translation, without translating it.
45 """
46 return (singular, plural, count)
47
48 -def gettexter(domain):
49 """
50 Return a function that takes a format string or tuple, and additional
51 format args,
52 and creates a L{Translatable} from it.
53
54 Example::
55
56 T_ = messages.gettexter('flumotion')
57 t = T_(N_("Could not find '%s'."), file)
58
59 @param domain: the gettext domain to create translatables for.
60 """
61 def create(format, *args):
62 if isinstance(format, str):
63 return TranslatableSingular(domain, format, *args)
64 else:
65 return TranslatablePlural(domain, format, *args)
66
67 return lambda *args: create(*args)
68
70 """
71 I represent a serializable translatable gettext msg.
72 """
73 domain = None
74
75
76
77
79 compareAttributes = ()
81 if not self.compareAttributes:
82 return self is other
83
84 if not isinstance(other, self.__class__):
85 return False
86 for attr in self.compareAttributes:
87 if hasattr(self, attr):
88 if not hasattr(other, attr):
89 return False
90 elif not getattr(self, attr) == getattr(other, attr):
91 return False
92 elif hasattr(other, attr):
93 return False
94 return True
95
97 return not self.__eq__(other)
98
99
100
101
102
103
104
105
106
108 """
109 I represent a translatable gettext msg in the singular form.
110 """
111
112 compareAttributes = ["domain", "format", "args"]
113
114 - def __init__(self, domain, format, *args):
115 """
116 @param domain: the text domain for translations of this message
117 @param format: a format string
118 @param args: any arguments to the format string
119 """
120 self.domain = domain
121 self.format = format
122 self.args = args
123
125 return self.format % self.args
126 pb.setUnjellyableForClass(TranslatableSingular, TranslatableSingular)
127
129 """
130 I represent a translatable gettext msg in the plural form.
131 """
132
133 compareAttributes = ["domain", "singular", "plural", "count", "args"]
134
135 - def __init__(self, domain, format, *args):
136 """
137 @param domain: the text domain for translations of this message
138 @param format: a (singular, plural, count) tuple
139 @param args: any arguments to the format string
140 """
141 singular, plural, count = format
142 self.domain = domain
143 self.singular = singular
144 self.plural = plural
145 self.count = count
146 self.args = args
147
149 return self.singular % self.args
150 pb.setUnjellyableForClass(TranslatablePlural, TranslatablePlural)
151
153 """
154 I translate translatables and messages.
155 I need to be told where locale directories can be found for all domains
156 I need to translate for.
157 """
158
159 logCategory = "translator"
160
162 self._localedirs = {}
163
165 """
166 Add a locale directory for the given text domain.
167 """
168 if not domain in self._localedirs.keys():
169 self._localedirs[domain] = []
170
171 if not dir in self._localedirs[domain]:
172 self.debug('Adding localedir %s for domain %s' % (dir, domain))
173 self._localedirs[domain].append(dir)
174
176 """
177 Translate a translatable object, in the given language.
178
179 @param lang: language code (or the current locale if None)
180 """
181
182 domain = translatable.domain
183 t = None
184 if domain in self._localedirs.keys():
185
186 for localedir in self._localedirs[domain]:
187 try:
188 t = gettext.translation(domain, localedir, lang)
189 except IOError:
190 pass
191 else:
192 self.debug('no locales for domain %s' % domain)
193
194 format = None
195 if not t:
196
197 self.debug('no translation found, falling back to C')
198 if isinstance(translatable, TranslatableSingular):
199 format = translatable.format
200 elif isinstance(translatable, TranslatablePlural):
201 if translatable.count == 1:
202 format = translatable.singular
203 else:
204 format = translatable.plural
205 else:
206 raise NotImplementedError('Cannot translate translatable %r' %
207 translatable)
208 else:
209
210 if isinstance(translatable, TranslatableSingular):
211 format = t.gettext(translatable.format)
212 elif isinstance(translatable, TranslatablePlural):
213 format = t.ngettext(translatable.singular, translatable.plural,
214 translatable.count)
215 else:
216 raise NotImplementedError('Cannot translate translatable %r' %
217 translatable)
218
219 if translatable.args:
220 return format % translatable.args
221 else:
222 return format
223
225 """
226 Translate a message, in the given language.
227 """
228 strings = []
229 for t in message.translatables:
230 strings.append(self.translateTranslatable(t, lang))
231 return "".join(strings)
232
233
234
235
236
237 -class Message(pb.Copyable, pb.RemoteCopy, FancyEqMixin):
238 """
239 I am a message to be shown in a UI.
240 """
241
242 compareAttributes = ["level", "translatables", "debug", "id", "priority",
243 "timestamp"]
244
245 - def __init__(self, level, translatable, debug=None, id=None, priority=50,
246 timestamp=None):
247 """Create a new message.
248
249 The id identifies this kind of message, and serves two purposes.
250
251 The first purpose is to serve as a key by which a kind of
252 message might be removed from a set of messages. For example, a
253 firewire component detecting that a cable has been plugged in
254 will remove any message that the cable is unplugged.
255
256 Secondly it serves so that the message viewers that watch the
257 'current state' of some object only see the latest message of a
258 given type. For example when messages are stored in persistent
259 state objects that can be transferred over the network, it
260 becomes inefficient to store the whole history of status
261 messages. Message stores can keep only the latest message of a
262 given ID.
263
264 @param level: ERROR, WARNING or INFO
265 @param translatable: a translatable possibly with markup for
266 linking to documentation or running commands.
267 @param debug: further, untranslated, debug information, not
268 always shown
269 @param priority: priority compared to other messages of the same
270 level
271 @param timestamp: time since epoch at which the message was
272 generated, in seconds.
273 @param id: A unique id for this kind of message, as
274 discussed above. If not given, will be
275 generated from the contents of the
276 translatable.
277 """
278 self.level = level
279 self.translatables = []
280 self.debug = debug
281 self.id = id or translatable.defaultMessageId()
282 self.priority = priority
283 self.timestamp = timestamp or time.time()
284
285 self.add(translatable)
286
288 return '<Message %r at %r>' % (self.id, id(self))
289
290 - def add(self, translatable):
291 if not isinstance(translatable, Translatable):
292 raise ValueError('%r is not Translatable' % translatable)
293 self.translatables.append(translatable)
294 pb.setUnjellyableForClass(Message, Message)
295
296
297
298 -def Error(*args, **kwargs):
299 """
300 Create a L{Message} at ERROR level, indicating a failure that needs
301 intervention to be resolved.
302 """
303 return Message(ERROR, *args, **kwargs)
304
306 """
307 Create a L{Message} at WARNING level, indicating a potential problem.
308 """
309 return Message(WARNING, *args, **kwargs)
310
311 -def Info(*args, **kwargs):
312 """
313 Create a L{Message} at INFO level.
314 """
315 return Message(INFO, *args, **kwargs)
316
317 -class Result(pb.Copyable, pb.RemoteCopy):
318 """
319 I am used in worker checks to return a result.
320
321 @ivar value: the result value of the check
322 @ivar failed: whether or not the check failed. Typically triggered
323 by adding an ERROR message to the result.
324 @ivar messages: list of messages
325 @type messages: list of L{Message}
326 """
328 self.messages = []
329 self.value = None
330 self.failed = False
331
333 """
334 Make the result be successful, setting the given result value.
335 """
336 self.value = value
337
338 - def add(self, message):
339 """
340 Add a message to the result.
341
342 @type message: L{Message}
343 """
344 self.messages.append(message)
345 if message.level == ERROR:
346 self.failed = True
347 self.value = None
348 pb.setUnjellyableForClass(Result, Result)
349