# HG changeset patch # User cmlenz # Date 1181303641 0 # Node ID da7efa40a9e252771f78f20935d9e24c75c25311 # Parent 2834e2a6fd269d6dba540841427309b9b81bf9c2 Move `Translations` and `LazyProxy` to new `babel.support` module, which should contain any convenience code that is useful for applications using Babel/I18n, but not used by Babel itself. ''(Note that [61] was an accidential check in of part of this change)'' diff --git a/babel/messages/catalog.py b/babel/messages/catalog.py --- a/babel/messages/catalog.py +++ b/babel/messages/catalog.py @@ -62,7 +62,7 @@ >>> Message(('foo', 'bar')).pluralizable True - :rtype: `bool` + :type: `bool` """) def python_format(self): @@ -78,7 +78,7 @@ >>> Message(('foo %(name)s', 'foo %(name)s')).python_format True - :rtype: `bool` + :type: `bool` """) diff --git a/babel/support.py b/babel/support.py new file mode 100644 --- /dev/null +++ b/babel/support.py @@ -0,0 +1,209 @@ +# -*- 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 +""" + +import gettext + +__all__ = ['LazyProxy', 'Translations'] +__docformat__ = 'restructuredtext en' + + +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, key, 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[name] = value + + +class Translations(gettext.GNUTranslations): + """An extended translation catalog class.""" + + DEFAULT_DOMAIN = 'messages' + + def __init__(self, fileobj=None): + """Initialize the translations catalog. + + :param fileobj: the file-like object the translation should be read + from + """ + gettext.GNUTranslations.__init__(self, fp=fileobj) + self.files = [getattr(fileobj, 'name')] + + 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 not isinstance(locales, (list, tuple)): + locales = [locales] + locales = [str(locale) for locale in locales] + filename = gettext.find(domain or self.DEFAULT_DOMAIN, dirname, locales) + if not filename: + return gettext.NullTranslations() + return cls(fileobj=open(filename, 'rb')) + load = classmethod(load) + + def merge(self, translations): + """Merge the given translations into the catalog. + + Message translations in the specfied 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, Translations): + self._catalog.update(translations._catalog) + self.files.extend(translations.files) + return self + + def __repr__(self): + return "<%s %r>" % (type(self).__name__) diff --git a/babel/tests/__init__.py b/babel/tests/__init__.py --- a/babel/tests/__init__.py +++ b/babel/tests/__init__.py @@ -14,7 +14,7 @@ import unittest def suite(): - from babel.tests import core, dates, localedata, numbers, util + from babel.tests import core, dates, localedata, numbers, support, util from babel.messages import tests as messages suite = unittest.TestSuite() suite.addTest(core.suite()) diff --git a/babel/tests/support.py b/babel/tests/support.py new file mode 100644 --- /dev/null +++ b/babel/tests/support.py @@ -0,0 +1,25 @@ +# -*- 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/. + +import doctest +import unittest + +from babel import support + +def suite(): + suite = unittest.TestSuite() + suite.addTest(doctest.DocTestSuite(support)) + return suite + +if __name__ == '__main__': + unittest.main(defaultTest='suite')