diff options
| author | Chris McDonough <chrism@plope.com> | 2013-08-29 05:08:53 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2013-08-29 05:08:53 -0400 |
| commit | 330164c3190d92a3e1df89baafba12570d03bd32 (patch) | |
| tree | 42b8647b1ea631cd53e75d157a6f1af13bcf9276 | |
| parent | 8a7e80dc64947691fa72925d701d35c3e1d8c87a (diff) | |
| download | pyramid-330164c3190d92a3e1df89baafba12570d03bd32.tar.gz pyramid-330164c3190d92a3e1df89baafba12570d03bd32.tar.bz2 pyramid-330164c3190d92a3e1df89baafba12570d03bd32.zip | |
make local_name an attribute of Request, move logic from get_localizer into Request.localizer, fix docs; closes #1099
| -rw-r--r-- | CHANGES.txt | 12 | ||||
| -rw-r--r-- | docs/api/request.rst | 14 | ||||
| -rw-r--r-- | docs/narr/i18n.rst | 77 | ||||
| -rw-r--r-- | pyramid/i18n.py | 60 | ||||
| -rw-r--r-- | pyramid/request.py | 13 | ||||
| -rw-r--r-- | pyramid/testing.py | 4 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_i18n.py | 13 | ||||
| -rw-r--r-- | pyramid/tests/test_i18n.py | 176 |
8 files changed, 177 insertions, 192 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 6cb2932fa..93349abe6 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -9,8 +9,10 @@ Features represent a "bare" ``{{a}}``. See https://github.com/Pylons/pyramid/pull/862 -- Add ``localizer`` property (reified) to the request. - See https://github.com/Pylons/pyramid/issues/508. +- Add ``localizer`` and ``locale_name`` properties (reified) to the request. + See https://github.com/Pylons/pyramid/issues/508. Note that the + ``pyramid.i18n.get_localizer`` and ``pyramid.i18n.get_locale_name`` functions + now simply look up these properties on the request. - Add ``pdistreport`` script, which prints the Python version in use, the Pyramid version in use, and the version number and location of all Python @@ -224,6 +226,12 @@ Backwards Incompatibilities as WSGI servers always unquote the slash anyway, and Pyramid never sees the quoted value. +- It is no longer possible to set a ``locale_name`` attribute of the request, + nor is it possible to set a ``localizer`` attribute of the request. These + are now "reified" properties that look up a locale name and localizer + respectively using the machinery described in the "Internationalization" + chapter of the documentation. + 1.4 (2012-12-18) ================ diff --git a/docs/api/request.rst b/docs/api/request.rst index a90cb1ac0..02290eaf3 100644 --- a/docs/api/request.rst +++ b/docs/api/request.rst @@ -311,6 +311,20 @@ .. versionadded:: 1.3 + .. attribute:: localizer + + A :term:`localizer` which will use the current locale name to + translate values. + + .. versionadded:: 1.5 + + .. attribute:: locale_name + + The locale name of the current request as computed by the + :term:`locale negotiator`. + + .. versionadded:: 1.5 + .. note:: For information about the API of a :term:`multidict` structure (such as diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst index 555b06e0f..b62c16ff0 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -495,7 +495,6 @@ translations will be available to :app:`Pyramid`. .. index:: single: localizer - single: get_localizer single: translation single: pluralization @@ -503,19 +502,17 @@ Using a Localizer ----------------- A :term:`localizer` is an object that allows you to perform translation or -pluralization "by hand" in an application. You may use the -:func:`pyramid.i18n.get_localizer` function to obtain a :term:`localizer`. -This function will return either the localizer object implied by the active -:term:`locale negotiator` or a default localizer object if no explicit locale -negotiator is registered. +pluralization "by hand" in an application. You may use the +:attr:`pyramid.request.Request.localizer` attribute to obtain a +:term:`localizer`. The localizer object will be configured to produce +translations implied by the active :term:`locale negotiator` or a default +localizer object if no explicit locale negotiator is registered. .. code-block:: python :linenos: - from pyramid.i18n import get_localizer - def aview(request): - locale = get_localizer(request) + localizer = request.localizer .. note:: @@ -538,22 +535,20 @@ translation in a view component of an application might look like so: .. code-block:: python :linenos: - from pyramid.i18n import get_localizer from pyramid.i18n import TranslationString ts = TranslationString('Add ${number}', mapping={'number':1}, domain='pyramid') def aview(request): - localizer = get_localizer(request) + localizer = request.localizer translated = localizer.translate(ts) # translation string # ... use translated ... -The :func:`~pyramid.i18n.get_localizer` function will return a -:class:`pyramid.i18n.Localizer` object bound to the locale name -represented by the request. The translation returned from its -:meth:`pyramid.i18n.Localizer.translate` method will depend on the -``domain`` attribute of the provided translation string as well as the +The ``request.localizer`` attribute will be a :class:`pyramid.i18n.Localizer` +object bound to the locale name represented by the request. The translation +returned from its :meth:`pyramid.i18n.Localizer.translate` method will depend +on the ``domain`` attribute of the provided translation string as well as the locale of the localizer. .. note:: @@ -586,10 +581,8 @@ pluralization rules for the number ``n``, and interpolates ``mapping``. .. code-block:: python :linenos: - from pyramid.i18n import get_localizer - def aview(request): - localizer = get_localizer(request) + localizer = request.localizer translated = localizer.pluralize('Item', 'Items', 1, 'mydomain') # ... use translated ... @@ -611,13 +604,13 @@ object, but the domain and mapping information attached is ignored. .. code-block:: python :linenos: - from pyramid.i18n import get_localizer - def aview(request): - localizer = get_localizer(request) + localizer = request.localizer num = 1 - translated = localizer.pluralize(_('item_plural', default="${number} items"), - None, num, 'mydomain', mapping={'number':num}) + translated = localizer.pluralize( + _('item_plural', default="${number} items"), + None, num, 'mydomain', mapping={'number':num} + ) The corresponding message catalog must have language plural definitions and plural alternatives set. @@ -638,7 +631,6 @@ More information on complex plurals can be found in the `gettext documentation .. index:: single: locale name - single: get_locale_name single: negotiate_locale_name .. _obtaining_the_locale_name: @@ -647,25 +639,23 @@ Obtaining the Locale Name for a Request --------------------------------------- You can obtain the locale name related to a request by using the -:func:`pyramid.i18n.get_locale_name` function. +:func:`pyramid.request.Request.locale_name` attribute of the request. .. code-block:: python :linenos: - from pyramid.i18n import get_locale_name - def aview(request): - locale_name = get_locale_name(request) + locale_name = request.locale_name -This returns the locale name negotiated by the currently active -:term:`locale negotiator` or the :term:`default locale name` if the -locale negotiator returns ``None``. You can change the default locale -name by changing the ``pyramid.default_locale_name`` setting; see -:ref:`default_locale_name_setting`. +The locale name of a request is dynamically computed; it will be the locale +name negotiated by the currently active :term:`locale negotiator` or +the :term:`default locale name` if the locale negotiator returns ``None``. +You can change the default locale name by changing the +``pyramid.default_locale_name`` setting; see :ref:`default_locale_name_setting`. -Once :func:`~pyramid.i18n.get_locale_name` is first run, the locale +Once :func:`~pyramid.request.Request.locale_name` is first run, the locale name is stored on the request object. Subsequent calls to -:func:`~pyramid.i18n.get_locale_name` will return the stored locale +:func:`~pyramid.request.Request.locale_name` will return the stored locale name without invoking the :term:`locale negotiator`. To avoid this caching, you can use the :func:`pyramid.i18n.negotiate_locale_name` function: @@ -684,15 +674,13 @@ You can also obtain the locale name related to a request using the .. code-block:: python :linenos: - from pyramid.i18n import get_localizer - def aview(request): - localizer = get_localizer(request) + localizer = request.localizer locale_name = localizer.locale_name Obtaining the locale name as an attribute of a localizer is equivalent -to obtaining a locale name by calling the -:func:`~pyramid.i18n.get_locale_name` function. +to obtaining a locale name by asking for the +:func:`~pyramid.request.Request.locale_name` attribute. .. index:: single: date and currency formatting (i18n) @@ -720,10 +708,9 @@ obtain the locale name for a request to pass to the :linenos: from babel.core import Locale - from pyramid.i18n import get_locale_name def aview(request): - locale_name = get_locale_name(request) + locale_name = request.locale_name locale = Locale(locale_name) .. index:: @@ -1005,8 +992,8 @@ a particular request. A locale negotiator is a bit of code which accepts a request and which returns a :term:`locale name`. It is consulted when :meth:`pyramid.i18n.Localizer.translate` or :meth:`pyramid.i18n.Localizer.pluralize` is invoked. It is also -consulted when :func:`~pyramid.i18n.get_locale_name` or -:func:`~pyramid.i18n.negotiate_locale_name` is invoked. +consulted when :func:`~pyramid.request.Request.locale_name` is accessed or +when :func:`~pyramid.i18n.negotiate_locale_name` is invoked. .. _default_locale_negotiator: diff --git a/pyramid/i18n.py b/pyramid/i18n.py index dc2c25f18..cdedbc877 100644 --- a/pyramid/i18n.py +++ b/pyramid/i18n.py @@ -12,6 +12,7 @@ TranslationString = TranslationString # PyFlakes TranslationStringFactory = TranslationStringFactory # PyFlakes from pyramid.compat import PY3 +from pyramid.decorator import reify from pyramid.interfaces import ( ILocalizer, @@ -127,7 +128,7 @@ def default_locale_negotiator(request): def negotiate_locale_name(request): """ Negotiate and return the :term:`locale name` associated with - the current request (never cached).""" + the current request.""" try: registry = request.registry except AttributeError: @@ -144,12 +145,9 @@ def negotiate_locale_name(request): def get_locale_name(request): """ Return the :term:`locale name` associated with the current - request (possibly cached).""" - locale_name = getattr(request, 'locale_name', None) - if locale_name is None: - locale_name = negotiate_locale_name(request) - request.locale_name = locale_name - return locale_name + request. Deprecated in favor of using request.locale_name directly as of + Pyramid 1.5.""" + return request.locale_name def make_localizer(current_locale_name, translation_directories): """ Create a :class:`pyramid.i18n.Localizer` object @@ -196,27 +194,10 @@ def make_localizer(current_locale_name, translation_directories): def get_localizer(request): """ Retrieve a :class:`pyramid.i18n.Localizer` object - corresponding to the current request's locale name. """ + corresponding to the current request's locale name. Deprecated in favor + of using the ``request.localizer`` attribute directly as of Pyramid 1.5""" + return request.localizer - # no locale object cached on request - try: - registry = request.registry - except AttributeError: - registry = get_current_registry() - - current_locale_name = get_locale_name(request) - localizer = registry.queryUtility(ILocalizer, name=current_locale_name) - - if localizer is None: - # no localizer utility registered yet - tdirs = registry.queryUtility(ITranslationDirectories, default=[]) - localizer = make_localizer(current_locale_name, tdirs) - - registry.registerUtility(localizer, ILocalizer, - name=current_locale_name) - - return localizer - class Translations(gettext.GNUTranslations, object): """An extended translation catalog class (ripped off from Babel) """ @@ -359,3 +340,28 @@ class Translations(gettext.GNUTranslations, object): return self._domains.get(domain, self).ungettext( singular, plural, num) +class LocalizerRequestMixin(object): + @reify + def localizer(self): + """ Convenience property to return a localizer """ + registry = self.registry + + current_locale_name = self.locale_name + localizer = registry.queryUtility(ILocalizer, name=current_locale_name) + + if localizer is None: + # no localizer utility registered yet + tdirs = registry.queryUtility(ITranslationDirectories, default=[]) + localizer = make_localizer(current_locale_name, tdirs) + + registry.registerUtility(localizer, ILocalizer, + name=current_locale_name) + + return localizer + + @reify + def locale_name(self): + locale_name = negotiate_locale_name(self) + return locale_name + + diff --git a/pyramid/request.py b/pyramid/request.py index 5dedee4f1..9b62bee00 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -24,7 +24,7 @@ from pyramid.compat import ( ) from pyramid.decorator import reify -from pyramid.i18n import get_localizer +from pyramid.i18n import LocalizerRequestMixin from pyramid.response import Response from pyramid.url import URLMethodsMixin from pyramid.util import InstancePropertyMixin @@ -303,7 +303,8 @@ class CallbackMethodsMixin(object): @implementer(IRequest) class Request(BaseRequest, DeprecatedRequestMethodsMixin, URLMethodsMixin, - CallbackMethodsMixin, InstancePropertyMixin): + CallbackMethodsMixin, InstancePropertyMixin, + LocalizerRequestMixin): """ A subclass of the :term:`WebOb` Request class. An instance of this class is created by the :term:`router` and is provided to a @@ -384,13 +385,7 @@ class Request(BaseRequest, DeprecatedRequestMethodsMixin, URLMethodsMixin, def json_body(self): return json.loads(text_(self.body, self.charset)) - @reify - def localizer(self): - """ Convenience property to return a localizer by calling - :func:`pyramid.i18n.get_localizer`. """ - return get_localizer(self) - - + def route_request_iface(name, bases=()): # zope.interface treats the __name__ as the __doc__ and changes __name__ # to None for interfaces that contain spaces if you do not pass a diff --git a/pyramid/testing.py b/pyramid/testing.py index 14c6f4acf..36c690117 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -39,6 +39,7 @@ from pyramid.request import ( CallbackMethodsMixin, ) +from pyramid.i18n import LocalizerRequestMixin from pyramid.url import URLMethodsMixin from pyramid.util import InstancePropertyMixin @@ -286,7 +287,8 @@ class DummySession(dict): @implementer(IRequest) class DummyRequest(DeprecatedRequestMethodsMixin, URLMethodsMixin, - CallbackMethodsMixin, InstancePropertyMixin): + CallbackMethodsMixin, InstancePropertyMixin, + LocalizerRequestMixin): """ A DummyRequest object (incompletely) imitates a :term:`request` object. The ``params``, ``environ``, ``headers``, ``path``, and diff --git a/pyramid/tests/test_config/test_i18n.py b/pyramid/tests/test_config/test_i18n.py index 25cb88cc3..fdee0416f 100644 --- a/pyramid/tests/test_config/test_i18n.py +++ b/pyramid/tests/test_config/test_i18n.py @@ -86,8 +86,10 @@ class TestI18NConfiguratorMixin(unittest.TestCase): def test_add_translation_dirs_registers_chameleon_translate(self): from pyramid.interfaces import IChameleonTranslate from pyramid.threadlocal import manager - request = DummyRequest() + from pyramid.request import Request config = self._makeOne(autocommit=True) + request = Request.blank('/') + request.registry = config.registry manager.push({'request':request, 'registry':config.registry}) try: config.add_translation_dirs('pyramid.tests.pkgs.localeapp:locale') @@ -103,12 +105,3 @@ class TestI18NConfiguratorMixin(unittest.TestCase): self.assertEqual(config.registry.getUtility(ITranslationDirectories), [locale]) -class DummyRequest: - subpath = () - matchdict = None - def __init__(self, environ=None): - if environ is None: - environ = {} - self.environ = environ - self.params = {} - self.cookies = {} diff --git a/pyramid/tests/test_i18n.py b/pyramid/tests/test_i18n.py index 336e51b6a..67b2ac356 100644 --- a/pyramid/tests/test_i18n.py +++ b/pyramid/tests/test_i18n.py @@ -6,7 +6,7 @@ here = os.path.dirname(__file__) localedir = os.path.join(here, 'pkgs', 'localeapp', 'locale') import unittest -from pyramid.testing import cleanUp +from pyramid import testing class TestTranslationString(unittest.TestCase): def _makeOne(self, *arg, **kw): @@ -84,10 +84,10 @@ class TestLocalizer(unittest.TestCase): class Test_negotiate_locale_name(unittest.TestCase): def setUp(self): - cleanUp() + testing.setUp() def tearDown(self): - cleanUp() + testing.tearDown() def _callFUT(self, request): from pyramid.i18n import negotiate_locale_name @@ -140,40 +140,27 @@ class Test_negotiate_locale_name(unittest.TestCase): class Test_get_locale_name(unittest.TestCase): def setUp(self): - cleanUp() + testing.setUp() def tearDown(self): - cleanUp() + testing.tearDown() def _callFUT(self, request): from pyramid.i18n import get_locale_name return get_locale_name(request) - def _registerImpl(self, impl): - from pyramid.threadlocal import get_current_registry - registry = get_current_registry() - from pyramid.interfaces import ILocaleNegotiator - registry.registerUtility(impl, ILocaleNegotiator) - def test_name_on_request(self): request = DummyRequest() request.locale_name = 'ie' result = self._callFUT(request) self.assertEqual(result, 'ie') - def test_name_not_on_request(self): - self._registerImpl(dummy_negotiator) - request = DummyRequest() - result = self._callFUT(request) - self.assertEqual(result, 'bogus') - self.assertEqual(request.locale_name, 'bogus') - class Test_make_localizer(unittest.TestCase): def setUp(self): - cleanUp() + testing.setUp() def tearDown(self): - cleanUp() + testing.tearDown() def _callFUT(self, locale, tdirs): from pyramid.i18n import make_localizer @@ -221,97 +208,26 @@ class Test_make_localizer(unittest.TestCase): class Test_get_localizer(unittest.TestCase): def setUp(self): - cleanUp() + testing.setUp() def tearDown(self): - cleanUp() + testing.tearDown() def _callFUT(self, request): from pyramid.i18n import get_localizer return get_localizer(request) - def test_default_localizer(self): - # `get_localizer` returns a default localizer for `en` - from pyramid.i18n import Localizer - request = DummyRequest() - result = self._callFUT(request) - self.assertEqual(result.__class__, Localizer) - self.assertEqual(result.locale_name, 'en') - - def test_custom_localizer_for_default_locale(self): - from pyramid.threadlocal import get_current_registry - from pyramid.interfaces import ILocalizer - registry = get_current_registry() - dummy = object() - registry.registerUtility(dummy, ILocalizer, name='en') - request = DummyRequest() - result = self._callFUT(request) - self.assertEqual(result, dummy) - - def test_custom_localizer_for_custom_locale(self): - from pyramid.threadlocal import get_current_registry - from pyramid.interfaces import ILocalizer - registry = get_current_registry() - dummy = object() - registry.registerUtility(dummy, ILocalizer, name='ie') - request = DummyRequest() - request.locale_name = 'ie' - result = self._callFUT(request) - self.assertEqual(result, dummy) - - def test_localizer_from_mo(self): - from pyramid.threadlocal import get_current_registry - from pyramid.interfaces import ITranslationDirectories - from pyramid.i18n import Localizer - registry = get_current_registry() - localedirs = [localedir] - registry.registerUtility(localedirs, ITranslationDirectories) - request = DummyRequest() - request.locale_name = 'de' - result = self._callFUT(request) - self.assertEqual(result.__class__, Localizer) - self.assertEqual(result.translate('Approve', 'deformsite'), - 'Genehmigen') - self.assertEqual(result.translate('Approve'), 'Approve') - self.assertTrue(hasattr(result, 'pluralize')) - - def test_localizer_from_mo_bad_mo(self): - from pyramid.threadlocal import get_current_registry - from pyramid.interfaces import ITranslationDirectories - from pyramid.i18n import Localizer - registry = get_current_registry() - localedirs = [localedir] - registry.registerUtility(localedirs, ITranslationDirectories) + def test_it(self): request = DummyRequest() - request.locale_name = 'be' - result = self._callFUT(request) - self.assertEqual(result.__class__, Localizer) - self.assertEqual(result.translate('Approve', 'deformsite'), - 'Approve') - - def test_request_has_localizer(self): - from pyramid.threadlocal import get_current_registry - from pyramid.interfaces import ILocalizer - from pyramid.request import Request - # register mock localizer - dummy = object() - registry = get_current_registry() - registry.registerUtility(dummy, ILocalizer, name='en') - request = Request(environ={}) - self.assertEqual(request.localizer, dummy) - # `get_localizer` is only called once... - other = object() - registry.registerUtility(other, ILocalizer, name='en') - self.assertNotEqual(request.localizer, other) - self.assertEqual(request.localizer, dummy) - + request.localizer = 'localizer' + self.assertEqual(self._callFUT(request), 'localizer') class Test_default_locale_negotiator(unittest.TestCase): def setUp(self): - cleanUp() + testing.setUp() def tearDown(self): - cleanUp() + testing.tearDown() def _callFUT(self, request): from pyramid.i18n import default_locale_negotiator @@ -477,6 +393,70 @@ class TestTranslations(unittest.TestCase): result = t.dungettext('messages', 'foo1', 'foos1', 2) self.assertEqual(result, 'foos1') +class TestLocalizerRequestMixin(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() + + def tearDown(self): + testing.tearDown() + + def _makeOne(self): + from pyramid.i18n import LocalizerRequestMixin + request = LocalizerRequestMixin() + request.registry = self.config.registry + request.cookies = {} + request.params = {} + return request + + def test_default_localizer(self): + # `localizer` returns a default localizer for `en` + from pyramid.i18n import Localizer + request = self._makeOne() + self.assertEqual(request.localizer.__class__, Localizer) + self.assertEqual(request.locale_name, 'en') + + def test_custom_localizer_for_default_locale(self): + from pyramid.interfaces import ILocalizer + dummy = object() + self.config.registry.registerUtility(dummy, ILocalizer, name='en') + request = self._makeOne() + self.assertEqual(request.localizer, dummy) + + def test_custom_localizer_for_custom_locale(self): + from pyramid.interfaces import ILocalizer + dummy = object() + self.config.registry.registerUtility(dummy, ILocalizer, name='ie') + request = self._makeOne() + request._LOCALE_ = 'ie' + self.assertEqual(request.localizer, dummy) + + def test_localizer_from_mo(self): + from pyramid.interfaces import ITranslationDirectories + from pyramid.i18n import Localizer + localedirs = [localedir] + self.config.registry.registerUtility( + localedirs, ITranslationDirectories) + request = self._makeOne() + request._LOCALE_ = 'de' + result = request.localizer + self.assertEqual(result.__class__, Localizer) + self.assertEqual(result.translate('Approve', 'deformsite'), + 'Genehmigen') + self.assertEqual(result.translate('Approve'), 'Approve') + self.assertTrue(hasattr(result, 'pluralize')) + + def test_localizer_from_mo_bad_mo(self): + from pyramid.interfaces import ITranslationDirectories + from pyramid.i18n import Localizer + localedirs = [localedir] + self.config.registry.registerUtility( + localedirs, ITranslationDirectories) + request = self._makeOne() + request._LOCALE_ = 'be' + result = request.localizer + self.assertEqual(result.__class__, Localizer) + self.assertEqual(result.translate('Approve', 'deformsite'), + 'Approve') class DummyRequest(object): def __init__(self): |
