Le multilingue

Principes

D’une façon générale, les langues se gèrent en 2 phases :

  • l’internationalisation (i18n) qui vise à rendre possible les langues multiples
  • la localisation (l10n) qui vise à intégrer une langue spécifique

Dans Osuny, le système a 2 facettes :

  • l’admin en Ruby on Rails, qui permet d’écrire les contenus
  • le site en Hugo, qui permet de lire les contenus

L’aspect front du multilingue est documenté dans la partie thème.

Dans l’admin d’Osuny il y a deux grands types d’objets :

  • les objets directs (qui ont une dépendance directe à un site web), comme les pages, les actualités…
  • les objets indirects (ceux qui sont “neutres”, non connectés à un site), comme une personne, une organisation…

Choix des langues disponibles pour une université

Actuellement une université hérite de la totalité des langues disponibles sur Osuny (mdeol Language). En réalité il faudrait permettre de choisir les langues disponibles pour une université, au niveau du setup des university, dans la partie Server.
Par la suite tous les objets indirects pourront être traduits dans toutes les langues disponibles pour l’université (donc actuellement toutes les langues disponibles pour Osuny globalement). Tous les objects indirects créés utilisent la langue par défaut de l’université comme langue “master”.

Choix des langues disponibles pour un site Web

A la création d’un site web on choisit une liste de langues disponibles pour le site Web en fonction des langues disponibles pour l’université (donc actuellement toutes les langues disponibles pour Osuny globalement). Et on choisit la langue par défaut du site parmi les langues sélectionnées (on force le choix d’au moins une langue pour pouvoir avoir cette langue par défaut). Tous les objets directs créés utilisent la langue par défaut du site web comme langue “master”.

Rendre un objet direct traduisible

Ajouter les propriétés au modèle

Créer une migration pour ajouter ces propriétés à un objet :

  • language_id (référence vers sa langue)
  • original_id (référence vers son “master”, l’objet lié créé dans la langue par défaut)
def change
  add_reference :**object**, :language, foreign_key: true, type: :uuid
  add_reference :**object**, :original, foreign_key: {to_table: :**object**}, type: :uuid
end

Ajouter les méthodes nécessaires au modèle

Inclure dans le modèle le concern WithTranslations qui va s’occuper d’établir les relations (belongs_to :language, …). Il ajoute également des scopes sur l’objet : for_language et for_language_id. Il y a aussi tout un tas de méthodes, notamment des fonctions pour créer les translations.

Ajouter un swith de langue dans la vue de l’objet

On est au niveau d’un objet direct, et donc scopé dans un website. On bénéficie du menu global du site web à gauche, qui inclut un switch de langue. En gros ce switch de langue affiche toutes les langues disponibles pour le site web et créé des liens vers l’url courantes en injectant un paramètre lang=iso_code. La langue en cours est déterminée grâce au helper current_website_language.

Ajouter le concern au niveau du controller

Il faut inclure le concern include Admin::Translatable au niveau du controller de l’objet visé. A chaque fois qu’on a besoin de charger un objet (show, edit, update, destroy), ce concern va automatiquement vérifier si on est bien sur la bonne traduction de l’objet, ou la créé à la volée si elle n’existe pas encore.
Il ne faut pas oublier, lorsqu’on charge une liste d’objets ou un objet, de scoper à la langue en cours (Page.for_language(current_website_language)).

Rendre un objet indirect traduisible

Ajouter les propriétés au modèle

Créer une migration pour ajouter ces propriétés à un objet :

  • language_id (référence vers sa langue)
  • original_id (référence vers son “master”, l’objet lié créé dans la langue par défaut)
def change
  add_reference :**object**, :language, foreign_key: true, type: :uuid
  add_reference :**object**, :original, foreign_key: {to_table: :**object**}, type: :uuid
end

Ajouter les méthodes nécessaires au modèle

Inclure dans le modèle le concern WithTranslations qui va s’occuper d’établir les relations (belongs_to :language, …). Il ajoute également des scopes sur l’objet : for_language et for_language_id. Il y a aussi tout un tas de méthodes, notamment des fonctions pour créer les translations.

Ajouter la route

Il faut ajouter une route à l’objet :

resources **object** do
    member do
      get "/translations/:lang" => "**object**#in_language", as: :show_in_language
    end
  end

(le nommage de la route en show_in_language est important).

Ajouter un switch de langue dans la vue de l’objet

Dans chaque vue (a priori dans le show) d’objet multilingue on a besoin d’avoir un switch de langue. On va donc forcer le render d’un partial commun :

render 'admin/application/i18n/widget', about: **object**

Ca va ajouter un choix de langue qui renvoie sur la route show_in_language de l’objet.
La langue en cours est déterminée en comparant avec la langue de l’objet affichée.

Gestion du controller

Il faut créer dans le controller une méthode correspondant à la route créée en step 3.

def in_language
    language = Language.find_by!(iso_code: params[:lang])
    translation = @**object**.find_or_translate!(language)
    if translation.newly_translated
      # There's an attribute accessor named "newly_translated" that we set to true
      # when we just created the translation. We use it to redirect to the form instead of the show.
      redirect_to [:edit, :admin, translation.becomes(translation.class.base_class)]
    else
      redirect_to [:admin, translation.becomes(translation.class.base_class)]
    end
  end

L’export statique

En cas de site monolingue les url des fichiers statiques ne doivent PAS être préfixées de la langue.
En cas de multilingue toutes les urls sont préfixées.
On créé les contenus dans content/:lang/. On crée les menus dans data/menus/:lang/.

A noter

Dans l’admin d’Osuny, l’affichage des noms de langues est géré par la gem i18n_data. On a créé une méthode helper (dans app/helpers/application_helper.rb) nommée language_name qui va chercher le nom de la langue en fonction du code ISO. On utilise ce helper à chaque fois qu’on a besoin d’afficher un nom de langue.