summaryrefslogtreecommitdiff
path: root/docs/narr/i18n.rst
diff options
context:
space:
mode:
Diffstat (limited to 'docs/narr/i18n.rst')
-rw-r--r--docs/narr/i18n.rst777
1 files changed, 777 insertions, 0 deletions
diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst
new file mode 100644
index 000000000..7ec6b2607
--- /dev/null
+++ b/docs/narr/i18n.rst
@@ -0,0 +1,777 @@
+.. index::
+ single: i18n
+ single: l10n
+ single: internationalization
+ single: localization
+
+.. _i18n_chapter:
+
+Internationalization and Localization
+=====================================
+
+:mod:`repoze.bfg` offers internationalization (i18n) and localization
+(l10n) subsystems that can be used to translate the text of buttons,
+the text of error messages and other software- and template-defined
+values into the native language of a user of your application.
+
+.. note: The APIs and functionality described in this chapter are new
+ as of :mod:`repoze.bfg` version 1.3.
+
+.. index::
+ single: translation string
+ pair: domain; translation
+ pair: msgid; translation
+ single: message identifier
+
+Creating a Translation String
+-----------------------------
+
+While you write your software, you can insert specialized markup into
+your Python code that makes it possible for the system to translate
+text values into the languages used by your application's users. This
+markup generates a :term:`translation string`. A translation string
+is an object that behave mostly like a normal Unicode object, except
+that it also carries around extra information related to its job as
+part of the :mod:`repoze.bfg` translation machinery.
+
+Using The ``TranslationString`` Class
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The most primitive way to create a translation string is to use the
+:class:`repoze.bfg.i18n.TranslationString` callable:
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.i18n import TranslationString
+ ts = TranslationString('Add')
+
+This creates a Unicode-like object that is a TranslationString.
+
+.. note::
+
+ For people more familiar with :term:`Zope` i8n, a TranslationString
+ is a lot like a ``zope.i18nmessageid.Message`` object. It is not a
+ subclass, however. For people more familiar with :term:`Pylons` or
+ :term:`Django` i8n, using a TranslationString is a lot like using
+ "lazy" versions of related gettext APIs.
+
+The first argument to :class:`repoze.bfg.i18n.TranslationString` is
+the ``msgid``; it is required. It represents the key into the
+translation mappings provided by a particular localization. The
+``msgid`` argument must be a Unicode object or an ASCII string. The
+msgid may optionally contain *replacement markers*. For instance:
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.i18n import TranslationString
+ ts = TranslationString('Add ${number}')
+
+Within the string above, ``${stuff}`` is a replacement marker. It
+will be replaced by whatever is in the *mapping* for a translation
+string. The mapping may be supplied at the same time as the
+replacement marker itself:
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.i18n import TranslationString
+ ts = TranslationString('Add ${number}', mapping={'number':1})
+
+Any number of replacement markers can be present in the msgid value,
+any number of times. Only markers which can be replaced by the values
+in the *mapping* will be replaced at translation time. The others
+will not be interpolated and will be output literally.
+
+A translation string should also usually carry a *domain*. The domain
+represents a translation category to disambiguate it from other
+translations of the same msgid, in case they conflict.
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.i18n import TranslationString
+ ts = TranslationString('Add ${number}', mapping={'number':1},
+ domain='form')
+
+The above translation string named a domain of ``form``. A
+:term:`translator` function will often use the domain to locate the
+right translator file on the filesystem which contains translations
+for a given domain. In this case, if it were trying to translate to
+our msgid to German, it might try to find a translation from a
+:term:`gettext` file within a :term:`translation directory` like this
+one::
+
+ locale/de/LC_MESSAGES/form.mo
+
+In other words, it would want to take translations from the ``form.mo``
+translation file in the German language.
+
+Finally, the TranslationString constructor accepts a ``default``
+argument. If a ``default`` argument is supplied, it replaces usages
+of the ``msgid`` as the *default value* for the translation string.
+When ``default`` is ``None``, the ``msgid`` value passed to a
+TranslationString is used as an implicit message identifier. Message
+identifiers are matched with translations in translation files, so it
+is often useful to create translation strings with "opaque" message
+identifiers unrelated to their default text:
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.i18n import TranslationString
+ ts = TranslationString('add-number', default='Add ${number}',
+ domain='form', mapping={'number':1})
+
+When default text is used, Default text objects may contain
+replacement values.
+
+.. index::
+ single: translation string factory
+
+Using the ``TranslationStringFactory`` Class
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Another way to generate a translation string is to use the
+:attr:`repoze.bfg.i18n.TranslationStringFactory` object. This object
+is a *translation string factory*. Basically a translation string
+factory presets the ``domain`` value of any :term:`translation string`
+generated by using it. For example:
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.i18n import TranslationStringFactory
+ _ = TranslationStringFactory('bfg')
+ ts = _('Add ${number}', msgid='add-number', mapping={'number':1})
+
+.. note:: We assigned the translation string factory to the name
+ ``_``. This is a convention which will be supported by translation
+ file generation tools.
+
+After assigning ``_`` to the result of a
+:func:`repoze.bfg.i18n.TranslationStringFactory`, the subsequent
+result of calling ``_`` will be a
+:class:`repoze.bfg.i18n.TranslationString` instance. Even though a
+``domain`` value was not passed to ``_` (as would have been necessary
+if the :class:`repoze.bfg.i18n.TranslationString` constructor were
+used instead of a translation string factory), the ``domain``
+attribute of the resulting translation string will be ``bfg``. As a
+result, the previous code example is completely equivalent (except for
+spelling) to:
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.i18n import TranslationString as _
+ ts = _('Add ${number}', msgid='add-number', mapping={'number':1},
+ domain='bfg')
+
+You can set up your own translation string factory much like the one
+provided above by using the
+:class:`repoze.bfg.i18n.TranslationStringFactory` class. For example,
+if you'd like to create a translation string factory which presets the
+``domain`` value of generated translation strings to ``form``, you'd
+do something like this:
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.i18n import TranslationStringFactory
+ _ = TranslationStringFactory('form')
+ ts = _('Add ${number}', msgid='add-number', mapping={'number':1})
+
+Creating a unique domain for your application via a translation string
+factory is best practice. Using your own unique translation domain
+allows another person to reuse your application without needing to
+merge your translation files with his own. Instead, he can just
+include your package's :term:`translation directory` via the
+:meth:`repoze.bfg.configuration.Configurator.add_translation_dirs`
+method.
+
+.. note::
+
+ For people familiar with Zope internationalization, a
+ TranslationStringFactory is a lot like a
+ ``zope.i18nmessageid.MessageFactory`` object. It is not a
+ subclass, however.
+
+.. index::
+ single: gettext
+ single: translation directories
+
+Working With ``gettext`` Translation Files
+------------------------------------------
+
+Once your application source code files and templates are marked up
+with translation markers, you can work on translations.
+
+.. note::
+
+ The steps used to work with gettext translation files in
+ :mod:`repoze.bfg` are very similar to the steps supported by
+ `Pylons internationalization
+ <http://wiki.pylonshq.com/display/pylonsdocs/Internationalization+and+Localization>`_.
+
+.. index::
+ single: Babel
+
+.. _installing_babel:
+
+Installing Babel
+~~~~~~~~~~~~~~~~
+
+In order for the commands related to working with ``gettext``
+translation files to work properly, you will need to have
+:term:`Babel` installed into the same environment in which
+:mod:`repoze.bfg` is installed.
+
+Installation on UNIX
+++++++++++++++++++++
+
+If the :term:`virtualenv` into which you've installed your
+:mod:`repoze.bfg` application lives in ``/my/virtualenv``, you can
+install Babel like so:
+
+.. code-block:: bash
+
+ $ cd /my/virtualenv
+ $ bin/easy_install Babel
+
+Installation on Windows
++++++++++++++++++++++++
+
+If the :term:`virtualenv` into which you've installed your
+:mod:`repoze.bfg` application lives in ``C:\my\virtualenv``, you can
+install Babel like so:
+
+.. code-block:: bash
+
+ C> cd \my\virtualenv
+ C> bin\easy_install Babel
+
+.. index::
+ pair: extracting; messages
+
+Extracting Messages from Code and Templates
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Once :term:`Babel` is installed, you may extract a message catalog
+template from the code and :term:`Chameleon` templates which reside in
+your application::
+
+ $ cd /place/where/myapplication/setup.py/lives
+ $ mkdir -p myapplication/locale
+ $ python setup.py extract_messages
+
+The message catalog template will end up in
+``myapplication/locale/myapplication.pot``.
+
+XXX finish, including hair about Chameleon template scraping
+
+.. index::
+ pair: initalizing; message catalog
+
+Initializaing a Message Catalog File
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Initialize a message catalog for a specific locale from a pregenerated
+``.pot`` template::
+
+ $ cd /place/where/myapplication/setup.py/lives
+ $ python setup.py init_catalog -l es
+
+The message catalog ``.po`` file will end up in
+``myapplication/locale/es/LC_MESSAGES/myapplication.po``.
+
+XXX finish
+
+.. index::
+ pair: updating; message catalog
+
+Updating a Catalog File
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Update ``.po`` files based on changes to the ``.pot`` file::
+
+ $ cd /place/where/myapplication/setup.py/lives
+ $ python setup.py update_catalog
+
+XXX finish
+
+.. index::
+ pair: compiling; message catalog
+
+Compiling a Message Catalog File
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Compile ``.po`` files to ``.mo`` files::
+
+ $ cd /place/where/myapplication/setup.py/lives
+ $ python setup.py compile_catalog
+
+XXX finish
+
+.. index::
+ single: localizer
+ single: get_localizer
+
+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:`repoze.bfg.i18n.get_localizer` function to obtain a
+:term:`localizer`. :func:`repoze.bfg.i18n.get_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.
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.i18n import get_localizer
+
+ def aview(request):
+ locale = get_localizer(request)
+
+.. index::
+ single: translating (i18n)
+
+.. _performing_a_translation:
+
+Performing a Translation
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+A :term:`localizer` has a ``translate`` method which accepts either a
+:term:`translation string` or a Unicode string and which returns a
+Unicode object representing the translation. So, generating a
+translation in a view component of an application might look like so:
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.i18n import get_localizer
+ from repoze.bfg.i18n import TranslationString
+
+ ts = TranslationString('Add ${number}', mapping={'number':1}, domain='bfg')
+
+ def aview(request):
+ localizer = get_localizer(request)
+ translated = localizer.translate(ts) # translation string
+ # ... use translated ...
+
+The :func:`repoze.bfg.i18n.get_localizer` function will return a
+:class:`repoze.bfg.i18n.Localizer` object bound to the locale name
+represented by the request. The translation returned from its
+:meth:`repoze.bfg.i18n.Localizer.translate` method will depend on the
+``domain`` attribute of the provided translation string as well as the
+locale of the localizer.
+
+.. note:: If you're using :term:`Chameleon` templates, you don't need
+ to pre-translate translation strings this way. See
+ :ref:`chameleon_translation_strings`.
+
+.. index::
+ single: pluralizing (i18n)
+
+.. _performing_a_pluralization:
+
+Performing a Pluralization
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A :term:`localizer` has a ``pluralize`` method with the following
+signature:
+
+.. code-block:: python
+ :linenos:
+
+ def pluralize(singular, plural, n, domain=None, mapping=None):
+ ...
+
+The ``singular`` and ``plural`` arguments should each be a Unicode
+value representing a :term:`message identifier`. ``num`` should be an
+integer. ``domain`` should be a :term:`translation domain`, and
+``mapping`` should be a dictionary that is used for *replacement
+value* interpolation of the translated string. If ``num`` is plural
+for the current locale, ``pluralize`` will return a Unicode
+translation for the message id ``plural``, otherwise it will return a
+Unicode translation for the message id ``singular``.
+
+The arguments provided as ``singular`` and/or ``plural`` may also be
+:term:`translation string` objects, but the domain and mapping
+information attached to those object is ignored.
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.i18n import get_localizer
+
+ def aview(request):
+ localizer = get_localizer(request)
+ translated = localizer.pluralize('Item', 'Items', 1, 'mydomain')
+ # ... use translated ...
+
+.. index::
+ single: locale name
+ single: get_locale_name
+ single: negotiate_locale_name
+
+.. _obtaining_the_locale_name:
+
+Obtaining the Locale Name for a Request
+---------------------------------------
+
+You can obtain the locale name related to a request by using the
+:func:`repoze.bfg.i18n.get_locale_name` function.
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.i18n import get_locale_name
+
+ def aview(request):
+ 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.
+
+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
+to :func:`repoze.bfg.i18n.get_locale_name` will return the stored
+locale name without invoking the :term:`locale negotiator`. To
+avoid this caching, you can use the
+:func:`repoze.bfg.i18n.negotiate_locale_name` function:
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.i18n import negotiate_locale_name
+
+ def aview(request):
+ locale_name = negotiate_locale_name(request)
+
+You can also obtain the locale name related to a request using the
+``locale_name`` attribute of a :term:`localizer`.
+
+ from repoze.bfg.i18n import get_localizer
+
+ def aview(request):
+ localizer = get_localizer(request)
+ 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:`repoze.bfg.i18n.get_locale_name` function.
+
+.. index::
+ single: date and currency formatting (i18n)
+ single: Babel
+
+Performing Date Formatting and Currency Formatting
+--------------------------------------------------
+
+:mod:`repoze.bfg` does not itself perform date and currency formatting
+for different locales. However, :term:`Babel` can help you do this
+via the :class:`babel.core.Locale` class. The `Babel documentation
+for this class
+<http://babel.edgewall.org/wiki/ApiDocs/babel.core#babel.core:Locale>`_
+provides minimal information about how to perform date and currency
+related locale operations. See :ref:`installing_babel` for
+information about how to install Babel.
+
+The :class:`babel.core.Locale` class requires a :term:`locale name` as
+an argument to its constructor. You can use :mod:`repoze.bfg` APIs to
+obtain the locale name for a request to pass to the
+:class:`babel.core.Locale` constructor; see
+:ref:`obtaining_the_locale_name`. For example:
+
+.. code-block:: python
+ :linenos:
+
+ from babel.core import Locale
+ from repoze.bfg.i18n import get_locale_name
+
+ def aview(request):
+ locale_name = get_locale_name(request)
+ locale = Locale(locale_name)
+
+.. index::
+ pair: translation strings; Chameleon
+
+.. _chameleon_translation_strings:
+
+Chameleon Template Support for Translation Strings
+--------------------------------------------------
+
+When a :term:`translation string` is used as the subject of textual
+rendering by a :term:`Chameleon` template renderer, it will
+automatically be translated to the requesting user's language if a
+suitable translation exists. This is true of both the ZPT and text
+variants of the Chameleon template renderers.
+
+For example, in a Chameleon ZPT template, the translation string
+represented by "some_translation_string" in each example below will go
+through translation before being rendered:
+
+.. code-block:: xml
+ :linenos:
+
+ <span tal:content="some_translation_string"/>
+
+.. code-block:: xml
+ :linenos:
+
+ <span tal:replace="some_translation_string"/>
+
+.. code-block:: xml
+ :linenos:
+
+ <span>${some_translation_string}</span>
+
+.. code-block:: xml
+ :linenos:
+
+ <a tal:attributes="href some_translation_string">Click here</a>
+ XXX this appears to not yet work as of Chameleon 1.2.3
+
+The features represented by attributes of the ``i18n`` namespace of
+Chameleon will also consult the :mod:`repoze.bfg` translations.
+See
+`http://chameleon.repoze.org/docs/latest/i18n.html#the-i18n-namespace
+<http://chameleon.repoze.org/docs/latest/i18n.html#the-i18n-namespace>`_.
+
+.. note::
+
+ Unlike when Chameleon is used outside of :mod:`repoze.bfg`, when it
+ is used *within* :mod:`repoze.bfg`, it does not support use of the
+ ``zope.i18n`` translation framework. Applications which use
+ :mod:`repoze.bfg` should use the features documented in this
+ chapter rather than ``zope.i18n``.
+
+Third party :mod:`repoze.bfg` template renderers might not provide
+this support out of the box and may need special code to do an
+equivalent. For those, you can always use the more manual translation
+facility described in :ref:`performing_a_translation`.
+
+.. index::
+ single: localization deployment settings
+ single: default_locale_name
+
+.. _localization_deployment_settings:
+
+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
+:mod:`repoze.bfg.configuration.Configurator` constructor at startup
+time:
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.configuration import Configurator
+ config = Configurator(settings={'default_locale_name':'de'})
+
+You may alternately supply a ``default_locale_name`` via an
+application's Paster ``.ini`` file:
+
+.. code-block:: ini
+ :linenos:
+
+ [app:main]
+ use = egg:MyProject#app
+ reload_templates = true
+ debug_authorization = false
+ debug_notfound = false
+ default_locale_name = de
+
+If this value is not supplied via the Configurator constructor or via
+a Paste onfig file, it will default to ``en``.
+
+If this setting is supplied within the :mod:`repoze.bfg` application
+``.ini`` file, it will be available as a settings key:
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.setttings import get_settings
+ settings = get_settings()
+ default_locale_name = settings['default_locale_name']
+
+.. index::
+ pair: translation; activating
+ pair: locale; negotiator
+ single: translation directory
+
+.. _activating_translation:
+
+Activating Translation
+----------------------
+
+By default, a :mod:`repoze.bfg` application performs no translation.
+To turn translation on, you must do both of these two things:
+
+- Add at least one :term:`translation directory` to your application.
+
+- Configure a :term:`locale negotiator` into your application's
+ configuration.
+
+:term:`gettext` is the underlying machinery behind the
+:mod:`repoze.bfg` translation machinery. A translation directory is a
+directory organized to be useful to :term:`gettext`. A translation
+directory usually includes a listing of language directories, each of
+which itself includes an ``LC_MESSAGES`` directory. Each
+``LC_MESSAGES`` directory should contain one or more ``.mo`` files.
+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`
+: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
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You may add a :term:`translation directory` to your application's
+configuration either imperatively or via ZCML. Adding a 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 directory in the translation directory)
+within your :mod:`repoze.bfg` application to be available to use for
+translation services.
+
+Imperative
+++++++++++
+
+You can add a translation directory imperatively by using the
+:meth:`repoze.bfg.configuration.Configurator.add_translation_dirs`
+during application startup.
+
+For example:
+
+.. code-block:: python
+ :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
+:meth:`repoze.bfg.configuration.Configurator.add_translation_dirs`
+will be merged into translations from an message catalog added earlier
+if both translation directories contain translations for the same
+locale and :term:`translation domain`.
+
+ZCML
+++++
+
+You can add a translation directory via ZCML by using the
+:ref:`translationdir_directive` ZCML directive:
+
+.. code-block:: xml
+ :linenos:
+
+ <translationdir dir="my.application:locale/"/>
+
+A message catalog in a translation directory added via
+:ref:`translationdir_directive` will be merged into translations from
+an message catalog added earlier if both translation directories
+contain translations for the same locale and :term:`translation
+domain`.
+
+.. _adding_a_locale_negotiator:
+
+Adding a Locale Negotiator
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You may add a :term:`locale negotiator` either imperatively or via
+ZCML. A 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`.
+
+Imperative
+++++++++++
+
+Pass an object which can act as the negotiator as the
+``locale_negotiator`` argument of the
+:class:`repoze.bfg.configuration.Configurator` instance during
+application startup.
+
+For example:
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.configuration import Configurator
+ from repoze.bfg.i18n import default_locale_negotiator
+ config = Configurator(locale_negotiator=default_locale_negotiator)
+
+Alternately, use the
+:meth:`repoze.bfg.configuration.Configurator.set_locale_negotiator`
+method.
+
+For example:
+
+.. code-block:: python
+ :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.end()
+
+ZCML
+++++
+
+You can add a translation directory 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`.