Spécifications Techniques - Architecture Multilingue Meristheme¶
Date : 21 Octobre 2025 Responsable technique : Fenohery Développeur : Fenohery Validateur : Andrianina
🏛️ Architecture Technique Globale¶
Configuration CMS¶
- Plateforme : [WordPress/Drupal/Custom] - À définir
- Version PHP : 8.1 minimum
- Base de données : MySQL 8.0+ / MariaDB 10.6+
- Hébergement : [À spécifier selon client]
Plugin Multilingue¶
- Solution sélectionnée : WPML Pro (WordPress Multilingual Plugin)
- Version requise : WPML CMS Nav + String Translation + Media Translation
- Coût estimé : ~\(79-\)299 (selon licence choisie)
- Configuration : Mode de traduction manuelle avec gestion complète
- Support : Documentation officielle WPML + support technique inclus
🗺️ Configuration URLs Multilingues¶
Structure URLs Recommandée¶
Structure par sous-répertoires (SEO optimal) :
- Français : https://meristheme.com/fr/
- Anglais International : https://meristheme.com/en/
Configuration Nginx/Apache pour WPML¶
# Nginx - Redirections WPML automatiques
location ~ ^/fr|en {
try_files $uri $uri/ /index.php?$args;
}
# Redirection langue par défaut WPML (détection navigateur)
if ($http_accept_language ~* ^fr) {
return 301 /fr$request_uri;
}
if ($http_accept_language ~* ^en) {
return 301 /en$request_uri;
}
Sitemap Multilingue¶
- Format : XML sitemap hreflang
- Génération : Automatique via plugin
- Soumission : Google Search Console
- Fréquence : Mise à jour automatique
⚙️ Configuration WPML Pro¶
Installation et Activation WPML¶
# Téléchargement WPML Pro depuis le site officiel
# Upload via WordPress admin ou FTP
# Installation automatique recommandée
wp plugin install wpml-multilingual-cms
wp plugin activate wpml-multilingual-cms
# Modules complémentaires WPML
wp plugin install wpml-string-translation
wp plugin install wpml-translation-management
wp plugin install wpml-media-translation
wp plugin activate wpml-string-translation
wp plugin activate wpml-translation-management
wp plugin activate wpml-media-translation
Configuration Initiale WPML¶
- Langues actives : FR (par défaut), EN (Anglais International)
- URL mode : Sous-répertoires (/fr/, /en/)
- Redirection automatique : Activée (basée sur navigateur)
- Sélecteur langue : Menu navigation + Footer (widget WPML)
- Mode traduction : Traduction manuelle avec Translation Editor
Paramètres Avancés WPML¶
// wp-config.php - Configuration WPML
define('WPML_LANGUAGE_NEGOTIATION_TYPE', '3'); // URL-based
define('WPML_STICKY_LANGUAGES', true);
define('WPML_SHOW_TRANSLOADED', true);
define('WPML_TM_EDITOR_TEAM', '1'); // Mode traduction manuelle
define('WPML_MEDIA_VERSION', '2'); // Support médias multilingues
Modules WPML Requis¶
- WPML Multilingual CMS - Core plugin
- WPML String Translation - Traduction des chaînes de texte
- WPML Translation Management - Gestion des traductions
- WPML Media Translation - Support médias multilingues
🧭 Navigation et Sélecteurs de Langue¶
Sélecteur Principal (Header)¶
<nav class="main-navigation">
<div class="language-selector">
<button class="current-lang" aria-expanded="false">
<span class="flag-icon flag-fr"></span>
<span>Français</span>
<svg class="chevron-down">...</svg>
</button>
<ul class="language-dropdown">
<li><a href="/fr/" hreflang="fr" class="active">
<span class="flag-icon flag-fr"></span>Français
</a></li>
<li><a href="/en/" hreflang="en">
<span class="flag-icon flag-en"></span>English
</a></li>
<li><a href="/es/" hreflang="es">
<span class="flag-icon flag-es"></span>Español
</a></li>
<li><a href="/de/" hreflang="de">
<span class="flag-icon flag-de"></span>Deutsch
</a></li>
<li><a href="/it/" hreflang="it">
<span class="flag-icon flag-it"></span>Italiano
</a></li>
</ul>
</div>
</nav>
Sélecteur Secondaire (Footer)¶
<footer class="site-footer">
<div class="language-switcher">
<span>Disponible en :</span>
<a href="/fr/" hreflang="fr">Français</a>
<a href="/en/" hreflang="en">English</a>
<a href="/es/" hreflang="es">Español</a>
<a href="/de/" hreflang="de">Deutsch</a>
<a href="/it/" hreflang="it">Italiano</a>
</div>
</footer>
CSS Sélecteur Langue¶
.language-selector {
position: relative;
margin: 0 20px;
}
.language-dropdown {
position: absolute;
top: 100%;
right: 0;
background: white;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
z-index: 1000;
min-width: 150px;
}
.language-dropdown a {
display: flex;
align-items: center;
padding: 8px 12px;
text-decoration: none;
color: #333;
transition: background-color 0.2s;
}
.language-dropdown a:hover,
.language-dropdown a.active {
background-color: #f5f5f5;
color: #0073aa;
}
.flag-icon {
width: 18px;
height: 12px;
margin-right: 8px;
background-size: cover;
}
🔄 Redirections Automatiques¶
Logique de Redirection¶
// Fonction détection langue navigateur
function detect_user_language() {
$accept_lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
$languages = ['fr', 'en', 'es', 'de', 'it'];
foreach ($languages as $lang) {
if (strpos($accept_lang, $lang) === 0) {
return $lang;
}
}
return 'fr'; // Langue par défaut
}
// Redirection automatique première visite
function redirect_to_language() {
if (!isset($_COOKIE['visited_language'])) {
$detected_lang = detect_user_language();
$current_url = $_SERVER['REQUEST_URI'];
if ($detected_lang !== 'fr' && strpos($current_url, '/') === 0) {
wp_redirect("/$detected_lang$current_url", 302);
exit;
}
setcookie('visited_language', 'true', time() + (86400 * 30), '/');
}
}
add_action('template_redirect', 'redirect_to_language');
Gestion Cookies Langue¶
// JavaScript - Gestion préférence langue
class LanguageManager {
constructor() {
this.currentLang = document.documentElement.lang;
this.cookieName = 'meristheme_language';
this.init();
}
init() {
this.setupLanguageSwitcher();
this.detectLanguageChange();
this.setLanguageCookie();
}
setupLanguageSwitcher() {
document.querySelectorAll('.language-dropdown a').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const newLang = this.extractLanguageFromURL(link.href);
this.switchLanguage(newLang);
});
});
}
switchLanguage(lang) {
// Sauvegarder préférence
document.cookie = `${this.cookieName}=${lang}; path=/; max-age=31536000`;
// Rediriger vers nouvelle langue
const currentPath = window.location.pathname;
const newPath = this.buildNewPath(currentPath, lang);
window.location.href = newPath;
}
buildNewPath(currentPath, newLang) {
// Remplacer ou ajouter langue dans l'URL
const langRegex = /^\/(fr|en|es|de|it)\//;
if (langRegex.test(currentPath)) {
return currentPath.replace(langRegex, `/${newLang}/`);
} else {
return `/${newLang}${currentPath}`;
}
}
setLanguageCookie() {
if (!document.cookie.includes(this.cookieName)) {
document.cookie = `${this.cookieName}=${this.currentLang}; path=/; max-age=31536000`;
}
}
}
// Initialisation
document.addEventListener('DOMContentLoaded', () => {
new LanguageManager();
});
📝 Intégration Contenu Traduit¶
Structure Base de Données Multilingue¶
-- Table wp_posts multilingue
ALTER TABLE wp_posts
ADD COLUMN post_language VARCHAR(10) DEFAULT 'fr',
ADD COLUMN post_translation_parent INT(11) DEFAULT NULL,
ADD INDEX idx_language (post_language),
ADD INDEX idx_translation_parent (post_translation_parent);
-- Table wp_postmeta multilingue
ALTER TABLE wp_postmeta
ADD COLUMN meta_language VARCHAR(10) DEFAULT 'fr',
ADD INDEX idx_meta_language (meta_language);
-- Table des traductions
CREATE TABLE wp_icl_translations (
translation_id INT(11) AUTO_INCREMENT PRIMARY KEY,
element_type VARCHAR(36) NOT NULL,
element_id INT(11) NOT NULL,
language_code VARCHAR(7) NOT NULL,
source_language_code VARCHAR(7),
trid INT(11) NOT NULL,
UNIQUE KEY unique_translation (element_type, element_id, language_code),
INDEX trid_lang (trid, language_code),
INDEX element_type_id (element_type, element_id)
);
Fonctions d'Extraction Contenu¶
class MultilingualContent {
private $current_lang;
public function __construct() {
$this->current_lang = $this->getCurrentLanguage();
}
public function getTranslatedPost($post_id) {
global $wpdb;
$translated_id = $wpdb->get_var($wpdb->prepare(
"SELECT element_id
FROM wp_icl_translations
WHERE element_type = 'post_post'
AND trid = (
SELECT trid FROM wp_icl_translations
WHERE element_id = %d AND element_type = 'post_post'
)
AND language_code = %s",
$post_id, $this->current_lang
));
return $translated_id ? get_post($translated_id) : get_post($post_id);
}
public function getTranslatedMeta($post_id, $meta_key) {
$translated_post = $this->getTranslatedPost($post_id);
return get_post_meta($translated_post->ID, $meta_key, true);
}
public function getCurrentLanguage() {
// Détection depuis URL
$path = $_SERVER['REQUEST_URI'];
if (preg_match('/^\/(fr|en|es|de|it)\//', $path, $matches)) {
return $matches[1];
}
// Détection depuis cookie
if (isset($_COOKIE['meristheme_language'])) {
return $_COOKIE['meristheme_language'];
}
// Détection depuis navigateur
return $this->detectBrowserLanguage();
}
}
🎨 Templates Multilingues¶
Structure Thème Multilingue¶
/wp-content/themes/meristheme/
├── languages/
│ ├── meristheme-fr_FR.po
│ ├── meristheme-en_US.po
│ ├── meristheme-es_ES.po
│ ├── meristheme-de_DE.po
│ └── meristheme-it_IT.po
├── template-parts/
│ ├── header-multilingual.php
│ ├── navigation-multilingual.php
│ └── footer-multilingual.php
├── single-multilingual.php
├── page-multilingual.php
└── functions-multilingual.php
Template Header Multilingue¶
<?php // header-multilingual.php ?>
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo('charset'); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="profile" href="https://gmpg.org/xfn/11">
<?php wp_head(); ?>
<!-- Hreflang tags pour SEO -->
<?php $this_page_id = get_the_ID(); ?>
<link rel="alternate" hreflang="fr" href="<?php echo get_permalink_in_language($this_page_id, 'fr'); ?>">
<link rel="alternate" hreflang="en" href="<?php echo get_permalink_in_language($this_page_id, 'en'); ?>">
<link rel="alternate" hreflang="es" href="<?php echo get_permalink_in_language($this_page_id, 'es'); ?>">
<link rel="alternate" hreflang="de" href="<?php echo get_permalink_in_language($this_page_id, 'de'); ?>">
<link rel="alternate" hreflang="it" href="<?php echo get_permalink_in_language($this_page_id, 'it'); ?>">
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
<header id="masthead" class="site-header">
<?php get_template_part('template-parts/navigation-multilingual'); ?>
</header>
<main id="primary" class="site-main">
Fonctions de Support Multilingue¶
// functions-multilingual.php
function get_permalink_in_language($post_id, $language) {
global $wpdb;
$translated_id = $wpdb->get_var($wpdb->prepare(
"SELECT element_id
FROM wp_icl_translations
WHERE element_type = 'post_post'
AND trid = (
SELECT trid FROM wp_icl_translations
WHERE element_id = %d AND element_type = 'post_post'
)
AND language_code = %s",
$post_id, $language
));
return get_permalink($translated_id);
}
function get_the_title_multilingual($post_id = null) {
$post = get_post($post_id);
$multilingual = new MultilingualContent();
$translated_post = $multilingual->getTranslatedPost($post->ID);
return get_the_title($translated_post);
}
function get_the_content_multilingual($post_id = null) {
$post = get_post($post_id);
$multilingual = new MultilingualContent();
$translated_post = $multilingual->getTranslatedPost($post->ID);
return apply_filters('the_content', $translated_post->post_content);
}
// Fonction traduction chaînes de caractères
function __meristheme($string, $domain = 'meristheme') {
return __($string, $domain);
}
function _e_meristheme($string, $domain = 'meristheme') {
_e($string, $domain);
}
🔍 Optimisation SEO Multilingue¶
Configuration hreflang Automatique¶
function add_hreflang_tags() {
if (is_singular()) {
$languages = ['fr', 'en', 'es', 'de', 'it'];
$post_id = get_the_ID();
foreach ($languages as $lang) {
$permalink = get_permalink_in_language($post_id, $lang);
echo '<link rel="alternate" hreflang="' . esc_attr($lang) . '" href="' . esc_url($permalink) . '">' . "\n";
}
// URL canonique
echo '<link rel="canonical" href="' . esc_url(get_permalink()) . '">' . "\n";
}
}
add_action('wp_head', 'add_hreflang_tags');
Sitemap Multilingue XML¶
function generate_multilingual_sitemap() {
$languages = ['fr', 'en', 'es', 'de', 'it'];
$posts = get_posts(['post_type' => 'page', 'numberposts' => -1]);
$xml = '<?xml version="1.0" encoding="UTF-8"?>';
$xml .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
foreach ($posts as $post) {
foreach ($languages as $lang) {
$permalink = get_permalink_in_language($post->ID, $lang);
$mod_date = get_the_modified_date('Y-m-d', $post->ID);
$xml .= '<url>';
$xml .= '<loc>' . esc_url($permalink) . '</loc>';
$xml .= '<lastmod>' . esc_html($mod_date) . '</lastmod>';
$xml .= '<changefreq>weekly</changefreq>';
$xml .= '<priority>0.8</priority>';
$xml .= '</url>';
}
}
$xml .= '</urlset>';
return $xml;
}
📱 Tests et Validation Technique¶
Checklist Tests Multilingue¶
- Installation plugin réussie sans erreur
- Configuration langues activées correctement
- URLs multilinges accessibles et formatées
- Redirections automatiques fonctionnelles
- Sélecteurs langue opérationnels (header + footer)
- Navigation cohérente entre langues
- Contenu traduit s'affiche correctement
- SEO balises hreflang générées
- Performance maintenue (< 3s)
- Mobile responsive préservé
- Compatibilité navigateurs (Chrome, Firefox, Safari, Edge)
- Tests formulaires multilinges fonctionnels
- Sitemap multilingue généré
- Cookies langue persistants
Scripts Tests Automatisés¶
#!/bin/bash
# tests_multilingue.sh
echo "🧪 Tests Architecture Multilingue Meristheme"
# Test URLs accessibilité
echo "📍 Test URLs multilingues..."
languages=("fr" "en" "es" "de" "it")
base_url="https://meristheme.com"
for lang in "${languages[@]}"; do
url="${base_url}/${lang}/"
status=$(curl -s -o /dev/null -w "%{http_code}" "$url")
echo " $url: $status"
if [ "$status" -eq 200 ]; then
echo " ✅ OK"
else
echo " ❌ ERREUR"
fi
done
# Test redirections
echo "🔄 Test redirections automatiques..."
for lang in "${languages[@]}"; do
# Simuler différentes langues navigateur
header="Accept-Language: $lang"
redirect=$(curl -s -I -H "$header" "$base_url" | grep -i location)
echo " Navigateur $lang: $redirect"
done
# Performance tests
echo "⚡ Tests performance..."
for lang in "${languages[@]}"; do
url="${base_url}/${lang}/"
time=$(curl -s -o /dev/null -w "%{time_total}" "$url")
echo " $url: ${time}s"
if (( $(echo "$time < 3.0" | bc -l) )); then
echo " ✅ Performance OK"
else
echo " ⚠️ Performance lente"
fi
done
echo "✅ Tests terminés"
🔧 Maintenance et Support¶
Tâches Maintenance Mensuelle¶
- Mises à jour plugin multilinge
- Vérification sitemap multilingue
- Tests performance toutes langues
- Validation redirections automatiques
- Backup configuration multilingue
- Monitoring erreurs 404 multilingues
- Analyse trafic par langue
Support Post-Livraison¶
// Script monitoring erreurs multilingues
function log_multilingual_errors() {
if (is_404()) {
$requested_url = $_SERVER['REQUEST_URI'];
$referer = $_SERVER['HTTP_REFERER'] ?? 'Direct';
error_log("Multilingual 404: $requested_url from $referer");
// Notification email si erreur répétée
if (get_transient('404_notification_sent') !== 'yes') {
wp_mail(
'support@meristheme.com',
'Erreur 404 Multilingue Détectée',
"URL: $requested_url\nReferer: $referer"
);
set_transient('404_notification_sent', 'yes', 3600);
}
}
}
add_action('template_redirect', 'log_multilingual_errors');
📊 Documentation API¶
Endpoints pour Gestion Contenu¶
// API REST pour récupérer contenu multilingue
add_action('rest_api_init', function () {
register_rest_route('meristheme/v1', '/multilingual/(?P<id>\d+)', [
'methods' => 'GET',
'callback' => function($request) {
$post_id = $request->get_param('id');
$language = $request->get_param('lang') ?? 'fr';
$multilingual = new MultilingualContent();
$translated_post = $multilingual->getTranslatedPost($post_id);
return [
'id' => $translated_post->ID,
'title' => get_the_title($translated_post),
'content' => apply_filters('the_content', $translated_post->post_content),
'language' => $language,
'permalink' => get_permalink_in_language($post_id, $language)
];
}
]);
});
Document technique créé le 21 Octobre 2025 - Spécifications complètes architecture multilingue