summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2010-04-27 16:44:52 +0000
committerChris McDonough <chrism@agendaless.com>2010-04-27 16:44:52 +0000
commit12cb6df7728c8321905a08b0864b3ff0386c62cf (patch)
tree945e3cbf094775c57f0a6b94f36e78885cbd4a53
parent4c8f4d965e06b6bd584151896bfb37663cd69501 (diff)
downloadpyramid-12cb6df7728c8321905a08b0864b3ff0386c62cf.tar.gz
pyramid-12cb6df7728c8321905a08b0864b3ff0386c62cf.tar.bz2
pyramid-12cb6df7728c8321905a08b0864b3ff0386c62cf.zip
Features
-------- - A locale negotiator no longer needs to be registered explicitly. The default locale negotiator at ``repoze.bfg.i18n.default_locale_negotiator`` is now used unconditionally as... um, the default locale negotiator. - The default locale negotiator has become more complex. * First, the negotiator looks for the ``_LOCALE_`` attribute of the request object (possibly set by an :term:`event listener`). * Then it looks for the ``request.params['_LOCALE_']`` value. * Then it looks for the ``request.cookies['_LOCALE_']`` value. Backwards Incompatibilities --------------------------- - The default locale negotiator now looks for the parameter named ``_LOCALE_`` rather than a parameter named ``locale`` in ``request.params``. Behavior Changes ---------------- - A locale negotiator may now return ``None``, signifying that the default locale should be used. Documentation ------------- - Documentation concerning locale negotiation in the Internationalizationa and Localization chapter was updated.
-rw-r--r--CHANGES.txt39
-rw-r--r--docs/narr/i18n.rst178
-rw-r--r--repoze/bfg/configuration.py10
-rw-r--r--repoze/bfg/i18n.py37
-rw-r--r--repoze/bfg/tests/test_configuration.py2
-rw-r--r--repoze/bfg/tests/test_i18n.py36
6 files changed, 210 insertions, 92 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index aeb018531..6a683f05d 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,42 @@
+Next release
+============
+
+Features
+--------
+
+- A locale negotiator no longer needs to be registered explicitly. The
+ default locale negotiator at
+ ``repoze.bfg.i18n.default_locale_negotiator`` is now used
+ unconditionally as... um, the default locale negotiator.
+
+- The default locale negotiator has become more complex.
+
+ * First, the negotiator looks for the ``_LOCALE_`` attribute of
+ the request object (possibly set by an :term:`event listener`).
+
+ * Then it looks for the ``request.params['_LOCALE_']`` value.
+
+ * Then it looks for the ``request.cookies['_LOCALE_']`` value.
+
+Backwards Incompatibilities
+---------------------------
+
+- The default locale negotiator now looks for the parameter named
+ ``_LOCALE_`` rather than a parameter named ``locale`` in
+ ``request.params``.
+
+Behavior Changes
+----------------
+
+- A locale negotiator may now return ``None``, signifying that the
+ default locale should be used.
+
+Documentation
+-------------
+
+- Documentation concerning locale negotiation in the
+ Internationalizationa and Localization chapter was updated.
+
1.3a1 (2010-04-26)
==================
diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst
index d7fd86840..c5c77f971 100644
--- a/docs/narr/i18n.rst
+++ b/docs/narr/i18n.rst
@@ -479,10 +479,10 @@ You can obtain the locale name related to a request by using the
locale_name = get_locale_name(request)
This returns the locale name negotiated by the currently active
-:term:`locale negotiator` or the default locale name (usually ``en``)
-if no locale negotiator is configured. You can change the default
-locale name by changing the ``default_locale_name`` setting; see
-:ref:`default_locale_name_setting`.
+:term:`locale negotiator` or the :term:`default locale name` (usually
+``en``) if the locale negotiator returns ``None``. You can change the
+default locale name by changing the ``default_locale_name`` setting;
+see :ref:`default_locale_name_setting`.
Once :func:`repoze.bfg.i18n.get_locale_name` has run once, the local
name is stored on the request object it is passed. Subsequent calls
@@ -617,8 +617,8 @@ Localization-Related Deployment Settings
----------------------------------------
A :mod:`repoze.bfg` application will have a ``default_locale_name``
-setting. This value represents the default locale name when no
-:term:`locale negotiator` is registered. Pass it to the
+setting. This value represents the :term:`default locale name` used
+when the :term:`locale negotiator` returns ``None``. Pass it to the
:mod:`repoze.bfg.configuration.Configurator` constructor at startup
time:
@@ -665,12 +665,14 @@ Activating Translation
----------------------
By default, a :mod:`repoze.bfg` application performs no translation.
-To turn translation on, you must do both of these two things:
+To turn translation on, you must:
-- Add at least one :term:`translation directory` to your application.
+- add at least one :term:`translation directory` to your application.
-- Configure a :term:`locale negotiator` into your application's
- configuration.
+- ensure that your application sets the :term:`locale` correctly.
+
+Adding a Translation Directory
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:term:`gettext` is the underlying machinery behind the
:mod:`repoze.bfg` translation machinery. A translation directory is a
@@ -681,23 +683,6 @@ which itself includes an ``LC_MESSAGES`` directory. Each
Each ``.mo`` file represents a :term:`message catalog`, which is used
to provide translations to your application.
-A locale negotiator is a bit of code which accepts a request and which
-returns a :term:`locale name`. It is consulted when
-:meth:`repoze.bfg.i18n.Localizer.translate` or
-:meth:`repoze.bfg.i18n.Localizer.pluralize` is invoked. It is also
-consulted when :func:`repoze.bfg.i18n.get_locale_name` or
-:func:`repoze.bfg.i18n.negotiate_locale_name` is invoked.
-
-At the time of this writing, only one (very weak) built-in locale
-negotiator implementation named
-:class:`repoze.bfg.i18n.default_locale_negotiator` ships as part of
-the :mod:`repoze.bfg` software. This negotiator looks only at the the
-``request.params['locale']`` value to determine the locale name. You
-can provide your own locale negotiator function as required.
-
-Adding a Translation Directory
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
Adding a :term:`translation directory` registers all of its
constituent :term:`message catalog` files (all of the ``.mo`` files
found within all ``LC_MESSAGES`` directories within each locale
@@ -719,12 +704,10 @@ configuration using either imperative configuration or ZCML.
:linenos:
from repoze.bfg.configuration import Configurator
- from repoze.bfg.i18n import default_locale_negotiator
- config = Configurator(
- locale_negotiator=default_locale_negotiator)
config.begin()
config.add_translation_dirs('my.application:locale/',
'another.application:locale/')
+ ...
config.end()
A message catalog in a translation directory added via
@@ -749,16 +732,106 @@ configuration using either imperative configuration or ZCML.
contain translations for the same locale and :term:`translation
domain`.
-.. _adding_a_locale_negotiator:
+Setting the Locale
+~~~~~~~~~~~~~~~~~~
-Adding a Locale Negotiator
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+When the *default locale negotiator* (see
+:ref:`default_locale_negotiator`) is in use, you can inform
+:mod:`repoze.bfg` of the current locale name by doing any of these
+things before any translations need to be performed:
+
+- Set the ``_LOCALE_`` attribute of the request to a valid locale name
+ (usually directly within view code). E.g. ``request._LOCALE_ =
+ 'de'``.
+
+- Ensure that a valid locale name value is in the ``request.params``
+ dictionary under the key named ``_LOCALE_``. This is usually the
+ result of passing a ``_LOCALE_`` value in the query string or in the
+ body of a form post associated with a request. For example,
+ visiting ``http://my.application?_LOCALE_=de``.
+
+- Ensure that a valid locale name value is in the ``request.cookies``
+ dictionary under the key named ``_LOCALE_``. This is usually the
+ result of setting a ``_LOCALE_`` cookie in a prior response,
+ e.g. ``response.set_cookie('_LOCALE_', 'de')``.
+
+.. note::
+
+ If this locale negotiation scheme is inappropriate for a particular
+ application, you can configure a custom :term:`locale negotiator`
+ function into that application as required. See
+ :ref:`custom_locale_negotiator`.
+
+.. _locale_negotiators:
+
+Locale Negotiators
+------------------
A :term:`locale negotiator` informs the operation of a
:term:`localizer` by telling it what :term:`locale name` is related to
-a particular request. See :ref:`creating_a_locale_negotiator`.
+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:`repoze.bfg.i18n.Localizer.translate` or
+:meth:`repoze.bfg.i18n.Localizer.pluralize` is invoked. It is also
+consulted when :func:`repoze.bfg.i18n.get_locale_name` or
+:func:`repoze.bfg.i18n.negotiate_locale_name` is invoked.
+
+.. _default_locale_negotiator:
+
+The Default Locale Negotiator
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Most applications can make use of the default locale negotiator, which
+requires no additional coding or configuration.
+
+The default locale negotiator implementation named
+:class:`repoze.bfg.i18n.default_locale_negotiator` uses the following
+set of steps to dermine the locale name.
-You may add a locale directory to your application's
+- First, the negotiator looks for the ``_LOCALE_`` attribute of
+ the request object (possibly set by an :term:`event listener`).
+
+- Then it looks for the ``request.params['_LOCALE_']`` value.
+
+- Then it looks for the ``request.cookies['_LOCALE_']`` value.
+
+- If no locale can be found via the request, it falls back to using
+ the :term:`default locale name` (see
+ :ref:`localization_deployment_settings`).
+
+- Finally, if the default locale name is not explicitly set, it uses
+ the locale name ``en``.
+
+.. _custom_locale_negotiator:
+
+Using a Custom Locale Negotiator
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Locale negotiation is sometimes policy-laden and complex. If the
+(simple) default locale negotiation scheme described in
+:ref:`activating translation` is inappropriate for your application,
+you may create and a special :term:`locale negotiator`. Subsequently
+you may override the default locale negotiator by adding your newly
+created locale negotiator to your application's configuration.
+
+A locale negotiator is simply a callable which
+accepts a request and returns a single :term:`locale name` or ``None``
+if no locale can be determined.
+
+Here's an implementation of a simple locale negotiator:
+
+.. code-block:: python
+ :linenos:
+
+ def my_locale_negotiator(request):
+ locale_name = request.params.get('my_locale')
+ return locale_name
+
+If a locale negotiator returns ``None``, it signifies to
+:mod:`repoze.bfg` that the default application locale name should be
+used.
+
+You may add your newly created locale negotiator to your application's
configuration using either imperative configuration or ZCML.
.. topic:: Using Imperative Configuration
@@ -774,9 +847,7 @@ configuration using either imperative configuration or ZCML.
:linenos:
from repoze.bfg.configuration import Configurator
- from repoze.bfg.i18n import default_locale_negotiator
- config = Configurator(
- locale_negotiator=default_locale_negotiator)
+ config = Configurator(locale_negotiator=my_locale_negotiator)
Alternately, use the
:meth:`repoze.bfg.configuration.Configurator.set_locale_negotiator`
@@ -788,44 +859,19 @@ configuration using either imperative configuration or ZCML.
:linenos:
from repoze.bfg.configuration import Configurator
- from repoze.bfg.i18n import default_locale_negotiator
config = Configurator()
config.begin()
- config.set_locale_negotiator(default_locale_negotiator)
+ config.set_locale_negotiator(my_locale_negotiator)
config.end()
.. topic:: Using ZCML
- You can add a translation directory via ZCML by using the
+ You can add a locale negotiator via ZCML by using the
:ref:`localenegotiator_directive` ZCML directive:
.. code-block:: xml
:linenos:
<localenegotiator
- negotiator="repoze.bfg.i18n.default_locale_negotiator"/>
-
-.. _creating_a_locale_negotiator:
-
-Creating a Locale Negotiator
-----------------------------
-
-A :term:`locale negotiator` is simply a callable which accepts a
-request and returns a single :term:`locale name`. Here's an
-implementation of a simple locale negotiator:
-
-.. code-block:: python
- :linenos:
-
- def default_locale_negotiator(request):
- locale_name = request.params.get('locale')
- if locale_name is None:
- settings = get_settings() or {}
- locale_name = settings.get('default_locale_name', 'en')
- return locale_name
-
-Locale negotiation can be complex. Your application may require a
-policy-laden locale negotiator policy, so you can write your own and
-supply it to an application configuration as per
-:ref:`adding_a_locale_negotiator`.
+ negotiator="my_application.my_module.my_locale_negotiator"/>
diff --git a/repoze/bfg/configuration.py b/repoze/bfg/configuration.py
index 9fa3fe8b2..546a6978b 100644
--- a/repoze/bfg/configuration.py
+++ b/repoze/bfg/configuration.py
@@ -145,6 +145,10 @@ class Configurator(object):
class. The debug logger is used by :mod:`repoze.bfg` itself to
log warnings and authorization debugging information.
+ If ``locale_negotiator`` is passed, it should be a
+ :term:`locale negotiator` implementation. See
+ :ref:`custom_locale_negotiator`.
+
"""
manager = manager # for testing injection
venusian = venusian # for testing injection
@@ -367,9 +371,9 @@ class Configurator(object):
initialization.
``setup_registry`` configures settings, a root factory,
- security policies, renderers, and a debug logger using the
- configurator's current registry, as per the descriptions in
- the Configurator constructor."""
+ security policies, renderers, a debug logger, and a locale
+ negotiator using the configurator's current registry, as per
+ the descriptions in the Configurator constructor."""
self._fix_registry()
self._set_settings(settings)
self._set_root_factory(root_factory)
diff --git a/repoze/bfg/i18n.py b/repoze/bfg/i18n.py
index e3b276331..640ee030b 100644
--- a/repoze/bfg/i18n.py
+++ b/repoze/bfg/i18n.py
@@ -96,14 +96,27 @@ class Localizer(object):
def default_locale_negotiator(request):
- """ The default :term:`locale negotiator`. Returns a locale
- name based on ``request.params.get('locale')`` or the
- ``default_locale_name`` settings option; or ``en`` if all else
- fails."""
- locale_name = request.params.get('locale')
+ """ The default :term:`locale negotiator`. Returns a locale name
+ or ``None``.
+
+ - First, the negotiator looks for the ``_LOCALE_`` attribute of
+ the request object (possibly set by an :term:`event listener`).
+
+ - Then it looks for the ``request.params['_LOCALE_']`` value.
+
+ - Then it looks for the ``request.cookies['_LOCALE_']`` value.
+
+ - Finally, the negotiator returns ``None`` if the locale could not
+ be determined via any of the previous checks (when a locale
+ negotiator returns ``None``, it signifies that the
+ :term:`default locale` should be used.)
+ """
+ name = '_LOCALE_'
+ locale_name = getattr(request, name, None)
if locale_name is None:
- settings = get_settings() or {}
- locale_name = settings.get('default_locale_name', 'en')
+ locale_name = request.params.get(name)
+ if locale_name is None:
+ locale_name = request.cookies.get(name)
return locale_name
def negotiate_locale_name(request):
@@ -113,12 +126,14 @@ def negotiate_locale_name(request):
registry = request.registry
except AttributeError:
registry = get_current_registry()
- negotiator = registry.queryUtility(ILocaleNegotiator)
- if negotiator is None:
+ negotiator = registry.queryUtility(ILocaleNegotiator,
+ default=default_locale_negotiator)
+ locale_name = negotiator(request)
+
+ if locale_name is None:
settings = get_settings() or {}
locale_name = settings.get('default_locale_name', 'en')
- else:
- locale_name = negotiator(request)
+
return locale_name
def get_locale_name(request):
diff --git a/repoze/bfg/tests/test_configuration.py b/repoze/bfg/tests/test_configuration.py
index 0a443720f..16b590cdd 100644
--- a/repoze/bfg/tests/test_configuration.py
+++ b/repoze/bfg/tests/test_configuration.py
@@ -3518,6 +3518,8 @@ class DummyRequest:
subpath = ()
def __init__(self):
self.environ = {'PATH_INFO':'/static'}
+ self.params = {}
+ self.cookies = {}
def copy(self):
return self
def get_response(self, app):
diff --git a/repoze/bfg/tests/test_i18n.py b/repoze/bfg/tests/test_i18n.py
index fb79a802c..95017807f 100644
--- a/repoze/bfg/tests/test_i18n.py
+++ b/repoze/bfg/tests/test_i18n.py
@@ -94,6 +94,15 @@ class Test_negotiate_locale_name(unittest.TestCase):
result = self._callFUT(request)
self.assertEqual(result, 'settings')
+ def test_use_default_locale_negotiator(self):
+ from repoze.bfg.threadlocal import get_current_registry
+ registry = get_current_registry()
+ request = DummyRequest()
+ request.registry = registry
+ request._LOCALE_ = 'locale'
+ result = self._callFUT(request)
+ self.assertEqual(result, 'locale')
+
def test_default_default(self):
request = DummyRequest()
result = self._callFUT(request)
@@ -219,24 +228,26 @@ class Test_default_locale_negotiator(unittest.TestCase):
from repoze.bfg.i18n import default_locale_negotiator
return default_locale_negotiator(request)
- def test_from_settings(self):
- from repoze.bfg.interfaces import ISettings
- from repoze.bfg.threadlocal import get_current_registry
- settings = {'default_locale_name':'dude'}
- registry = get_current_registry()
- registry.registerUtility(settings, ISettings)
+ def test_from_none(self):
request = DummyRequest()
result = self._callFUT(request)
- self.assertEqual(result, 'dude')
-
- def test_settings_empty(self):
+ self.assertEqual(result, None)
+
+ def test_from_request_attr(self):
request = DummyRequest()
+ request._LOCALE_ = 'foo'
result = self._callFUT(request)
- self.assertEqual(result, 'en')
-
+ self.assertEqual(result, 'foo')
+
def test_from_params(self):
request = DummyRequest()
- request.params['locale'] = 'foo'
+ request.params['_LOCALE_'] = 'foo'
+ result = self._callFUT(request)
+ self.assertEqual(result, 'foo')
+
+ def test_from_cookies(self):
+ request = DummyRequest()
+ request.cookies['_LOCALE_'] = 'foo'
result = self._callFUT(request)
self.assertEqual(result, 'foo')
@@ -370,6 +381,7 @@ class TestTranslations(unittest.TestCase):
class DummyRequest(object):
def __init__(self):
self.params = {}
+ self.cookies = {}
def dummy_negotiator(request):
return 'bogus'