Internationalisation (i18n) et gestion des langues

Apprenez à créer des sites multilingues en PHP en utilisant différentes techniques de traduction comme les fichiers de langue, gettext et les bibliothèques spécialisées.

Introduction à l'internationalisation

L'internationalisation (abrégée en i18n car il y a 18 lettres entre le "i" et le "n") est le processus de conception et de développement d'une application qui peut être adaptée à différentes langues et cultures sans nécessiter de modifications techniques.

Pourquoi internationaliser votre site ?
  • Audience plus large : atteindre des utilisateurs dans leur langue maternelle
  • Meilleure expérience utilisateur : adaptation aux préférences culturelles
  • Conformité légale : certaines régions exigent des contenus dans la langue locale
  • Avantage concurrentiel : se démarquer des concurrents sur des marchés spécifiques

Concepts clés

Internationalisation vs. Localisation
  • Internationalisation (i18n) : Préparer votre application pour qu'elle puisse être adaptée à différentes langues sans modifier le code.
  • Localisation (l10n) : Adapter votre application à une langue et culture spécifiques (traductions, formats de date, monnaie, etc.).

i18n est le travail technique pour que votre code soit prêt à supporter plusieurs langues, tandis que l10n est le processus d'adaptation culturelle pour une langue spécifique.

Éléments à considérer
  • Textes et messages : Tous les textes visibles par l'utilisateur
  • Formats de date et heure : 01/05/2025 peut signifier 1er mai ou 5 janvier selon les pays
  • Formats numériques : Séparateurs décimaux (1,000.00 vs 1.000,00)
  • Devises : Symboles et positions (€50 vs 50€)
  • Pluralisation : Règles différentes selon les langues
  • Direction d'écriture : De gauche à droite ou de droite à gauche

Méthode simple : Utilisation de fichiers de langue

Une approche simple pour débuter consiste à stocker vos traductions dans des fichiers PHP séparés pour chaque langue. Cette méthode est facile à mettre en œuvre et convient aux petits projets.

Structure de fichiers

Organisation des dossiers et fichiers
📁 projet/
  ├── 📁 languages/
  │   ├── 📄 fr.php
  │   ├── 📄 en.php
  │   ├── 📄 es.php
  │   └── 📄 de.php
  ├── 📄 index.php
  └── 📄 page.php

Fichiers de traduction

languages/fr.php
// Fichier de langue français (fr.php)
return [
    'welcome' => 'Bienvenue sur notre site',
    'home' => 'Accueil',
    'about' => 'À propos',
    'contact' => 'Contact',
    'login' => 'Connexion',
    'register' => 'Inscription',
    'email' => 'Adresse e-mail',
    'password' => 'Mot de passe',
    'form_success' => 'Formulaire envoyé avec succès !',
    'form_error' => 'Erreur lors de l\'envoi du formulaire.',
    'items_count' => 'Vous avez {count} article(s) dans votre panier.'
];
languages/en.php
// Fichier de langue anglais (en.php)
return [
    'welcome' => 'Welcome to our website',
    'home' => 'Home',
    'about' => 'About',
    'contact' => 'Contact',
    'login' => 'Login',
    'register' => 'Register',
    'email' => 'Email address',
    'password' => 'Password',
    'form_success' => 'Form submitted successfully!',
    'form_error' => 'Error submitting the form.',
    'items_count' => 'You have {count} item(s) in your cart.'
];

Implémentation de la gestion des langues

Classe pour la traduction
// Classe I18n.php
class I18n {
    private static $instance = null;
    private $translations = [];
    private $lang = 'fr'; // Langue par défaut
    private $availableLangs = ['fr', 'en', 'es', 'de'];
    
    private function __construct() {
        // Définir la langue en fonction des préférences utilisateur
        $this->setLanguage();
        // Charger les traductions
        $this->loadTranslations();
    }
    
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new I18n();
        }
        return self::$instance;
    }
    
    private function setLanguage() {
        // Priorité 1: Paramètre de l'URL
        if (isset($_GET['lang']) && in_array($_GET['lang'], $this->availableLangs)) {
            $this->lang = $_GET['lang'];
            setcookie('lang', $this->lang, time() + 30 * 24 * 3600, '/');
        }
        // Priorité 2: Cookie
        elseif (isset($_COOKIE['lang']) && in_array($_COOKIE['lang'], $this->availableLangs)) {
            $this->lang = $_COOKIE['lang'];
        }
        // Priorité 3: En-tête Accept-Language du navigateur
        elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
            $browserLangs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
            foreach ($browserLangs as $browserLang) {
                $code = substr($browserLang, 0, 2);
                if (in_array($code, $this->availableLangs)) {
                    $this->lang = $code;
                    break;
                }
            }
        }
    }
    
    private function loadTranslations() {
        $file = dirname(__FILE__) . '/languages/' . $this->lang . '.php';
        if (file_exists($file)) {
            $this->translations = require($file);
        } else {
            // Fallback à la langue par défaut
            $fallbackFile = dirname(__FILE__) . '/languages/fr.php';
            $this->translations = require($fallbackFile);
        }
    }
    
    public function get($key, $params = []) {
        if (isset($this->translations[$key])) {
            $text = $this->translations[$key];
            // Remplacement des variables dans le texte
            foreach ($params as $param => $value) {
                $text = str_replace('{' . $param . '}', $value, $text);
            }
            return $text;
        }
        return $key; // Fallback: retourne la clé elle-même
    }
    
    public function getCurrentLanguage() {
        return $this->lang;
    }
    
    public function getAvailableLanguages() {
        return $this->availableLangs;
    }
}

Cette classe utilise le pattern Singleton pour garantir une seule instance de traduction et implémente une logique de détection de langue basée sur plusieurs sources (URL, cookie, navigateur).

Utilisation dans vos pages
// Inclure la classe I18n
require_once 'I18n.php';

// Créer une fonction helper pour faciliter les traductions
function __($key, $params = []) {
    $i18n = I18n::getInstance();
    return $i18n->get($key, $params);
}

// Exemple d'utilisation dans une page
<!DOCTYPE html>
<html lang="<?= I18n::getInstance()->getCurrentLanguage() ?>">
<head>
    <meta charset="UTF-8">
    <title><?= __('welcome') ?></title>
</head>
<body>
    <nav>
        <ul>
            <li><a href="index.php"><?= __('home') ?></a></li>
            <li><a href="about.php"><?= __('about') ?></a></li>
            <li><a href="contact.php"><?= __('contact') ?></a></li>
        </ul>
        
        <div class="lang-switcher">
            <?php foreach (I18n::getInstance()->getAvailableLanguages() as $lang) : ?>
                <a href="?lang=<?= $lang ?>"><?= strtoupper($lang) ?></a>
            <?php endforeach; ?>
        </div>
    </nav>
    
    <h1><?= __('welcome') ?></h1>
    
    <p><?= __('items_count', ['count' => 5]) ?></p>
</body>
</html>

L'utilisation d'une fonction helper __() simplifie l'accès aux traductions et rend le code plus lisible. Le sélecteur de langues permet aux utilisateurs de changer facilement de langue.

Utilisation de gettext

gettext est une bibliothèque standard et éprouvée pour l'internationalisation, largement utilisée dans de nombreuses applications. Elle offre des fonctionnalités avancées comme la pluralisation et est compatible avec de nombreux outils de traduction.

Avantages de gettext
  • Solution robuste et mature avec des décennies d'utilisation
  • Support natif des formes plurielles complexes
  • Séparation claire du code et des traductions
  • Nombreux outils disponibles pour les traducteurs (comme Poedit)
  • Performances optimisées (fichiers MO compilés)

Configuration et utilisation de gettext

Configuration de base
// Prérequis : installer l'extension gettext PHP
// sudo apt-get install php-gettext (Linux)
// Activer l'extension dans php.ini (Windows/WAMP)

// Structure de dossiers
// locale/
//   ├── fr_FR/
//   │   └── LC_MESSAGES/
//   │       ├── messages.po
//   │       └── messages.mo
//   ├── en_US/
//   │   └── LC_MESSAGES/
//   │       ├── messages.po
//   │       └── messages.mo

// Fonction pour configurer gettext
function setupLocale($locale = 'fr_FR') {
    // Liste des locales disponibles
    $availableLocales = [
        'fr' => 'fr_FR',
        'en' => 'en_US',
        'es' => 'es_ES',
        'de' => 'de_DE'
    ];
    
    // Déterminer la locale à utiliser
    $selectedLocale = $availableLocales['fr']; // Par défaut
    
    if (isset($_GET['lang']) && isset($availableLocales[$_GET['lang']])) {
        $selectedLocale = $availableLocales[$_GET['lang']];
        setcookie('lang', $_GET['lang'], time() + 30 * 24 * 3600, '/');
    } elseif (isset($_COOKIE['lang']) && isset($availableLocales[$_COOKIE['lang']])) {
        $selectedLocale = $availableLocales[$_COOKIE['lang']];
    }
    
    // Configurer l'environnement
    putenv('LANG=' . $selectedLocale);
    setlocale(LC_ALL, $selectedLocale . '.UTF-8');
    
    // Spécifier le domaine et le répertoire
    bindtextdomain('messages', dirname(__FILE__) . '/locale');
    bind_textdomain_codeset('messages', 'UTF-8');
    textdomain('messages');
    
    return substr($selectedLocale, 0, 2); // Retourne le code de langue (fr, en, etc.)
}
Utilisation dans les pages
// Inclure la configuration gettext
require_once 'locale_setup.php';
$lang = setupLocale();

<!DOCTYPE html>
<html lang="<?= $lang ?>">
<head>
    <meta charset="UTF-8">
    <title><?= gettext('Bienvenue sur notre site') ?></title>
</head>
<body>
    <nav>
        <ul>
            <li><a href="index.php"><?= _('Accueil') ?></a></li>
            <li><a href="about.php"><?= _('À propos') ?></a></li>
            <li><a href="contact.php"><?= _('Contact') ?></a></li>
        </ul>
    </nav>
    
    <h1><?= gettext('Bienvenue sur notre site') ?></h1>
    
    <p>
        <?php
        $count = 5;
        echo sprintf(
            ngettext(
                'Vous avez %d article dans votre panier.',
                'Vous avez %d articles dans votre panier.',
                $count
            ),
            $count
        );
        ?>
    </p>
</body>
</html>

Dans cet exemple :

  • gettext() et son alias _() sont utilisés pour les traductions simples
  • ngettext() gère la pluralisation en fonction de la valeur

Création et gestion des fichiers de traduction

Workflow de traduction
  1. Extraction des chaînes à traduire :
    xgettext --from-code=UTF-8 -o messages.pot *.php

    Cette commande analyse vos fichiers PHP et extrait toutes les chaînes de traduction dans un fichier template (.pot).

  2. Création des fichiers de traduction spécifiques à la langue :
    msginit -i messages.pot -o locale/fr_FR/LC_MESSAGES/messages.po -l fr_FR

    Cette commande génère un fichier .po pour la langue française à partir du template.

  3. Traduction des fichiers .po :

    Utilisez un éditeur comme Poedit pour traduire les chaînes dans le fichier .po.

  4. Compilation en fichiers binaires .mo :
    msgfmt locale/fr_FR/LC_MESSAGES/messages.po -o locale/fr_FR/LC_MESSAGES/messages.mo

    Cette étape transforme les fichiers .po en fichiers .mo binaires optimisés pour gettext.

  5. Mise à jour des traductions existantes :
    msgmerge --update locale/fr_FR/LC_MESSAGES/messages.po messages.pot

    Cette commande met à jour un fichier .po existant avec de nouvelles chaînes du template.

Vous pouvez également utiliser des outils graphiques comme Poedit qui simplifient considérablement ce processus pour les traducteurs non techniques.

Bibliothèques PHP pour l'internationalisation

Pour les projets plus complexes ou si vous utilisez un framework PHP, plusieurs bibliothèques spécialisées peuvent faciliter la gestion des traductions.

Symfony Translation

Installation
composer require symfony/translation
Utilisation de base
use Symfony\Component\Translation\Translator;
use Symfony\Component\Translation\Loader\PhpFileLoader;

// Créer le traducteur
$translator = new Translator('fr');
$translator->addLoader('php', new PhpFileLoader());

// Ajouter une ressource de traduction
$translator->addResource(
    'php',
    'translations/messages.fr.php',
    'fr',
    'messages'
);

// Traductions pour l'anglais
$translator->addResource(
    'php',
    'translations/messages.en.php',
    'en',
    'messages'
);

// Utiliser le traducteur
echo $translator->trans('welcome', [], 'messages');

// Avec des paramètres
echo $translator->trans(
    'hello_name',
    ['%name%' => 'John'],
    'messages'
);

// Avec pluralisation
echo $translator->trans(
    'items_count',
    ['%count%' => 5],
    'messages'
);

Le composant Translation de Symfony prend en charge plusieurs formats de fichiers (PHP, YAML, JSON, etc.) et offre des fonctionnalités avancées comme la pluralisation, les domaines et la détection automatique de la locale.

Laravel Localization

Structure de fichiers
resources/
└── lang/
    ├── en/
    │   ├── messages.php
    │   └── validation.php
    └── fr/
        ├── messages.php
        └── validation.php

Exemple de fichier de langue (resources/lang/fr/messages.php) :

return [
    'welcome' => 'Bienvenue sur notre site',
    'hello_name' => 'Bonjour, :name!',
    'items' => 'Vous avez :count article|Vous avez :count articles',
];
Utilisation dans Laravel
// Dans un contrôleur ou une vue

// Traduction simple
echo __('messages.welcome');

// Avec remplacement de paramètres
echo __('messages.hello_name', ['name' => 'Marie']);

// Avec pluralisation
echo trans_choice('messages.items', 5, ['count' => 5]);

Laravel intègre nativement la gestion des traductions avec :

  • La fonction helper __() pour les traductions simples
  • La fonction trans_choice() pour la pluralisation
  • Les middleware pour définir automatiquement la locale

Bonnes pratiques pour l'internationalisation

Conseils pour une internationalisation réussie
  1. Planifiez dès le début : Il est bien plus facile d'internationaliser une application dès sa conception que de l'adapter après coup.
  2. Utilisez des clés descriptives : Préférez 'login.welcome_message' à 'welcome' pour éviter les collisions.
  3. Contextualisez vos traductions : Le mot "post" peut signifier "publier" ou "article" selon le contexte.
  4. Soyez attentif à la pluralisation : Ne vous limitez pas à "1" et "plusieurs", certaines langues ont des règles complexes.
  5. Externalisez tout le texte : Y compris les messages d'erreur, notifications, et textes d'interface.
  6. Localisez les formats : Dates, nombres, devises, formats d'adresse selon les conventions locales.
  7. Testez avec des traducteurs réels : Une traduction automatique ne suffit pas pour une expérience de qualité.
  8. Prenez en compte l'expansion du texte : Le texte traduit peut être jusqu'à 30% plus long dans certaines langues.

Outils et ressources

Outils de traduction
  • Poedit - Éditeur de fichiers .po/.mo
  • Lokalise - Plateforme de gestion de traductions
  • Transifex - Plateforme collaborative de traduction
  • Crowdin - Solution de gestion de localisation
Extensions et plugins utiles