Rencontres Django FR

L'internationalisation et la traduction d'une application écrite en Django.
25 avril 2010

Appuyez sur pour avancer.

Django* speaks Marseillais

internationaliser et traduire son site

* pas le musicien mais le poney

À propos de cette présentation

    Stéphane Raimbault

Licence Apache, version 2.0 sauf les photos en références.

Plan pour vendre de l'anis au monde entier
Django + ? + ? = Profit
Plan pour vendre de l'anis au monde entier
Django + i18n + l10n = Profit
Que signifie i18n ?

Étape technique qui consiste à internationaliser un logiciel afin qu'il puisse être traduit dans différentes langues et cultures

Note : 18 caractères entre le « i » et le « n »

Que signifie l10n ?

La régionalisation de logiciel concerne le processus de traduction de l'interface utilisateur d'un logiciel d'une langue vers une autre et en l'adaptant à la culture locale

Note : 10 caractères entre le « l » et le « n »

À quoi ressemble un fichier PO

GNU gettext est une collection d'outils permettant de construire des catalogues de messages, de les fusionner, de les mettre à jour et d'afficher leur traduction au moment de l'exécution

#: lib/gai_strerror.c:75
msgid "Unknown error"
msgstr "Erreur inconnue"

#: lib/getopt.c:529 lib/getopt.c:545
#, c-format
msgid "%s: option '%s' is ambiguous\n"
msgstr "%s : l'option « %s » est ambiguë\n"
            
La chaîne de référence est usuellement la chaîne anglaise.
Plan
  • i18n
    • L'activation du mécanisme
    • Les formes plurielles autour du monde
    • Les solutions côté Javascript
    • L'absence de contexte en Django
    • La traduction simplifiée avec Django 1.2

  • l10n
    • Manipuler une traduction
    • Coordonner les contributions ?
    • Étude de cas sur un chouette projet
    • Tour d'horizon des plateformes de traduction
i18n
i18n

Activation du mécanisme

  • Les booléens USE_I18N et USE_L10N
    • respectivement à vrai et faux dans la configuration globale mais actifs tous les deux dans le projet !
    • utilisés par le système de cache
    • import des fonctions de traductions réelles
    • parcours des répertoires de régionalisation
    • parcours des modules de formatage
i18n

Activation du mécanisme

  • Le « context processor » django.core.context_processors.i18n doit être présent dans TEMPLATE_CONTEXT_PROCESSORS
    • LANGUAGES (liste complète par défaut)
    • LANGUAGE_CODE
    • LANGUAGE_BIDI
i18n

Activation du mécanisme

  • Le « middleware » django.middleware.locale.LocaleMiddleware doit être présent dans MIDDLEWARE_CLASSES
    • Recherche dans l'ordre suivant :
    • la clé django_language dans la session
    • le cookie défini par LANGUAGE_COOKIE_NAME
    • la variable HTTP_ACCEPT_LANGUAGE dans la requête
i18n

Résumé

  • Les booléens USE_I18N et USE_L10N
  • Le « context processor » django.core.context_processors.i18n doit être présent dans TEMPLATE_CONTEXT_PROCESSORS
  • Le « middleware » django.middleware.locale.LocaleMiddleware doit être présent dans MIDDLEWARE_CLASSES
i18n

Marquage des chaînes - Modèle

  • La fonction ugettext_lazy permet de différer la traduction de la chaîne en argument
    from django.utils.translation import ugettext_lazy
    
    class Estranger(models.Model):
        name = models.CharField(
            help_text=ugettext_lazy('Nom de l'estranger du dehors'))
    
  • La traduction sera effectuée seulement quand la chaîne sera utilisée, la locale n'étant pas connue à l'avance.
i18n

Marquage des chaînes - Modèle

  • Dans la pratique, il est commode d'utiliser un alias plus court
    from django.utils.translation import ugettext_lazy as _
    
    class Estranger(models.Model):
        name = models.CharField(help_text=_('Nom de l'estranger du dehors'))
    
  • Attention à l'utilisation de la traduction dans une autre chaîne
    return u"%s" % ugettext_lazy("Ce gâteau est estouffe belle-mère")
    
i18n

Marquage des chaînes - Vue

  • La fonction ugettext est utilisée car la langue est connue
    from django.utils.translation import ugettext as _
    
    def my_view(request):
        output = _("Adieu estranger.")
        return HttpResponse(output)
    
  • La chaîne sera par la suite automatiquement ajoutée au catalogue.
i18n

Marquage des chaînes - Vue

  • Dans le cas d'utilisation d'une variable, la chaîne n'est pas automatiquement ajoutée au catalogue
    from django.utils.translation import ugettext as _
    
    def my_view(request):
        sentence = u"Se manger 4 panisses à l'Estaque c'est marseillais 45°."
        output = _(sentence)
        return HttpResponse(output)
    
  • Il faut donc l'ajouter manuellement (sauf si présente ailleurs) avec ugettext_noop
i18n

Marquage des chaînes - Vue

  • Supposez que nous marquions une chaîne de la sorte :
  • def my_view(request, m, d):
        output = _('Today is %s, %d') % (m, d)
        return HttpResponse(output)
    
  • La chaîne dans le fichier PO serait :
    #: hello/views.py:75
    msgid "Today is %s, %d"
    
    Le traducteur ne dispose d'aucune information sur les arguments et ne peut pas changer l'ordre, la phrase « Today is November, 26 » doit être traduite par « Hoy es 26 de Noviembre » en espagnol. Heureusement, gettext vous avertira du problème.
i18n

Marquage des chaînes - Vue

  • La version correcte :
  • def my_view(request, m, d):
        output = _('Today is %(month)s, %(day)s.') % {'month': m, 'day': d}
        return HttpResponse(output)
    
  • La chaîne dans le fichier PO sera :
    #: hello/views.py:75
    msgid "Today is %(month)s, %(day)s"
    
Si plusieurs arguments, pensez à les nommer.
i18n

Marquage des chaînes - Le pluriel

    La règle d'écriture des formes plurielles dépend de la langue

  • En français, un singulier, par ex. « un cheval » et un pluriel « des chevals chevaux »
    Le fichier PO contient alors la règle :
    Plural-Forms: nplurals=2; plural=(n > 1);
  • En polonais, il existe une forme de singulier pour 1, une forme plurielle pour les nombres terminant par 2, 3 ou 4 sauf pour 12 à 14, et une autre pour tous les autres.
    Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 &&
    (n%100<10 ""|| n%100>=20) ? 1 : 2 msgid "Really delete this note?" msgid_plural "Really delete these %1% notes?" msgstr[0] "Ne pewno usunąć tę notatkę?" msgstr[1] "Ne pewno usunąć %1% notatki?" msgstr[2] "Ne pewno usunąć %1% notatek?"
i18n

Marquage des chaînes - Vue

  • Vous ne pouvez pas faire de test if nb_bouteille == 1
  • La fonction django.utils.translation.ungettext() arrive à la rescousse
    from django.utils.translation import ungettext
    def hello_world(request, count):
        page = ungettext(
            'there is %(count)d bottle of pastaga',
            'there are %(count)d bottles of pastaga', count) % { 'count': count }
        return HttpResponse(page)
    
i18n

Marquage des chaînes - Template

  • L'accès aux tags nécessite un appel à {% load i18n %}
  • Le tag « trans » permet de traduire de simples chaînes
    {% trans "Home" %} {% trans name %}
  • Le tag « blocktrans » permet de traduire des chaînes composées
    {% blocktrans with language.team.get_description as team_name %}
    You need to be authenticated and to be member of the {{ team_name }} team.
    {% endblocktrans %}

    Mon exemple est incorrect, attention aux retours à la ligne !

    #, python-format
    msgid "You need to be authenticated and to be member of the %(team_name)s team."
                    
i18n

Marquage des chaînes - Template

  • Il aussi possible d'utiliser les formes plurielles avec blocktrans
    {% blocktrans count bottle_list|length as counter %}
    There is {{ counter}} bottle.
    {% plural %}
    There are {{ counter }} bottles.
    {% endblocktrans %}
i18n

Les solutions du côté Javascript

Problèmes :

  • Pas d'accès direct aux fichiers PO/MO
  • Pas d'implémentation de gettext
i18n

Les solutions du côté Javascript

Solutions :

  • Django offre une vue dédiée nommée « javascript_catalog »
  • Exemple :
    js_info_dict = {
        'domain': 'djangojs',
        'packages': ('hello',),
    }
    urlpatterns = patterns('',
        (r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
    )
    

    La documentation de Django ne précise pas quel est le domaine par défaut (djangojs.po)

  • Par exemple, la vue de l'interface d'administration
i18n

L'absence de contexte en Django

  • Imaginons que nous ayons à traduire la chaîne « Edit »
  • Comment la traduiriez-vous en français ?
i18n

L'absence de contexte en Django

  • Cela dépend du contexte !
  • Est-ce un titre (« Modification »), le titre d'un menu (« Édition ») ou une action (« Modifier ») ?
  • Si la chaîne est présente plusieurs fois, elle n'est présente qu'une seule fois dans le fichier PO
    #: ../data/GNOME_GnoteApplet.xml.h:2 ../src/actionmanager.cpp:208
    #: ../src/tray.cpp:468
    msgid "_Help"
    msgstr "Aid_e"
i18n

L'absence de contexte en Django

Permet d'indiquer que les chaînes identiques doivent être fusionnées selon leur contexte

  • gettext insère un champ supplémentaire dans le fichier PO (msgctxt)
  • Limite dans l'implémentation en Python : http://bugs.python.org/issue2504
  • Impossible en Django
i18n

Nouveautés de Django 1.2

  • Merci à Marc Garcia (SOC 2009)
  • Un nouveau fichier fait son apparition formats.py
  • Saisie et affichage en fonction de la langue
  • Le premier jour de la semaine est connu (utilisé dans le calendrier)
  • Régionalisation du regroupement et des séparateurs sur les nombres (il faut activer USE_THOUSAND_SEPARATOR)
  • Mise en forme simplifiée des dates
  • Exemples : localhost
l10n
l10n

Manipuler une traduction

  • Créez un répertoire locale
  • ./manage.py makemessages -l fr
    ./manage.py compilemessages
  • gettext permet de vérifier que le fichier est valide
    msgfmt --statistics -c -v -o django.mo django.po
  • Un bon éditeur, disons Emacs :), ou à defaut :
l10n

L'inclusion des traductions

Votre application est formidable, les traductions affluent dans votre messagerie mais :

  • Vous n'êtes pas en mesure de juger la qualité, qui parle le Marseillais ?
  • Certaines sont sous forme de diff (peu adapté aux fichiers PO)
  • Plusieurs traductions pour la même langue, que faire ?
  • À quelle version de votre application s'appliquent les traductions ?

Bref, vous êtes dans la mouscaille !

l10n

Étude de cas

Par le passé, l'état de la traduction française de GNOME :

  • Un seul responsable pas toujours disponible
  • Cohérence des traductions
  • Uniquement des envois par courriel
  • Pas de processus de relecture au sein d'une équipe

Mange ton poing et garde l'autre pour demain, à moins que...

l10n

Étude de cas

Des solutions se profilent à l'horizon

  • Damned-Lies, par Danilo Šegan en Python, compile les fichiers PO et offre des statistiques.
  • Vertimus, par Vincent Untz et moi, PHP puis PHP 4.0 puis Zend Framework, utilise les statistiques de Damned-Lies, permet de réserver une traduction et offre un processus de relecture. Franc succès au sein de l'équipe française !
  • Launchpad et Rosetta, par Canonical (non-libre au début), écrit en Zope, interface très lente, travaille en « downstream », pas d'informations contextuelles
  • Transifex, Dimitri Glezos, en Turbogears, permet de committer depuis l'interface Web

Beaucoup d'effervescence !

l10n

Étude de cas

La maturité pour le projet GNOME arrive début 2009

  • Claude Paroz et moi, ré-écrivons Damned-Lies en Django et j'intègre les fonctionnalités de Vertimus
  • Damned-Lies est utilisé pour la traduction de GNOME dans la majorité des 133 langues du projet
l10n

Tour d'horizon des solutions

Quelle solution actuelle pour vos besoins ?

  • Damned-Lies (en Django), vous devrez déployer une instance et contient des spécificités du projet GNOME
  • Launchpad a fait quelques progrès...
  • Transifex (maintenant en Django), a beaucoup évolué depuis la création de Indifex, il est très générique, il est possible d'inscrire un projet en quelques minutes
  • Pootle (lui aussi réécrit en Django !), reste moins actif.

Bref, je vous recommande aujourd'hui d'utiliser Transifex pour vos projets.

l10n

Tour d'horizon des solutions

Quelles solutions pour le futur ?

  • Intégration de Vertimus au sein de Transifex et de la création d'une extension pour le projet GNOME ?
  • Danilo Šegan (Canonical), devrait annoncer de nouvelles solutions pour rapprocher GNOME et Ubuntu
  • Et pour la traduction de Django ?
l10n + i18n

Remarques finales

  • Ce n'est pas le vélo qui fait le cycliste ! La qualité de l'équipe de traduction est primordiale.
  • À mon avis, les traductions seraient de meilleure qualité si les équipes n'étaient pas centrées autour d'un projet mais autour de leur langue (par ex., un traduc.org actif)
  • Avertissez vos traducteurs au moins 10 jours avant la sortie d'une nouvelle version.
  • Chaque changement dans une chaîne originale a une incidence sur les traductions (avec le format PO).
  • Il ne suffit pas d'être français, pour être en mesure de le traduire ! Consultez les guides et documents de référence, faites vous relire.