IncludeBeer

Création d'un site web multilingue avec CodeIgniter 4 (1ère partie)

Publié le 24 juin 2021
Image de l'article
Origine de l'image: Brett Zeck

La création d'un site bilingue est toujours compliqué, mais pas avec CodeIgniter 4 ! Grâce aux fonctionnalités de localisation et aux fichiers de traductions en plusieurs langues, créer un site multilingue est très facile et rapide. Dans cet article j'explique comment créer un site web en cinq langues. Vous allez voir que c'est aussi facile de faire un site en deux, cinq ou 25 langues avec les fichiers de traductions.

Les fichiers de langues de CodeIgniter

D'abord, il faut copier les fichiers de langues de CodeIgniter. Ces fichiers contiennent les messages du framework, entre autre les messages affichés lors de la validation d'un formulaire. Allez sur GitHub pour récupérer le projet codeigniter4/translations. Vous pouvez copier uniquement les répertoires des langues dont vous avez besoin dans le répertoire app/Language/. Pour cet exemple, nous allons traduire notre site en anglais, français, espagnol, allemand et japonais. On va donc copier les répertoires de, es, fr et ja. Les fichiers pour l'anglais sont déjà inclus avec CodeIgniter dans system/Language/en. Ils sont bien à leur place. Inutile de les copier ou de les déplacer dans notre application.

Les fichiers de langues de notre application

Pour traduire une application, il suffit de créer un ou plusieurs fichiers pour chacune des langues prises en charge par l'application. Dans cet exemple, un seul fichier suffira. Créer un fichier Blog.php dans le répertoire de chacune des cinq langues et ajoutez les phrases suivantes. Comme je ne parle aucunement l'espagnol, l'allemand, ni le japonais, j'ai utilisé une application de traduction (DeepL). Je m'excuse d'avance si les phrases ne sont pas correctes. C'est uniquement pour démontrer les capacités multilingues de CodeIgniter.

app/Language/de/Blog.php

<?php
/**
 *
 * Blog - German / Allemand
 *
 */
return [
    'title' => "Willkommen in meinem Blog",
    'metaDescription' => "Beispiel für eine mit CodeIgniter erstellte mehrsprachige Website",
    'metaKeywords' => "CodeIgniter, Sprache, Beispiel",
    'languageName_de' => "Deutsch",
    'languageName_en' => "Englisch (English)",
    'languageName_es' => "Spanisch (Español)",
    'languageName_fr' => "Französisch (Français)",
    'languageName_ja' => "Japanisch (日本語)",
    'aboutTitle' => "Über uns",
    'aboutText' => "Hallo, hier ist ein Beispiel für einen mehrsprachigen Blog.",
];

app/Language/en/Blog.php

<?php
/**
 *
 * Blog - English / Anglais
 *
 */
return [
    'title' => "Welcome to my blog",
    'metaDescription' => "Example of a multilingual web site made with CodeIgniter",
    'metaKeywords' => "CodeIgniter, language, example",
    'languageName_de' => "German (Deutsch)",
    'languageName_en' => "English",
    'languageName_es' => "Spanish (Español)",
    'languageName_fr' => "French (Français)",
    'languageName_ja' => "Japanese (日本語)",
    'aboutTitle' => "About",
    'aboutText' => "Hello, here is an example of a multilingual blog.",
];

app/Language/es/Blog.php

<?php
/**
 *
 * Blog - Spanish / Espagnol
 *
 */
return [
    'title' => "Bienvenido a mi blog",
    'metaDescription' => "Ejemplo de sitio web multilingüe realizado con CodeIgniter",
    'metaKeywords' => "CodeIgniter, lenguaje, ejemplo",
    'languageName_de' => "Alemán (Deutsch)",
    'languageName_en' => "Inglés (English)",
    'languageName_es' => "Español",
    'languageName_fr' => "Francés (Français)",
    'languageName_ja' => "Japonés (日本語)",
    'aboutTitle' => "Sobre nosotros",
    'aboutText' => "Hola, este es un ejemplo de blog multilingüe.",
];

app/Language/fr/Blog.php

<?php
/**
 *
 * Blog - French / Français
 *
 */
return [
    'title' => "Bienvenue sur mon blog",
    'metaDescription' => "Exemple d'un site web multilingue créé avec CodeIgniter",
    'metaKeywords' => "CodeIgniter, languege, exemple",
    'languageName_de' => "Allemand (Deutsch)",
    'languageName_en' => "Anglais (English)",
    'languageName_es' => "Espagnol (Español)",
    'languageName_fr' => "Français",
    'languageName_ja' => "Japonais (日本語)",
    'aboutTitle' => "À propos",
    'aboutText' => "Bonjour, voici un exemple d'un blog multilingue.",
];

app/Language/ja/Blog.php

<?php
/**
 *
 * Blog - Japanese / Japonais
 *
 */
return [
    'title' => "私のブログへようこそ",
    'metaDescription' => "CodeIgniterで作られた多言語Webサイトの例",
    'metaKeywords' => "CodeIgniter, 言語, 例",
    'languageName_de' => "ドイツ語 (Deutsch)",
    'languageName_en' => "英語 (English)",
    'languageName_es' => "スペイン語 (Español)",
    'languageName_fr' => "フランス語 (Français)",
    'languageName_ja' => "日本語",
    'aboutTitle' => "私たちについて",
    'aboutText' => "こんにちは、ここに多言語ブログの例があります。",
];

La configuration des langues

Dans le fichier de configuration de l'application, définissez quelles seront les langues prises en charge avec le paramètre $supportedLocales. Pour que l'application détecte automatiquement la langue du visiteur, mettez le paramètre $negotiateLocale à true. Dans les cas où la langue du visiteur n'est pas prise en charge, on lui présentera le site dans la langue définie dans $defaultLocale. Dans ce cas-ci, la langue par défaut est l'anglais.

app/Config/App.php

public $defaultLocale = 'en';
public $negotiateLocale = true;
public $supportedLocales = ['en', 'es', 'fr', 'de', 'ja'];

Les routes

La sélection de la langue se fait via l'URL. Si on accède / et que $negotiateLocale est à true, CodeIgniter tentera de déterminer la langue du visiteur. S'il est à false, c'est la langue par défaut qui est sélectionnée. Si on accède à /fr, c'est le français qui est sélectionné. Et ainsi de suite pour toutes les langues. Pour bénéficier de cette fonctionnalité, il suffit d'ajouter {locale} dans nos routes. Prenez note que la route / doit toujours être définit. Sinon, vous aurez une erreur 404 en allant sur / sans spécifier la langue.

app/Config/Routes.php

$routes->setAutoRoute(false);
$routes->get('/', 'HomeController::index');
$routes->get('/{locale}', 'HomeController::index');

Les contrôleurs

Dans le BaseController, j'aime bien garder un tableau des données qui doivent être disponibles pour toutes les vues.

protected $viewData = [];

J'y ajoute la langue du visiteur ainsi que toutes les langues prises en charge par l'application.

$this->viewData['locale'] = $request->getLocale();
$this->viewData['supportedLocales'] = $request->config->supportedLocales;

Voici le code complet du contrôleur de base :

app/Controllers/BaseController.php

<?php

namespace App\Controllers;

use CodeIgniter\Controller;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;

/**
 * Class BaseController
 *
 * BaseController provides a convenient place for loading components
 * and performing functions that are needed by all your controllers.
 * Extend this class in any new controllers:
 *     class Home extends BaseController
 *
 * For security be sure to declare any new methods as protected or private.
 */

class BaseController extends Controller
{
    /**
     * An array of helpers to be loaded automatically upon
     * class instantiation. These helpers will be available
     * to all other controllers that extend BaseController.
     *
     * @var array
     */
    protected $helpers = [];

    /**
     * Les données à passer à la vues.
     * @var array
     */
    protected $viewData = [];

    /**
     * Constructor.
     *
     * @param RequestInterface  $request
     * @param ResponseInterface $response
     * @param LoggerInterface   $logger
     */
    public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
    {
        // Do Not Edit This Line
        parent::initController($request, $response, $logger);

        //--------------------------------------------------------------------
        // Preload any models, libraries, etc, here.
        //--------------------------------------------------------------------
        // E.g.: $this->session = \Config\Services::session();

        $this->viewData['locale'] = $request->getLocale();
        $this->viewData['supportedLocales'] = $request->config->supportedLocales;
    }
}

Ensuite il suffit de passer $this->viewData à la vue:

app/Controllers/HomeController.php

<?php

namespace App\Controllers;

class HomeController extends BaseController
{
    public function index()
    {
        return view('index', $this->viewData);
    }
}

Les vues

Dans le template de notre application, on spécifie la langue du contenu de la page avec la variable $locale qu'on a définit dans le tableau $viewData du contrôleur de base :

<html lang="<?= $locale ?>">

Ce qui donnera ceci :

<html lang="fr">

Pour afficher une phrase dans la bonne langue, on utilise la fonction lang() :

<title><?= lang('Blog.title') ?></title>

Le premier mot Blog correspond au nom du fichier où se trouve cette phrase. Le mot suivant, title, correspond à la clé de la phrase telle que définit dans le fichier de langue :

'title' => "Bienvenue sur mon blog",

Voici la vue pour notre template. Le contenu de chaque page sera inséré dans la section main_content :

<?= $this->renderSection('main_content') ?>

Sous le titre, on affiche un menu nous permettant de sélectionner une des langues disponibles. Pour ce faire, on boucle sur les valeurs de $supportedLocales :

app/Views/template.php

<?php
/**
 * @var \CodeIgniter\View\View $this
 * @var string $locale
 */
?>
<!DOCTYPE html>
<html lang="<?= $locale ?>">
<head>
    <title><?= lang('Blog.title') ?></title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="<?= lang('Blog.metaDescription') ?>">
    <meta name="keywords" content="<?= lang('Blog.metaKeywords') ?>">
    <meta name="author" content="includebeer.com">
    <link rel="stylesheet"
          href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
          integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
          crossorigin="anonymous">
</head>

<body>

    <header class="text-center mt-4">
        <h1><?= lang('Blog.title') ?></h1>
<?php foreach ($supportedLocales as $supLocale): ?>
        <?= anchor($supLocale, lang("Blog.languageName_{$supLocale}"), ['class' => 'mx-3'])?>
<?php endforeach; ?>
    </header>

    <div class="container">
<?= $this->renderSection('main_content') ?>
    </div>

    <footer class="my-5">
        <p class="text-center">&copy; 2021 <?= anchor($locale, lang('Blog.title'))?></p>
    </footer>

</body>
</html>

Voici le contenu de la page d'accueil qui affiche simplement une petite description du site web :

app/Views/index.php

<?php
/**
 * @var \CodeIgniter\View\View $this
 */
?>
<?= $this->extend('template') ?>
<?= $this->section('main_content') ?>

        <section class="px-3 py-5">
            <div class="container">
                <div class="mb-5">
                    <h3><?= lang('Blog.aboutTitle') ?></h3>
                    <p><?= lang('Blog.aboutText') ?></p>
                </div>
            </div>
        </section>

<?= $this->endSection() ?>

Le filtre Localize

À ce point-ci, le site est déjà multilingue et tout fonctionne parfaitement... ou presque !

Si on va vers / ça affiche le site dans notre langue (le même contenu que si on va vers /fr). Si on va vers /ru ça affiche le site dans la langue par défaut (le même contenu que /en), car le russe n'est pas disponible dans notre application. Ce qui est un peu étrange. Selon moi, si on va vers / on devrait être redirigé vers notre langue. Et si on demande le site dans une langue qui n'est pas prise en charge, on devrait soit avoir une erreur, soit être redirigé vers la langue par défaut. Dans cet exemple, j'ai décidé d'afficher une erreur 404 quand on demande une langue qui n'est pas prise en charge.

J'ai créé un filtre nommé Localize pour nous permettre de gérer ce petit problème.

app/Filters/Localize.php

<?php
namespace App\Filters;

use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;

class Localize implements FilterInterface
{
    public function before(RequestInterface $request, $arguments = null)
    {
        log_message('debug', "FilterLocalize --- start ---");
        $uri = &$request->uri;
        $segments = array_filter($uri->getSegments());
        $nbSegments = count($segments);
        log_message('debug', "FilterLocalize - {$nbSegments} segments = " . print_r($segments, true));

        // Garder seulement les 2 premières lettres (fr-FR => fr)
        $userLocale = strtolower(substr($request->getLocale(), 0, 2));
        log_message('debug', "FilterLocalize - Locale du visiteur $userLocale");

        // Si la langue de l'utilisateurs n'est pas une langue prise en charge, prendre la langue par défaut
        $locale = in_array($userLocale, $request->config->supportedLocales) ? $userLocale : $request->config->defaultLocale;
        log_message('debug', "FilterLocalize - Locale sélectionnée $locale");

        // Si on demande /, rediriger vers /{locale}
        if ($nbSegments == 0)
        {
            log_message('debug', "FilterLocalize - Rediriger / vers /{$locale}");
            log_message('debug', "FilterLocalize --- end ---");
            return redirect()->to("/{$locale}");
        }

        log_message('debug', "FilterLocalize - segments[0] = " . $segments[0]);
        $locale = $segments[0];

        // Si le premier segment de l'URI n'est pas une locale valide, déclencher une erreur 404
        if ( ! in_array($locale, $request->config->supportedLocales))
        {
            log_message('debug', "FilterLocalize - ERREUR Locale '{$locale}' invalide");
            log_message('debug', "FilterLocalize --- end ---");
            throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
        }

        log_message('debug', "FilterLocalize - Locale '$locale' valide");
        log_message('debug', "FilterLocalize --- end ---");
    }

    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
    {
        // Do something here
    }

}

Pour que le nouveau filtre s'exécute, il suffit de l'ajouter dans le fichier de configuration des filtres. Définissez un alias avec le nom de la classe incluant son namespace. Puis ajoutez cet alias dans la section before pour que le filtre soit exécuté avant d'appeller le contrôleur.

app/Config/Filters.php

    public $aliases = [
        'csrf'     => CSRF::class,
        'toolbar'  => DebugToolbar::class,
        'honeypot' => Honeypot::class,
         // Définir l'alias localize
        'localize' => \App\Filters\Localize::class,
    ];

    public $globals = [
        'before' => [
            // Exécuter le filtre localize avant le contrôlleur
            'localize',
        ],
        'after'  => [
            'toolbar',
        ],
    ];

Première partie terminée !

Vous avez maintenant un site web en cinq langues ! Vous pouvez accéder à http://ci4.test:8888/fr, http://ci4.test:8888/en, http://ci4.test:8888/de, etc.

Ok, pas si vite. On ne va quand même pas écrire des articles de blog dans des fichiers de traductions ! On devrait les sauvegarder dans une base de données et afficher uniquement les articles dans la langue sélectionnée. C'est ce que nous allons faire dès maintenant dans la deuxième partie.

Beer Si vous appréciez mes tutoriels, vous pouvez me payer l'équivalent d'un café, une bière ou un lunch !

Paypal