Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/babel/support.py
diff options
context:
space:
mode:
Diffstat (limited to 'babel/support.py')
-rw-r--r--babel/support.py398
1 files changed, 398 insertions, 0 deletions
diff --git a/babel/support.py b/babel/support.py
new file mode 100644
index 0000000..74f7162
--- /dev/null
+++ b/babel/support.py
@@ -0,0 +1,398 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://babel.edgewall.org/wiki/License.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://babel.edgewall.org/log/.
+
+"""Several classes and functions that help with integrating and using Babel
+in applications.
+
+.. note: the code in this module is not used by Babel itself
+"""
+
+from datetime import date, datetime, time
+import gettext
+
+try:
+ set
+except NameError:
+ from sets import set
+
+from babel.core import Locale
+from babel.dates import format_date, format_datetime, format_time, LC_TIME
+from babel.numbers import format_number, format_decimal, format_currency, \
+ format_percent, format_scientific, LC_NUMERIC
+from babel.util import UTC
+
+__all__ = ['Format', 'LazyProxy', 'Translations']
+__docformat__ = 'restructuredtext en'
+
+
+class Format(object):
+ """Wrapper class providing the various date and number formatting functions
+ bound to a specific locale and time-zone.
+
+ >>> fmt = Format('en_US', UTC)
+ >>> fmt.date(date(2007, 4, 1))
+ u'Apr 1, 2007'
+ >>> fmt.decimal(1.2345)
+ u'1.234'
+ """
+
+ def __init__(self, locale, tzinfo=None):
+ """Initialize the formatter.
+
+ :param locale: the locale identifier or `Locale` instance
+ :param tzinfo: the time-zone info (a `tzinfo` instance or `None`)
+ """
+ self.locale = Locale.parse(locale)
+ self.tzinfo = tzinfo
+
+ def date(self, date=None, format='medium'):
+ """Return a date formatted according to the given pattern.
+
+ >>> fmt = Format('en_US')
+ >>> fmt.date(date(2007, 4, 1))
+ u'Apr 1, 2007'
+
+ :see: `babel.dates.format_date`
+ """
+ return format_date(date, format, locale=self.locale)
+
+ def datetime(self, datetime=None, format='medium'):
+ """Return a date and time formatted according to the given pattern.
+
+ >>> from pytz import timezone
+ >>> fmt = Format('en_US', tzinfo=timezone('US/Eastern'))
+ >>> fmt.datetime(datetime(2007, 4, 1, 15, 30))
+ u'Apr 1, 2007 11:30:00 AM'
+
+ :see: `babel.dates.format_datetime`
+ """
+ return format_datetime(datetime, format, tzinfo=self.tzinfo,
+ locale=self.locale)
+
+ def time(self, time=None, format='medium'):
+ """Return a time formatted according to the given pattern.
+
+ >>> from pytz import timezone
+ >>> fmt = Format('en_US', tzinfo=timezone('US/Eastern'))
+ >>> fmt.time(datetime(2007, 4, 1, 15, 30))
+ u'11:30:00 AM'
+
+ :see: `babel.dates.format_time`
+ """
+ return format_time(time, format, tzinfo=self.tzinfo, locale=self.locale)
+
+ def number(self, number):
+ """Return an integer number formatted for the locale.
+
+ >>> fmt = Format('en_US')
+ >>> fmt.number(1099)
+ u'1,099'
+
+ :see: `babel.numbers.format_number`
+ """
+ return format_number(number, locale=self.locale)
+
+ def decimal(self, number, format=None):
+ """Return a decimal number formatted for the locale.
+
+ >>> fmt = Format('en_US')
+ >>> fmt.decimal(1.2345)
+ u'1.234'
+
+ :see: `babel.numbers.format_decimal`
+ """
+ return format_decimal(number, format, locale=self.locale)
+
+ def currency(self, number, currency):
+ """Return a number in the given currency formatted for the locale.
+
+ :see: `babel.numbers.format_currency`
+ """
+ return format_currency(number, currency, locale=self.locale)
+
+ def percent(self, number, format=None):
+ """Return a number formatted as percentage for the locale.
+
+ >>> fmt = Format('en_US')
+ >>> fmt.percent(0.34)
+ u'34%'
+
+ :see: `babel.numbers.format_percent`
+ """
+ return format_percent(number, format, locale=self.locale)
+
+ def scientific(self, number):
+ """Return a number formatted using scientific notation for the locale.
+
+ :see: `babel.numbers.format_scientific`
+ """
+ return format_scientific(number, locale=self.locale)
+
+
+class LazyProxy(object):
+ """Class for proxy objects that delegate to a specified function to evaluate
+ the actual object.
+
+ >>> def greeting(name='world'):
+ ... return 'Hello, %s!' % name
+ >>> lazy_greeting = LazyProxy(greeting, name='Joe')
+ >>> print lazy_greeting
+ Hello, Joe!
+ >>> u' ' + lazy_greeting
+ u' Hello, Joe!'
+ >>> u'(%s)' % lazy_greeting
+ u'(Hello, Joe!)'
+
+ This can be used, for example, to implement lazy translation functions that
+ delay the actual translation until the string is actually used. The
+ rationale for such behavior is that the locale of the user may not always
+ be available. In web applications, you only know the locale when processing
+ a request.
+
+ The proxy implementation attempts to be as complete as possible, so that
+ the lazy objects should mostly work as expected, for example for sorting:
+
+ >>> greetings = [
+ ... LazyProxy(greeting, 'world'),
+ ... LazyProxy(greeting, 'Joe'),
+ ... LazyProxy(greeting, 'universe'),
+ ... ]
+ >>> greetings.sort()
+ >>> for greeting in greetings:
+ ... print greeting
+ Hello, Joe!
+ Hello, universe!
+ Hello, world!
+ """
+ __slots__ = ['_func', '_args', '_kwargs', '_value']
+
+ def __init__(self, func, *args, **kwargs):
+ # Avoid triggering our own __setattr__ implementation
+ object.__setattr__(self, '_func', func)
+ object.__setattr__(self, '_args', args)
+ object.__setattr__(self, '_kwargs', kwargs)
+ object.__setattr__(self, '_value', None)
+
+ def value(self):
+ if self._value is None:
+ value = self._func(*self._args, **self._kwargs)
+ object.__setattr__(self, '_value', value)
+ return self._value
+ value = property(value)
+
+ def __contains__(self, key):
+ return key in self.value
+
+ def __nonzero__(self):
+ return bool(self.value)
+
+ def __dir__(self):
+ return dir(self.value)
+
+ def __iter__(self):
+ return iter(self.value)
+
+ def __len__(self):
+ return len(self.value)
+
+ def __str__(self):
+ return str(self.value)
+
+ def __unicode__(self):
+ return unicode(self.value)
+
+ def __add__(self, other):
+ return self.value + other
+
+ def __radd__(self, other):
+ return other + self.value
+
+ def __mod__(self, other):
+ return self.value % other
+
+ def __rmod__(self, other):
+ return other % self.value
+
+ def __mul__(self, other):
+ return self.value * other
+
+ def __rmul__(self, other):
+ return other * self.value
+
+ def __call__(self, *args, **kwargs):
+ return self.value(*args, **kwargs)
+
+ def __lt__(self, other):
+ return self.value < other
+
+ def __le__(self, other):
+ return self.value <= other
+
+ def __eq__(self, other):
+ return self.value == other
+
+ def __ne__(self, other):
+ return self.value != other
+
+ def __gt__(self, other):
+ return self.value > other
+
+ def __ge__(self, other):
+ return self.value >= other
+
+ def __delattr__(self, name):
+ delattr(self.value, name)
+
+ def __getattr__(self, name):
+ return getattr(self.value, name)
+
+ def __setattr__(self, name, value):
+ setattr(self.value, name, value)
+
+ def __delitem__(self, key):
+ del self.value[key]
+
+ def __getitem__(self, key):
+ return self.value[key]
+
+ def __setitem__(self, key, value):
+ self.value[key] = value
+
+
+class Translations(gettext.GNUTranslations, object):
+ """An extended translation catalog class."""
+
+ DEFAULT_DOMAIN = 'messages'
+
+ def __init__(self, fileobj=None, domain=DEFAULT_DOMAIN):
+ """Initialize the translations catalog.
+
+ :param fileobj: the file-like object the translation should be read
+ from
+ """
+ gettext.GNUTranslations.__init__(self, fp=fileobj)
+ self.files = filter(None, [getattr(fileobj, 'name', None)])
+ self.domain = domain
+ self._domains = {}
+
+ def load(cls, dirname=None, locales=None, domain=DEFAULT_DOMAIN):
+ """Load translations from the given directory.
+
+ :param dirname: the directory containing the ``MO`` files
+ :param locales: the list of locales in order of preference (items in
+ this list can be either `Locale` objects or locale
+ strings)
+ :param domain: the message domain
+ :return: the loaded catalog, or a ``NullTranslations`` instance if no
+ matching translations were found
+ :rtype: `Translations`
+ """
+ if locales is not None:
+ if not isinstance(locales, (list, tuple)):
+ locales = [locales]
+ locales = [str(locale) for locale in locales]
+ if not domain:
+ domain = cls.DEFAULT_DOMAIN
+ filename = gettext.find(domain, dirname, locales)
+ if not filename:
+ return gettext.NullTranslations()
+ return cls(fileobj=open(filename, 'rb'), domain=domain)
+ load = classmethod(load)
+
+ def __repr__(self):
+ return '<%s: "%s">' % (type(self).__name__,
+ self._info.get('project-id-version'))
+
+ def add(self, translations, merge=True):
+ """Add the given translations to the catalog.
+
+ If the domain of the translations is different than that of the
+ current catalog, they are added as a catalog that is only accessible
+ by the various ``d*gettext`` functions.
+
+ :param translations: the `Translations` instance with the messages to
+ add
+ :param merge: whether translations for message domains that have
+ already been added should be merged with the existing
+ translations
+ :return: the `Translations` instance (``self``) so that `merge` calls
+ can be easily chained
+ :rtype: `Translations`
+ """
+ domain = getattr(translations, 'domain', self.DEFAULT_DOMAIN)
+ if merge and domain == self.domain:
+ return self.merge(translations)
+
+ existing = self._domains.get(domain)
+ if merge and existing is not None:
+ existing.merge(translations)
+ else:
+ translations.add_fallback(self)
+ self._domains[domain] = translations
+
+ return self
+
+ def merge(self, translations):
+ """Merge the given translations into the catalog.
+
+ Message translations in the specified catalog override any messages
+ with the same identifier in the existing catalog.
+
+ :param translations: the `Translations` instance with the messages to
+ merge
+ :return: the `Translations` instance (``self``) so that `merge` calls
+ can be easily chained
+ :rtype: `Translations`
+ """
+ if isinstance(translations, gettext.GNUTranslations):
+ self._catalog.update(translations._catalog)
+ if isinstance(translations, Translations):
+ self.files.extend(translations.files)
+
+ return self
+
+ def dgettext(self, domain, message):
+ """Like ``gettext()``, but look the message up in the specified
+ domain.
+ """
+ return self._domains.get(domain, self).gettext(message)
+
+ def ldgettext(self, domain, message):
+ """Like ``lgettext()``, but look the message up in the specified
+ domain.
+ """
+ return self._domains.get(domain, self).lgettext(message)
+
+ def dugettext(self, domain, message):
+ """Like ``ugettext()``, but look the message up in the specified
+ domain.
+ """
+ return self._domains.get(domain, self).ugettext(message)
+
+ def dngettext(self, domain, singular, plural, num):
+ """Like ``ngettext()``, but look the message up in the specified
+ domain.
+ """
+ return self._domains.get(domain, self).ngettext(singular, plural, num)
+
+ def ldngettext(self, domain, singular, plural, num):
+ """Like ``lngettext()``, but look the message up in the specified
+ domain.
+ """
+ return self._domains.get(domain, self).lngettext(singular, plural, num)
+
+ def dungettext(self, domain, singular, plural, num):
+ """Like ``ungettext()`` but look the message up in the specified
+ domain.
+ """
+ return self._domains.get(domain, self).ungettext(singular, plural, num)