Blog Jaune

RubyOnRails et Gettext


Créer une application ruby on rails en plusieurs langues avec gettext

    Gettext est une bibliothèque qui permet de réaliser une application en plusieurs langues, facilement, et en facilitant la traduction.

    Dans le jargon RubyOnRails, l'internationalisation (ou l'internationalization en anglais), est le fait de créer une application en plusieurs langues, gettext est justement recommandé pour cela.
    Gettext a pour avantages, le fait qu'il ne soit pas limité à ruby on rails, qu'il soit très utilisé, qu'il existe des interfaces graphiques pour la traduction, qu'il ne nécessite pas de base de données, qu'il soit léger, sous licence gnu, etc…

    Pour utiliser gettext avec ruby, il existe Ruby-GetText-Package. Il est complet, et propose un bon support pour aller avec ruby on rails.

Pour l'installation, on peut utiliser les gems:
gem install gettext
Il faut ensuite charger gettext dans votre application ruby on rails.
Pour cela, il faut ajouter dans le fichier config/environment.rb les lignes suivantes:

$KCODE = 'u'
require 'jcode'
require 'gettext/rails'
LANG = 'fr'

    La constante LANG est importante, c'est elle qui définie qu'elle sera la langue par défaut de votre application.
Il est important de noter que l'application doit maintenant être en utf-8, en lisant la documentation, vous pouvez adapter les réglages pour d'autres encodages, mais l'unicode est recommandé pour ce genre d'utilisation.

    Il faut maintenant rajouter les méthodes rake pour la gestion des langues, ceci n'étant pas automatique. Pour cela, il faut créer un nouveau fichier lib/tasks/gettext.rake, contenant:

desc "Update pot/po files."
task :updatepo do
  require 'gettext/utils'
  GetText.update_pofiles("nom_de_votre_application", Dir.glob( "{app,lib,bin}/**/*.{rb,rhtml,rxml}"), "nom_de_votre_application")
end

desc "Create mo-files"
task :makemo do
  require 'gettext/utils'
  GetText.create_mofiles(true, "po", "locale")
end

N'hésitez pas à remplacer le nom de votre application
Je ne détaillerais pas, mais en lisant la documentation, on peut facilement modifier ce fichier pour l'adapter à ses besoins.

Il faut maintenant initialiser gettext.
À placer sans le fichier app/controllers/application.rb:
init_gettext "nom_de_votre_application"

Évidemment, il s'agit du même nom que celui définie plus haut.

    L'application va contenir deux nouveaux dossiers.
Un dossier po qui contiendra les traductions en langage humain (des fichiers en .po).
Un dossier locale qui contiendra les traductions en langage machine (des fichiers en .mo).
Dans chacun de ces dossiers, il y aura un dossier par traduction (fr, en, fr_FR, en_US, etc…).
    Le dossier po contient aussi à sa racine un fichier qui sert de base pour commencer de nouvelles traductions, il ne sert jamais à l'application (il a pour extension .pot).

Pour créer l'arborescence des fichiers en langage humain, il faut donc utiliser la commande:

rake updatepo

    Un dossier équivaut à une traduction (fr, en, de, it, es, ja…).
Ces dossiers contiennent chacun une copie du fichier en .pot renommé en .po
Vous pouvez maintenant commencer à intégrer les traductions dans votre application.

    À chaque fois qu'il y a du texte à utiliser en plusieurs langues, il faut lui donner un texte d'identification (aussi appelé clé), et utiliser la fonction _()
<h1>Connexion </h1> #devient:
<h1><%= _('titre_connexion') %></h1>
Une fois les textes remplacés, la commande rake updatepo permet d'insérer ces références automatiquement des les fichiers du dossier po.

On trouvera par exemple:
#: app/views/user/login.rhtml:17
msgid "titre_connexion"
msgstr ""

Il suffit maintenant de mettre le texte traduit pour chaque langue, ce qui donne pour la version japonaise (contenu dans le dossier po/ja/):
#: app/views/user/login.rhtml:17
msgid "titre_connexion"
msgstr " 関係"

    Lorsque vous affichez la page, <h1>titre_connexion</h1>, est affiché. Pour que la traduction soit prise en compte, il faut générer les fichiers en langage machine pour que le texte traduit soit affiché.
rake makemo

Pour la version japonaise, ça donne:
<h1>関係</h1>

    À chaque nouveau texte à traduire, après l'avoir identifié, il faudra mettre à jour les fichiers .po (rake updatepo).
À chaque modification d'un fichier .po, pour que les modifications soient affichées, il faudra régénérer les fichiers .mo (rake makemo).

Tout ça,est très pratique, mais il manque le principal: le choix de la langue.
before_init_gettext :set_languages
init_gettext "alternc"

def set_languages
        #Règlage la langue
        if !params[:lang].nil?
                 #Si il y a un paramètre pour changer la langue, vérification de l'existence de la langue
                 if File.exist?(RAILS_ROOT+ '/po/'+params[ :lang]) || File.exist?(RAILS_ROOT+ '/po/'+params[ :lang]+'_'+params[:lang].upcase)
                        session[ :lang] = params[:lang]
                 else
                        session[ :lang] = nil
                 end
        end
        begin
                 #Si la langue choisie n'existe pas, ou si c'est la première visite
                 if session[:lang].nil?
                         break
                 end
                 #Si non, on règle simplement la langue
                set_locale session[:lang]
        rescue
                 #Si le navigateur envoi des informations sur la langue
                 if !@request.env['HTTP_ACCEPT_LANGUAGE'].nil?
                         #recherche de la langue du client
                        langs = @request.env['HTTP_ACCEPT_LANGUAGE']. gsub(/;q=[0-1]\.[0-9]/, '').split( ',')
                        langs.each do |i|
                                 #si elle n'existe pas, les langues secondaires sont étudiés
                                 if File.exist?(RAILS_ROOT+ '/po/'+i) || File.exist?(RAILS_ROOT+ '/po/'+i+ '_'+i.upcase)
                                        session[ :lang] = i
                                         break
                                 end
                         end
                 end
                 #Si il n'en existe toujours pas, la langue est celle utilisée par default, dans la configuration de l'application
                 if session[:lang].nil?
                        session[ :lang] = LANG
                 end
                 #Réglage final de la langue
                set_locale session[:lang]
        end
end

    Cette méthode, à placer dans app/controllers/application.rb détecte la langue du navigateur, et celles subsidiaires. Si il y en a aucune de traduite, c'est celle définie par la constante LANG qui est choisie. Cette méthode permet aussi de sauvegarder la langue d'une page à l'autre. Pour changer la langue manuellement, il faut donner un paramètre get lang. Par exemple ?lang=en

    Parfois, on a aussi besoin d'inclure des variables dans un texte à traduire, comme le nom du serveur.
#: app/controllers/user_controller.rb:3
msgid "user_login_page_title"
msgstr "Se connecter au serveur: %{SERVER_NAME}"

@page_title = _('user_login_page_title')% {:SERVER_NAME =>"yellowimac"}

Ce qui donne:
"Se connecter au serveur: yellowimac"

    Ceci grâce à la méthode % qui a pour paramètre un hashage. C'est une extension de la class String, c'est à dire que vous pouvez aussi utiliser cette méthode sur un texte qu'y n'a rien à voir avec gettext.
"La page a pour titre %{titre}, elle sur le serveur %{serveur}"% {:titre => "Accueil", :serveur => "yellowimac"}
    Voilà, maintenant vous êtes près à créer des applications parfaitement multilingues, rapidement et facilement.

Pour aller plus loin:

Capture TextMate

Commentaires

Le 05/07/2007 à 19:51

 

Intéressant, ce blog.
J'hésitais à le mettre au Ruby... Mais une question me dérange encore, certes probablement idiote, mais le Ruby n'est pas adapté qu'au web, comme PHP ou ASP ? On peut faire des applications indépendantes avec ? (une réponse par mail serait appréciable, étant donné que je n'ai pas le temps de revenir visiter en ce moment..) Merci !

Le 08/07/2007 à 00:11

Le pauvre 

Le pauvre petit, il n'a pas le temps ...

Tu veux que j'écrives la réponse à cette question existentielle sur un papyrus et que je le dépose chez toi ?

Le 05/07/2008 à 21:06

Maintenant, ça plante 

À cette date, la gestion n'est plus exactement la même.

Pour une raison inconnue, il faut rajouter require 'gettext/rails' en haut du fichier application.rb (ne pas chercher à comprendre).

Mais aussi un petit bout de code si le besoin se fait sentir: (dans environnement.rb par exemple, c'est pas beau, mais ça ne devrait pas rester longtemps).

module ActionView
  class Base
    delegate :file_exists?, :to => :finder unless respond_to?(:file_exists?)
  end
end

Ajouter un commentaire