Skip to content

Commit

Permalink
make local_name an attribute of Request, move logic from get_localize…
Browse files Browse the repository at this point in the history
…r into Request.localizer, fix docs; closes #1099
  • Loading branch information
mcdonc committed Aug 29, 2013
1 parent 8a7e80d commit 330164c
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 192 deletions.
12 changes: 10 additions & 2 deletions CHANGES.txt
Expand Up @@ -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
Expand Down Expand Up @@ -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)
================

Expand Down
14 changes: 14 additions & 0 deletions docs/api/request.rst
Expand Up @@ -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
Expand Down
77 changes: 32 additions & 45 deletions docs/narr/i18n.rst
Expand Up @@ -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::

Expand All @@ -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::
Expand Down Expand Up @@ -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 ...
Expand All @@ -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.
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -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::
Expand Down Expand Up @@ -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:

Expand Down
60 changes: 33 additions & 27 deletions pyramid/i18n.py
Expand Up @@ -12,6 +12,7 @@
TranslationStringFactory = TranslationStringFactory # PyFlakes

from pyramid.compat import PY3
from pyramid.decorator import reify

from pyramid.interfaces import (
ILocalizer,
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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) """

Expand Down Expand Up @@ -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


13 changes: 4 additions & 9 deletions pyramid/request.py
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion pyramid/testing.py
Expand Up @@ -39,6 +39,7 @@
CallbackMethodsMixin,
)

from pyramid.i18n import LocalizerRequestMixin
from pyramid.url import URLMethodsMixin
from pyramid.util import InstancePropertyMixin

Expand Down Expand Up @@ -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
Expand Down
13 changes: 3 additions & 10 deletions pyramid/tests/test_config/test_i18n.py
Expand Up @@ -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')
Expand All @@ -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 = {}

0 comments on commit 330164c

Please sign in to comment.