I18n Hack
À 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 = 14est 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|1isole le nom de la page sans le suffixe ;index 1|2isole 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 :
- LocalSettings.php : Ajouter `de` dans la regex de détection et dans le Hook `InterwikiLoadPrefix`.
- Modèle:Langues : Ajouter une condition
. - 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
- ↑ Intégralité de la session de chat avec Gemini consulté le 16 janvier 2026