Steve Piercy
2017-06-26 19d341b5be789e97000d3dcbd33de75d8b061829
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
.. index::
   single: i18n
   single: l10n
   single: internationalization
   single: localization
 
.. _i18n_chapter:
 
Internationalization and Localization
=====================================
 
:term:`Internationalization` (i18n) is the act of creating software with a user
interface that can potentially be displayed in more than one language or
cultural context.  :term:`Localization` (l10n) is the process of displaying the
user interface of an internationalized application in a *particular* language
or cultural context.
 
:app:`Pyramid` offers internationalization and localization subsystems that can
be used to translate the text of buttons, error messages, and other software-
and template-defined values into the native language of a user of your
application.
 
.. 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 creates a
:term:`translation string`.  A translation string is an object that behaves
mostly like a normal Unicode object, except that it also carries around extra
information related to its job as part of the :app:`Pyramid` translation
machinery.
 
Using the ``TranslationString`` Class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
The most primitive way to create a translation string is to use the
:class:`pyramid.i18n.TranslationString` callable:
 
.. code-block:: python
   :linenos:
 
   from pyramid.i18n import TranslationString
   ts = TranslationString('Add')
 
This creates a Unicode-like object that is a TranslationString.
 
.. note::
 
   For people more familiar with :term:`Zope` i18n, 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`
   i18n, using a TranslationString is a lot like using "lazy" versions of
   related gettext APIs.
 
The first argument to :class:`~pyramid.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 pyramid.i18n import TranslationString
   ts = TranslationString('Add ${number}')
 
Within the string above, ``${number}`` 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 pyramid.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 pyramid.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 our msgid to German, it might try to find a
translation from a :term:`gettext` file within a :term:`translation directory`
like this one:
 
.. code-block:: text
 
   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 pyramid.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:`~pyramid.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 pyramid.i18n import TranslationStringFactory
   _ = TranslationStringFactory('pyramid')
   ts = _('add-number', default='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:`~pyramid.i18n.TranslationStringFactory`, the subsequent result of
calling ``_`` will be a :class:`~pyramid.i18n.TranslationString` instance.
Even though a ``domain`` value was not passed to ``_`` (as would have been
necessary if the :class:`~pyramid.i18n.TranslationString` constructor were used
instead of a translation string factory), the ``domain`` attribute of the
resulting translation string will be ``pyramid``.  As a result, the previous
code example is completely equivalent (except for spelling) to:
 
.. code-block:: python
   :linenos:
 
   from pyramid.i18n import TranslationString as _
   ts = _('add-number', default='Add ${number}', mapping={'number':1},
          domain='pyramid')
 
You can set up your own translation string factory much like the one provided
above by using the :class:`~pyramid.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 pyramid.i18n import TranslationStringFactory
   _ = TranslationStringFactory('form')
   ts = _('add-number', default='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 their own.  Instead they can just include your package's
:term:`translation directory` via the
:meth:`pyramid.config.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
------------------------------------------
 
The basis of :app:`Pyramid` translation services is GNU :term:`gettext`. Once
your application source code files and templates are marked up with translation
markers, you can work on translations by creating various kinds of gettext
files.
 
.. note::
 
   The steps a developer must take to work with :term:`gettext` :term:`message
   catalog` files within a :app:`Pyramid` application are very similar to the
   steps a :term:`Pylons` developer must take to do the same.  See the
   :ref:`Pylons Internationalization and Localization documentation
   <pylonswebframework:i18n>` for more information.
 
GNU gettext uses three types of files in the translation framework, ``.pot``
files, ``.po`` files, and ``.mo`` files.
 
``.pot`` (Portable Object Template) files
 
  A ``.pot`` file is created by a program which searches through your project's
  source code and which picks out every :term:`message identifier` passed to
  one of the ``_()`` functions (e.g., :term:`translation string`
  constructions). The list of all message identifiers is placed into a ``.pot``
  file, which serves as a template for creating ``.po`` files.
 
``.po`` (Portable Object) files
 
  The list of messages in a ``.pot`` file are translated by a human to a
  particular language; the result is saved as a ``.po`` file.
 
``.mo`` (Machine Object) files
 
  A ``.po`` file is turned into a machine-readable binary file, which is the
  ``.mo`` file. Compiling the translations to machine code makes the
  localized program start faster.
 
The tools for working with :term:`gettext` translation files related to a
:app:`Pyramid` application are :term:`Lingua` and :term:`Gettext`. Lingua can
scrape i18n references out of Python and Chameleon files and create the
``.pot`` file. Gettext includes ``msgmerge`` tool to update a ``.po`` file from
an updated ``.pot`` file and ``msgfmt`` to compile ``.po`` files to ``.mo``
files.
 
.. index::
   single: Gettext
   single: Lingua
 
.. _installing_babel:
 
Installing Lingua and Gettext
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
In order for the commands related to working with ``gettext`` translation files
to work properly, you will need to have :term:`Lingua` and :term:`Gettext`
installed into the same environment in which :app:`Pyramid` is installed.
 
Installation on UNIX
++++++++++++++++++++
 
Gettext is often already installed on UNIX systems. You can check if it is
installed by testing if the ``msgfmt`` command is available. If it is not
available you can install it through the packaging system from your OS; the
package name is almost always ``gettext``. For example on a Debian or Ubuntu
system run this command:
 
.. code-block:: bash
 
   $ sudo apt-get install gettext
 
Installing Lingua is done with the Python packaging tools. If the
:term:`virtual environment` into which you've installed your :app:`Pyramid`
application lives at the environment variable ``$VENV``, you can install Lingua
like so:
 
.. code-block:: bash
 
   $ $VENV/bin/pip install lingua
 
Installation on Windows
+++++++++++++++++++++++
 
There are several ways to install Gettext on Windows: it is included in the
`Cygwin <http://www.cygwin.com/>`_ collection, or you can use the `installer
from the GnuWin32 <http://gnuwin32.sourceforge.net/packages/gettext.htm>`_, or
compile it yourself. Make sure the installation path is added to your
``$PATH``.
 
Installing Lingua is done with the Python packaging tools. If the
:term:`virtual environment` into which you've installed your :app:`Pyramid`
application lives at the environment variable ``%VENV%``, you can install
Lingua like so:
 
.. code-block:: doscon
 
   c:\> %VENV%\Scripts\pip install lingua
 
 
.. index::
   pair: extracting; messages
 
.. _extracting_messages:
 
Extracting Messages from Code and Templates
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
Once Lingua is installed, you may extract a message catalog template from the
code and :term:`Chameleon` templates which reside in your :app:`Pyramid`
application.  You run a ``pot-create`` command to extract the messages:
 
.. code-block:: bash
 
   $ cd /file/path/to/myapplication_setup.py
   $ mkdir -p myapplication/locale
   $ $VENV/bin/pot-create -o myapplication/locale/myapplication.pot src
 
The message catalog ``.pot`` template will end up in
``myapplication/locale/myapplication.pot``.
 
 
.. index::
   pair: initializing; message catalog
 
Initializing a Message Catalog File
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
Once you've extracted messages into a ``.pot`` file (see
:ref:`extracting_messages`), to begin localizing the messages present in the
``.pot`` file, you need to generate at least one ``.po`` file. A ``.po`` file
represents translations of a particular set of messages to a particular locale.
Initialize a ``.po`` file for a specific locale from a pre-generated ``.pot``
template by using the ``msginit`` command from Gettext:
 
.. code-block:: bash
 
   $ cd /file/path/to/myapplication_setup.py
   $ cd myapplication/locale
   $ mkdir -p es/LC_MESSAGES
   $ msginit -l es -o es/LC_MESSAGES/myapplication.po
 
This will create a new message catalog ``.po`` file in
``myapplication/locale/es/LC_MESSAGES/myapplication.po``.
 
Once the file is there, it can be worked on by a human translator. One tool
which may help with this is `Poedit <https://poedit.net/>`_.
 
Note that :app:`Pyramid` itself ignores the existence of all ``.po`` files.
For a running application to have translations available, a ``.mo`` file must
exist.  See :ref:`compiling_message_catalog`.
 
.. index::
   pair: updating; message catalog
 
Updating a Catalog File
~~~~~~~~~~~~~~~~~~~~~~~
 
If more translation strings are added to your application, or translation
strings change, you will need to update existing ``.po`` files based on changes
to the ``.pot`` file, so that the new and changed messages can also be
translated or re-translated.
 
First, regenerate the ``.pot`` file as per :ref:`extracting_messages`. Then use
the ``msgmerge`` command from Gettext.
 
.. code-block:: bash
 
   $ cd /file/path/to/myapplication_setup.py
   $ cd myapplication/locale
   $ msgmerge --update es/LC_MESSAGES/myapplication.po myapplication.pot
 
.. index::
   pair: compiling; message catalog
 
.. _compiling_message_catalog:
 
Compiling a Message Catalog File
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
Finally, to prepare an application for performing actual runtime translations,
compile ``.po`` files to ``.mo`` files using the ``msgfmt`` command from
Gettext:
 
.. code-block:: bash
 
   $ cd /file/path/to/myapplication_setup.py
   $ msgfmt -o myapplication/locale/es/LC_MESSAGES/myapplication.mo \
         myapplication/locale/es/LC_MESSAGES/myapplication.po
 
This will create a ``.mo`` file for each ``.po`` file in your application.  As
long as the :term:`translation directory` in which the ``.mo`` file ends up in
is configured into your application (see
:ref:`adding_a_translation_directory`), these translations will be available to
:app:`Pyramid`.
 
.. index::
   single: localizer
   single: translation
   single: pluralization
 
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
:attr:`pyramid.request.Request.localizer` attribute to obtain a
:term:`localizer`.  The localizer object will be configured to produce
translations implied by the active :term:`locale negotiator`, or a default
localizer object if no explicit locale negotiator is registered.
 
.. code-block:: python
   :linenos:
 
   def aview(request):
       localizer = request.localizer
 
.. note::
 
    If you need to create a localizer for a locale, use the
    :func:`pyramid.i18n.make_localizer` function.
 
.. 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.  Generating a translation in a view
component of an application might look like so:
 
.. code-block:: python
   :linenos:
 
   from pyramid.i18n import TranslationString
 
   ts = TranslationString('Add ${number}', mapping={'number':1},
                          domain='pyramid')
 
   def aview(request):
       localizer = request.localizer
       translated = localizer.translate(ts) # translation string
       # ... use translated ...
 
The ``request.localizer`` attribute will be a :class:`pyramid.i18n.Localizer`
object bound to the locale name represented by the request.  The translation
returned from its :meth:`pyramid.i18n.Localizer.translate` method will depend
on the ``domain`` attribute of the provided translation string as well as the
locale of the localizer.
 
.. note::
 
   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 simplest case is the ``singular`` and ``plural`` arguments being passed as
Unicode literals. This returns the appropriate literal according to the locale
pluralization rules for the number ``n``, and interpolates ``mapping``.
 
.. code-block:: python
   :linenos:
 
   def aview(request):
       localizer = request.localizer
       translated = localizer.pluralize('Item', 'Items', 1, 'mydomain')
       # ... use translated ...
 
However, for support of other languages, the ``singular`` argument should be a
Unicode value representing a :term:`message identifier`.  In this case the
``plural`` value is ignored. ``domain`` should be a :term:`translation domain`,
and ``mapping`` should be a dictionary that is used for *replacement value*
interpolation of the translated string.
 
The value of ``n`` will be used to find the appropriate plural form for the
current language, and ``pluralize`` will return a Unicode translation for the
message id ``singular``. The message file must have defined ``singular`` as a
translation with plural forms.
 
The argument provided as ``singular`` may be a :term:`translation string`
object, but the domain and mapping information attached is ignored.
 
.. code-block:: python
   :linenos:
 
   def aview(request):
       localizer = request.localizer
       num = 1
       translated = localizer.pluralize('item_plural', '${number} items',
           num, 'mydomain', mapping={'number':num})
 
The corresponding message catalog must have language plural definitions and
plural alternatives set.
 
.. code-block:: text
    :linenos:
    
    "Plural-Forms: nplurals=3; plural=n==0 ? 0 : n==1 ? 1 : 2;"
    
    msgid "item_plural"
    msgid_plural ""
    msgstr[0] "No items"
    msgstr[1] "${number} item"
    msgstr[2] "${number} items"
 
More information on complex plurals can be found in the `gettext documentation
<https://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/Plural-forms.html>`_.
 
.. index::
   single: 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:`pyramid.request.Request.locale_name` attribute of the request.
 
.. code-block:: python
   :linenos:
 
   def aview(request):
       locale_name = request.locale_name
 
The locale name of a request is dynamically computed; it will be the locale
name negotiated by the currently active :term:`locale negotiator`, or the
:term:`default locale name` if the locale negotiator returns ``None``. You can
change the default locale name by changing the ``pyramid.default_locale_name``
setting. See :ref:`default_locale_name_setting`.
 
Once :func:`~pyramid.request.Request.locale_name` is first run, the locale name
is stored on the request object.  Subsequent calls to
:func:`~pyramid.request.Request.locale_name` will return the stored locale name
without invoking the :term:`locale negotiator`.  To avoid this caching, you can
use the :func:`pyramid.i18n.negotiate_locale_name` function:
 
.. code-block:: python
   :linenos:
 
   from pyramid.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`.
 
.. code-block:: python
   :linenos:
 
   def aview(request):
       localizer = request.localizer
       locale_name = localizer.locale_name
 
Obtaining the locale name as an attribute of a localizer is equivalent to
obtaining a locale name by asking for the
:func:`~pyramid.request.Request.locale_name` attribute.
 
.. index::
   single: date and currency formatting (i18n)
   single: Babel
 
Performing Date Formatting and Currency Formatting
--------------------------------------------------
 
:app:`Pyramid` 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.pocoo.org/en/latest/api/core.html#basic-interface>`_ 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 :app:`Pyramid` 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
 
   def aview(request):
       locale_name = request.locale_name
       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 the last example above 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 :app:`Pyramid` translations. See
http://chameleon.readthedocs.org/en/latest/reference.html#translation-i18n.
 
.. note::
 
   Unlike when Chameleon is used outside of :app:`Pyramid`, when it is used
   *within* :app:`Pyramid`, it does not support use of the ``zope.i18n``
   translation framework.  Applications which use :app:`Pyramid` should use the
   features documented in this chapter rather than ``zope.i18n``.
 
Third party :app:`Pyramid` 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: Mako i18n
 
Mako Pyramid i18n Support
-------------------------
 
There exists a recipe within the :term:`Pyramid Community Cookbook` named
:ref:`Mako Internationalization <cookbook:mako_i18n>` which explains how to add
idiomatic i18n support to :term:`Mako` templates.
 
 
.. index::
   single: Jinja2 i18n
 
Jinja2 Pyramid i18n Support
---------------------------
 
The add-on `pyramid_jinja2 <https://github.com/Pylons/pyramid_jinja2>`_
provides a scaffold with an example of how to use internationalization with
Jinja2 in Pyramid. See the documentation sections `Internalization (i18n)
<https://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/#internalization-i18n>`_
and `Paster Template I18N
<https://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/#paster-template-i18n>`_.
 
 
.. index::
   single: localization deployment settings
   single:  default_locale_name
 
.. _localization_deployment_settings:
 
Localization-Related Deployment Settings
----------------------------------------
 
A :app:`Pyramid` application will have a ``pyramid.default_locale_name``
setting.  This value represents the :term:`default locale name` used when the
:term:`locale negotiator` returns ``None``.  Pass it to the
:mod:`~pyramid.config.Configurator` constructor at startup time:
 
.. code-block:: python
   :linenos:
 
   from pyramid.config import Configurator
   config = Configurator(settings={'pyramid.default_locale_name':'de'})
 
You may alternately supply a ``pyramid.default_locale_name`` via an
application's ``.ini`` file:
 
.. code-block:: ini
   :linenos:
 
   [app:main]
   use = egg:MyProject
   pyramid.reload_templates = true
   pyramid.debug_authorization = false
   pyramid.debug_notfound = false
   pyramid.default_locale_name = de
 
If this value is not supplied via the Configurator constructor or via a config
file, it will default to ``en``.
 
If this setting is supplied within the :app:`Pyramid` application ``.ini``
file, it will be available as a settings key:
 
.. code-block:: python
   :linenos:
 
   from pyramid.threadlocal import get_current_registry
   settings = get_current_registry().settings
   default_locale_name = settings['pyramid.default_locale_name']
 
.. index::
   single: detecting languages
 
"Detecting" Available Languages
-------------------------------
 
Other systems provide an API that returns the set of "available languages" as
indicated by the union of all languages in all translation directories on disk
at the time of the call to the API.
 
It is by design that :app:`Pyramid` doesn't supply such an API. Instead the
application itself is responsible for knowing the "available languages".  The
rationale is this: any particular application deployment must always know which
languages it should be translatable to anyway, regardless of which translation
files are on disk.
 
Here's why: it's not a given that because translations exist in a particular
language within the registered set of translation directories that this
particular deployment wants to allow translation to that language.  For
example, some translations may exist but they may be incomplete or incorrect.
Or there may be translations to a language but not for all translation domains.
 
Any nontrivial application deployment will always need to be able to
selectively choose to allow only some languages even if that set of languages
is smaller than all those detected within registered translation directories.
The easiest way to allow for this is to make the application entirely
responsible for knowing which languages are allowed to be translated to instead
of relying on the framework to divine this information from translation
directory file info.
 
You can set up a system to allow a deployer to select available languages based
on convention by using the :mod:`pyramid.settings` mechanism.
 
Allow a deployer to modify your application's ``.ini`` file:
 
.. code-block:: ini
   :linenos:
 
   [app:main]
   use = egg:MyProject
   # ...
   available_languages = fr de en ru
 
Then as a part of the code of a custom :term:`locale negotiator`:
 
.. code-block:: python
   :linenos:
 
   from pyramid.settings import aslist
 
   def my_locale_negotiator(request):
       languages = aslist(request.registry.settings['available_languages'])
       # ...
 
This is only a suggestion.  You can create your own "available languages"
configuration scheme as necessary.
 
.. index::
   pair: translation; activating
   pair: locale; negotiator
   single: translation directory
 
.. index::
   pair: activating; translation
 
.. _activating_translation:
 
Activating Translation
----------------------
 
By default, a :app:`Pyramid` application performs no translation. To turn
translation on you must:
 
- add at least one :term:`translation directory` to your application.
 
- ensure that your application sets the :term:`locale name` correctly.
 
.. index::
   pair: translation directory; adding
 
.. _adding_a_translation_directory:
 
Adding a Translation Directory
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
:term:`gettext` is the underlying machinery behind the :app:`Pyramid`
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.
 
Adding a :term:`translation directory` registers all of its constituent
:term:`message catalog` files within your :app:`Pyramid` application to be
available to use for translation services.  This includes all of the ``.mo``
files found within all ``LC_MESSAGES`` directories within each locale directory
in the translation directory.
 
You can add a translation directory imperatively by using the
:meth:`pyramid.config.Configurator.add_translation_dirs` during application
startup.  For example:
 
.. code-block:: python
   :linenos:
 
   from pyramid.config import Configurator
   config.add_translation_dirs('my.application:locale/',
                               'another.application:locale/')
 
A message catalog in a translation directory added via
:meth:`~pyramid.config.Configurator.add_translation_dirs` will be merged into
translations from a message catalog added earlier if both translation
directories contain translations for the same locale and :term:`translation
domain`.
 
.. index::
   pair: setting; locale
 
Setting the Locale
~~~~~~~~~~~~~~~~~~
 
When the *default locale negotiator* (see :ref:`default_locale_negotiator`) is
in use, you can inform :app:`Pyramid` of the current locale name by doing any
of these things before any translations need to be performed:
 
- Set the ``_LOCALE_`` attribute of the request to a valid locale name (usually
  directly within view code), e.g., ``request._LOCALE_ = 'de'``.
 
- Ensure that a valid locale name value is in the ``request.params`` dictionary
  under the key named ``_LOCALE_``.  This is usually the result of passing a
  ``_LOCALE_`` value in the query string or in the body of a form post
  associated with a request.  For example, visiting
  ``http://my.application?_LOCALE_=de``.
 
- Ensure that a valid locale name value is in the ``request.cookies``
  dictionary under the key named ``_LOCALE_``.  This is usually the result of
  setting a ``_LOCALE_`` cookie in a prior response, e.g.,
  ``response.set_cookie('_LOCALE_', 'de')``.
 
.. note::
 
   If this locale negotiation scheme is inappropriate for a particular
   application, you can configure a custom :term:`locale negotiator` function
   into that application as required.  See :ref:`custom_locale_negotiator`.
 
.. index::
   single: locale negotiator
 
.. _locale_negotiators:
 
Locale Negotiators
------------------
 
A :term:`locale negotiator` informs the operation of a :term:`localizer` by
telling it what :term:`locale name` is related to a particular request.  A
locale negotiator is a bit of code which accepts a request and which returns a
:term:`locale name`.  It is consulted when
:meth:`pyramid.i18n.Localizer.translate` or
:meth:`pyramid.i18n.Localizer.pluralize` is invoked.  It is also consulted when
:func:`~pyramid.request.Request.locale_name` is accessed or when
:func:`~pyramid.i18n.negotiate_locale_name` is invoked.
 
.. _default_locale_negotiator:
 
The Default Locale Negotiator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
Most applications can make use of the default locale negotiator, which requires
no additional coding or configuration.
 
The default locale negotiator implementation named
:class:`~pyramid.i18n.default_locale_negotiator` uses the following set of
steps to determine the locale name.
 
- First the negotiator looks for the ``_LOCALE_`` attribute of the request
  object (possibly set directly by view code or by a listener for an
  :term:`event`).
 
- Then it looks for the ``request.params['_LOCALE_']`` value.
 
- Then it looks for the ``request.cookies['_LOCALE_']`` value.
 
- If no locale can be found via the request, it falls back to using the
  :term:`default locale name` (see :ref:`localization_deployment_settings`).
 
- Finally if the default locale name is not explicitly set, it uses the locale
  name ``en``.
 
.. _custom_locale_negotiator:
 
Using a Custom Locale Negotiator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
Locale negotiation is sometimes policy-laden and complex.  If the (simple)
default locale negotiation scheme described in :ref:`activating_translation` is
inappropriate for your application, you may create a special :term:`locale
negotiator`.  Subsequently you may override the default locale negotiator by
adding your newly created locale negotiator to your application's
configuration.
 
A locale negotiator is simply a callable which accepts a request and returns a
single :term:`locale name` or ``None`` if no locale can be determined.
 
Here's an implementation of a simple locale negotiator:
 
.. code-block:: python
    :linenos:
 
    def my_locale_negotiator(request):
        locale_name = request.params.get('my_locale')
        return locale_name
 
If a locale negotiator returns ``None``, it signifies to :app:`Pyramid` that
the default application locale name should be used.
 
You may add your newly created locale negotiator to your application's
configuration by passing an object which can act as the negotiator (or a
:term:`dotted Python name` referring to the object) as the
``locale_negotiator`` argument of the :class:`~pyramid.config.Configurator`
instance during application startup.  For example:
 
.. code-block:: python
   :linenos:
 
   from pyramid.config import Configurator
   config = Configurator(locale_negotiator=my_locale_negotiator)
 
Alternatively, use the
:meth:`pyramid.config.Configurator.set_locale_negotiator` method.
 
For example:
 
.. code-block:: python
   :linenos:
 
   from pyramid.config import Configurator
   config = Configurator()
   config.set_locale_negotiator(my_locale_negotiator)