Skip to content
Browse files

make local_name an attribute of Request, move logic from get_localize…

…r into Request.localizer, fix docs; closes #1099
  • Loading branch information...
1 parent 8a7e80d commit 330164c3190d92a3e1df89baafba12570d03bd32 @mcdonc mcdonc committed Aug 29, 2013
Showing with 177 additions and 192 deletions.
  1. +10 −2 CHANGES.txt
  2. +14 −0 docs/api/request.rst
  3. +32 −45 docs/narr/i18n.rst
  4. +33 −27 pyramid/i18n.py
  5. +4 −9 pyramid/request.py
  6. +3 −1 pyramid/testing.py
  7. +3 −10 pyramid/tests/test_config/test_i18n.py
  8. +78 −98 pyramid/tests/test_i18n.py
View
12 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)
================
View
14 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
View
77 docs/narr/i18n.rst
@@ -495,27 +495,24 @@ translations will be available to :app:`Pyramid`.
.. index::
single: localizer
- single: get_localizer
single: translation
single: pluralization
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:
View
60 pyramid/i18n.py
@@ -12,6 +12,7 @@
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 @@ def dungettext(self, domain, singular, plural, num):
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
+
+
View
13 pyramid/request.py
@@ -24,7 +24,7 @@
)
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 @@ def _process_finished_callbacks(self):
@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 @@ def is_response(self, ob):
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
View
4 pyramid/testing.py
@@ -39,6 +39,7 @@
CallbackMethodsMixin,
)
+from pyramid.i18n import LocalizerRequestMixin
from pyramid.url import URLMethodsMixin
from pyramid.util import InstancePropertyMixin
@@ -286,7 +287,8 @@ def get_csrf_token(self):
@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
View
13 pyramid/tests/test_config/test_i18n.py
@@ -86,8 +86,10 @@ def test_add_translation_dirs_multiple_specs_multiple_calls(self):
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 @@ def test_add_translation_dirs_abspath(self):
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 = {}
View
176 pyramid/tests/test_i18n.py
@@ -6,7 +6,7 @@
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 @@ def test_pluralize_default_translations(self):
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 @@ def test_default_default(self):
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 @@ def test_territory_fallback(self):
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 @@ def test_default_germanic_pluralization(self):
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):

0 comments on commit 330164c

Please sign in to comment.
Something went wrong with that request. Please try again.