Répartition

Principes généraux

Ce qui est traduisible va dans la localisation, ce qui ne l’est pas reste dans l’objet.

Images à la une

ℹ️
Tous les attributs liés aux images à la une sont transférés dans la localisation.
Propriétédans l’objet ?dans la localisation ?
featured_image
featured_image_alt
featured_image_credit

Le bénéfice de garder des éléments comme l’image et le crédit dans l’objet est de permettre la mise à jour en une fois. Le bénéfice de tout localiser est de permettre des choix différents par langue. Techniquement, il y a un avantage de simplicité à tout grouper. Comme on ne peut grouper que sur la localisation, on fait ce choix.

Published

ℹ️
Tous les attributs de publication sont transférés dans la localisation.
Propriétédans l’objet ?dans la localisation ?
published
published_at
pinned

Pour permettre de publier une localisation et pas une autre, published doit être lié à la localisation et pas à l’objet. La date de publication doit donc suivre. La propriété pinned, qui permet d’épingler un article, pourrait être d’un côté ou de l’autre, selon que l’on veut permettre une gestion éditoriale distincte dans chaque langue. Là encore, la simplicité technique l’emporte, nous laissons pinned avec published.

Objets directs

Page

Propriété localiséeExplication
breadcrumb_titleTitre traduit
featured_imageL’image peut être différente, notamment si elle contient des textes (une affiche par ex.)
featured_image_altDescription alternative traduite
featured_image_creditCrédit traduit
header_ctaUne langue peut avoir un CTA et l’autre pas
header_cta_labelTexte traduit
header_cta_urlURL traduite
header_textTexte traduit
meta_descriptionTexte traduit
migration_identifierIdentifiant utilisé par l’API, présent des 2 côtés
publishedPublication différenciée
published_atDate différenciée
shared_imageL’image de partage contient souvent des textes
slugLe slug dépend du name
summaryTexte traduit
textTexte traduit
titleTexte traduit
Propriété non localiséeExplication
bodyclassGestion centralisée
full_widthGestion centralisée
positionGestion centralisée
migration_identifierIdentifiant utilisé par l’API, présent des 2 côtés
parent_idL’arbre des pages est indépendant des localisations
typePropriété utilisée pour la STI

Pages spéciales

Une grande question concerne les pages spéciales. Ce sont les pages comme l’accueil, ou l’index des actualités, qui ont un fonctionnement et/ou un rôle particulier dans le site. Ces objets sont implémentés en utilisant la Single-Table Inheritance (STI) native de Ruby on Rails. Conceptuellement, rien ne change, ce sont bien les pages qui ont des types spéciaux.

Toutefois cela impacte l’implémentation :

  • les dépendances partagées avec les localisations
  • le git_path
  • une petite partie du fichier statique

Parent spécial manquant

Un cas particulier se produit quand un objet, qu’il s’agisse d’une actualité ou d’une personne, est localisé dans une langue alors que sa page spéciale ne l’est pas.

2 possibilités :

  1. soit on considère qu’on ne peut pas afficher l’objet du tout, ce qui ne paraît pas très pertinent
  2. soit on considère qu’il faut afficher l’objet, avec un chemin pas trop faux Concrètement, ça peut donner /en/equipe/pierre-andre-boissinot. Quand la page spéciale Équipe sera traduite, l’url deviendra /en/team/pierre-andre-boissinot, et le système de permalinks gèrera la redirection.

Blog

Actualité

Communication::Website::Post

Propriété localiséeExplication
featured_imageL’image d’illustration peut être une image avec des textes, comme une affiche par exemple
featured_image_altIl faut donc traduire les textes en question 
featured_image_credit Le crédit peut être traduit (“Photo par…”, “Photo by…”)
 meta_descriptionLa description de l’actualité est dans la langue
migration_identifierIl faut des identifiants de migration pour tout
pinned La gestion éditoriale peut être différente dans chaque langue
publishedIl faut pouvoir choisir quand une version est prête.
published_atPas forcément de cas d’usage au published_at par language, mais l’algo s’appuie sur une combinaison de published et published_at donc ça paraît une très mauvaise idée de les séparer
slugLe slug dépend du title
summaryLe résumé est traduit
textLe texte est traduit
titleLe titre est traduit
Propriété non localiséeExplication
migration_identifierUn identifiant dans le Post, un autre dans la Localization

Catégorie

Communication::Website::Post::Category

Propriété localiséeExplication
featured_imageL’image d’illustration peut être une image avec des textes, comme une affiche par exemple
featured_image_altIl faut donc traduire les textes en question 
featured_image_credit Le crédit peut être traduit (“Photo par…”, “Photo by…”)
 meta_descriptionLa description de la catégorie est dans la langue
nameLe nom est traduit
pathLe chemin est lié au slug
slugLe slug dépend du name
summaryLe résumé est traduit
Propriété non localiséeExplication
is_programs_rootLié à l’arbre de catégories des formations
parent_idL’arbre de catégories est indépendant des localisations
positionLa position est lié à l’arbre de catégories
program_idLié à l’arbre de catégories des formations

Menu

Communication::Website::Menu::Item

Les menus sont différents dans chaque langue, il ne s’agit pas de localisation. On garde la logique de language_id, mais on supprime la notion d’original_id.

Comme les menus dans différentes langues partagent un identifiant (par exemple primary), on s’appuie sur cet identifiant pour passer d’une langue à l’autre dans un menu.

Les identifiants ne peuvent être définis qu’à la création du menu.

Élément

Communication::Website::Menu::Item

Les items n’ont pas besoin d’un language_id car ils existent dans un menu qui a déjà cette langue définie.

De même, les items de type blank et url ne sont pas impactés car ne sont liés à aucun objet.

Pour les autres types d’items de menu, ils ont un about qui a besoin d’évoluer pour être sur l’objet parent, et non pas une localisation. Il faut effectuer une migration pour bien être connecté à l’original des objets existants.

db/migrate/20240801152544_migrate_communication_website_menu_items_about.rb
class MigrateCommunicationWebsiteMenuItemsAbout < ActiveRecord::Migration[7.1]
  def up
    Communication::Website::Menu::Item.where.not(kind: [:blank, :url]).find_each do |menu_item|
      about = menu_item.about
      next if about.nil?
      # Kind like paper can't respond to original_id
      about_id = about.try(:original_id) || about.id
      menu_item.update_column :about_id, about_id
    end
  end

  def down
  end
end

Ensuite dans les formulaires, on rajoute le scope tmp_original en attendant la fin de la migration.

On centralise la récupération des collections dans le modèle :

app/models/communication/website/menu/item.rb
class Communication::Website::Menu::Item < ApplicationRecord
  def self.collection_for(kind, website)
    # TODO L10N : remove every tmp_orginal below
    case kind
    when 'page'
      website.pages.tmp_original
    when 'diploma'
      website.education_diplomas.tmp_original
    when 'program'
      website.education_programs.tmp_original
    when 'category'
      website.post_categories.tmp_original
    when 'post'
      website.posts.tmp_original
    when 'volume'
      website.research_volumes.tmp_original
    when 'paper'
      website.research_papers.tmp_original
    when 'location'
      website.administration_locations.tmp_original
    end
  end
end

Dans le fichier statique, on tente de récupérer la localisation de l’objet associé. Si elle n’existe pas, on ne renvoie pas de chemin, l’item de menu ne sera donc pas affiché.

Avant :

app/models/communication/website/menu/item.rb
class Communication::Website::Menu::Item < ApplicationRecord
  def static_target
    # ...
    case kind
    when "blank"
      ''
    when "url"
      url
    else
      about.new_permalink_in_website(website).computed_path
    end
  end
end

Après :

app/models/communication/website/menu/item.rb
class Communication::Website::Menu::Item < ApplicationRecord
  def static_target
    # ...
    case kind
    when "blank"
      ''
    when "url"
      url
    else
      about_l10n = about.localization_for(language)
      if about_l10n.present?
        about_l10n_permalink = about_l10n.new_permalink_in_website(website)
        about_l10n_permalink.computed_path
      else
        nil
      end
    end
  end
end

Agenda

Événement

Agenda::Event

Propriété localiséeExplication
add_to_calendar_urlsLa dénormalisation des URL d’ajout au calendrier dépendent du title
featured_imageL’image d’illustration peut être une image avec des textes, comme une affiche par exemple
featured_image_altIl faut donc traduire les textes en question 
featured_image_credit Le crédit peut être traduit (“Photo par…”, “Photo by…”)
 meta_descriptionLa description de l’actualité est dans la langue
migration_identifierIl faut des identifiants de migration pour tout
publishedIl faut pouvoir choisir quand une version est prête.
published_atPas forcément de cas d’usage au published_at par language, mais l’algo s’appuie sur une combinaison de published et published_at donc ça paraît une très mauvaise idée de les séparer
shared_imageL’image de partage peut être une image avec des textes, comme une affiche par exemple
slugLe slug dépend du title
subtitleLe sous-titre est traduit
summaryLe résumé est traduit
textLe texte est traduit
titleLe titre est traduit
Propriété non localiséeExplication
from_dayLes dates de l’évènement sont indépendantes des localisations
from_hourIdem from_day
migration_identifierUn identifiant dans le Event, un autre dans la Localization
parent_idL’arbre des évènements est indépendant des localisations
time_zoneIdem from_day
to_dayIdem from_day
to_hourIdem from_day

Catégorie

Agenda::Category

Propriété localiséeExplication
featured_imageL’image d’illustration peut être une image avec des textes, comme une affiche par exemple
featured_image_altIl faut donc traduire les textes en question 
featured_image_credit Le crédit peut être traduit (“Photo par…”, “Photo by…”)
 meta_descriptionLa description de la catégorie est dans la langue
nameLe nom est traduit
pathLe chemin est lié au slug
slugLe slug dépend du name
summaryLe résumé est traduit
Propriété non localiséeExplication
is_programs_rootLié à l’arbre de catégories des formations
parent_idL’arbre de catégories est indépendant des localisations
positionLa position est lié à l’arbre de catégories
program_idLié à l’arbre de catégories des formations

Portfolio

Projet

Portfolio::Project

Propriété localiséeExplication
featured_imageL’image d’illustration peut être une image avec des textes, comme une affiche par exemple
featured_image_altIl faut donc traduire les textes en question 
featured_image_credit Le crédit peut être traduit (“Photo par…”, “Photo by…”)
 meta_descriptionLa description de l’actualité est dans la langue
migration_identifierIl faut des identifiants de migration pour tout
publishedIl faut pouvoir choisir quand une version est prête.
published_atPas forcément de cas d’usage au published_at par language, mais l’algo s’appuie sur une combinaison de published et published_at donc ça paraît une très mauvaise idée de les séparer
shared_imageL’image de partage peut être une image avec des textes, comme une affiche par exemple
slugLe slug dépend du title
summaryLe résumé est traduit
titleLe titre est traduit
Propriété non localiséeExplication
yearL’année du projet est indépendant des localisations

Catégorie

Portfolio::Category

Propriété localiséeExplication
featured_imageL’image d’illustration peut être une image avec des textes, comme une affiche par exemple
featured_image_altIl faut donc traduire les textes en question 
featured_image_credit Le crédit peut être traduit (“Photo par…”, “Photo by…”)
 meta_descriptionLa description de la catégorie est dans la langue
nameLe nom est traduit
pathLe chemin est lié au slug
slugLe slug dépend du name
summaryLe résumé est traduit
Propriété non localiséeExplication
is_programs_rootLié à l’arbre de catégories des formations
is_taxonomyLe système de facettes est indépendant des localisations
parent_idL’arbre de catégories est indépendant des localisations
positionLa position est lié à l’arbre de catégories
program_idLié à l’arbre de catégories des formations

Objets indirects

Organisation

University::Organization

Propriété localiséeExplication
address_additionalCette propriété est très versatile, mais on peut imaginer traduire “Bureau 508”
address_name“Head office”, “Siège social”…
linkedinLes urls sont différentes en fonction des langues
long_name“Groupe d’experts intergouvernemental sur l’évolution du climat”, “Intergovernmental Panel on Climate Change”
mastodonLes comptes peuvent être différents pour chaque langue
meta_descriptionLa description de page est dans la langue
name“GIEC”, “IPCC”
summaryLe résumé est dans la langue
slugLe slug dépend du nom
textLa présentation de l’organisation est localisée
twitterLes comptes peuvent être différents pour chaque langue
urlLes urls peuvent être distinctes, qu’il s’agissent de domaines différents ou d’un /fr /en
logoLe logo peut être complètement différent (si le nom l’est) ou juste avoir des variantes locales
logo_on_dark_backgroundIdem logo
Propriété non localiséeExplication
activePas évident du tout : faut-il pouvoir désactiver l’orga entière ? Ou bien les locas ?
addressL’adresse est identique quelle que soit la langue
cityLa ville est identique quelle que soit la langue
countryLe pays est un code (FR, IT), donc neutre linguistiquement
emailPas évident du tout : pourquoi un mail unique ?
kindLe type (asso, organisation publique) ne dépend pas de la langue, même si la traduction peut varier :ASBL en Belgique, association loi 1901 en France, non-profit au UK…
latitudeLa latitude ne change pas en fonction de la langue
longitudeLa longitude ne change pas en fonction de la langue
nicCette propriété est probablement inutilisée, c’est un ajout au SIREN pour composer le SIRET
phonePas évident du tout : il pourrait y avoir un numéro par langue
sirenSiren et NIC sont trop français, il faudrait un nom de propriété international. Quoi qu’il en soit, ça ne change pas en fonction de la langue.
zipcodeLa traduction ne change rien au code postal

Catégorie

University::Organization::Category

Propriété localiséeExplication
nameLe nom est traduit
slugLe slug dépend du name
Propriété non localiséeExplication
parent_idL’arbre de catégories est indépendant des localisations
positionLa position est lié à l’arbre de catégories

Person

University::Organization

Propriété localiséeExplication
biographyLa biographie doit être traduite
first_nameLe prénom semble être non traduisible, mais… Sacha en russe, c’est Саша
last_nameIdem prénom
linkedinLes urls sont différentes par langue
mastodonIl peut y avoir un compte différent par langue
meta_descriptionLa description doit être traduite
nameDénormalisation de first name + last name
picture_creditCrédit traduit (par, by…)
slugdépend du name
summaryRésumé traduit
twitterIl peut y avoir un compte par langue
urlLes urls sont différentes par langue, soit /fr /en, soit des sous-domaines, soit des extensions .fr ou .co.uk
Propriété non localiséeExplication
addressL’adresse est identique quelle que soit la langue
address_visibility
birthdateLa date de naissance ne dépend pas de la langue
cityLa ville est identique quelle que soit la langue
countryLe pays est un code (FR, IT), donc neutre linguistiquement
emailPas évident du tout : pourquoi un mail unique ? C’est probablement le cas le plus commun
email_visibilityLes réglages de visibilité ne changent pas en fonction des langues
genderLe genre ne dépend pas des langues
habilitationLes états de la personne ne dépendent pas des langues
is_administrationLes états de la personne ne dépendent pas des langues
is_alumnusLes états de la personne ne dépendent pas des langues
is_authorLes états de la personne ne dépendent pas des langues
is_researcherLes états de la personne ne dépendent pas des langues
is_teacherLes états de la personne ne dépendent pas des langues
linkedin_visibilityLes réglages de visibilité ne changent pas en fonction des langues
mastodon_visibilityLes réglages de visibilité ne changent pas en fonction des langues
tenureLes états de la personne ne dépendent pas des langues
twitter_visibilityLes réglages de visibilité ne changent pas en fonction des langues
zipcodeLes états de la personne ne dépendent pas des langues

Les personnes ont 3 tables liées :

  • university_person_experiences
  • university_person_involvements
  • university_roles

Ces 3 tables n’avaient pas de gestion des langues avant l’énorme PR 2025. Il n’y a donc rien à migrer.

Facettes

Les personnes ont des facettes : Administrator, Author, Researcher, Teacher. Les facettes sont activées par des booléens, comme is_author (attention il y a des subtilités). Ces 4 facettes permettent de gérer les 5 pages différentes possibles pour une même personne dans Hugo. Les facettes sont devenues des classes qui héritent de la localisation et plus de la personne elle-même, parce qu’elles ont un slug qui dépend du nom (traduit). Dans les dépendances des pages spéciales, comme la page qui liste les administrateurs, il faut lister les administrateurs. Attention, prendre toutes les personnes qui ont un rôle d’administration amènerait à une situation fausse : une personne pourrait être administratrice dans un site d’une formation, alors qu’elle n’a pas de rôle administratif dans la formation, mais dans l’école. Il faut donc se limiter au contexte du site.

app/models/communication/website/page/administrator.rb
def dependencies_administrators
  University::Person::Localization::Administrator.where(
    about_id: website.administrators.pluck(:id),
    language_id: website.active_language_ids
  )
end

On récupère les facettes de localisations Administrator des personnes qui ont un rôle administratif pour ce site. La limite aux langues actives évite d’envoyer des langues non utilisées dans le site.

Catégorie

University::Organization::Category

Propriété localiséeExplication
nameLe nom est traduit
slugLe slug dépend du name
Propriété non localiséeExplication
parent_idL’arbre de catégories est indépendant des localisations
positionLa position est liée à l’arbre de catégories