Mercurial > babel > old > mirror
annotate 0.9.x/babel/support.py @ 510:4c473bedd528 stable
Fix Python 2.3 compatibility for 0.9 branch (closes #233)
author | fschwarz |
---|---|
date | Fri, 04 Mar 2011 14:16:15 +0000 |
parents | 0fb7125f0b7a |
children |
rev | line source |
---|---|
263 | 1 # -*- coding: utf-8 -*- |
2 # | |
3 # Copyright (C) 2007 Edgewall Software | |
4 # All rights reserved. | |
5 # | |
6 # This software is licensed as described in the file COPYING, which | |
7 # you should have received as part of this distribution. The terms | |
8 # are also available at http://babel.edgewall.org/wiki/License. | |
9 # | |
10 # This software consists of voluntary contributions made by many | |
11 # individuals. For the exact contribution history, see the revision | |
12 # history and logs, available at http://babel.edgewall.org/log/. | |
13 | |
14 """Several classes and functions that help with integrating and using Babel | |
15 in applications. | |
16 | |
17 .. note: the code in this module is not used by Babel itself | |
18 """ | |
19 | |
20 from datetime import date, datetime, time | |
21 import gettext | |
22 | |
23 from babel.core import Locale | |
24 from babel.dates import format_date, format_datetime, format_time, LC_TIME | |
25 from babel.numbers import format_number, format_decimal, format_currency, \ | |
26 format_percent, format_scientific, LC_NUMERIC | |
510
4c473bedd528
Fix Python 2.3 compatibility for 0.9 branch (closes #233)
fschwarz
parents:
415
diff
changeset
|
27 from babel.util import set, UTC |
263 | 28 |
29 __all__ = ['Format', 'LazyProxy', 'Translations'] | |
30 __docformat__ = 'restructuredtext en' | |
31 | |
32 | |
33 class Format(object): | |
34 """Wrapper class providing the various date and number formatting functions | |
35 bound to a specific locale and time-zone. | |
36 | |
37 >>> fmt = Format('en_US', UTC) | |
38 >>> fmt.date(date(2007, 4, 1)) | |
39 u'Apr 1, 2007' | |
40 >>> fmt.decimal(1.2345) | |
41 u'1.234' | |
42 """ | |
43 | |
44 def __init__(self, locale, tzinfo=None): | |
45 """Initialize the formatter. | |
46 | |
47 :param locale: the locale identifier or `Locale` instance | |
48 :param tzinfo: the time-zone info (a `tzinfo` instance or `None`) | |
49 """ | |
50 self.locale = Locale.parse(locale) | |
51 self.tzinfo = tzinfo | |
52 | |
53 def date(self, date=None, format='medium'): | |
54 """Return a date formatted according to the given pattern. | |
55 | |
56 >>> fmt = Format('en_US') | |
57 >>> fmt.date(date(2007, 4, 1)) | |
58 u'Apr 1, 2007' | |
59 | |
60 :see: `babel.dates.format_date` | |
61 """ | |
62 return format_date(date, format, locale=self.locale) | |
63 | |
64 def datetime(self, datetime=None, format='medium'): | |
65 """Return a date and time formatted according to the given pattern. | |
66 | |
67 >>> from pytz import timezone | |
68 >>> fmt = Format('en_US', tzinfo=timezone('US/Eastern')) | |
69 >>> fmt.datetime(datetime(2007, 4, 1, 15, 30)) | |
70 u'Apr 1, 2007 11:30:00 AM' | |
71 | |
72 :see: `babel.dates.format_datetime` | |
73 """ | |
74 return format_datetime(datetime, format, tzinfo=self.tzinfo, | |
75 locale=self.locale) | |
76 | |
77 def time(self, time=None, format='medium'): | |
78 """Return a time formatted according to the given pattern. | |
79 | |
80 >>> from pytz import timezone | |
81 >>> fmt = Format('en_US', tzinfo=timezone('US/Eastern')) | |
351 | 82 >>> fmt.time(datetime(2007, 4, 1, 15, 30)) |
263 | 83 u'11:30:00 AM' |
84 | |
85 :see: `babel.dates.format_time` | |
86 """ | |
87 return format_time(time, format, tzinfo=self.tzinfo, locale=self.locale) | |
88 | |
89 def number(self, number): | |
90 """Return an integer number formatted for the locale. | |
91 | |
92 >>> fmt = Format('en_US') | |
93 >>> fmt.number(1099) | |
94 u'1,099' | |
95 | |
96 :see: `babel.numbers.format_number` | |
97 """ | |
98 return format_number(number, locale=self.locale) | |
99 | |
100 def decimal(self, number, format=None): | |
101 """Return a decimal number formatted for the locale. | |
102 | |
103 >>> fmt = Format('en_US') | |
104 >>> fmt.decimal(1.2345) | |
105 u'1.234' | |
106 | |
107 :see: `babel.numbers.format_decimal` | |
108 """ | |
109 return format_decimal(number, format, locale=self.locale) | |
110 | |
111 def currency(self, number, currency): | |
112 """Return a number in the given currency formatted for the locale. | |
113 | |
114 :see: `babel.numbers.format_currency` | |
115 """ | |
116 return format_currency(number, currency, locale=self.locale) | |
117 | |
118 def percent(self, number, format=None): | |
119 """Return a number formatted as percentage for the locale. | |
120 | |
121 >>> fmt = Format('en_US') | |
122 >>> fmt.percent(0.34) | |
123 u'34%' | |
124 | |
125 :see: `babel.numbers.format_percent` | |
126 """ | |
127 return format_percent(number, format, locale=self.locale) | |
128 | |
129 def scientific(self, number): | |
130 """Return a number formatted using scientific notation for the locale. | |
131 | |
132 :see: `babel.numbers.format_scientific` | |
133 """ | |
134 return format_scientific(number, locale=self.locale) | |
135 | |
136 | |
137 class LazyProxy(object): | |
138 """Class for proxy objects that delegate to a specified function to evaluate | |
139 the actual object. | |
140 | |
141 >>> def greeting(name='world'): | |
142 ... return 'Hello, %s!' % name | |
143 >>> lazy_greeting = LazyProxy(greeting, name='Joe') | |
144 >>> print lazy_greeting | |
145 Hello, Joe! | |
146 >>> u' ' + lazy_greeting | |
147 u' Hello, Joe!' | |
148 >>> u'(%s)' % lazy_greeting | |
149 u'(Hello, Joe!)' | |
150 | |
151 This can be used, for example, to implement lazy translation functions that | |
152 delay the actual translation until the string is actually used. The | |
153 rationale for such behavior is that the locale of the user may not always | |
154 be available. In web applications, you only know the locale when processing | |
155 a request. | |
156 | |
157 The proxy implementation attempts to be as complete as possible, so that | |
158 the lazy objects should mostly work as expected, for example for sorting: | |
159 | |
160 >>> greetings = [ | |
161 ... LazyProxy(greeting, 'world'), | |
162 ... LazyProxy(greeting, 'Joe'), | |
163 ... LazyProxy(greeting, 'universe'), | |
164 ... ] | |
165 >>> greetings.sort() | |
166 >>> for greeting in greetings: | |
167 ... print greeting | |
168 Hello, Joe! | |
169 Hello, universe! | |
170 Hello, world! | |
171 """ | |
172 __slots__ = ['_func', '_args', '_kwargs', '_value'] | |
173 | |
174 def __init__(self, func, *args, **kwargs): | |
175 # Avoid triggering our own __setattr__ implementation | |
176 object.__setattr__(self, '_func', func) | |
177 object.__setattr__(self, '_args', args) | |
178 object.__setattr__(self, '_kwargs', kwargs) | |
179 object.__setattr__(self, '_value', None) | |
180 | |
181 def value(self): | |
182 if self._value is None: | |
183 value = self._func(*self._args, **self._kwargs) | |
184 object.__setattr__(self, '_value', value) | |
185 return self._value | |
186 value = property(value) | |
187 | |
188 def __contains__(self, key): | |
189 return key in self.value | |
190 | |
191 def __nonzero__(self): | |
192 return bool(self.value) | |
193 | |
194 def __dir__(self): | |
195 return dir(self.value) | |
196 | |
197 def __iter__(self): | |
198 return iter(self.value) | |
199 | |
200 def __len__(self): | |
201 return len(self.value) | |
202 | |
203 def __str__(self): | |
204 return str(self.value) | |
205 | |
206 def __unicode__(self): | |
207 return unicode(self.value) | |
208 | |
209 def __add__(self, other): | |
210 return self.value + other | |
211 | |
212 def __radd__(self, other): | |
213 return other + self.value | |
214 | |
215 def __mod__(self, other): | |
216 return self.value % other | |
217 | |
218 def __rmod__(self, other): | |
219 return other % self.value | |
220 | |
221 def __mul__(self, other): | |
222 return self.value * other | |
223 | |
224 def __rmul__(self, other): | |
225 return other * self.value | |
226 | |
227 def __call__(self, *args, **kwargs): | |
228 return self.value(*args, **kwargs) | |
229 | |
230 def __lt__(self, other): | |
231 return self.value < other | |
232 | |
233 def __le__(self, other): | |
234 return self.value <= other | |
235 | |
236 def __eq__(self, other): | |
237 return self.value == other | |
238 | |
239 def __ne__(self, other): | |
240 return self.value != other | |
241 | |
242 def __gt__(self, other): | |
243 return self.value > other | |
244 | |
245 def __ge__(self, other): | |
246 return self.value >= other | |
247 | |
248 def __delattr__(self, name): | |
249 delattr(self.value, name) | |
250 | |
251 def __getattr__(self, name): | |
252 return getattr(self.value, name) | |
253 | |
254 def __setattr__(self, name, value): | |
255 setattr(self.value, name, value) | |
256 | |
257 def __delitem__(self, key): | |
258 del self.value[key] | |
259 | |
260 def __getitem__(self, key): | |
261 return self.value[key] | |
262 | |
263 def __setitem__(self, key, value): | |
264 self.value[key] = value | |
265 | |
409 | 266 |
267 class Translations(gettext.GNUTranslations, object): | |
263 | 268 """An extended translation catalog class.""" |
269 | |
270 DEFAULT_DOMAIN = 'messages' | |
271 | |
415 | 272 def __init__(self, fileobj=None, domain=DEFAULT_DOMAIN): |
263 | 273 """Initialize the translations catalog. |
415 | 274 |
263 | 275 :param fileobj: the file-like object the translation should be read |
276 from | |
277 """ | |
278 gettext.GNUTranslations.__init__(self, fp=fileobj) | |
348
05975a0e7021
Merged revisions [358:360], [364:370], [373:378], [380:382] from [source:trunk].
cmlenz
parents:
286
diff
changeset
|
279 self.files = filter(None, [getattr(fileobj, 'name', None)]) |
415 | 280 self.domain = domain |
281 self._domains = {} | |
263 | 282 |
283 def load(cls, dirname=None, locales=None, domain=DEFAULT_DOMAIN): | |
284 """Load translations from the given directory. | |
415 | 285 |
263 | 286 :param dirname: the directory containing the ``MO`` files |
287 :param locales: the list of locales in order of preference (items in | |
288 this list can be either `Locale` objects or locale | |
289 strings) | |
290 :param domain: the message domain | |
291 :return: the loaded catalog, or a ``NullTranslations`` instance if no | |
292 matching translations were found | |
293 :rtype: `Translations` | |
294 """ | |
348
05975a0e7021
Merged revisions [358:360], [364:370], [373:378], [380:382] from [source:trunk].
cmlenz
parents:
286
diff
changeset
|
295 if locales is not None: |
05975a0e7021
Merged revisions [358:360], [364:370], [373:378], [380:382] from [source:trunk].
cmlenz
parents:
286
diff
changeset
|
296 if not isinstance(locales, (list, tuple)): |
05975a0e7021
Merged revisions [358:360], [364:370], [373:378], [380:382] from [source:trunk].
cmlenz
parents:
286
diff
changeset
|
297 locales = [locales] |
05975a0e7021
Merged revisions [358:360], [364:370], [373:378], [380:382] from [source:trunk].
cmlenz
parents:
286
diff
changeset
|
298 locales = [str(locale) for locale in locales] |
415 | 299 if not domain: |
300 domain = cls.DEFAULT_DOMAIN | |
301 filename = gettext.find(domain, dirname, locales) | |
263 | 302 if not filename: |
303 return gettext.NullTranslations() | |
415 | 304 return cls(fileobj=open(filename, 'rb'), domain=domain) |
263 | 305 load = classmethod(load) |
306 | |
415 | 307 def __repr__(self): |
308 return '<%s: "%s">' % (type(self).__name__, | |
309 self._info.get('project-id-version')) | |
310 | |
311 def add(self, translations, merge=True): | |
312 """Add the given translations to the catalog. | |
313 | |
314 If the domain of the translations is different than that of the | |
315 current catalog, they are added as a catalog that is only accessible | |
316 by the various ``d*gettext`` functions. | |
317 | |
318 :param translations: the `Translations` instance with the messages to | |
319 add | |
320 :param merge: whether translations for message domains that have | |
321 already been added should be merged with the existing | |
322 translations | |
323 :return: the `Translations` instance (``self``) so that `merge` calls | |
324 can be easily chained | |
325 :rtype: `Translations` | |
326 """ | |
327 domain = getattr(translations, 'domain', self.DEFAULT_DOMAIN) | |
328 if merge and domain == self.domain: | |
329 return self.merge(translations) | |
330 | |
331 existing = self._domains.get(domain) | |
332 if merge and existing is not None: | |
333 existing.merge(translations) | |
334 else: | |
335 translations.add_fallback(self) | |
336 self._domains[domain] = translations | |
337 | |
338 return self | |
339 | |
263 | 340 def merge(self, translations): |
341 """Merge the given translations into the catalog. | |
415 | 342 |
343 Message translations in the specified catalog override any messages | |
344 with the same identifier in the existing catalog. | |
345 | |
263 | 346 :param translations: the `Translations` instance with the messages to |
347 merge | |
348 :return: the `Translations` instance (``self``) so that `merge` calls | |
349 can be easily chained | |
350 :rtype: `Translations` | |
351 """ | |
415 | 352 if isinstance(translations, gettext.GNUTranslations): |
263 | 353 self._catalog.update(translations._catalog) |
415 | 354 if isinstance(translations, Translations): |
355 self.files.extend(translations.files) | |
356 | |
263 | 357 return self |
358 | |
415 | 359 def dgettext(self, domain, message): |
360 """Like ``gettext()``, but look the message up in the specified | |
361 domain. | |
362 """ | |
363 return self._domains.get(domain, self).gettext(message) | |
364 | |
365 def ldgettext(self, domain, message): | |
366 """Like ``lgettext()``, but look the message up in the specified | |
367 domain. | |
368 """ | |
369 return self._domains.get(domain, self).lgettext(message) | |
370 | |
371 def dugettext(self, domain, message): | |
372 """Like ``ugettext()``, but look the message up in the specified | |
373 domain. | |
374 """ | |
375 return self._domains.get(domain, self).ugettext(message) | |
376 | |
377 def dngettext(self, domain, singular, plural, num): | |
378 """Like ``ngettext()``, but look the message up in the specified | |
379 domain. | |
380 """ | |
381 return self._domains.get(domain, self).ngettext(singular, plural, num) | |
382 | |
383 def ldngettext(self, domain, singular, plural, num): | |
384 """Like ``lngettext()``, but look the message up in the specified | |
385 domain. | |
386 """ | |
387 return self._domains.get(domain, self).lngettext(singular, plural, num) | |
388 | |
389 def dungettext(self, domain, singular, plural, num): | |
390 """Like ``ungettext()`` but look the message up in the specified | |
391 domain. | |
392 """ | |
393 return self._domains.get(domain, self).ungettext(singular, plural, num) |