"gettext" — Services d'internationalisation multilingue
*******************************************************

**Code source :** Lib/gettext.py

======================================================================

Le module "gettext" fournit un service d'internationalisation (*I18N*)
et de localisation linguistique (*L10N*) pour vos modules et
applications Python. Il est compatible avec l'API du catalogue de
messages GNU **gettext** et à un plus haut niveau, avec l'API basée
sur les classes qui serait peut-être plus adaptée aux fichiers Python.
L'interface décrite ci-dessous vous permet d'écrire les textes de vos
modules et applications dans une langue naturelle, puis de fournir un
catalogue de traductions pour les lancer ensuite dans d'autres langues
naturelles.

Quelques astuces sur la localisation de vos modules et applications
Python sont également données.


API GNU **gettext**
===================

Le module "gettext" définit l'API suivante, qui est très proche de
l'API de GNU **gettext**.  Si vous utilisez cette API, cela affectera
la traduction de toute votre application.  C'est souvent le
comportement attendu si votre application est monolingue, avec le
choix de la langue qui dépend des paramètres linguistiques de
l'utilisateur.  Si vous localisez un module Python ou si votre
application a besoin de changer de langue à la volée, il est plus
judicieux d'utiliser l'API basée sur des classes.

gettext.bindtextdomain(domain, localedir=None)

   Lie *domain* au répertoire *localedir* des localisations. Plus
   spécifiquement, "gettext" va chercher les fichiers binaires ".mo"
   pour un domaine donné, en utilisant le chemin suivant (sous Unix) :
   "*localedir*/*language*/LC_MESSAGES/*domain*.mo", où *language* est
   recherché dans l'une des variables d'environnement suivantes :
   "LANGUAGE", "LC_ALL", "LC_MESSAGES" et "LANG".

   Si *localedir* n'est pas renseigné ou vaut "None", alors le lien
   actuel de *domain* est renvoyé. [1]

gettext.textdomain(domain=None)

   Change ou interroge le domaine global actuel.  Si *domain* vaut
   "None", alors le domaine global actuel est renvoyé.  Sinon, le
   domaine global est positionné à *domain*, puis renvoyé.

gettext.gettext(message)

   Return the localized translation of *message*, based on the current
   global domain, language, and locale directory.  This function is
   usually aliased as "_()" in the local namespace (see examples
   below).

gettext.dgettext(domain, message)

   Comme "gettext()", mais cherche le message dans le domaine
   spécifié.

gettext.ngettext(singular, plural, n)

   Comme "gettext()", mais prend en compte les formes au pluriel.  Si
   une traduction a été trouvée, utilise la formule pour trouver le
   pluriel à *n* et renvoie le message généré (quelques langues ont
   plus de deux formes au pluriel).  Si aucune traduction n'a été
   trouvée, renvoie *singular* si *n* vaut 1, *plural* sinon.

   La formule pour trouver le pluriel est récupérée dans l'entête du
   catalogue.  C'est une expression en C ou en Python qui a une
   variable libre *n* et qui évalue l'index du pluriel dans le
   catalogue.  Voir la documentation de GNU gettext pour la syntaxe
   précise à utiliser dans les fichiers ".po" et pour les formules
   dans différents langues.

gettext.dngettext(domain, singular, plural, n)

   Comme "ngettext()", mais cherche le message dans le domaine
   spécifié.

gettext.pgettext(context, message)

gettext.dpgettext(domain, context, message)

gettext.npgettext(context, singular, plural, n)

gettext.dnpgettext(domain, context, singular, plural, n)

   Semblable aux fonctions correspondantes sans le "p" dans le préfixe
   (c'est-à-dire "gettext()", "dgettext()", "ngettext()" et
   "dngettext()"), mais la traduction est limitée au *context* du
   message donné.

   Nouveau dans la version 3.8.

Note that GNU **gettext** also defines a "dcgettext()" method, but
this was deemed not useful and so it is currently unimplemented.

Voici un exemple classique d'utilisation de cette API :

   import gettext
   gettext.bindtextdomain('myapplication', '/path/to/my/language/directory')
   gettext.textdomain('myapplication')
   _ = gettext.gettext
   # ...
   print(_('This is a translatable string.'))


API basée sur les classes
=========================

The class-based API of the "gettext" module gives you more flexibility
and greater convenience than the GNU **gettext** API.  It is the
recommended way of localizing your Python applications and modules.
"gettext" defines a "GNUTranslations" class which implements the
parsing of GNU ".mo" format files, and has methods for returning
strings. Instances of this class can also install themselves in the
built-in namespace as the function "_()".

gettext.find(domain, localedir=None, languages=None, all=False)

   Cette fonction implémente l'algorithme standard de recherche de
   fichier "mo". Il prend en entrée un *domain*, tout comme la
   fonction "textdomain()". Le paramètre optionnel *localedir* est le
   même que celui de "bindtextdomain()". Le paramètre optionnel
   *langages* est une liste de chaînes de caractères correspondants au
   code d'une langue.

   Si *localedir* n'est pas renseigné, alors le répertoire de la
   locale par défaut du système est utilisé. [2]  Si *languages* n'est
   pas renseigné, alors les variables d'environnement suivantes sont
   utilisées : "LANGUAGE", "LC_ALL", "LC_MESSAGES" et "LANG".  La
   première à renvoyer une valeur non vide est alors utilisée pour
   *languages*.  Ces variables d'environnement doivent contenir une
   liste de langues, séparées par des deux-points, qui sera utilisée
   pour générer la liste des codes de langues attendue.

   Recherche avec "find()", découvre et normalise les langues, puis
   itère sur la liste obtenue afin de trouver un fichier de traduction
   existant et correspondant :

   "*localedir*/*language*/LC_MESSAGES/*domain*.mo"

   Le premier nom de fichier trouvé est renvoyé par "find()".  Si
   aucun fichier n'a été trouvé, alors "None" est renvoyé.  Si *all*
   est vrai, est renvoyée la liste de tous les noms de fichiers, dans
   l'ordre dans lequel ils apparaissent dans *languages* ou dans les
   variables d'environnement.

gettext.translation(domain, localedir=None, languages=None, class_=None, fallback=False)

   Return a "*Translations" instance based on the *domain*,
   *localedir*, and *languages*, which are first passed to "find()" to
   get a list of the associated ".mo" file paths.  Instances with
   identical ".mo" file names are cached.  The actual class
   instantiated is *class_* if provided, otherwise "GNUTranslations".
   The class's constructor must take a single *file object* argument.

   Si plusieurs fichiers ont été trouvés, les derniers sont utilisés
   comme substitut des premiers.  Pour rendre possible cette
   substitution, "copy.copy()" est utilisé pour copier chaque objet
   traduit depuis le cache ; les vraies données de l'instance étant
   toujours recopiées dans le cache.

   Si aucun fichier ".mo" n'a été trouvé, soit *fallback* vaut *False*
   (valeur par défaut) et une exception "OSError" est levée, soit
   *fallback* vaut *True* et une instance "NullTranslations" est
   renvoyée.

   Modifié dans la version 3.3: "IOError" used to be raised, it is now
   an alias of "OSError".

   Modifié dans la version 3.11: *codeset* parameter is removed.

gettext.install(domain, localedir=None, *, names=None)

   This installs the function "_()" in Python's builtins namespace,
   based on *domain* and *localedir* which are passed to the function
   "translation()".

   Concernant le paramètre *names*, se référer à la description de la
   méthode "install()".

   As seen below, you usually mark the strings in your application
   that are candidates for translation, by wrapping them in a call to
   the "_()" function, like this:

      print(_('This string will be translated.'))

   For convenience, you want the "_()" function to be installed in
   Python's builtins namespace, so it is easily accessible in all
   modules of your application.

   Modifié dans la version 3.11: *names* est désormais un paramètre à
   mot-clé uniquement.


La classe "NullTranslations"
----------------------------

Les classes de traduction implémentent le fait de passer d'une chaîne
de caractères du fichier original à traduire à la traduction de celle-
ci.  La classe de base utilisée est "NullTranslations".  C'est
l'interface de base à utiliser lorsque vous souhaitez écrire vos
propres classes spécifiques à la traduction.  Voici les méthodes de
"NullTranslations" :

class gettext.NullTranslations(fp=None)

   Prend un paramètre optionnel un *file object* *fp*, qui est ignoré
   par la classe de base.  Initialise les variables d'instance
   "protégées" *_info* et *_chardet*, définies par des classes
   dérivées, tout comme *_fallback* qui est définie au travers de
   "add_fallback()".  Puis appelle "self._parse(fp)" si *fp* ne vaut
   pas "None".

   _parse(fp)

      Cette méthode, non exécutée dans la classe de base, prend en
      paramètre un objet fichier *fp* et lit les données de ce
      dernier.  Si vous avez un catalogue de messages dont le format
      n'est pas pris en charge, vous devriez surcharger cette méthode
      pour analyser votre format.

   add_fallback(fallback)

      Ajoute *fallback* comme objet de substitution pour l'objet de
      traduction courant.  Un objet de traduction devrait interroger
      cet objet de substitution s'il ne peut fournir une traduction
      pour un message donné.

   gettext(message)

      Si un objet de substitution a été défini, transmet "gettext()" à
      celui-ci.  Sinon, renvoie *message*.  Surchargé dans les classes
      dérivées.

   ngettext(singular, plural, n)

      Si un objet de substitution a été défini, transmet "ngettext()"
      à celui-ci.  Sinon, renvoie *singular* si *n* vaut 1, *plural*
      sinon.  Surchargé dans les classes dérivées.

   pgettext(context, message)

      Si un objet de substitution a été défini, transmet "pgettext()"
      à celui-ci. Sinon, renvoie le message traduit. Surchargé dans
      les classes dérivées.

      Nouveau dans la version 3.8.

   npgettext(context, singular, plural, n)

      Si un objet de substitution a été défini, transmet "npgettext()"
      à celui-ci. Sinon, renvoie le message traduit. Surchargé dans
      les classes dérivées.

      Nouveau dans la version 3.8.

   info()

      Return a dictionary containing the metadata found in the message
      catalog file.

   charset()

      Renvoie l'encodage du fichier du catalogue de messages.

   install(names=None)

      Cette méthode positionne "gettext()" dans l'espace de nommage
      natif, en le liant à "_".

      If the *names* parameter is given, it must be a sequence
      containing the names of functions you want to install in the
      builtins namespace in addition to "_()".  Supported names are
      "'gettext'", "'ngettext'", "'pgettext'", and "'npgettext'".

      Note that this is only one way, albeit the most convenient way,
      to make the "_()" function available to your application.
      Because it affects the entire application globally, and
      specifically the built-in namespace, localized modules should
      never install "_()". Instead, they should use this code to make
      "_()" available to their module:

         import gettext
         t = gettext.translation('mymodule', ...)
         _ = t.gettext

      This puts "_()" only in the module's global namespace and so
      only affects calls within this module.

      Modifié dans la version 3.8: Ajout de "'pgettext'" et
      "'npgettext'".


La classe "GNUTranslations"
---------------------------

The "gettext" module provides one additional class derived from
"NullTranslations": "GNUTranslations".  This class overrides
"_parse()" to enable reading GNU **gettext** format ".mo" files in
both big-endian and little-endian format.

"GNUTranslations" parses optional metadata out of the translation
catalog. It is convention with GNU **gettext** to include metadata as
the translation for the empty string. This metadata is in **RFC
822**-style "key: value" pairs, and should contain the "Project-Id-
Version" key.  If the key "Content-Type" is found, then the "charset"
property is used to initialize the "protected" "_charset" instance
variable, defaulting to "None" if not found.  If the charset encoding
is specified, then all message ids and message strings read from the
catalog are converted to Unicode using this encoding, else ASCII is
assumed.

Since message ids are read as Unicode strings too, all "*gettext()"
methods will assume message ids as Unicode strings, not byte strings.

The entire set of key/value pairs are placed into a dictionary and set
as the "protected" "_info" instance variable.

Si le nombre magique du fichier ".mo" est invalide, le numéro de la
version majeure inattendu, ou si d'autres problèmes apparaissent
durant la lecture du fichier, instancier une classe "GNUTranslations"
peut lever une exception "OSError".

class gettext.GNUTranslations

   Les méthodes suivantes, provenant de l'implémentation de la classe
   de base, ont été surchargée :

   gettext(message)

      Recherche l'identifiant de *message* dans le catalogue et
      renvoie le message de la chaîne de caractères correspondante
      comme une chaîne Unicode.  Si aucun identifiant n'a été trouvé
      pour *message* et qu'un substitut a été défini, la recherche est
      transmise à la méthode "gettext()" du substitut.  Sinon,
      l'identifiant de *message* est renvoyé.

   ngettext(singular, plural, n)

      Effectue une recherche sur les formes plurielles de
      l'identifiant d'un message.  *singular* est utilisé pour la
      recherche de l'identifiant dans le catalogue, alors que *n*
      permet de savoir quelle forme plurielle utiliser.  La chaîne de
      caractère du message renvoyée est une chaîne Unicode.

      Si l'identifiant du message n'est pas trouvé dans le catalogue
      et qu'un substitut a été spécifié, la requête est transmise à la
      méthode "ngettext()" du substitut.  Sinon, est renvoyé
      *singular* lorsque *n* vaut 1, *plural* dans tous les autres
      cas.

      Voici un exemple :

         n = len(os.listdir('.'))
         cat = GNUTranslations(somefile)
         message = cat.ngettext(
             'There is %(num)d file in this directory',
             'There are %(num)d files in this directory',
             n) % {'num': n}

   pgettext(context, message)

      Recherche le *contexte* et l'identifiant de *message* dans le
      catalogue et renvoie le message de la chaîne de caractères
      correspondante comme une chaîne Unicode. Si aucun identifiant
      n'a été trouvé pour l'identifiant du *message* et du *context*
      et qu'un substitut a été défini, la recherche est transmise à la
      méthode "pgettext()" du substitut. Sinon, l'identifiant de
      *message* est renvoyé.

      Nouveau dans la version 3.8.

   npgettext(context, singular, plural, n)

      Effectue une recherche sur les formes plurielles de
      l'identifiant d'un message. *singular* est utilisé pour la
      recherche de l'identifiant dans le catalogue, alors que *n*
      permet de savoir quelle forme plurielle utiliser.

      Si l'identifiant du message pour le *context* n'est pas trouvé
      dans le catalogue et qu'un substitut a été spécifié, la requête
      est transmise à la méthode "npgettext()" du substitut. Sinon,
      est renvoyé *singular* lorsque *n* vaut 1, *plural* dans tous
      les autres cas.

      Nouveau dans la version 3.8.


Support du catalogue de message de Solaris
------------------------------------------

Le système d'exploitation Solaris possède son propre format de fichier
binaire ".mo", mais pour l'heure, puisqu'on ne peut trouver de
documentation sur ce format, il n'est pas géré.


Le constructeur *Catalog*
-------------------------

GNOME utilise une version du module "gettext" de James Henstridge,
mais qui a une API légèrement différente.  D'après la documentation,
elle s'utilise ainsi :

   import gettext
   cat = gettext.Catalog(domain, localedir)
   _ = cat.gettext
   print(_('hello world'))

For compatibility with this older module, the function "Catalog()" is
an alias for the "translation()" function described above.

Une différence entre ce module et celui de Henstridge : les objets de
son catalogue étaient accessibles depuis un schéma de l'API, mais cela
semblait ne pas être utilisé et donc n'est pas pris en charge.


Internationaliser vos programmes et modules
===========================================

L'internationalisation (*I18N*) consiste à permettre à un programme de
recevoir des traductions dans plusieurs langues.  La localisation
(*L10N*) consiste à adapter un programme à la langue et aux habitudes
culturelles locales, une fois celui-ci internationalisé.  Afin de
fournir du texte multilingue à votre programme Python, les étapes
suivantes sont nécessaires :

1. préparer votre programme ou module en marquant spécifiquement les
   chaînes à traduire

2. lancer une suite d'outils sur les fichiers contenant des chaînes à
   traduire pour générer des catalogues de messages brut

3. créer les traductions spécifiques à une langue des catalogues de
   messages

4. utiliser le module "gettext" pour que les chaînes de caractères
   soient bien traduites

In order to prepare your code for I18N, you need to look at all the
strings in your files.  Any string that needs to be translated should
be marked by wrapping it in "_('...')" --- that is, a call to the
function "_".  For example:

   filename = 'mylog.txt'
   message = _('writing a log message')
   with open(filename, 'w') as fp:
       fp.write(message)

Dans cet exemple, la chaîne "'writing a log message'" est maquée comme
traduite, contrairement aux chaînes "'mylog.txt'" et "'w'".

There are a few tools to extract the strings meant for translation.
The original GNU **gettext** only supported C or C++ source code but
its extended version **xgettext** scans code written in a number of
languages, including Python, to find strings marked as translatable.
Babel is a Python internationalization library that includes a
"pybabel" script to extract and compile message catalogs.  François
Pinard's program called **xpot** does a similar job and is available
as part of his po-utils package.

(Python inclut également des versions en Python de ces programmes,
**pygettext.py** et **msgfmt.py**, que certaines distributions Python
installeront pour vous.  **pygettext.py** est similaire à
**xgettext**, mais ne comprend que le code source écrit en Python et
ne peut prendre en charge d'autres langages de programmation tels que
le C ou C++.  **pygettext.py** possède une interface en ligne de
commande similaire à celle de **xgettext** --- pour plus de détails
sur son utilisation, exécuter "pygettext.py --help".  **msgfmt.py**
est compatible avec GNU **msgfmt**.  Avec ces deux programmes, vous ne
devriez pas avoir besoin du paquet GNU **gettext** pour
internationaliser vos applications en Python.)

**xgettext**, **pygettext** et d'autres outils similaires génèrent des
fichiers ".po" représentant les catalogues de messages.  Il s'agit de
fichiers structurés et lisibles par un être humain, qui contiennent
toutes les chaînes du code source marquées comme traduisible, ainsi
que leur traduction à utiliser.

Les copies de ces fichiers ".po" sont ensuite remises à des êtres
humains qui traduisent le contenu pour chaque langue naturelle prise
en charge.  Pour chacune des langues, ces derniers renvoient la
version complétée sous la forme d'un fichier "<code-langue>.po" qui a
été compilé dans un fichier binaire ".mo" représentant le catalogue
lisible par une machine à l'aide du programme **msgfmt**.  Les
fichiers ".mo" sont utilisés par le module "gettext" pour la
traduction lors de l'exécution.

La façon dont vous utilisez le module "gettext" dans votre code dépend
de si vous internationalisez un seul module ou l'ensemble de votre
application.  Les deux sections suivantes traitent chacune des cas.


Localiser votre module
----------------------

Si vous localisez votre module, veillez à ne pas faire de changements
globaux, e.g. dans l'espace de nommage natif. Vous ne devriez pas
utiliser l’API GNU **gettext** mais plutôt celle basée sur les
classes.

Disons que votre module s'appelle "spam" et que les fichiers ".mo" de
traduction dans les différentes langues naturelles soient dans
"/usr/share/locale" au format GNU **gettext**.  Voici ce que vous
pouvez alors mettre en haut de votre module :

   import gettext
   t = gettext.translation('spam', '/usr/share/locale')
   _ = t.gettext


Localiser votre application
---------------------------

If you are localizing your application, you can install the "_()"
function globally into the built-in namespace, usually in the main
driver file of your application.  This will let all your application-
specific files just use "_('...')" without having to explicitly
install it in each file.

Dans ce cas, vous n'aurez à ajouter que le bout de code suivant au
fichier principal de votre application :

   import gettext
   gettext.install('myapplication')

Si vous avez besoin de définir le dossier des localisations, vous
pouvez le mettre en argument de la fonction "install()" :

   import gettext
   gettext.install('myapplication', '/usr/share/locale')


Changer de langue à la volée
----------------------------

Si votre programme a besoin de prendre en charge plusieurs langues en
même temps, vous pouvez créer plusieurs instances de traduction, puis
basculer entre elles de façon explicite, comme ceci :

   import gettext

   lang1 = gettext.translation('myapplication', languages=['en'])
   lang2 = gettext.translation('myapplication', languages=['fr'])
   lang3 = gettext.translation('myapplication', languages=['de'])

   # start by using language1
   lang1.install()

   # ... time goes by, user selects language 2
   lang2.install()

   # ... more time goes by, user selects language 3
   lang3.install()


Traductions différées
---------------------

Dans la plupart des cas, en programmation, les chaînes de caractères
sont traduites à l'endroit où on les écrit.  Cependant, il peut
arriver que vous ayez besoin de traduire une chaîne de caractères un
peu plus loin.  Un exemple classique est :

   animals = ['mollusk',
              'albatross',
              'rat',
              'penguin',
              'python', ]
   # ...
   for a in animals:
       print(a)

Ici, vous voulez marquer les chaînes de caractères de la liste
"animals" comme étant traduisibles, mais ne les traduire qu'au moment
de les afficher.

Voici un moyen de gérer ce cas :

   def _(message): return message

   animals = [_('mollusk'),
              _('albatross'),
              _('rat'),
              _('penguin'),
              _('python'), ]

   del _

   # ...
   for a in animals:
       print(_(a))

This works because the dummy definition of "_()" simply returns the
string unchanged.  And this dummy definition will temporarily override
any definition of "_()" in the built-in namespace (until the "del"
command). Take care, though if you have a previous definition of "_()"
in the local namespace.

Note that the second use of "_()" will not identify "a" as being
translatable to the **gettext** program, because the parameter is not
a string literal.

Voici une autre solution :

   def N_(message): return message

   animals = [N_('mollusk'),
              N_('albatross'),
              N_('rat'),
              N_('penguin'),
              N_('python'), ]

   # ...
   for a in animals:
       print(_(a))

In this case, you are marking translatable strings with the function
"N_()", which won't conflict with any definition of "_()". However,
you will need to teach your message extraction program to look for
translatable strings marked with "N_()". **xgettext**, **pygettext**,
"pybabel extract", and **xpot** all support this through the use of
the "-k" command-line switch. The choice of "N_()" here is totally
arbitrary; it could have just as easily been
"MarkThisStringForTranslation()".


Remerciements
=============

Les personnes suivantes ont contribué au code, ont fait des retours,
ont participé aux suggestions de conception et aux implémentations
précédentes, et ont partagé leur expérience précieuse pour la création
de ce module :

* Peter Funk

* James Henstridge

* Juan David Ibáñez Palomar

* Marc-André Lemburg

* Martin von Löwis

* François Pinard

* Barry Warsaw

* Gustavo Niemeyer

-[ Notes ]-

[1] The default locale directory is system dependent; for example, on
    Red Hat Linux it is "/usr/share/locale", but on Solaris it is
    "/usr/lib/locale". The "gettext" module does not try to support
    these system dependent defaults; instead its default is
    "*sys.base_prefix*/share/locale" (see "sys.base_prefix"). For this
    reason, it is always best to call "bindtextdomain()" with an
    explicit absolute path at the start of your application.

[2] Voir la note de "bindtextdomain()" ci-dessus.
