Changes between Version 32 and Version 33 of SandBox

03/29/2012 02:17:40 PM (6 years ago)



  • SandBox

    v32 v33  
    3 ================================================================================== 
    4 Project Internationalization with Nagare : setting the locale in your applications  
    5 ================================================================================== 
    8 Goals 
    9 ===== 
    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. 
    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. 
    19 The ``i18n`` module from Nagare provides two ways to configure the locale, that we will describe 
    20 hereafter: 
    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 
    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. 
    32 .. _blog post: 
    35 Getting Started with Nagare i18n 
    36 ================================ 
    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. 
    41 First, install Nagare with the ``i18n`` option: 
     3Internationalization of applications 
     6Since version 0.4.0, the Nagare framework includes a ``nagare.i18n`` module that can help you 
     7internationalize a Web application. 
     9There are many aspects to take into account when internationalizing an application: 
     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 
     18All these aspects are addressed by the ``nagare.i18n`` module. 
     21Setup an application for internationalization 
     24First, the Nagare ``i18n`` extras should be installed in your virtual environment: 
    4326.. code-block:: sh 
    4528    $ easy_install nagare[i18n] 
    48 Then, create a sample application: 
     30This command installs ``Babel`` and ``pytz`` which are necessary to use the ``nagare.i18n`` module. 
     32However, if you plan to use the internationalization feature of Nagare in your Web application, you'd 
     33better change the ```` file that is generated when you create the application, in order to specify 
     34that you need the ``i18n`` extra as a requirement: 
     36.. code-block:: python 
     38    setup( 
     39          ... 
     40          install_requires = ('nagare[i18n]', ...), 
     41          ... 
     42    ) 
     44Then, Nagare is installed with the ``i18n`` extra when you install your application. 
     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:  
     49.. code-block:: ini 
     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 
     55    [init_catalog] 
     56    input_file = data/locale/messages.pot 
     57    output_dir = data/locale 
     58    domain = messages 
     60    [update_catalog] 
     61    input_file = data/locale/messages.pot 
     62    output_dir = data/locale 
     63    domain = messages 
     65    [compile_catalog] 
     66    directory = data/locale 
     67    domain = messages 
     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: 
     72===============================================  ================================================================= 
     73                 Command                                                   Effect 
     74===============================================  ================================================================= 
     75``python extract_messages``             Extract the messages from the python sources to a ``.pot`` file 
     76``python init_catalog -l <lang>``       Create a new message catalog (``.po`` file) for a given 
     77                                                 language, initialized from the ``.pot`` file 
     78``python 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 compile_catalog [-l <lang>``]  Compile the message catalog(s) in binary form so that they can be 
     81                                                 used inside the application 
     82===============================================  ================================================================= 
     84See the `Babel setup documentation`_ for a complete reference of the commands options. 
     86.. _Babel setup documentation: 
     88The catalog files are stored in the ``data/locale`` directory by default, which is automatically included into your 
     89application source package thanks to the ```` 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: 
    5092.. code-block:: sh 
    52     $ nagare create-app i18n_example 
    53     $ cd i18n_example 
    54     $ python develop 
    57 Edit the file, import the ``nagare.i18n._`` function and mark the messages to be translated with it: 
    59 .. code-block:: python 
    61     # -*- coding: utf-8 -*- 
    63     from __future__ import with_statement 
     94    $ mkdir data/locale 
     97Marking the messages to translate 
     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: 
     103..  code-block:: python 
     104    :hl_lines: 2, 9 
    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 !')) 
    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% }') 
    78         with h.div(id='body'): 
    79             h << h.a(h.img(src='/static/nagare/img/logo.png'), id='logo', href='', title='Nagare home') 
    81             with h.div(id='content'): 
    82                 h << h.div(_('Congratulations!'), id='title') 
    84                 with h.div(id='main'): 
    85                     h << h.h1(_('Your application is localized !')) 
    87                     with h.p: 
    88                         h << _('Here is some localized text...') 
     114        h << _('This is a translated string') << 
     115        h << 'This string is not translated' 
    89116        return h.root 
    91     # --------------------------------------------------------------- 
    93118    app = I18nExample 
    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: 
     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=============================================  ================================================================ 
     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: 
     150..  code-block:: python 
     151    :hl_lines: 6, 11 
     153    from nagare import presentation 
     154    from nagare.i18n import _, _L 
     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") 
     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') << 
     165        h << 'This string is not translated' 
     166        return h.root 
     168    ... 
     171Extracting the messages 
     174``Babel`` automatically takes care of the messages extraction from the sources when you run the  
     175``python 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: 
     179.. code-block:: ini 
     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 
     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. 
     188Furthermore, the ```` 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: 
     191.. code-block:: python 
     193    setup( 
     194          ... 
     195          message_extractors = { 'i18n_example' : [('**.py', 'python', None)] }, 
     196          ... 
     197    ) 
     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. 
     203.. _Babel messages extractor documentation: 
     206If you run the ``python extract_messages`` command on the previous code, you should obtain a ``messages.pot`` 
     207file that looks like this one: 
     209.. code-block:: po 
     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 <>\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" 
     231    #: nagare/examples/ 
     232    msgid "Internationalization Example" 
     233    msgstr "" 
     235    #: nagare/examples/ 
     236    msgid "This is a translated string" 
     237    msgstr "" 
     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. 
     244Translating the messages 
     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: 
    98250.. code-block:: sh 
    100     $ mkdir data/locale 
     252    $ python init_catalog -l fr 
     253    $ python init_catalog -l en 
     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. 
     260.. _`PO Files section of the Gettext Manual`: 
     262Finally, compile the catalogs in binary form: 
     264.. code-block:: sh 
     266    $ python compile_catalog 
     268If you change the source files and add new messages to translate, you have to update the message catalogs: 
     270.. code-block:: sh 
    101272    $ python extract_messages 
    102     $ python init_catalog -l en_US 
    103     $ python init_catalog -l fr_FR 
    105 Edit the ``data/locale/<lang>/LC_MESSAGES/messages.po`` files and fill in the missing translations, then type: 
     273    $ python update_catalog 
     275Then, fill in the missing translations in the ``.po`` files and compile the catalogs again. 
    107277.. code-block:: sh 
    109279    $ python compile_catalog 
    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. 
    114 If anything is not clear at this point, please refer to the first `blog post`_ for details. 
    117 Switching the locale using browser negotiation 
    118 ============================================== 
    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: 
     282Formatting numbers, dates, times, currencies and dealing with timezones 
     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. 
     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 
     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 
     307``parse_number``                               Parse the localized number string into a long integer 
     308``parse_decimal``                              Parse the localized decimal string into a float 
     309=============================================  ================================================================ 
     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=============================================  ================================================================ 
     324Dates & timezones 
     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 
     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 
     348``parse_time``                                 Parse a time from a string 
     349``parse_date``                                 Parse a date from a string 
     351``to_timezone``                                Return a localized datetime object 
     352``to_utc``                                     Return a UTC datetime object 
     353=============================================  ================================================================ 
     356Setting the locale of the application 
     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: 
     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 
     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. 
     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. 
     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. 
     384Example 1: using  a negociated browser locale 
     387As an illustration, let's use a negociated browser locale in the previous example: 
    125389.. code-block:: python 
    127     from nagare import wsgi, i18n 
     391    from nagare import presentation, component, wsgi, i18n 
     393    ... 
    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        ) 
    139405            # let nagare install the default locale 
    140406            super(I18nExampleApp, self).start_request(root, request, response) 
    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. 
    149 Then, we use an instance of ``I18nExampleApp`` as our app: 
     409    app = I18nExampleApp(lambda: component.Component(I18nExample())) 
     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. 
     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. 
     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. 
     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). 
     430Example 2: using a per-session locale setting 
     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: 
    151436.. code-block:: python 
    153     from nagare import component 
     438    from nagare import presentation, component, wsgi, i18n 
     439    from nagare.i18n import _, _L 
     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") 
     446        AVAILABLE_LOCALES = ( 
     447            (i18n.Locale('en'), u'English'),  # English 
     448            (i18n.Locale('fr'), u'Français'),  # French 
     449        ) 
     451        def __init__(self, locale=AVAILABLE_LOCALES[0][0]): 
     452            self.set_locale(locale) 
     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 
     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 
     463            # change the locale for the current request so that the rendering is performed with the new locale 
     464            i18n.set_locale(self.locale) 
     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') << 
     472        h << 'This string is not translated' 
     474        h << 
     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 << " " 
     481        return h.root 
     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) 
     489            if root(): 
     490                # reinstall the locale stored in the session 
     491                locale = root().locale 
     492                self.set_locale(locale) 
    155495    app = I18nExampleApp(lambda: component.Component(I18nExample())) 
    158 Serve the application: 
    160 .. code-block:: sh 
    162     $ nagare-admin serve i18_example --reload 
    164 And that's it! The browser now displays the web application either in English or in French depending on your browser language settings. 
    167 Allowing the user to change the locale 
    168 ====================================== 
    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. 
    174 Let's start. First, create a view with the language change links: 
    176 .. code-block:: python 
    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         ) 
    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 << " " 
    190         return h.root 
    192 Also, don't forget to render the ``locale_change`` view in the default view: 
    194 .. code-block:: python 
    196     with h.div(id='content'): 
    197         h << h.div(_('Congratulations!'), id='title') 
    198         h << comp.render(h, model='locale_change') 
    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: 
    203 .. code-block:: python 
    205     class I18nExample(object): 
    206         def __init__(self): 
    207             self.locale_override = None 
    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 
    213             # create a new locale object from the locale information provided 
    214             locale = i18n.Locale(*locale_info, dirname=locale_dir_name) 
    216             # change the locale immediately so that it affects the current request 
    217             i18n.set_locale(locale) 
    219             # also remember the locale setting for the next requests 
    220             self.locale_override = locale 
    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. 
    227 The last step is to re-install the overridden locale in each request, so let's go back to the ``start_request`` method: 
    229 .. code-block:: python 
    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])) 
    235         # let nagare install the default locale 
    236         super(I18nExampleApp, self).start_request(root, request, response) 
    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) 
    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. 
    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. 
    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. 
     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. 
     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. 
     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. 
     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). 
     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. 
     530.. wikiname: Internationalization