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 from twisted.python import util
32
33 ERROR = 1
34 WARNING = 2
35 INFO = 3
36
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
71 """
72 I represent a serializable translatable gettext msg.
73 """
74 domain = None
75
76
77
78
79
80
81
82
83
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
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
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
133 self._localedirs = {}
134
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
147 """
148 Translate a translatable object, in the given language.
149
150 @param lang: language code (or the current locale if None)
151 """
152
153 domain = translatable.domain
154 t = None
155 if domain in self._localedirs.keys():
156
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
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
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
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
205
206
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
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
248
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
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 """
279 self.messages = []
280 self.value = None
281 self.failed = False
282
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