Aller au contenu

I18n Hack

De WikiVlist


À part un essai peu concluant il y a plusieurs années, je n'ai pas de connaissance particulière de MédiaWiki. Peu désireux de me confronter à sa complexité pour un projet personnel j'ai fait appel à Gemini (Google) pour mettre le projet sur les rails.

La version intégrale de la session avec Gemini est disponible [1].

Principes

Les pages sont déclinées en pages "racines" et pages "feuilles" suffixées par le code de leur langue.

La page que vous lisez est ainsi déclinée en :

WikiVlist:I18n_Hack             <-- La Racine (Hub de redirection)
├── WikiVlist:I18n_Hack/fr      <-- Feuille Française (Contenu FR)
└── WikiVlist:I18n_Hack/en      <-- Feuille Anglaise (Contenu EN)

La racine n'a pas vocation a être visualisée et elle est redirigée vers la feuille correspondant à la langue définie par le navigateur ou choisie par l'utilisateur. La sélection se fait par ULS, la langue du contenu étant synchronisée avec celle du site.

Un mécanisme similaire est utilisé pour les catégories :

Catégorie:XML             <-- La Racine (Hub de redirection)
├── Catégorie:XML/fr      <-- Feuille Française (Contenu FR)
└── Catégorie:XML/en      <-- Feuille Anglaise (Contenu EN)

Implémentation

L'implémentation repose sur du code PHP pour assurer les redirections et d'un modèle gérant le choix de la langue et l'insertion automatique de catégories.

PHP (LocalSettings.php)

Ce code gère la détection de la langue du navigateur, la redirection vers les feuilles `/fr` ou `/en`, et le détournement des préfixes interwikis pour un usage local.

// 1. LANGUAGE DETECTION & AUTOMATIC REDIRECTION
$scriptName   = $_SERVER['SCRIPT_NAME'] ?? '';
$uri          = $_SERVER['REQUEST_URI'] ?? '';
$queryString  = $_SERVER['QUERY_STRING'] ?? '';
$acceptLang   = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '';

if ( str_ends_with($scriptName, 'index.php') && empty($queryString) ) {
    $allowedNamespaces = ['', 'Témoignage:', 'Projet:', 'Aide:', 'Utilisateur:', 'WikiVlist:', 'Category:', 'Catégorie:'];
    $path = ltrim($uri, '/');
    $firstColon = strpos($path, ':');
    $currentNS = ($firstColon !== false) ? substr($path, 0, $firstColon + 1) : '';

    if ( in_array($currentNS, $allowedNamespaces) ) {
        if ( !preg_match('/\/(fr|en)$/', $uri) && $uri !== '/' && $uri !== '' ) {
            $cleanLang = strtolower(substr($acceptLang, 0, 2));
            $target = '';
            if ( $cleanLang === 'fr' ) { $target = $uri . (str_ends_with($uri, '/') ? 'fr' : '/fr'); }
            elseif ( $cleanLang === 'en' ) { $target = $uri . (str_ends_with($uri, '/') ? 'en' : '/en'); }
            if ( $target ) { header("Location: $target", true, 302); exit; }
        }
    }
}

// 2. FORCE INTERFACE LANGUAGE BASED ON SUFFIX
if ( preg_match( '/\/(en|fr)(?=[&#?]|$)/', $uri, $matches ) ) {
    $forcedLang = $matches[1];
    $wgLanguageCode = $forcedLang;
    $wgDefaultUserOptions['language'] = $forcedLang;
    $wgHooks['UserGetLanguageObject'][] = function ( $user, &$langCode, $context ) use ( $forcedLang ) {
        $langCode = $forcedLang;
        return true;
    };
}

// 3. HANDLE LANGUAGE PREFIXES AS LOCAL LINKS
$wgHooks['InterwikiLoadPrefix'][] = function ( $prefix, &$data ) {
    if ( $prefix === 'en' || $prefix === 'fr' ) {
        $data = ['iw_url' => "/$1", 'iw_local' => 1, 'iw_trans' => 0];
        return false;
    }
    return true;
};

Modèle:Langues

Le modèle analyse le titre de la page via les fonctions de parseur pour identifier si l'on se trouve sur une racine ou une feuille, et adapte l'affichage et les métadonnées en conséquence.

<includeonly>{{#ifexpr: {{NAMESPACENUMBER}} = 14
  | <div style="margin: 1em 0; padding: 8px 12px; background-color: #f8f9fa; border: 1px solid #c8ccd1; border-radius: 4px; display: inline-block;">🌐 {{#ifeq: {{#titleparts:{{PAGENAME}}|1|2}} | fr | [[:Catégorie:{{#titleparts:{{PAGENAME}}|1|1}}|Racine]] · '''Français''' · [[:en:Catégorie:{{#titleparts:{{PAGENAME}}|1|1}}/en|English]] | {{#ifeq: {{#titleparts:{{PAGENAME}}|1|2}} | en | [[:Catégorie:{{#titleparts:{{PAGENAME}}|1|1}}|Racine]] · [[:fr:Catégorie:{{#titleparts:{{PAGENAME}}|1|1}}/fr|Français]] · '''English''' | '''Racine''' · [[:fr:{{FULLPAGENAME}}/fr|Français]] · [[:en:{{FULLPAGENAME}}/en|English]] }} }}</div>
  | {{#if: {{#titleparts:{{FULLPAGENAME}}|1|2}}
      | {{#ifeq: {{#titleparts:{{FULLPAGENAME}}|1|2}} | en
          | {{DISPLAYTITLE:{{{en|{{{1|{{#titleparts:{{PAGENAME}}|1|1}}}}}}}}}}[[en:{{#titleparts:{{FULLPAGENAME}}/en}}]][[Category:English pages|{{#titleparts:{{PAGENAME}}|1|1}}]]
          | {{DISPLAYTITLE:{{{fr|{{{1|{{#titleparts:{{PAGENAME}}|1|1}}}}}}}}}}[[fr:{{#titleparts:{{FULLPAGENAME}}/fr}}]][[Category:Pages en français|{{#titleparts:{{PAGENAME}}|1|1}}]]
        }}
      | <div style="margin: 1em 0; padding: 8px 12px; background-color: #f8f9fa; border: 1px solid #c8ccd1; border-radius: 4px; display: inline-block;">🌐 '''[[{{FULLPAGENAME}}/fr|Français]]''' · '''[[{{FULLPAGENAME}}/en|English]]'''</div>[[Category:Multilingual roots]]
    }}
}}</includeonly>

Points clés de l'implémentation du modèle

L'architecture du modèle repose sur plusieurs subtilités techniques qui permettent de compenser l'absence de l'extension Translate :

  • Gestion de l'Espace de Noms 14 (Catégories) :
Le test {{#ifexpr: 3000 = 14 est crucial. Pour créer de simples liens de navigation entre les versions d'une catégorie sans hiérarchie infinie, le modèle force l'usage du préfixe : (ex: Catégorie:Nom/fr).
  • Manipulation via titleparts :
index 1|1 isole le nom de la page sans le suffixe ; index 1|2 isole le suffixe seul (fr ou en). Cela permet au modèle d'identifier automatiquement la langue.
  • Usage détourné du DISPLAYTITLE :
Permet d'afficher "Mon Article" au lieu de "Mon Article/en", masquant la structure technique des sous-pages.
  • Indépendance des "Feuilles" et Langues Interwikis :
L'usage de [[en:Nom/en]] (sans deux-points) délègue le lien de langue à la barre latérale (sidebar), respectant les standards Wikipédia sans polluer le corps du texte.
  • Catégorisation automatique :
Le modèle injecte dynamiquement [[Category:Pages en français]] ou [[Category:English pages]].

Maintenance : Ajouter une langue

Le système est extensible (ex: pour l'allemand `/de`) en trois étapes :

  1. LocalSettings.php : Ajouter `de` dans la regex de détection et dans le Hook `InterwikiLoadPrefix`.
  2. Modèle:Langues : Ajouter une condition .
  3. Categories : Créer la catégorie de maintenance [[Category:Deutsche Seiten]].

Choix techniques

Synthèse

Le choix a été influencé par la volonté de garder une grande simplicité d'utilisation et de ne pas privilégier une langue "pivot".

Critère Extension Translate (Écartée) Le I18n_Hack (Retenu)
Philosophie Centralisation : Une langue pivot (source) dicte les autres versions. Autonomie : Chaque version peut diverger indépendamment.
Workflow d'édition Rigide : Cycle "Édition → Marquage → Traduction". Fluide : Édition directe du Wikitexte.
Maintenance Automatisée : Détection des changements obsolètes. Manuelle : Repose sur la rigueur de l'auteur.
Sémantique Balises propriétaires <translate>. Structure native MediaWiki (portabilité).
Interface (UI) Gérée par ULS. Gérée par ULS seule.

Historique détaillé

Voici la restitution simplifiée des échanges ayant mené à la conception de ce système.

---

Avec l'aide de Gemini, l'installation de base a été rapide mais les choses ont commencé à se corser quand j'ai abordé l'aspect "multilinguisme":

Eric
Pour gérer plusieurs langues, je peux l'installer en français et ajouter d'autre langues par la suite ?
Gemini
Oui, MediaWiki est nativement conçu pour le multilinguisme. Voici comment gérer cela pour votre projet :
1. Installation initiale
Choisissez le Français comme langue par défaut.
2. Gérer le contenu
Option A : Le système de sous-pages (Recommandée).
$wgNamespacesWithSubpages[NS_MAIN] = true;
Option B : L'extension "Translate" (Trop complexe pour Docker).

---

Nous en étions alors à un stade où je commençais à être confiant dans la faisabilité du projet et j'ai remis ce choix à plus tard pour régler d'autres problèmes :

Eric
OK, ça part un peu dans tous les sens mais on peut considérer que nous avons un "POC" qui montre la faisabilité... Faisons un plan d'actions...
Gemini
C'est une excellente nouvelle ! Voici votre plan d'actions :
1. La Version Mobile
Installez MinervaNeue.
2. L'Aspect Multilingue
Utilisez l'extension Translate. Elle gère les traductions par blocs.
3. Charte et Terminologie
Le terme "Selfpedia" est puissant : l'autobiographie avec la rigueur d'une encyclopédie.

---

Je n'ai compris que plus tard ce que Gemini voulait dire par "enfer de copier-coller" et nous sommes partis sur cette option "Translate".

Eric
Je préfère voir d'abord la question multilingue...
Gemini
C'est un choix pragmatique. L'extension de référence est Translate, avec UniversalLanguageSelector (ULS).
Mise à jour du Dockerfile
# Installation de ULS et Translate via curl/tar dans /extensions/
Configuration dans LocalSettings.php
wfLoadExtension( 'UniversalLanguageSelector' );
wfLoadExtension( 'Translate' );
Le point critique
Le "Job Runner"
MediaWiki traite les traductions en arrière-plan. Sur Docker, il faudra un conteneur séparé pour runJobs.php.

---

Après avoir bataillé nous avons réussi à installer et faire fonctionner l'extension Translate pour découvrir sa lourdeur d'utilisation : chaque section doit être traduite de manière indépendante et une erreur de balisage bloque le mécanisme.

Eric
C'est l'horreur ce système, il n'y a pas d'autre option ?
Gemini
Je comprends votre exaspération. Voici l'alternative :
Option 1
Le "Manuel" (Simple, Propre, Flexible)
On utilise des sous-pages manuelles et un modèle .
Avantages : Pas de balises complexes, ça ne casse jamais, liberté totale.
Eric
Essayons l'option 1
Gemini
C'est une excellente décision. Pour votre Selfpedia, l'Option 1 est bien plus saine : elle vous redonne le contrôle total.

---

C'est ainsi que nous sommes revenus à une solution plus "bricolage" qui n'est pas parfaite et a nécessité plusieurs ajustements successifs mais fonctionne plutôt bien.

Références