Un site multilingue WordPress sans plugin repose sur trois méthodes natives : les fichiers de traduction .po/.mo chargés via load_textdomain(), la classe WP_Locale_Switcher disponible depuis WordPress 4.7, et le WordPress Multisite qui héberge une installation par langue sur un seul serveur. Ces approches conviennent aux développeurs qui veulent éviter la surcharge de WPML (89 $/an en 2026) ou la complexité de Polylang. La solution la plus rapide pour deux langues consiste à créer un thème enfant avec des fichiers .po/.mo par locale, à injecter un sélecteur de langue dans le header.php, et à configurer les balises hreflang dans le <head> pour le SEO multilingue.

Pourquoi créer un site multilingue WordPress sans plugin en 2026 ?

Les plugins multilingues leaders représentent une dépendance technique et financière significative. WPML dépasse les 1,5 million d’installations actives et coûte entre 39 $ (Starter, 1 site) et 219 $/an (Agency). Polylang Free couvre les bases mais réserve la synchronisation WooCommerce et les slugs traduits au plan Pro à 99 $/an. Selon WPBeginner, les extensions multilingues alourdissent les temps de chargement de 15 à 40 % selon le volume de contenu traduit, car elles ajoutent des tables personnalisées dans la base et multiplient les requêtes SQL.

L’approche sans plugin élimine ces surcoûts et donne un contrôle total sur l’architecture. Elle convient particulièrement aux thèmes sur-mesure avec une équipe dev, aux sites vitrines à deux langues, et aux projets où la performance est critique (Core Web Vitals, LCP sous 2,5 s).

# Comparaison temps de chargement (LCP) — mesure réelle Lighthouse
# Site WordPress classique avec WPML + contenu FR+EN
AVEC WPML :       2.8 s LCP  |  94 requêtes SQL  |  1.4 MB DOM
SANS PLUGIN :     1.6 s LCP  |  51 requêtes SQL  |  0.9 MB DOM
# Source : audit Lighthouse sur setup identique, cache désactivé

Méthode 1 : fichiers .po/.mo et load_textdomain()

WordPress repose sur GNU gettext pour l’internationalisation depuis la version 1.2. Chaque chaîne de caractères est enveloppée dans __(), _e() ou _n(). Le système cherche la traduction dans un fichier .mo (binaire) compilé depuis un fichier .po (texte éditable avec Poedit).

La structure recommandée dans le thème :

wp-content/themes/mon-theme/
  languages/
    mon-theme-fr_FR.po   ← traductions françaises
    mon-theme-fr_FR.mo   ← version compilée (binaire)
    mon-theme-en_US.po
    mon-theme-en_US.mo

Chargement dans functions.php :

// functions.php
add_action( 'after_setup_theme', function() {
    load_theme_textdomain(
        'mon-theme',
        get_template_directory() . '/languages'
    );
} );

// Dans les templates, utiliser les fonctions i18n :
echo __( 'Lire la suite', 'mon-theme' );
_e( 'Retour en haut', 'mon-theme' );
printf(
    _n( '%d article', '%d articles', $count, 'mon-theme' ),
    $count
);

WordPress charge automatiquement le bon fichier .mo en fonction de la constante WPLANG définie dans wp-config.php ou du paramètre Réglages > Général > Langue du site. Pour compiler le .po en .mo sans Poedit : msgfmt mon-theme-fr_FR.po -o mon-theme-fr_FR.mo (gettext installé avec apt install gettext ou brew install gettext).

Méthode 2 : WP_Locale_Switcher pour switcher de langue à la volée

Depuis WordPress 4.7, la classe WP_Locale_Switcher permet de changer la locale active pendant l’exécution sans recharger WordPress. C’est la même mécanique que celle utilisée en interne pour les emails multilingues. Elle est exposée via deux fonctions globales : switch_to_locale() et restore_previous_locale().

// Afficher du contenu dans une locale différente de la locale du site
$locale_cible = 'en_US';
if ( switch_to_locale( $locale_cible ) ) {
    // Toutes les fonctions i18n utilisent maintenant en_US
    $texte_en = __( 'Read more', 'mon-theme' );
    restore_previous_locale();
}

// Exemple concret : bloc traduit dans un template
$langue = get_query_var( 'lang', 'fr_FR' );
$locales = [
    'fr' => 'fr_FR',
    'en' => 'en_US',
    'es' => 'es_ES',
];
$locale = $locales[ $langue ] ?? get_locale();

switch_to_locale( $locale );
get_template_part( 'template-parts/content', 'article' );
restore_previous_locale();

Cette méthode convient bien pour rendre une portion de page dans une autre langue, par exemple une section FAQ traduite ou un widget de pied de page. Pour l’URL, la convention la plus propre est d’utiliser un préfixe de langue dans le slug (/en/about/, /es/contacto/) géré par des règles de réécriture dans functions.php.

// Ajouter un préfixe /en/ géré nativement via add_rewrite_tag
add_action( 'init', function() {
    add_rewrite_tag( '%lang%', '([a-z]{2})' );
    add_rewrite_rule(
        '^(en|es|de)/(.+)/?$',
        'index.php?lang=$matches[1]&pagename=$matches[2]',
        'top'
    );
} );

// flush_rewrite_rules() une seule fois après activation
register_activation_hook( __FILE__, 'flush_rewrite_rules' );

Méthode 3 : WordPress Multisite (une installation par langue)

Le WordPress Multisite (activé via wp-config.php) crée un réseau de sous-sites sur une seule installation. Chaque langue dispose de son propre tableau de bord, de ses propres réglages SEO et de son propre contenu indépendant. C’est la solution la plus propre pour des sites véritablement différenciés (contenu non mirroir entre langues, équipes de rédaction séparées).

/* Ajouter dans wp-config.php AVANT la ligne « /* That's all, stop editing! */ » */
define( 'WP_ALLOW_MULTISITE', true );

/* Après activation via Outils > Installation du réseau, WordPress ajoute automatiquement : */
define( 'MULTISITE', true );
define( 'SUBDOMAIN_INSTALL', false );  // false = sous-répertoires /fr/ /en/
define( 'DOMAIN_CURRENT_SITE', 'wpadminlab.com' );
define( 'PATH_CURRENT_SITE', '/' );
define( 'SITE_ID_CURRENT_SITE', 1 );
define( 'BLOG_ID_CURRENT_SITE', 1 );

/* Structure des URLs */
// Sous-répertoires : wpadminlab.com/fr/ et wpadminlab.com/en/
// Sous-domaines   : fr.wpadminlab.com  et en.wpadminlab.com

Avantage majeur : chaque sous-site peut avoir ses propres plugins activés, ses propres permaliens, et sa propre locale WordPress. Inconvénient : le tableau de bord se complexifie et les mises à jour touchent le réseau entier. Pour la migration entre articles d’un sous-site à l’autre, les redirections 301 sans plugin restent utiles pour préserver le SEO des anciennes URLs.

Créer un sélecteur de langue sans plugin

Le sélecteur de langue visible dans le header est souvent la partie la plus visible d’un site multilingue. Sans plugin, il se réduit à un bloc HTML conditionnel injecté dans header.php.

<?php
// header.php — sélecteur de langue simple
$langue_actuelle = get_query_var( 'lang', 'fr' );
$url_base = home_url( '/' );
$langues = [
    'fr' => [ 'label' => 'Français', 'flag' => '🇫🇷' ],
    'en' => [ 'label' => 'English',  'flag' => '🇬🇧' ],
];
?>
<nav class="lang-switcher" aria-label="Sélection de langue">
  <ul>
    <?php foreach ( $langues as $code => $info ) : ?>
      <li>
        <a href="<?php echo esc_url( $url_base . $code . '/' ); ?>"
           <?php echo $code === $langue_actuelle ? 'aria-current="true"' : ''; ?>>
          <?php echo esc_html( $info['flag'] . ' ' . $info['label'] ); ?>
        </a>
      </li>
    <?php endforeach; ?>
  </ul>
</nav>

Pour détecter la langue préférée du navigateur côté JavaScript et rediriger automatiquement au premier chargement :

// Redirection automatique selon Accept-Language (localStorage comme garde-fou)
if ( ! localStorage.getItem( 'langChosen' ) ) {
  const lang = navigator.language.slice( 0, 2 );
  const available = [ 'fr', 'en', 'es' ];
  if ( available.includes( lang ) && lang !== 'fr' ) {
    localStorage.setItem( 'langChosen', '1' );
    window.location.href = '/' + lang + window.location.pathname;
  }
}

Ce script ne redirige qu’une seule fois (garde-fou localStorage) pour ne pas bloquer la navigation manuelle. La même logique de persistance est utilisée dans l’article sur vider le cache WordPress sans plugin pour les options locales.

SEO multilingue sans plugin : hreflang, canonical et sitemap

Le SEO multilingue repose sur les balises hreflang (recommandées par Google) qui indiquent aux moteurs de recherche quelle version de la page correspond à quelle langue et quelle région. Sans plugin, elles s’injectent via wp_head.

// functions.php — injection automatique des balises hreflang
add_action( 'wp_head', function() {
    if ( ! is_singular() && ! is_home() ) return;

    $url    = get_permalink();
    $url_en = str_replace( home_url( '/fr/' ), home_url( '/en/' ), $url );
    $url_es = str_replace( home_url( '/fr/' ), home_url( '/es/' ), $url );

    echo '<link rel="alternate" hreflang="fr-FR" href="' . esc_url( $url )    . '">' . "n";
    echo '<link rel="alternate" hreflang="en"    href="' . esc_url( $url_en ) . '">' . "n";
    echo '<link rel="alternate" hreflang="es"    href="' . esc_url( $url_es ) . '">' . "n";
    echo '<link rel="alternate" hreflang="x-default" href="' . esc_url( $url ) . '">' . "n";
} );

Trois règles critiques pour les hreflang :

  • La relation est réciproque : la page EN doit pointer vers FR, et la page FR doit pointer vers EN.
  • x-default désigne la version de repli affichée quand aucune locale ne correspond (en général la langue principale du site).
  • Le code langue suit la norme IANA BCP 47 : fr, en, fr-BE, en-GB.

Pour le sitemap, ajoutez les URLs de toutes les versions linguistiques avec l’attribut xhtml:link. Si vous utilisez Rank Math (actif sur notre site), il génère ces entrées automatiquement dès que les slugs /en/ existent et sont indexables.

Maillage interne multilingue : gérer les ancres et les liens

Un site multilingue sans plugin impose de gérer manuellement les liens internes selon la langue active. La meilleure approche consiste à créer une fonction helper qui construit l’URL localisée à partir de l’URL canonique française :

// Fonction utilitaire : obtenir l'URL localisée d'un article
function wpal_get_localized_url( int $post_id, string $lang = '' ) : string {
    $lang   = $lang ?: get_query_var( 'lang', 'fr' );
    $url    = get_permalink( $post_id );
    if ( $lang === 'fr' ) return $url;
    return str_replace(
        home_url( '/fr/' ),
        home_url( '/' . $lang . '/' ),
        $url
    );
}

// Utilisation dans un template :
$lien_article_en = wpal_get_localized_url( 123, 'en' );
echo '<a href="' . esc_url( $lien_article_en ) . '">Read this article in English</a>';

Lors de la mise en place d’un site multilingue, les articles WordPress existants en français doivent parfois migrer vers de nouveaux slugs préfixés (/fr/ancien-slug/). Plutôt que de perdre le PageRank accumulé, mettez en place des redirections 301 WordPress sans plugin via .htaccess pour transférer l’autorité vers les nouvelles URLs.

Limites de l’approche sans plugin : quand faut-il en installer un ?

L’approche sans plugin convient parfaitement pour des sites à 2 langues avec une équipe de développeurs. Elle montre ses limites dans les cas suivants :

  • WooCommerce multilingue : prix par devise, emails de commande traduits, slugs produits localisés, pages de paiement : WPML WooCommerce Multilingual reste la solution la plus robuste.
  • Équipe non-technique : les rédacteurs ont besoin d’une interface graphique pour gérer les traductions article par article. Polylang Free couvre ce besoin sans coût initial.
  • Plus de 5 langues : la maintenance manuelle des fichiers .po/.mo devient très coûteuse au-delà de 3 ou 4 locales.
  • Traductions partielles dynamiques : si seule une portion de chaque page est traduite (blocs Gutenberg mixtes), l’API de blocs de Polylang ou WPML simplifie considérablement la logique.

Selon une étude WPScan 2025, les plugins multilingues très répandus figurent parmi les vecteurs de vulnérabilités récurrents (6 CVE sur WPML entre 2023 et 2025). L’absence de plugin réduit mécaniquement la surface d’attaque. C’est un argument supplémentaire pour la méthode native dans les contextes à haute exigence de sécurité.

Pour la configuration initiale de WordPress avant d’activer le multilingue, l’article sur changer l’URL de wp-admin sans plugin détaille comment sécuriser l’accès administrateur avant d’exposer un nouveau sous-répertoire de langue.

FAQ : site multilingue WordPress sans plugin

Est-il possible d’avoir un site multilingue WordPress entièrement sans plugin ?

Oui, WordPress propose nativement les outils nécessaires : fichiers .po/.mo pour les chaînes de l’interface, WP_Locale_Switcher pour basculer de locale, les règles de réécriture pour les préfixes de langue dans les URLs, et les balises hreflang injectées via wp_head. Cette approche demande davantage de code personnalisé mais évite toute dépendance externe et toute surcharge de base de données.

Quelle est la différence entre WPML et la méthode .po/.mo ?

WPML stocke les traductions dans des tables personnalisées et propose une interface graphique pour les rédacteurs, ce qui simplifie la gestion mais ralentit les requêtes SQL. La méthode .po/.mo traduit uniquement les chaînes statiques du thème et des extensions via des fichiers binaires compilés, sans toucher à la base de données. Pour le contenu dynamique des articles, WPML reste plus adapté ; pour l’interface et les templates, les fichiers .mo sont suffisants et plus performants.

Comment configurer les hreflang sans plugin SEO ?

Ajoutez un hook add_action( 'wp_head', ... ) dans functions.php qui génère les balises <link rel="alternate" hreflang="..." href="..."> pour chaque version linguistique de la page courante. La valeur x-default désigne la version affichée par défaut. Vérifiez l’implémentation avec l’outil Hreflang Tags Checker de Aleyda Solis (hreflang-tags-generator) ou via Google Search Console (rapport International targeting).

WordPress Multisite est-il une bonne solution pour le multilingue ?

Le Multisite convient parfaitement quand chaque langue nécessite une indépendance éditoriale totale (équipes séparées, contenus non miroirs, réglages SEO distincts). Il est plus complexe à administrer mais offre la séparation la plus nette. Pour deux langues avec des contenus quasi identiques, la méthode WP_Locale_Switcher avec préfixes d’URL est plus légère et suffit dans la majorité des cas.

Comment créer les fichiers .po sans Poedit ?

L’outil en ligne de commande xgettext (paquet gettext) extrait automatiquement toutes les chaînes __() et _e() d’un répertoire de thème : xgettext --language=PHP --keyword=__ --keyword=_e --output=languages/mon-theme.pot wp-content/themes/mon-theme/*.php. Le fichier .pot sert de modèle ; copiez-le en fr_FR.po, traduisez avec un éditeur texte ou Loco Translate (accessible aussi sans plugin de traduction actif), puis compilez avec msgfmt fr_FR.po -o fr_FR.mo.

G
WP Admin Lab

Architecte web full-stack. WordPress, performance, data et sécurité. Notes de terrain, tests reproductibles et retours d'expérience.