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
Copyright (C) 2012-2017 Edgewall Software