Changes between Version 32 and Version 33 of SandBox


Ignore:
Timestamp:
03/29/2012 02:17:40 PM (6 years ago)
Author:
apoirier
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • SandBox

    v32 v33  
    11{{{ 
    22#!rst 
    3 ================================================================================== 
    4 Project Internationalization with Nagare : setting the locale in your applications  
    5 ================================================================================== 
    6  
    7  
    8 Goals 
    9 ===== 
    10  
    11 In a first `blog post`_, we explained how to extract the messages from the python sources in 
    12 order to provide localized translations for them. We will now explain how to change the locale 
    13 in your applications in order to make use of the localized string we first extracted. 
    14  
    15 Time has passed since the first blog post. From version 4.0 on, nagare integrates a ``i18n``   
    16 module that addresses common internationalization concerns. We will show how to use it 
    17 effectively. 
    18  
    19 The ``i18n`` module from Nagare provides two ways to configure the locale, that we will describe 
    20 hereafter: 
    21  
    22 * using browser locale negotiation, i.e. using the language settings configured on the client 
    23   browser 
    24 * by allowing the user to change the language at runtime, for example by using language change links 
    25   and/or users' profile settings 
    26  
    27 We will not explain the message extraction here, so we encourage you to read the previous `blog post`_ 
    28 first. However, note that Nagare now greatly simplifies the message extraction process by providing a 
    29 default ``setup.cfg`` file on application creation (i.e. ``nagare-admin create-app``), which includes 
    30 the python ``Babel`` command customizations mentioned in the first blog post. 
    31  
    32 .. _blog post: http://www.nagare.org/trac/blog/nagare-internationalization-message-extraction 
    33  
    34  
    35 Getting Started with Nagare i18n 
    36 ================================ 
    37  
    38 To get started with the Nagare ``i18n`` feature, we will create a basic application with localized messages 
    39 in two languages (French and English) in order to demonstrate how to switch the language at runtime. 
    40  
    41 First, install Nagare with the ``i18n`` option: 
     3Internationalization of applications 
     4==================================== 
     5 
     6Since version 0.4.0, the Nagare framework includes a ``nagare.i18n`` module that can help you 
     7internationalize a Web application. 
     8 
     9There are many aspects to take into account when internationalizing an application: 
     10 
     11- extracting the messages (strings) used inside the application and translating them in each 
     12  language 
     13- formatting numbers, dates, times, currencies for display or parsing them after user input 
     14- dealing with timezone calculations 
     15- displaying the requested pages in the proper language according to the client browser language 
     16  settings or a setting in the application 
     17 
     18All these aspects are addressed by the ``nagare.i18n`` module. 
     19 
     20 
     21Setup an application for internationalization 
     22--------------------------------------------- 
     23 
     24First, the Nagare ``i18n`` extras should be installed in your virtual environment: 
    4225 
    4326.. code-block:: sh 
     
    4528    $ easy_install nagare[i18n] 
    4629 
    47  
    48 Then, create a sample application: 
     30This command installs ``Babel`` and ``pytz`` which are necessary to use the ``nagare.i18n`` module. 
     31 
     32However, if you plan to use the internationalization feature of Nagare in your Web application, you'd 
     33better change the ``setup.py`` file that is generated when you create the application, in order to specify 
     34that you need the ``i18n`` extra as a requirement: 
     35 
     36.. code-block:: python 
     37 
     38    setup( 
     39          ... 
     40          install_requires = ('nagare[i18n]', ...), 
     41          ... 
     42    ) 
     43 
     44Then, Nagare is installed with the ``i18n`` extra when you install your application. 
     45 
     46When you create a new application with ``nagare-admin create-app``, Nagare automatically creates a 
     47``setup.cfg`` file that contains a default configuration for internationalization:  
     48 
     49.. code-block:: ini 
     50 
     51    [extract_messages] 
     52    keywords = _ , _N:1,2 , _L , _LN:1,2 , gettext , ugettext , ngettext:1,2 , ungettext:1,2 , lazy_gettext , lazy_ugettext , lazy_ngettext:1,2 , lazy_ungettext:1,2 
     53    output_file = data/locale/messages.pot 
     54     
     55    [init_catalog] 
     56    input_file = data/locale/messages.pot 
     57    output_dir = data/locale 
     58    domain = messages 
     59     
     60    [update_catalog] 
     61    input_file = data/locale/messages.pot 
     62    output_dir = data/locale 
     63    domain = messages 
     64     
     65    [compile_catalog] 
     66    directory = data/locale 
     67    domain = messages 
     68 
     69This configuration file is used by the distutils commands that are automatically registered to ``setuptools`` 
     70when ``Babel`` is installed. Here is the list of the commands: 
     71 
     72===============================================  ================================================================= 
     73                 Command                                                   Effect 
     74===============================================  ================================================================= 
     75``python setup.py extract_messages``             Extract the messages from the python sources to a ``.pot`` file 
     76``python setup.py init_catalog -l <lang>``       Create a new message catalog (``.po`` file) for a given 
     77                                                 language, initialized from the ``.pot`` file 
     78``python setup.py update_catalog [-l <lang>``]   Update the message catalog(s) with the new messages found in the 
     79                                                 ``.pot`` file after an ``extract_messages`` 
     80``python setup.py compile_catalog [-l <lang>``]  Compile the message catalog(s) in binary form so that they can be 
     81                                                 used inside the application 
     82===============================================  ================================================================= 
     83 
     84See the `Babel setup documentation`_ for a complete reference of the commands options. 
     85 
     86.. _Babel setup documentation: http://babel.edgewall.org/wiki/Documentation/setup.html 
     87 
     88The catalog files are stored in the ``data/locale`` directory by default, which is automatically included into your 
     89application source package thanks to the ``MANIFEST.in`` file that was generated by ``nagare-admin create-app``. The 
     90directory is not created by default, so you have to create it before issuing any of the ``Babel`` commands: 
    4991 
    5092.. code-block:: sh 
    5193 
    52     $ nagare create-app i18n_example 
    53     $ cd i18n_example 
    54     $ python setup.py develop 
    55      
    56      
    57 Edit the app.py file, import the ``nagare.i18n._`` function and mark the messages to be translated with it: 
    58  
    59 .. code-block:: python 
    60  
    61     # -*- coding: utf-8 -*- 
    62      
    63     from __future__ import with_statement 
     94    $ mkdir data/locale 
     95 
     96 
     97Marking the messages to translate 
     98--------------------------------- 
     99 
     100The ``nagare.i18n`` module provides functions to translate the strings that appear in your code. The most 
     101useful function is ``nagare.i18n._`` which returns the translation of the given string as a unicode string: 
     102 
     103..  code-block:: python 
     104    :hl_lines: 2, 9 
    64105     
    65106    from nagare import presentation 
     
    71112    @presentation.render_for(I18nExample) 
    72113    def render(self, h, *args): 
    73         h.head << h.head.title(_('Up and Running !')) 
    74      
    75         h.head.css_url('/static/nagare/application.css') 
    76         h.head.css('defaultapp', '#main { margin-left: 20px; padding-bottom: 100px; background: url(/static/nagare/img/sakura.jpg) no-repeat 123px 100% }') 
    77      
    78         with h.div(id='body'): 
    79             h << h.a(h.img(src='/static/nagare/img/logo.png'), id='logo', href='http://www.nagare.org/', title='Nagare home') 
    80      
    81             with h.div(id='content'): 
    82                 h << h.div(_('Congratulations!'), id='title') 
    83      
    84                 with h.div(id='main'): 
    85                     h << h.h1(_('Your application is localized !')) 
    86      
    87                     with h.p: 
    88                         h << _('Here is some localized text...') 
     114        h << _('This is a translated string') << h.br 
     115        h << 'This string is not translated' 
    89116        return h.root 
    90117     
    91     # --------------------------------------------------------------- 
    92      
    93118    app = I18nExample 
    94119 
    95120 
    96 Now, extract the messages and create the localized versions of the applications messages: 
     121There are also many more functions that deals with strings in the ``nagare.i18n`` module: 
     122 
     123=============================================  ================================================================ 
     124                Function                                                   Effect 
     125=============================================  ================================================================ 
     126``_``                                          Shortcut to ``ugettext`` 
     127``_N``                                         Shortcut to ``ungettext`` 
     128``_L``                                         Shortcut to ``lazy_ugettext`` 
     129``_LN``                                        Shortcut to ``lazy_ungettext`` 
     130``gettext``                                    Return the localized translation of a message as a 8-bit string 
     131                                               encoded with the catalog's charset encoding, based on the current 
     132                                               locale set by Nagare 
     133``ugettext``                                   Return the localized translation of a message as a unicode string, 
     134                                               based on the current locale set by Nagare 
     135``ngettext``                                   Like ``gettext`` but consider plurals forms 
     136``nugettext``                                  Like ``ugettext`` but consider plurals forms 
     137``lazy_gettext``                               Like ``gettext`` but with lazy evaluation 
     138``lazy_ugettext``                              Like ``ugettext`` but with lazy evaluation 
     139``lazy_ngettext``                              Like ``ngettext`` but with lazy evaluation 
     140``lazy_nugettext``                             Like ``nugettext`` but with lazy evaluation 
     141=============================================  ================================================================ 
     142 
     143 
     144Note that the ``lazy_*`` variants don't fetch the translation when they are evaluated. Instead Nagare automatically 
     145takes care of fetching the translation at the rendering time when it encounters a lazy translation. This is especially 
     146useful when you want to use translated strings in module or class constants, which are evaluated at definition 
     147time and consequently don't have access to the current locale which, as you will see later, is scoped to the request. 
     148Here is how you would define a class constant for use in a view: 
     149 
     150..  code-block:: python 
     151    :hl_lines: 6, 11 
     152     
     153    from nagare import presentation 
     154    from nagare.i18n import _, _L 
     155     
     156    class I18nExample(object): 
     157        # the following line is evaluated when the module is loaded, so _ would not work here 
     158        TITLE = _L("Internationalization Example") 
     159     
     160    @presentation.render_for(I18nExample) 
     161    def render(self, h, *args): 
     162        with h.h1: 
     163            h << self.TITLE 
     164        h << _('This is a translated string') << h.br 
     165        h << 'This string is not translated' 
     166        return h.root 
     167     
     168    ... 
     169 
     170 
     171Extracting the messages 
     172----------------------- 
     173 
     174``Babel`` automatically takes care of the messages extraction from the sources when you run the  
     175``python setup.py extract_messages`` command. Specifically, when it extracts the localizable strings from the sources 
     176files, it only considers the strings enclosed in specific keywords calls. The list of the supported keywords appear in 
     177the ``setup.cfg`` file: 
     178 
     179.. code-block:: ini 
     180 
     181    [extract_messages] 
     182    keywords = _ , _N:1,2 , _L , _LN:1,2 , gettext , ugettext , ngettext:1,2 , ungettext:1,2 , lazy_gettext , lazy_ugettext , lazy_ngettext:1,2 , lazy_ungettext:1,2 
     183 
     184All the ``nagare.i18n`` functions that deal with strings appear in this list, so you don't have to change this line  
     185in most cases. However, if you need to add additional translation functions, don't forget to update the keywords 
     186list in this file. 
     187 
     188Furthermore, the ``setup.py`` file created by ``nagare-admin create-app`` contains a line that configures the messages 
     189extractors that ``Babel`` will use in order to extract the messages from your source files: 
     190 
     191.. code-block:: python 
     192 
     193    setup( 
     194          ... 
     195          message_extractors = { 'i18n_example' : [('**.py', 'python', None)] }, 
     196          ... 
     197    ) 
     198 
     199By default, Nagare set up an extractor for the python files. If you have other files that contain localizable 
     200strings (such as ``.js`` files), you'll have to add additional message extractors by yourself. Please refer the 
     201`Babel messages extractor documentation`_ to understand how to add additional message extractors. 
     202 
     203.. _Babel messages extractor documentation: http://babel.edgewall.org/wiki/Documentation/messages.html 
     204 
     205 
     206If you run the ``python setup.py extract_messages`` command on the previous code, you should obtain a ``messages.pot`` 
     207file that looks like this one: 
     208 
     209.. code-block:: po 
     210 
     211    # Translations template for nagare.examples. 
     212    # Copyright (C) 2012 ORGANIZATION 
     213    # This file is distributed under the same license as the nagare.examples 
     214    # project. 
     215    # FIRST AUTHOR <EMAIL@ADDRESS>, 2012. 
     216    # 
     217    #, fuzzy 
     218    msgid "" 
     219    msgstr "" 
     220    "Project-Id-Version: nagare.examples 0.3.0\n" 
     221    "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" 
     222    "POT-Creation-Date: 2012-03-05 11:05+0100\n" 
     223    "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 
     224    "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" 
     225    "Language-Team: LANGUAGE <LL@li.org>\n" 
     226    "MIME-Version: 1.0\n" 
     227    "Content-Type: text/plain; charset=utf-8\n" 
     228    "Content-Transfer-Encoding: 8bit\n" 
     229    "Generated-By: Babel 0.9.6\n" 
     230     
     231    #: nagare/examples/i18n.py:18 
     232    msgid "Internationalization Example" 
     233    msgstr "" 
     234     
     235    #: nagare/examples/i18n.py:25 
     236    msgid "This is a translated string" 
     237    msgstr "" 
     238 
     239All the localizable strings of your application should appear in this file after extraction. Note that the string  
     240``This string is not translated`` of the previous example was not included because it was not enclosed in a call 
     241to a translation keyword. 
     242 
     243 
     244Translating the messages 
     245------------------------ 
     246 
     247Once the ``.pot`` file is produced (after message extraction), you have to create a message catalog for each language 
     248with the help of the ``init_catalog`` catalog, for example: 
    97249 
    98250.. code-block:: sh 
    99251 
    100     $ mkdir data/locale 
     252    $ python setup.py init_catalog -l fr 
     253    $ python setup.py init_catalog -l en 
     254 
     255Then update the ``.po`` files that have just been created and provide the translations for all the strings that 
     256do not have one. You can use either a dedicated ``.po`` file editor, or a simple text editor and fill the 
     257``msgstr`` lines for each ``msgid``. Please refer to the `PO Files section of the Gettext Manual`_ for more 
     258information about this process. 
     259 
     260.. _`PO Files section of the Gettext Manual`: http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files 
     261 
     262Finally, compile the catalogs in binary form: 
     263 
     264.. code-block:: sh 
     265 
     266    $ python setup.py compile_catalog 
     267 
     268If you change the source files and add new messages to translate, you have to update the message catalogs: 
     269 
     270.. code-block:: sh 
     271 
    101272    $ python setup.py extract_messages 
    102     $ python setup.py init_catalog -l en_US 
    103     $ python setup.py init_catalog -l fr_FR 
    104  
    105 Edit the ``data/locale/<lang>/LC_MESSAGES/messages.po`` files and fill in the missing translations, then type: 
    106      
     273    $ python setup.py update_catalog 
     274     
     275Then, fill in the missing translations in the ``.po`` files and compile the catalogs again. 
     276 
    107277.. code-block:: sh 
    108278 
    109279    $ python setup.py compile_catalog 
    110280 
    111 You should now have ``.mo`` files in the ``data/locale/<lang>/LC_MESSAGES`` directories, ready to be used by our 
    112 brand new application. Now, it's time to switch the locale of the application. 
    113  
    114 If anything is not clear at this point, please refer to the first `blog post`_ for details. 
    115  
    116  
    117 Switching the locale using browser negotiation 
    118 ============================================== 
    119  
    120 Since the ``i18n`` locale is scoped to a request (because a user may change the language settings of his 
    121 browser between two requests), the proper place to change the locale of the application is in the ``start_request`` 
    122 method of the ``wsgi.WSGIApp`` class. So, we must first introduce an application class that inherit from Nagare's 
    123 ``nagare.wsgi.WSGIApp`` class: 
     281 
     282Formatting numbers, dates, times, currencies and dealing with timezones 
     283----------------------------------------------------------------------- 
     284 
     285The ``nagare.i18n`` module also defines functions for formatting numbers, dates, times, currencies and for timezones 
     286calculations, according to the locale of the user. We have grouped these functions by category. See the Nagare 
     287:apidoc:`i18n` API documentation for more information on these functions. 
     288 
     289 
     290Numbers 
     291~~~~~~~ 
     292 
     293=============================================  ================================================================ 
     294                Function                                                   Effect 
     295=============================================  ================================================================ 
     296``get_decimal_symbol``                         Return the symbol used to separate decimal fractions 
     297``get_plus_sign_symbol``                       Return the plus sign symbol 
     298``get_minus_sign_symbol``                      Return the minus sign symbol 
     299``get_exponential_symbol``                     Return the symbol used to separate mantissa and exponent 
     300``get_group_symbol``                           Return the symbol used to separate groups of thousands 
     301 
     302``format_number``                              Return the given integer number formatted 
     303``format_decimal``                             Return the given decimal number formatted 
     304``format_percent``                             Return the formatted percentage value 
     305``format_scientific``                          Return the value formatted in scientific notation, e.g. 1.23E06 
     306 
     307``parse_number``                               Parse the localized number string into a long integer 
     308``parse_decimal``                              Parse the localized decimal string into a float 
     309=============================================  ================================================================ 
     310 
     311 
     312Currencies 
     313~~~~~~~~~~ 
     314 
     315=============================================  ================================================================ 
     316                Function                                                   Effect 
     317=============================================  ================================================================ 
     318``get_currency_name``                          Return the name used for the specified currency 
     319``get_currency_symbol``                        Return the symbol used for the specified currency 
     320``format_currency``                            Return the formatted currency value 
     321=============================================  ================================================================ 
     322 
     323 
     324Dates & timezones 
     325~~~~~~~~~~~~~~~~~ 
     326 
     327=============================================  ================================================================ 
     328                Function                                                   Effect 
     329=============================================  ================================================================ 
     330``get_period_names``                           Return the names for day periods (AM/PM) 
     331``get_day_names``                              Return the day names in the specified format 
     332``get_month_names``                            Return the month names in the specified format 
     333``get_quarter_names``                          Return the quarter names in the specified format 
     334``get_era_names``                              Return the era names used in the specified format 
     335``get_date_format``                            Return the date formatting pattern in the specified format 
     336``get_datetime_format``                        Return the datetime formatting pattern in the specified format 
     337``get_time_format``                            Return the time formatting pattern in the specified format 
     338``get_timezone_gmt``                           Return the timezone associated with the given datetime object, 
     339                                               formatted as a string indicating the offset from GMT 
     340``get_timezone_location``                      Return a representation of the given timezone using the "location 
     341                                               format", i.e. the human-readable name of the timezone 
     342``get_timezone_name``                          Return the localized display name for the given timezone 
     343 
     344``format_time``                                Return a time formatted according to the given pattern 
     345``format_date``                                Return a date formatted according to the given pattern 
     346``format_datetime``                            Return a datetime formatted according to the given pattern 
     347 
     348``parse_time``                                 Parse a time from a string 
     349``parse_date``                                 Parse a date from a string 
     350 
     351``to_timezone``                                Return a localized datetime object 
     352``to_utc``                                     Return a UTC datetime object 
     353=============================================  ================================================================ 
     354 
     355 
     356Setting the locale of the application 
     357------------------------------------- 
     358 
     359The last step of the internationalization process is to initialize the locale of the application according to a 
     360*language setting*. There are many ways to implement a language setting: 
     361  
     362- use the client browser language settings, i.e. deal with the ``Accept-Language`` headers from the HTTP request 
     363- use a URL prefix, for example ``/en`` for english and ``/fr`` for french (see :wiki:`RestfulUrl` to learn how 
     364  to set up URLs) 
     365- use a per-user setting: for example, read the locale setting from a user profile stored in a database 
     366- use a per-session setting, for example store the language setting as an attribute of a component 
     367- use a per-application setting, for example provide the language setting in the application configuration 
     368  file 
     369 
     370You may even want to combine two or more of these schemes in your application!  As you will see next, Nagare makes 
     371it easy to change the locale at runtime, it doesn't really matter where the language setting comes from. 
     372 
     373The best place where to change the locale is in the ``start_request`` method of the application object 
     374(``wsgi.WSGIApp`` by default) since we have access to both the request and to the authenticated user (when necessary) 
     375there. Furthermore, since a user may change his language setting between two requests, the locale setting is scoped 
     376to the HTTP request by design in Nagare. 
     377 
     378The locale can be changed with either the ``WSGIApp.set_locale`` method or the ``WSGIApp.set_default_locale`` method 
     379(see :apidoc:`wsgi`). The ``WSGIApp.start_request`` method itself use ``WSGIApp.set_locale`` to install the default 
     380locale, set with ``WSGIApp.set_default_locale``. By default, the default locale is initialized to a locale that does 
     381not perform any translation, it just returns the original string. 
     382 
     383 
     384Example 1: using  a negociated browser locale 
     385~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     386 
     387As an illustration, let's use a negociated browser locale in the previous example: 
    124388 
    125389.. code-block:: python 
    126  
    127     from nagare import wsgi, i18n 
     390     
     391    from nagare import presentation, component, wsgi, i18n 
     392     
     393    ... 
    128394     
    129395    class I18nExampleApp(wsgi.WSGIApp): 
    130396        AVAILABLE_LOCALES = ( 
    131             ('en', 'US'),  # English (US) 
    132             ('fr', 'FR'),  # French (France) 
     397            ('en',),  # English 
     398            ('fr',),  # French 
    133399        ) 
    134400     
     
    139405            # let nagare install the default locale 
    140406            super(I18nExampleApp, self).start_request(root, request, response) 
    141  
    142 In the ``start_request`` method, we set the default locale object to a ``i18n.NegotiatedLocale`` built from the request. This means 
    143 that Nagare will scan the ``Accept-Language`` headers of the request to find out the best locale to use according to the available 
    144 locales (expressed as an iterable of ``(language, territory)`` tuples). We also set the default locale to the first one available 
    145 in case the negotiation fail. We then let Nagare install the default locale via the base ``start_request`` method that itself call 
    146 the ``WSGIApp.set_locale`` method which initialize the dirname of the locale (i.e. ``data/locale``) and initialize the current locale 
    147 to the default locale. 
    148  
    149 Then, we use an instance of ``I18nExampleApp`` as our app: 
     407     
     408     
     409    app = I18nExampleApp(lambda: component.Component(I18nExample())) 
     410 
     411We first create an application class that inherit from ``wsgi.WSGIApp`` so that we can override the default 
     412``start_request`` behavior in order to install a negociated browser locale. 
     413 
     414Since we must pass the list of the available locales to the ``i18n.NegotiatedLocale`` constructor, we define a 
     415``AVAILABLE_LOCALES`` class constant. The available locales should be given as a list of ``(language, territory)`` 
     416tuples. The territory has been omitted here since we don't use territory variants of the languages yet. 
     417 
     418Then, we set the default locale in our new ``start_request`` method and let nagare install the default locale by 
     419calling the base ``start_request`` method. Note that you should always call the base ``start_request`` method since 
     420it also installs the security manager and authenticate the user. The ``i18n.NegotiatedLocale`` constructor accepts 
     421the request as first parameter, the list of the available locales as second parameter and some optional parameters. 
     422It examines the ``Accept-Language`` headers of the request in order to determine the best locale to use for the 
     423current request according to the available locales. The ``default_locale`` optional parameter can be used to define 
     424the default ``(language, territory)`` value when the negociation fails. 
     425 
     426Finally, we use a ``I18nExampleApp`` instance as our application instead of the default ``wsgi.WSGIApp`` (implied 
     427when you provide a component instance as ``app`` instead of an application instance). 
     428 
     429 
     430Example 2: using a per-session locale setting 
     431~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     432 
     433Let's show another example: we will allow the user to change the language setting using links and store the setting 
     434in the session in order to persist the change for the next requests. Here is the code: 
    150435 
    151436.. code-block:: python 
    152437 
    153     from nagare import component 
     438    from nagare import presentation, component, wsgi, i18n 
     439    from nagare.i18n import _, _L 
     440     
     441     
     442    class I18nExample(object): 
     443        # the following line is evaluated when the module is loaded, so _ would not work here 
     444        TITLE = _L("Internationalization Example") 
     445     
     446        AVAILABLE_LOCALES = ( 
     447            (i18n.Locale('en'), u'English'),  # English 
     448            (i18n.Locale('fr'), u'Français'),  # French 
     449        ) 
     450     
     451        def __init__(self, locale=AVAILABLE_LOCALES[0][0]): 
     452            self.set_locale(locale) 
     453     
     454        def set_locale(self, locale): 
     455            # remember the locale to use for the next requests in the session (see I18nExampleApp) 
     456            self.locale = locale 
     457     
     458            # we should also initialize the locale dirname since it may not have been set yet 
     459            # we use the current locale dirname, which is set by the WSGIApp.set_locale method 
     460            dirname = i18n.get_locale().dirname 
     461            locale.dirname = dirname 
     462     
     463            # change the locale for the current request so that the rendering is performed with the new locale 
     464            i18n.set_locale(self.locale) 
     465     
     466     
     467    @presentation.render_for(I18nExample) 
     468    def render(self, h, comp, *args): 
     469        with h.h1: 
     470            h << self.TITLE 
     471        h << _('This is a translated string') << h.br 
     472        h << 'This string is not translated' 
     473     
     474        h << h.hr 
     475     
     476        # language change 
     477        for locale, label in self.AVAILABLE_LOCALES: 
     478            h << h.a(label, title=label).action(lambda locale=locale: self.set_locale(locale)) 
     479            h << " " 
     480     
     481        return h.root 
     482     
     483     
     484    class I18nExampleApp(wsgi.WSGIApp): 
     485        def start_request(self, root, request, response): 
     486            # call base implementation 
     487            super(I18nExampleApp, self).start_request(root, request, response) 
     488     
     489            if root(): 
     490                # reinstall the locale stored in the session 
     491                locale = root().locale 
     492                self.set_locale(locale) 
     493     
    154494     
    155495    app = I18nExampleApp(lambda: component.Component(I18nExample())) 
    156496 
    157497 
    158 Serve the application: 
    159  
    160 .. code-block:: sh 
    161  
    162     $ nagare-admin serve i18_example --reload 
    163  
    164 And that's it! The browser now displays the web application either in English or in French depending on your browser language settings. 
    165  
    166  
    167 Allowing the user to change the locale 
    168 ====================================== 
    169  
    170 We can also allow the user to change the locale of the application. For example, we will demonstrate how to use language change links. 
    171 But if you have an application with authentication and user profiles, you can also store and use the preferred locale of the authenticated 
    172 user in his profile. As you will see, it doesn't really matter where the locale setting comes from. 
    173  
    174 Let's start. First, create a view with the language change links: 
    175  
    176 .. code-block:: python 
    177  
    178     @presentation.render_for(I18nExample, model='locale_change') 
    179     def render(self, h, *args): 
    180         AVAILABLE_LOCALES = ( 
    181             (('en', 'US'), u'English'), 
    182             (('fr', 'FR'), u'Français'), 
    183         ) 
    184      
    185         with h.div(class_='locale-change'): 
    186             for locale_info, label in AVAILABLE_LOCALES: 
    187                 h << h.a(label, title=label).action(lambda locale_info=locale_info: self.set_locale(locale_info)) 
    188                 h << " " 
    189      
    190         return h.root 
    191  
    192 Also, don't forget to render the ``locale_change`` view in the default view: 
    193  
    194 .. code-block:: python 
    195  
    196     with h.div(id='content'): 
    197         h << h.div(_('Congratulations!'), id='title') 
    198         h << comp.render(h, model='locale_change') 
    199  
    200 Nothing hard there: we added a view to the ``I18nExample`` component (the root component of our application) that shows two HTML ``<a>`` 
    201 elements which set the locale to either English or French. The ``set_locale`` method has not been defined yet, so let us define it: 
    202  
    203 .. code-block:: python 
    204  
    205     class I18nExample(object): 
    206         def __init__(self): 
    207             self.locale_override = None 
    208      
    209         def set_locale(self, locale_info): 
    210             # use the same locale directory as the default/current locale 
    211             locale_dir_name = i18n.get_locale().dirname 
    212      
    213             # create a new locale object from the locale information provided 
    214             locale = i18n.Locale(*locale_info, dirname=locale_dir_name) 
    215      
    216             # change the locale immediately so that it affects the current request 
    217             i18n.set_locale(locale) 
    218      
    219             # also remember the locale setting for the next requests 
    220             self.locale_override = locale 
    221  
    222 First, we retrieve the locale directory of the current (default) locale. Then, we create a new locale object using the locale information 
    223 provided in the link actions of the view and the directory name we just retrieved. Once created, we set the locale of the ``i18n`` service 
    224 for the current request (remember, the locale setting is scoped to the request), and we also store the new locale object in a  
    225 ``locale_override`` attribute in order to re-install this locale in subsequent requests. 
    226  
    227 The last step is to re-install the overridden locale in each request, so let's go back to the ``start_request`` method: 
    228  
    229 .. code-block:: python 
    230  
    231     def start_request(self, root, request, response): 
    232         # set the default locale to a browser negotiated locale 
    233         self.set_default_locale(i18n.NegotiatedLocale(request, self.AVAILABLE_LOCALES, default_locale=self.AVAILABLE_LOCALES[0])) 
    234  
    235         # let nagare install the default locale 
    236         super(I18nExampleApp, self).start_request(root, request, response) 
    237  
    238         # if the user has overridden the locale, use it instead of the default locale 
    239         locale_override = root().locale_override 
    240         if locale_override is not None: 
    241             self.set_locale(locale_override) 
    242  
    243 See, if the ``locale_override`` attribute from the root component (an ``I18nExample`` instance) is not ``None`` (which means that the user has 
    244 overridden the locale in a previous request), we use it instead of the default one. 
    245  
    246 Let's see if everything is OK. First, browse the application URL (i.e. http://localhost:8080/i18n_example). You should see the application in 
    247 a language negotiated with the browser. Click on a language link: the page refreshes and the page content appears in the selected language. Let's 
    248 ask the same page again, that is, refresh the page: same language again. The language setting is actually stored in the session since Nagare 
    249 automatically saves the state of the components in the session at the end of each request. So let's remove the session information from the URL (that 
    250 is, the part after the last slash, like this: ``?_s=3363610770854449&_c=00000&_action450653339``): we're back to the negotiated locale. 
    251  
    252 It's now up to you to store the language setting persistently (either via a language prefix in the URL or in authenticated users' profiles) or keep it 
    253 in the session. 
     498We moved the ``AVAILABLE_LOCALES`` list to the ``I18nExample`` component since we'll show a list of 
     499language change links in its view. We also added a label to each entry and we use ``i18n.Locale`` 
     500instances instead of the ``(language, territory)`` tuples. 
     501 
     502In the associated view, we iterate the ``AVAILABLE_LOCALES`` list in order to display a link for each locale. 
     503The links actions call the ``set_locale`` method, passing the associated ``i18n.Locale`` instance to it. 
     504 
     505The ``I18nExample.set_locale`` method performs two operations. First, it remembers the locale setting as an 
     506attribute of the component. Since the component will be pickled into the session after the request, the locale 
     507setting is effectively stored in the session and will be back in the next request when the session is unpickled. 
     508The locale setting is then used in the ``I18nExampleApp.start_request`` method for reinstalling the locale for 
     509subsequent requests. 
     510 
     511Second, it changes the locale in the current request (where the language change action is received) so that the 
     512rendering of the component is immediately affected by the change. However, we can't call 
     513``I18nExampleApp.set_locale`` here, because we don't have access to the application instance from the component. 
     514Instead, we have to install the locale ourselves by calling the ``i18n.set_locale`` directly. But, the locale 
     515``dirname`` attribute (i.e. the base directory where the locale catalog files are to be found) may not have been 
     516initialized yet, because it's the responsibility of the ``I18nExampleApp.set_locale`` method. So, we set the 
     517``dirname`` attribute of the new locale to the ``dirname`` of the currently installed locale, which should have 
     518been initialized by the ``I18nExampleApp.set_locale`` method at the very beginning of the request. 
     519 
     520Finally, the ``I18nExampleApp.start_request`` calls the ``set_locale`` method in order to reinstall the previously 
     521saved locale for the active request. It's important to call ``set_locale`` *after* calling the base implementation 
     522because we want to override the default locale set by the base implementation (remember that ``set_locale`` also 
     523intialize the security context and authenticate the user). 
     524 
     525These two examples explain the basics for setting the locale in a Nagare application. Real-world implementations may 
     526store the language setting by other means (see the list at the top-most paragraph of this section) and use a 
     527combination of these two techniques. 
     528 
     529 
     530.. wikiname: Internationalization 
    254531}}}