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 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
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
72 """
73 I represent a serializable translatable gettext msg.
74 """
75 domain = None
76
77
78
79
80
81
82
83
84
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
103 txt = self.format % self.args
104 return txt
105
106 pb.setUnjellyableForClass(TranslatableSingular, TranslatableSingular)
107
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
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
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
147 self._localedirs = {}
148
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
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
169 domain = translatable.domain
170 t = None
171 if domain in self._localedirs.keys():
172
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
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
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
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
224
225
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
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
267
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
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 """
298 self.messages = []
299 self.value = None
300 self.failed = False
301
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