Mercurial > babel > mirror
comparison babel/support.py @ 596:f63a07d648b6 trunk
add babel.support.NullTranslations class similar to gettext.NullTranslations but with all of Babel's new *gettext methods (#277)
author | fschwarz |
---|---|
date | Mon, 20 Aug 2012 19:21:22 +0000 |
parents | 57a08cc52623 |
children | 92e3eb0a317a |
comparison
equal
deleted
inserted
replaced
595:57a08cc52623 | 596:f63a07d648b6 |
---|---|
26 format_timedelta | 26 format_timedelta |
27 from babel.numbers import format_number, format_decimal, format_currency, \ | 27 from babel.numbers import format_number, format_decimal, format_currency, \ |
28 format_percent, format_scientific | 28 format_percent, format_scientific |
29 from babel.util import UTC | 29 from babel.util import UTC |
30 | 30 |
31 __all__ = ['Format', 'LazyProxy', 'Translations'] | 31 __all__ = ['Format', 'LazyProxy', 'NullTranslations', 'Translations'] |
32 __docformat__ = 'restructuredtext en' | 32 __docformat__ = 'restructuredtext en' |
33 | 33 |
34 | 34 |
35 class Format(object): | 35 class Format(object): |
36 """Wrapper class providing the various date and number formatting functions | 36 """Wrapper class providing the various date and number formatting functions |
279 return self.value[key] | 279 return self.value[key] |
280 | 280 |
281 def __setitem__(self, key, value): | 281 def __setitem__(self, key, value): |
282 self.value[key] = value | 282 self.value[key] = value |
283 | 283 |
284 | 284 |
285 class Translations(gettext.GNUTranslations, object): | 285 class NullTranslations(gettext.NullTranslations, object): |
286 """An extended translation catalog class.""" | 286 |
287 | 287 DEFAULT_DOMAIN = None |
288 DEFAULT_DOMAIN = 'messages' | 288 |
289 | 289 def __init__(self, fp=None): |
290 def __init__(self, fileobj=None, domain=None): | 290 """Initialize a simple translations class which is not backed by a |
291 """Initialize the translations catalog. | 291 real catalog. Behaves similar to gettext.NullTranslations but also |
292 | 292 offers Babel's on *gettext methods (e.g. 'dgettext()'). |
293 :param fileobj: the file-like object the translation should be read | 293 |
294 from | 294 :param fp: a file-like object (ignored in this class) |
295 :param domain: the message domain (default: 'messages') | 295 """ |
296 """ | 296 # These attributes are set by gettext.NullTranslations when a catalog |
297 if domain is None: | 297 # is parsed (fp != None). Ensure that they are always present because |
298 domain = self.DEFAULT_DOMAIN | 298 # some *gettext methods (including '.gettext()') rely on the attributes. |
299 gettext.GNUTranslations.__init__(self, fp=fileobj) | 299 self._catalog = {} |
300 self.files = filter(None, [getattr(fileobj, 'name', None)]) | 300 self.plural = lambda n: int(n != 1) |
301 self.domain = domain | 301 super(NullTranslations, self).__init__(fp=fp) |
302 self.files = filter(None, [getattr(fp, 'name', None)]) | |
303 self.domain = self.DEFAULT_DOMAIN | |
302 self._domains = {} | 304 self._domains = {} |
303 | |
304 @classmethod | |
305 def load(cls, dirname=None, locales=None, domain=None): | |
306 """Load translations from the given directory. | |
307 | |
308 :param dirname: the directory containing the ``MO`` files | |
309 :param locales: the list of locales in order of preference (items in | |
310 this list can be either `Locale` objects or locale | |
311 strings) | |
312 :param domain: the message domain (default: 'messages') | |
313 :return: the loaded catalog, or a ``NullTranslations`` instance if no | |
314 matching translations were found | |
315 :rtype: `Translations` | |
316 """ | |
317 if locales is not None: | |
318 if not isinstance(locales, (list, tuple)): | |
319 locales = [locales] | |
320 locales = [str(locale) for locale in locales] | |
321 if not domain: | |
322 domain = cls.DEFAULT_DOMAIN | |
323 filename = gettext.find(domain, dirname, locales) | |
324 if not filename: | |
325 return gettext.NullTranslations() | |
326 return cls(fileobj=open(filename, 'rb'), domain=domain) | |
327 | |
328 def __repr__(self): | |
329 return '<%s: "%s">' % (type(self).__name__, | |
330 self._info.get('project-id-version')) | |
331 | |
332 def add(self, translations, merge=True): | |
333 """Add the given translations to the catalog. | |
334 | |
335 If the domain of the translations is different than that of the | |
336 current catalog, they are added as a catalog that is only accessible | |
337 by the various ``d*gettext`` functions. | |
338 | |
339 :param translations: the `Translations` instance with the messages to | |
340 add | |
341 :param merge: whether translations for message domains that have | |
342 already been added should be merged with the existing | |
343 translations | |
344 :return: the `Translations` instance (``self``) so that `merge` calls | |
345 can be easily chained | |
346 :rtype: `Translations` | |
347 """ | |
348 domain = getattr(translations, 'domain', self.DEFAULT_DOMAIN) | |
349 if merge and domain == self.domain: | |
350 return self.merge(translations) | |
351 | |
352 existing = self._domains.get(domain) | |
353 if merge and existing is not None: | |
354 existing.merge(translations) | |
355 else: | |
356 translations.add_fallback(self) | |
357 self._domains[domain] = translations | |
358 | |
359 return self | |
360 | |
361 def merge(self, translations): | |
362 """Merge the given translations into the catalog. | |
363 | |
364 Message translations in the specified catalog override any messages | |
365 with the same identifier in the existing catalog. | |
366 | |
367 :param translations: the `Translations` instance with the messages to | |
368 merge | |
369 :return: the `Translations` instance (``self``) so that `merge` calls | |
370 can be easily chained | |
371 :rtype: `Translations` | |
372 """ | |
373 if isinstance(translations, gettext.GNUTranslations): | |
374 self._catalog.update(translations._catalog) | |
375 if isinstance(translations, Translations): | |
376 self.files.extend(translations.files) | |
377 | |
378 return self | |
379 | 305 |
380 def dgettext(self, domain, message): | 306 def dgettext(self, domain, message): |
381 """Like ``gettext()``, but look the message up in the specified | 307 """Like ``gettext()``, but look the message up in the specified |
382 domain. | 308 domain. |
383 """ | 309 """ |
590 with ``bind_textdomain_codeset()``. | 516 with ``bind_textdomain_codeset()``. |
591 """ | 517 """ |
592 return self._domains.get(domain, self).lnpgettext(context, singular, | 518 return self._domains.get(domain, self).lnpgettext(context, singular, |
593 plural, num) | 519 plural, num) |
594 | 520 |
521 | |
522 class Translations(NullTranslations, gettext.GNUTranslations): | |
523 """An extended translation catalog class.""" | |
524 | |
525 DEFAULT_DOMAIN = 'messages' | |
526 | |
527 def __init__(self, fileobj=None, domain=None): | |
528 """Initialize the translations catalog. | |
529 | |
530 :param fileobj: the file-like object the translation should be read | |
531 from | |
532 :param domain: the message domain (default: 'messages') | |
533 """ | |
534 super(Translations, self).__init__(fp=fileobj) | |
535 self.domain = domain or self.DEFAULT_DOMAIN | |
536 | |
537 @classmethod | |
538 def load(cls, dirname=None, locales=None, domain=None): | |
539 """Load translations from the given directory. | |
540 | |
541 :param dirname: the directory containing the ``MO`` files | |
542 :param locales: the list of locales in order of preference (items in | |
543 this list can be either `Locale` objects or locale | |
544 strings) | |
545 :param domain: the message domain (default: 'messages') | |
546 :return: the loaded catalog, or a ``NullTranslations`` instance if no | |
547 matching translations were found | |
548 :rtype: `Translations` | |
549 """ | |
550 if locales is not None: | |
551 if not isinstance(locales, (list, tuple)): | |
552 locales = [locales] | |
553 locales = [str(locale) for locale in locales] | |
554 if not domain: | |
555 domain = cls.DEFAULT_DOMAIN | |
556 filename = gettext.find(domain, dirname, locales) | |
557 if not filename: | |
558 return gettext.NullTranslations() | |
559 return cls(fileobj=open(filename, 'rb'), domain=domain) | |
560 | |
561 def __repr__(self): | |
562 return '<%s: "%s">' % (type(self).__name__, | |
563 self._info.get('project-id-version')) | |
564 | |
565 def add(self, translations, merge=True): | |
566 """Add the given translations to the catalog. | |
567 | |
568 If the domain of the translations is different than that of the | |
569 current catalog, they are added as a catalog that is only accessible | |
570 by the various ``d*gettext`` functions. | |
571 | |
572 :param translations: the `Translations` instance with the messages to | |
573 add | |
574 :param merge: whether translations for message domains that have | |
575 already been added should be merged with the existing | |
576 translations | |
577 :return: the `Translations` instance (``self``) so that `merge` calls | |
578 can be easily chained | |
579 :rtype: `Translations` | |
580 """ | |
581 domain = getattr(translations, 'domain', self.DEFAULT_DOMAIN) | |
582 if merge and domain == self.domain: | |
583 return self.merge(translations) | |
584 | |
585 existing = self._domains.get(domain) | |
586 if merge and existing is not None: | |
587 existing.merge(translations) | |
588 else: | |
589 translations.add_fallback(self) | |
590 self._domains[domain] = translations | |
591 | |
592 return self | |
593 | |
594 def merge(self, translations): | |
595 """Merge the given translations into the catalog. | |
596 | |
597 Message translations in the specified catalog override any messages | |
598 with the same identifier in the existing catalog. | |
599 | |
600 :param translations: the `Translations` instance with the messages to | |
601 merge | |
602 :return: the `Translations` instance (``self``) so that `merge` calls | |
603 can be easily chained | |
604 :rtype: `Translations` | |
605 """ | |
606 if isinstance(translations, gettext.GNUTranslations): | |
607 self._catalog.update(translations._catalog) | |
608 if isinstance(translations, Translations): | |
609 self.files.extend(translations.files) | |
610 | |
611 return self | |
612 |