CHANGES.txt | ●●●●● patch | view | raw | blame | history | |
docs/api/request.rst | ●●●●● patch | view | raw | blame | history | |
docs/narr/i18n.rst | ●●●●● patch | view | raw | blame | history | |
pyramid/i18n.py | ●●●●● patch | view | raw | blame | history | |
pyramid/request.py | ●●●●● patch | view | raw | blame | history | |
pyramid/testing.py | ●●●●● patch | view | raw | blame | history | |
pyramid/tests/test_config/test_i18n.py | ●●●●● patch | view | raw | blame | history | |
pyramid/tests/test_i18n.py | ●●●●● patch | view | raw | blame | history |
CHANGES.txt
@@ -9,8 +9,10 @@ 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 @@ 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) ================ 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 docs/narr/i18n.rst
@@ -495,7 +495,6 @@ .. index:: single: localizer single: get_localizer single: translation single: pluralization @@ -503,19 +502,17 @@ ----------------- 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 @@ .. 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 @@ .. 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 @@ .. 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 @@ .. index:: single: locale name single: get_locale_name single: negotiate_locale_name .. _obtaining_the_locale_name: @@ -647,25 +639,23 @@ --------------------------------------- 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 @@ .. 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 @@ :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 @@ 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: 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 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 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 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 @@ 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 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 @@ @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 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 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 @@ @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 pyramid/tests/test_config/test_i18n.py
@@ -86,8 +86,10 @@ 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 @@ 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 = {} 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 @@ 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,20 +140,14 @@ 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() @@ -161,19 +155,12 @@ 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_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 def test_it(self): 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) 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 @@ 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):