Oct/090
Internationalisation des vues
Moteur de transformation
L’application est bien évidemment internationalisée et donc disponible en plusieurs langues.
Le principe est le suivant :
- On ne travaille que sur un seul modèle de vue
- Tous les textes sont traduits dans un fichier de propriétés
- Les vues sont uniquement statiques, pas de traduction à la volée
Le premier point est d’utiliser un mécanisme permettant de traduire les clés des fichiers modèles en traductions. Il est possible de définir son propre mécanisme de traduction assez simplement… cependant, ici, il semble plus intéressant de se reposer sur l’API
fournie par la dernière version de Rails, qui est disponible en tant que gem
et utilisable de façon autonome.
Pour cela, on installe le composant :
sudo gem install i18n
On ajoute alors la dépendance dans le fichier Rakefile.rb
, et on configure le système :
require "i18n" I18n.load_path << Dir[ File.join(ROOT, 'support', 'locales', '**', '*.yml') ]
Les fichiers de traduction seront donc stockés dans le répertoire support/locales
(ou tout sous-répertoire) et seront nommés par la langue, par exemple en.yml
ou fr.yml
. Par défaut, le code pays EN
est utilisé.
Exemple de stockage :
- support/ |- locales/ | |- en.yml | |- fr.yml
Exemple de fichier de traduction en.yml
:
en: signin: Sign in
On peut alors traduire le fichier de cette façon :
%body #header.fixed #ids %a#signin{ 'data-event' => "login:start", :href => "/login.html" }=I18n.t 'signin'
Pour plus de facilités, on redéfini la méthode de traduction.
Pour cela, on crée une classe d’aide qui sera ensuite utilisée par le moteur HAML
:
class ViewHelper def t(key) I18n.t key end end .. dans la tâche de transformation puts "Creating view #{target}" #final.puts Haml::Engine.new(File.read(src), :format => :html5).render final.puts Haml::Engine.new(File.read(src), :format => :html5).render(ViewHelper.new)
Le moteur de rendu prend donc en paramètre une instance de notre classe et rend donc visible les méthodes de la classe aux fichiers modèles. Le code des modèles est un peu simplifié :
%body #header.fixed #ids %a#signin{ 'data-event' => "login:start", :href => "/login.html" }=t 'signin'
Vu la fréquence d’utilisation, c’est indispensable… et on suit le même mécanisme que Rails ce faisant.
Gestion des valeurs par défaut
Si une clé n’existe pas, on se retrouve avec le message suivant :
translation missing: en, Sign_in
Si on lance la traduction pour une autre langue, on obtient le même message.
On va surcharger notre méthode de traduction afin de palier le comportement par défaut du module i18n. On veut que dans tous les cas, une traduction s’affiche. Pour cela, on procède ainsi :
- si la clé est traduite, on renvoi la traduction
- sinon on essaie de traduire dans la langue par défaut, en donnant une valeur par défaut au cas où la clé n’est pas traduite.
- la valeur par défaut est la clé (moyennant quelques transformations)
Ainsi, on peut écrire le code suivant :
%body #header.fixed #ids %a#signin{ 'data-event' => "login:start", :href => "/login.html" }=t 'Sign_in'
Si aucun fichier de traduction n’est présent, et que l’on essaie de traduire en français, voilà ce qu’il se passe :
- la clé française n’est pas trouvée
- on essaie en anglais
- la clé n’est pas trouvée
- on affiche la valeur par défaut
- la valeur par défaut est le nom de la clé dont les ‘_’ sont remplacé par des ’ ‘.
Donc on obtient au final la bonne valeur par défaut Sign in
sans avoir rien écrit dans un fichier de traduction. Ce qui amène à quelques saisies superflues en moins
Globalement : traduction locale > traduction par défaut > valeur de la clé
Build
On modifie le script de build car maintenant chaque fichier modèle donne lieu à plusieurs fichiers HTML :
LANGUAGES = %w(en fr) def render(src, target) puts "Creating view #{target}" final = File.new(target, "w+"); final.puts Haml::Engine.new(File.read(src), :format => :html5).render(ViewHelper.new) final.close end haml = FileList["#{ROOT}/priv/templates/**/*.haml"].map do |src| LANGUAGES.each do |locale| src.gsub("/templates/views/", "/www/").gsub(".haml", "_#{locale}.html").tap do |target| file locale => src do I18n.locale = locale render src, target exec "create default page", "ln -s #{target} #{target.gsub('_en', '')}" if locale == 'en' end end end end
Chaque fichier de langue sera recréé séparément si nécessaire. Enfin, on ajoute un lien permettant de simuler facilement la langue par défaut (codé de façon un peu naze mais ce n’est pas grave sur cette phase).