cmlenz@61: # -*- coding: utf-8 -*- cmlenz@61: # jruigrok@530: # Copyright (C) 2007-2011 Edgewall Software cmlenz@61: # All rights reserved. cmlenz@61: # cmlenz@61: # This software is licensed as described in the file COPYING, which cmlenz@61: # you should have received as part of this distribution. The terms cmlenz@61: # are also available at http://babel.edgewall.org/wiki/License. cmlenz@61: # cmlenz@61: # This software consists of voluntary contributions made by many cmlenz@61: # individuals. For the exact contribution history, see the revision cmlenz@61: # history and logs, available at http://babel.edgewall.org/log/. cmlenz@61: cmlenz@61: """Several classes and functions that help with integrating and using Babel cmlenz@61: in applications. cmlenz@61: cmlenz@61: .. note: the code in this module is not used by Babel itself cmlenz@61: """ cmlenz@61: jruigrok@522: from datetime import date, datetime, timedelta cmlenz@61: import gettext palgarvio@420: import locale cmlenz@61: cmlenz@101: from babel.core import Locale cmlenz@401: from babel.dates import format_date, format_datetime, format_time, \ jruigrok@522: format_timedelta cmlenz@101: from babel.numbers import format_number, format_decimal, format_currency, \ jruigrok@522: format_percent, format_scientific jruigrok@522: from babel.util import UTC cmlenz@101: fschwarz@596: __all__ = ['Format', 'LazyProxy', 'NullTranslations', 'Translations'] cmlenz@61: __docformat__ = 'restructuredtext en' cmlenz@61: cmlenz@61: cmlenz@101: class Format(object): cmlenz@101: """Wrapper class providing the various date and number formatting functions cmlenz@101: bound to a specific locale and time-zone. cmlenz@101: cmlenz@101: >>> fmt = Format('en_US', UTC) cmlenz@101: >>> fmt.date(date(2007, 4, 1)) cmlenz@101: u'Apr 1, 2007' cmlenz@101: >>> fmt.decimal(1.2345) cmlenz@101: u'1.234' cmlenz@101: """ cmlenz@101: cmlenz@101: def __init__(self, locale, tzinfo=None): cmlenz@101: """Initialize the formatter. cmlenz@101: cmlenz@101: :param locale: the locale identifier or `Locale` instance cmlenz@101: :param tzinfo: the time-zone info (a `tzinfo` instance or `None`) cmlenz@101: """ cmlenz@101: self.locale = Locale.parse(locale) cmlenz@101: self.tzinfo = tzinfo cmlenz@101: cmlenz@101: def date(self, date=None, format='medium'): cmlenz@101: """Return a date formatted according to the given pattern. cmlenz@101: cmlenz@101: >>> fmt = Format('en_US') cmlenz@101: >>> fmt.date(date(2007, 4, 1)) cmlenz@101: u'Apr 1, 2007' cmlenz@101: cmlenz@101: :see: `babel.dates.format_date` cmlenz@101: """ cmlenz@101: return format_date(date, format, locale=self.locale) cmlenz@101: cmlenz@101: def datetime(self, datetime=None, format='medium'): cmlenz@101: """Return a date and time formatted according to the given pattern. cmlenz@101: cmlenz@101: >>> from pytz import timezone cmlenz@101: >>> fmt = Format('en_US', tzinfo=timezone('US/Eastern')) cmlenz@101: >>> fmt.datetime(datetime(2007, 4, 1, 15, 30)) cmlenz@101: u'Apr 1, 2007 11:30:00 AM' cmlenz@101: cmlenz@101: :see: `babel.dates.format_datetime` cmlenz@101: """ cmlenz@101: return format_datetime(datetime, format, tzinfo=self.tzinfo, cmlenz@101: locale=self.locale) cmlenz@101: cmlenz@101: def time(self, time=None, format='medium'): cmlenz@101: """Return a time formatted according to the given pattern. cmlenz@101: cmlenz@101: >>> from pytz import timezone cmlenz@101: >>> fmt = Format('en_US', tzinfo=timezone('US/Eastern')) cmlenz@348: >>> fmt.time(datetime(2007, 4, 1, 15, 30)) cmlenz@101: u'11:30:00 AM' cmlenz@101: cmlenz@101: :see: `babel.dates.format_time` cmlenz@101: """ cmlenz@101: return format_time(time, format, tzinfo=self.tzinfo, locale=self.locale) cmlenz@101: cmlenz@401: def timedelta(self, delta, granularity='second', threshold=.85): cmlenz@401: """Return a time delta according to the rules of the given locale. cmlenz@401: cmlenz@401: >>> fmt = Format('en_US') cmlenz@401: >>> fmt.timedelta(timedelta(weeks=11)) jruigrok@430: u'3 mths' cmlenz@401: cmlenz@401: :see: `babel.dates.format_timedelta` cmlenz@401: """ cmlenz@401: return format_timedelta(delta, granularity=granularity, cmlenz@401: threshold=threshold, locale=self.locale) cmlenz@401: cmlenz@101: def number(self, number): cmlenz@101: """Return an integer number formatted for the locale. cmlenz@101: cmlenz@101: >>> fmt = Format('en_US') cmlenz@101: >>> fmt.number(1099) cmlenz@101: u'1,099' cmlenz@101: cmlenz@101: :see: `babel.numbers.format_number` cmlenz@101: """ cmlenz@101: return format_number(number, locale=self.locale) cmlenz@101: cmlenz@101: def decimal(self, number, format=None): cmlenz@101: """Return a decimal number formatted for the locale. cmlenz@101: cmlenz@101: >>> fmt = Format('en_US') cmlenz@101: >>> fmt.decimal(1.2345) cmlenz@101: u'1.234' cmlenz@101: cmlenz@101: :see: `babel.numbers.format_decimal` cmlenz@101: """ cmlenz@101: return format_decimal(number, format, locale=self.locale) cmlenz@101: cmlenz@101: def currency(self, number, currency): cmlenz@101: """Return a number in the given currency formatted for the locale. cmlenz@101: cmlenz@101: :see: `babel.numbers.format_currency` cmlenz@101: """ cmlenz@101: return format_currency(number, currency, locale=self.locale) cmlenz@101: cmlenz@101: def percent(self, number, format=None): cmlenz@101: """Return a number formatted as percentage for the locale. cmlenz@101: cmlenz@101: >>> fmt = Format('en_US') cmlenz@101: >>> fmt.percent(0.34) cmlenz@101: u'34%' cmlenz@101: cmlenz@101: :see: `babel.numbers.format_percent` cmlenz@101: """ cmlenz@101: return format_percent(number, format, locale=self.locale) cmlenz@101: cmlenz@101: def scientific(self, number): cmlenz@101: """Return a number formatted using scientific notation for the locale. cmlenz@101: cmlenz@101: :see: `babel.numbers.format_scientific` cmlenz@101: """ cmlenz@101: return format_scientific(number, locale=self.locale) cmlenz@101: cmlenz@101: cmlenz@61: class LazyProxy(object): cmlenz@61: """Class for proxy objects that delegate to a specified function to evaluate cmlenz@61: the actual object. cmlenz@61: cmlenz@61: >>> def greeting(name='world'): cmlenz@61: ... return 'Hello, %s!' % name cmlenz@61: >>> lazy_greeting = LazyProxy(greeting, name='Joe') cmlenz@61: >>> print lazy_greeting cmlenz@61: Hello, Joe! cmlenz@61: >>> u' ' + lazy_greeting cmlenz@61: u' Hello, Joe!' cmlenz@61: >>> u'(%s)' % lazy_greeting cmlenz@61: u'(Hello, Joe!)' cmlenz@61: cmlenz@61: This can be used, for example, to implement lazy translation functions that cmlenz@61: delay the actual translation until the string is actually used. The cmlenz@61: rationale for such behavior is that the locale of the user may not always cmlenz@61: be available. In web applications, you only know the locale when processing cmlenz@61: a request. cmlenz@61: cmlenz@61: The proxy implementation attempts to be as complete as possible, so that cmlenz@61: the lazy objects should mostly work as expected, for example for sorting: cmlenz@61: cmlenz@61: >>> greetings = [ cmlenz@61: ... LazyProxy(greeting, 'world'), cmlenz@61: ... LazyProxy(greeting, 'Joe'), cmlenz@61: ... LazyProxy(greeting, 'universe'), cmlenz@61: ... ] cmlenz@61: >>> greetings.sort() cmlenz@61: >>> for greeting in greetings: cmlenz@61: ... print greeting cmlenz@61: Hello, Joe! cmlenz@61: Hello, universe! cmlenz@61: Hello, world! cmlenz@61: """ fschwarz@559: __slots__ = ['_func', '_args', '_kwargs', '_value', '_is_cache_enabled'] cmlenz@61: cmlenz@61: def __init__(self, func, *args, **kwargs): fschwarz@559: is_cache_enabled = kwargs.pop('enable_cache', True) cmlenz@61: # Avoid triggering our own __setattr__ implementation cmlenz@61: object.__setattr__(self, '_func', func) cmlenz@61: object.__setattr__(self, '_args', args) cmlenz@61: object.__setattr__(self, '_kwargs', kwargs) fschwarz@559: object.__setattr__(self, '_is_cache_enabled', is_cache_enabled) cmlenz@61: object.__setattr__(self, '_value', None) cmlenz@61: fschwarz@579: @property cmlenz@61: def value(self): cmlenz@61: if self._value is None: cmlenz@61: value = self._func(*self._args, **self._kwargs) fschwarz@559: if not self._is_cache_enabled: fschwarz@559: return value cmlenz@61: object.__setattr__(self, '_value', value) cmlenz@61: return self._value cmlenz@61: cmlenz@61: def __contains__(self, key): cmlenz@61: return key in self.value cmlenz@61: cmlenz@61: def __nonzero__(self): cmlenz@61: return bool(self.value) cmlenz@61: cmlenz@61: def __dir__(self): cmlenz@61: return dir(self.value) cmlenz@61: cmlenz@61: def __iter__(self): cmlenz@61: return iter(self.value) cmlenz@61: cmlenz@61: def __len__(self): cmlenz@61: return len(self.value) cmlenz@61: cmlenz@61: def __str__(self): cmlenz@61: return str(self.value) cmlenz@61: cmlenz@61: def __unicode__(self): cmlenz@61: return unicode(self.value) cmlenz@61: cmlenz@61: def __add__(self, other): cmlenz@61: return self.value + other cmlenz@61: cmlenz@61: def __radd__(self, other): cmlenz@61: return other + self.value cmlenz@61: cmlenz@61: def __mod__(self, other): cmlenz@61: return self.value % other cmlenz@61: cmlenz@61: def __rmod__(self, other): cmlenz@61: return other % self.value cmlenz@61: cmlenz@61: def __mul__(self, other): cmlenz@61: return self.value * other cmlenz@61: cmlenz@61: def __rmul__(self, other): cmlenz@61: return other * self.value cmlenz@61: cmlenz@61: def __call__(self, *args, **kwargs): cmlenz@61: return self.value(*args, **kwargs) cmlenz@61: cmlenz@61: def __lt__(self, other): cmlenz@61: return self.value < other cmlenz@61: cmlenz@61: def __le__(self, other): cmlenz@61: return self.value <= other cmlenz@61: cmlenz@61: def __eq__(self, other): cmlenz@61: return self.value == other cmlenz@61: cmlenz@61: def __ne__(self, other): cmlenz@61: return self.value != other cmlenz@61: cmlenz@61: def __gt__(self, other): cmlenz@61: return self.value > other cmlenz@61: cmlenz@61: def __ge__(self, other): cmlenz@61: return self.value >= other cmlenz@61: cmlenz@61: def __delattr__(self, name): cmlenz@61: delattr(self.value, name) cmlenz@61: cmlenz@61: def __getattr__(self, name): cmlenz@61: return getattr(self.value, name) cmlenz@61: pjenvey@100: def __setattr__(self, name, value): cmlenz@61: setattr(self.value, name, value) cmlenz@61: cmlenz@61: def __delitem__(self, key): cmlenz@61: del self.value[key] cmlenz@61: cmlenz@61: def __getitem__(self, key): cmlenz@61: return self.value[key] cmlenz@61: cmlenz@61: def __setitem__(self, key, value): pjenvey@100: self.value[key] = value cmlenz@61: cmlenz@61: fschwarz@596: class NullTranslations(gettext.NullTranslations, object): cmlenz@408: fschwarz@596: DEFAULT_DOMAIN = None cmlenz@408: fschwarz@596: def __init__(self, fp=None): fschwarz@596: """Initialize a simple translations class which is not backed by a fschwarz@596: real catalog. Behaves similar to gettext.NullTranslations but also fschwarz@596: offers Babel's on *gettext methods (e.g. 'dgettext()'). cmlenz@408: fschwarz@596: :param fp: a file-like object (ignored in this class) cmlenz@61: """ fschwarz@596: # These attributes are set by gettext.NullTranslations when a catalog fschwarz@596: # is parsed (fp != None). Ensure that they are always present because fschwarz@596: # some *gettext methods (including '.gettext()') rely on the attributes. fschwarz@596: self._catalog = {} fschwarz@596: self.plural = lambda n: int(n != 1) fschwarz@596: super(NullTranslations, self).__init__(fp=fp) fschwarz@596: self.files = filter(None, [getattr(fp, 'name', None)]) fschwarz@596: self.domain = self.DEFAULT_DOMAIN fschwarz@596: self._domains = {} cmlenz@61: cmlenz@408: def dgettext(self, domain, message): cmlenz@408: """Like ``gettext()``, but look the message up in the specified cmlenz@408: domain. cmlenz@408: """ cmlenz@408: return self._domains.get(domain, self).gettext(message) cmlenz@408: cmlenz@408: def ldgettext(self, domain, message): cmlenz@408: """Like ``lgettext()``, but look the message up in the specified cmlenz@408: domain. cmlenz@408: """ cmlenz@408: return self._domains.get(domain, self).lgettext(message) cmlenz@408: fschwarz@561: def udgettext(self, domain, message): cmlenz@408: """Like ``ugettext()``, but look the message up in the specified cmlenz@408: domain. cmlenz@408: """ cmlenz@408: return self._domains.get(domain, self).ugettext(message) fschwarz@561: # backward compatibility with 0.9 fschwarz@561: dugettext = udgettext cmlenz@408: cmlenz@408: def dngettext(self, domain, singular, plural, num): cmlenz@408: """Like ``ngettext()``, but look the message up in the specified cmlenz@408: domain. cmlenz@408: """ cmlenz@408: return self._domains.get(domain, self).ngettext(singular, plural, num) cmlenz@408: cmlenz@408: def ldngettext(self, domain, singular, plural, num): cmlenz@408: """Like ``lngettext()``, but look the message up in the specified cmlenz@408: domain. cmlenz@408: """ cmlenz@408: return self._domains.get(domain, self).lngettext(singular, plural, num) cmlenz@408: fschwarz@561: def udngettext(self, domain, singular, plural, num): cmlenz@408: """Like ``ungettext()`` but look the message up in the specified cmlenz@408: domain. cmlenz@408: """ cmlenz@408: return self._domains.get(domain, self).ungettext(singular, plural, num) fschwarz@561: # backward compatibility with 0.9 fschwarz@561: dungettext = udngettext palgarvio@420: palgarvio@420: # Most of the downwards code, until it get's included in stdlib, from: palgarvio@420: # http://bugs.python.org/file10036/gettext-pgettext.patch palgarvio@420: # palgarvio@420: # The encoding of a msgctxt and a msgid in a .mo file is palgarvio@420: # msgctxt + "\x04" + msgid (gettext version >= 0.15) palgarvio@420: CONTEXT_ENCODING = '%s\x04%s' palgarvio@420: palgarvio@420: def pgettext(self, context, message): palgarvio@420: """Look up the `context` and `message` id in the catalog and return the palgarvio@420: corresponding message string, as an 8-bit string encoded with the palgarvio@420: catalog's charset encoding, if known. If there is no entry in the palgarvio@420: catalog for the `message` id and `context` , and a fallback has been palgarvio@420: set, the look up is forwarded to the fallback's ``pgettext()`` palgarvio@420: method. Otherwise, the `message` id is returned. palgarvio@420: """ palgarvio@420: ctxt_msg_id = self.CONTEXT_ENCODING % (context, message) palgarvio@420: missing = object() palgarvio@420: tmsg = self._catalog.get(ctxt_msg_id, missing) palgarvio@420: if tmsg is missing: palgarvio@420: if self._fallback: palgarvio@420: return self._fallback.pgettext(context, message) palgarvio@420: return message palgarvio@420: # Encode the Unicode tmsg back to an 8-bit string, if possible palgarvio@420: if self._output_charset: palgarvio@420: return tmsg.encode(self._output_charset) palgarvio@420: elif self._charset: palgarvio@420: return tmsg.encode(self._charset) palgarvio@420: return tmsg palgarvio@420: palgarvio@420: def lpgettext(self, context, message): palgarvio@420: """Equivalent to ``pgettext()``, but the translation is returned in the palgarvio@420: preferred system encoding, if no other encoding was explicitly set with palgarvio@420: ``bind_textdomain_codeset()``. palgarvio@420: """ palgarvio@420: ctxt_msg_id = self.CONTEXT_ENCODING % (context, message) palgarvio@420: missing = object() palgarvio@420: tmsg = self._catalog.get(ctxt_msg_id, missing) palgarvio@420: if tmsg is missing: palgarvio@420: if self._fallback: palgarvio@420: return self._fallback.lpgettext(context, message) palgarvio@420: return message palgarvio@420: if self._output_charset: palgarvio@420: return tmsg.encode(self._output_charset) palgarvio@420: return tmsg.encode(locale.getpreferredencoding()) palgarvio@420: palgarvio@420: def npgettext(self, context, singular, plural, num): palgarvio@420: """Do a plural-forms lookup of a message id. `singular` is used as the palgarvio@420: message id for purposes of lookup in the catalog, while `num` is used to palgarvio@420: determine which plural form to use. The returned message string is an palgarvio@420: 8-bit string encoded with the catalog's charset encoding, if known. palgarvio@420: palgarvio@420: If the message id for `context` is not found in the catalog, and a palgarvio@420: fallback is specified, the request is forwarded to the fallback's palgarvio@420: ``npgettext()`` method. Otherwise, when ``num`` is 1 ``singular`` is palgarvio@420: returned, and ``plural`` is returned in all other cases. palgarvio@420: """ palgarvio@420: ctxt_msg_id = self.CONTEXT_ENCODING % (context, singular) palgarvio@420: try: palgarvio@420: tmsg = self._catalog[(ctxt_msg_id, self.plural(num))] palgarvio@420: if self._output_charset: palgarvio@420: return tmsg.encode(self._output_charset) palgarvio@420: elif self._charset: palgarvio@420: return tmsg.encode(self._charset) palgarvio@420: return tmsg palgarvio@420: except KeyError: palgarvio@420: if self._fallback: palgarvio@420: return self._fallback.npgettext(context, singular, plural, num) palgarvio@420: if num == 1: palgarvio@420: return singular palgarvio@420: else: palgarvio@420: return plural palgarvio@420: palgarvio@420: def lnpgettext(self, context, singular, plural, num): palgarvio@420: """Equivalent to ``npgettext()``, but the translation is returned in the palgarvio@420: preferred system encoding, if no other encoding was explicitly set with palgarvio@420: ``bind_textdomain_codeset()``. palgarvio@420: """ palgarvio@420: ctxt_msg_id = self.CONTEXT_ENCODING % (context, singular) palgarvio@420: try: palgarvio@420: tmsg = self._catalog[(ctxt_msg_id, self.plural(num))] palgarvio@420: if self._output_charset: palgarvio@420: return tmsg.encode(self._output_charset) palgarvio@420: return tmsg.encode(locale.getpreferredencoding()) palgarvio@420: except KeyError: palgarvio@420: if self._fallback: palgarvio@420: return self._fallback.lnpgettext(context, singular, plural, num) palgarvio@420: if num == 1: palgarvio@420: return singular palgarvio@420: else: palgarvio@420: return plural palgarvio@420: palgarvio@420: def upgettext(self, context, message): palgarvio@420: """Look up the `context` and `message` id in the catalog and return the palgarvio@420: corresponding message string, as a Unicode string. If there is no entry palgarvio@420: in the catalog for the `message` id and `context`, and a fallback has palgarvio@420: been set, the look up is forwarded to the fallback's ``upgettext()`` palgarvio@420: method. Otherwise, the `message` id is returned. palgarvio@420: """ palgarvio@420: ctxt_message_id = self.CONTEXT_ENCODING % (context, message) palgarvio@420: missing = object() palgarvio@420: tmsg = self._catalog.get(ctxt_message_id, missing) palgarvio@420: if tmsg is missing: palgarvio@420: if self._fallback: palgarvio@420: return self._fallback.upgettext(context, message) palgarvio@420: return unicode(message) palgarvio@420: return tmsg palgarvio@420: palgarvio@420: def unpgettext(self, context, singular, plural, num): palgarvio@420: """Do a plural-forms lookup of a message id. `singular` is used as the palgarvio@420: message id for purposes of lookup in the catalog, while `num` is used to palgarvio@420: determine which plural form to use. The returned message string is a palgarvio@420: Unicode string. palgarvio@420: palgarvio@420: If the message id for `context` is not found in the catalog, and a palgarvio@420: fallback is specified, the request is forwarded to the fallback's palgarvio@420: ``unpgettext()`` method. Otherwise, when `num` is 1 `singular` is palgarvio@420: returned, and `plural` is returned in all other cases. palgarvio@420: """ palgarvio@420: ctxt_message_id = self.CONTEXT_ENCODING % (context, singular) palgarvio@420: try: palgarvio@420: tmsg = self._catalog[(ctxt_message_id, self.plural(num))] palgarvio@420: except KeyError: palgarvio@420: if self._fallback: palgarvio@420: return self._fallback.unpgettext(context, singular, plural, num) palgarvio@420: if num == 1: palgarvio@420: tmsg = unicode(singular) palgarvio@420: else: palgarvio@420: tmsg = unicode(plural) palgarvio@420: return tmsg palgarvio@420: palgarvio@420: def dpgettext(self, domain, context, message): palgarvio@420: """Like `pgettext()`, but look the message up in the specified palgarvio@420: `domain`. palgarvio@420: """ palgarvio@420: return self._domains.get(domain, self).pgettext(context, message) palgarvio@420: fschwarz@561: def udpgettext(self, domain, context, message): palgarvio@420: """Like `upgettext()`, but look the message up in the specified palgarvio@420: `domain`. palgarvio@420: """ palgarvio@420: return self._domains.get(domain, self).upgettext(context, message) fschwarz@561: # backward compatibility with 0.9 fschwarz@561: dupgettext = udpgettext palgarvio@420: palgarvio@420: def ldpgettext(self, domain, context, message): palgarvio@420: """Equivalent to ``dpgettext()``, but the translation is returned in the palgarvio@420: preferred system encoding, if no other encoding was explicitly set with palgarvio@420: ``bind_textdomain_codeset()``. palgarvio@420: """ palgarvio@420: return self._domains.get(domain, self).lpgettext(context, message) palgarvio@420: palgarvio@420: def dnpgettext(self, domain, context, singular, plural, num): palgarvio@420: """Like ``npgettext``, but look the message up in the specified palgarvio@420: `domain`. palgarvio@420: """ palgarvio@420: return self._domains.get(domain, self).npgettext(context, singular, palgarvio@420: plural, num) palgarvio@420: fschwarz@561: def udnpgettext(self, domain, context, singular, plural, num): palgarvio@420: """Like ``unpgettext``, but look the message up in the specified palgarvio@420: `domain`. palgarvio@420: """ palgarvio@420: return self._domains.get(domain, self).unpgettext(context, singular, palgarvio@420: plural, num) fschwarz@561: # backward compatibility with 0.9 fschwarz@561: dunpgettext = udnpgettext palgarvio@420: palgarvio@420: def ldnpgettext(self, domain, context, singular, plural, num): palgarvio@420: """Equivalent to ``dnpgettext()``, but the translation is returned in palgarvio@420: the preferred system encoding, if no other encoding was explicitly set palgarvio@420: with ``bind_textdomain_codeset()``. palgarvio@420: """ palgarvio@420: return self._domains.get(domain, self).lnpgettext(context, singular, palgarvio@420: plural, num) palgarvio@420: fschwarz@596: fschwarz@596: class Translations(NullTranslations, gettext.GNUTranslations): fschwarz@596: """An extended translation catalog class.""" fschwarz@596: fschwarz@596: DEFAULT_DOMAIN = 'messages' fschwarz@596: fschwarz@599: def __init__(self, fp=None, domain=None): fschwarz@596: """Initialize the translations catalog. fschwarz@596: fschwarz@599: :param fp: the file-like object the translation should be read from fschwarz@596: :param domain: the message domain (default: 'messages') fschwarz@596: """ fschwarz@599: super(Translations, self).__init__(fp=fp) fschwarz@596: self.domain = domain or self.DEFAULT_DOMAIN fschwarz@596: fschwarz@596: @classmethod fschwarz@596: def load(cls, dirname=None, locales=None, domain=None): fschwarz@596: """Load translations from the given directory. fschwarz@596: fschwarz@596: :param dirname: the directory containing the ``MO`` files fschwarz@596: :param locales: the list of locales in order of preference (items in fschwarz@596: this list can be either `Locale` objects or locale fschwarz@596: strings) fschwarz@596: :param domain: the message domain (default: 'messages') fschwarz@596: :return: the loaded catalog, or a ``NullTranslations`` instance if no fschwarz@596: matching translations were found fschwarz@596: :rtype: `Translations` fschwarz@596: """ fschwarz@596: if locales is not None: fschwarz@596: if not isinstance(locales, (list, tuple)): fschwarz@596: locales = [locales] fschwarz@596: locales = [str(locale) for locale in locales] fschwarz@596: if not domain: fschwarz@596: domain = cls.DEFAULT_DOMAIN fschwarz@596: filename = gettext.find(domain, dirname, locales) fschwarz@596: if not filename: fschwarz@597: return NullTranslations() fschwarz@603: return cls(fp=open(filename, 'rb'), domain=domain) fschwarz@596: fschwarz@596: def __repr__(self): fschwarz@596: return '<%s: "%s">' % (type(self).__name__, fschwarz@596: self._info.get('project-id-version')) fschwarz@596: fschwarz@596: def add(self, translations, merge=True): fschwarz@596: """Add the given translations to the catalog. fschwarz@596: fschwarz@596: If the domain of the translations is different than that of the fschwarz@596: current catalog, they are added as a catalog that is only accessible fschwarz@596: by the various ``d*gettext`` functions. fschwarz@596: fschwarz@596: :param translations: the `Translations` instance with the messages to fschwarz@596: add fschwarz@596: :param merge: whether translations for message domains that have fschwarz@596: already been added should be merged with the existing fschwarz@596: translations fschwarz@596: :return: the `Translations` instance (``self``) so that `merge` calls fschwarz@596: can be easily chained fschwarz@596: :rtype: `Translations` fschwarz@596: """ fschwarz@596: domain = getattr(translations, 'domain', self.DEFAULT_DOMAIN) fschwarz@596: if merge and domain == self.domain: fschwarz@596: return self.merge(translations) fschwarz@596: fschwarz@596: existing = self._domains.get(domain) fschwarz@596: if merge and existing is not None: fschwarz@596: existing.merge(translations) fschwarz@596: else: fschwarz@596: translations.add_fallback(self) fschwarz@596: self._domains[domain] = translations fschwarz@596: fschwarz@596: return self fschwarz@596: fschwarz@596: def merge(self, translations): fschwarz@596: """Merge the given translations into the catalog. fschwarz@596: fschwarz@596: Message translations in the specified catalog override any messages fschwarz@596: with the same identifier in the existing catalog. fschwarz@596: fschwarz@596: :param translations: the `Translations` instance with the messages to fschwarz@596: merge fschwarz@596: :return: the `Translations` instance (``self``) so that `merge` calls fschwarz@596: can be easily chained fschwarz@596: :rtype: `Translations` fschwarz@596: """ fschwarz@596: if isinstance(translations, gettext.GNUTranslations): fschwarz@596: self._catalog.update(translations._catalog) fschwarz@596: if isinstance(translations, Translations): fschwarz@596: self.files.extend(translations.files) fschwarz@596: fschwarz@596: return self fschwarz@596: