Site non compatible avec cette vieillerie d'Internet Explorer. Pour une expérience complète, voir la liste des navigateurs supportés.
Thumbnail

 Memo Réécriture d'URL pour la langue locale par défaut sous Symfony 4

Cette base est une réponse à l'un de mes besoins qui s'apparente plus à de l'esthétisme d'url lors de l'utilisation de locale sous Symfony.


Post-it | Symfony

Auteur : Guillaume M.
Créé le : 17 Nov 2019
Dernière mise à jour : 25 Dec 2019

Description


Lien github du projethttps://github.com/GuiM0x/Symfony_locale

De base le framework symfony gère très bien le multilingue mais pour la langue configurée par défaut la locale n'apparaît pas dans l'URL (elle est implicite) et seuls les autres langues apparaissent dans l'URL.

Le but de ce projet est donc d'avoir une URL contenant la langue par défaut si celle-ci est choisie.

 

Comportement général


Si un utilisateur saisie une adresse tel que :

https://monsite.com/

Il sera alors redirigé automatiquement vers une adresse contenant la "locale" :

https://monsite.com/fr/

Idem pour toutes les routes :

https://monsite.com/another-route

devient :

https://monsite.com/fr/another-route
Note : La locale est définie par défaut via le fichier de configuration services.yaml mais peut aussi être définie via une variable de session lors d'un clic sur un lien spécifique.

Pour ajouter une nouvelle langue prise en charge, 3 choses suffises :

  • ajouter la locale dans les langages supportés au niveau du fichier services.yaml
  • utiliser la commande de mise à jour de translation de Symfony
  • ajouter un lien vers une route spécifique de traitement avec en paramètre la nouvelle locale (cela permet d'initialiser une variable de session avec une valeur par défaut pour la locale)

 

Les fichiers créés/modifiés


+---config
|   |   services.yaml
|   |             
|   +---routes
|       |   annotations.yaml
|       |   
|       +---dev
|               twig.yaml
|               web_profiler.yaml
|                    
+---src
|   |   
|   +---Controller
|   |       HomeController.php
|   |            
|   +---EventSubscriber
|   |       LocaleRewriteSubscriber.php
|           
+---templates
|   |   base.html.twig
|   |   
|   +---home
|           another-route.html.twig
|           index.html.twig
|                
+---translations
|       messages.en.xlf
|       messages.fr.xlf
|       security.en.xlf
|       security.fr.xlf
|       validators.en.xlf
|       validators.fr.xlf
|       

config/services.yaml :

Première chose, déclarations de 2 globales importantes : app.default_locale et app.supported_locales. Elle peuvent ainsi être utilisées à divers endroits et centralise la configuration principale.

Elle sont ensuite directement utilisées dans ce même fichier pour être injectées dans l'event subscriber utilisé pour contrôler la route.

parameters:
    #...
    app.default_locale: fr
    app.supported_locales: fr|en

services:
    # ...
    App\EventSubscriber\LocaleRewriteSubscriber:
        arguments:
            $defaultLocale: '%app.default_locale%'
            $supportedLocales: '%app.supported_locales%'

config/routes/annotations.yaml :

Ici, ayant pour habitude d'utiliser les annotations dans les controllers, j'ai ajouté un préfixe pour toutes les routes sans exceptions : {_locale}. Ainsi que les requirements et la valeur par défaut.

Note : Réutilisation des 2 globales
controllers:
    resource: ../../src/Controller/
    type: annotation
    prefix: /{_locale}
    requirements:
        _locale: '%app.supported_locales%'
    defaults: {_locale: '%app.default_locale%'}

config/routes/dev/twig.yaml (environnement dev) :

Étant donné que toutes les routes doivent être préfixées, il ne faut pas oublié les routes supplémentaires de l'environnement dev, ici donc les erreurs twig.

_errors:
    resource: '@TwigBundle/Resources/config/routing/errors.xml'
    prefix: /{_locale}/_error

config/routes/dev/web_profiler.yaml (environnement dev) :

Idem que pour les erreurs twig mais pour le profiler.

web_profiler_wdt:
    resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
    prefix: /{_locale}/_wdt

web_profiler_profiler:
    resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
    prefix: /{_locale}/_profiler

src/Controller/HomeController.php :

Pour le test avec session, une route a été ajoutée au HomeController : /default-language. Cette route permet d'enregistrer le choix de l'utilisateur en session. Cette partie est amenée à changer.

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\SessionInterface;

class HomeController extends AbstractController
{
    private $session;

    public function __construct(SessionInterface $session)
    {
        $this->session = $session;
    }

    /**
     * @Route("/", name="index")
     */
    public function index()
    {
        return $this->render('home/index.html.twig');
    }

    /**
     * @Route("/another-route", name="another_route")
     */
    public function anotherRoute()
    {
        return $this->render("home/another-route.html.twig");
    }

    /**
     * @Route("/default-language", name="default_language")
     */
    public function defaultLanguage(Request $request)
    {
        $locale = $request->query->get("language");
        $this->session->set("_locale", $locale);
        
        return $this->redirectToRoute("index");
    }
}

src/EventSubscriber/LocalRewriteListener.php :

Note : Le dossier "EventSubscriber" n'existe pas par défaut dans Symfony, il faut donc le créer

La pièce maîtresse de cette configuration.

C'est l'event subscriber crée pour retravailler la route si celle-ci n'est pas valide.

L'ordre de priorité de cet event doit être supérieur à l'event LocaleListener déjà existant. Pour connaître l'ordre des priorité des différents events il suffit d'utiliser cette commande :

php bin/console debug:event

On pourra ainsi voir quel nombre donné à la fonction getSubscribedEvents(). À l'heure où j'écris ces lignes, une priorité de 101 était suffisante.

<?php

namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class LocaleRewriteSubscriber implements EventSubscriberInterface
{
    private $defaultLocale;
    private $supportedLocales;

    /**
     * $defaultLocale and $supportedLocales injected from services.yaml
     */
    public function __construct(string $defaultLocale, string $supportedLocales)
    {
        $this->defaultLocale = $defaultLocale;
        $this->supportedLocales = explode("|", $supportedLocales);
    }

    public function onKernelRequest(GetResponseEvent $event)
    {
        $request = $event->getRequest();
        $oldUrl = $request->getPathInfo();
        $exploded = explode("/", $oldUrl);
        $locale = $request->getSession()->get("_locale", $this->defaultLocale);
        $newUrl = null;
        
        if(!in_array($exploded[1], $this->supportedLocales))
        {  // If no prefix or prefix not found in supported locales
            $newUrl = "/" . $locale . $oldUrl;
        }
        else
        {   // If prefix found in supported locales but different from actual
            if($exploded[1] !== $locale){
                $exploded[1] = $locale;
                $implode = implode("/", $exploded);
                $newUrl = $implode;
            }
        }

        if($newUrl){
            $event->setResponse(new RedirectResponse($newUrl));
        }
    }

    public static function getSubscribedEvents()
    {
        return array(
            // must be registered before the default Locale listener
            KernelEvents::REQUEST => [['onKernelRequest', 101]],
        );
    }
}

templates/base.html.twig :

Ici, 2 liens ont été ajoutés sur le template de base permettant de définir la variale _locale de session.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Welcome!{% endblock %}</title>
        {% block stylesheets %}{% endblock %}
    </head>
    <body>
        <p><a href="{{ path('default_language', {"language": "fr"}) }}">French version</a></p>
        <p><a href="{{ path('default_language', {"language": "en"}) }}">English version</a></p>
        {% block body %}{% endblock %}
        {% block javascripts %}{% endblock %}
    </body>
</html>

templates/home/index.html.twig :

Rien de particulier ici, juste une page pour vérifier la traduction.

{% extends 'base.html.twig' %}

{% block title %}Hello HomeController!{% endblock %}

{% block body %}
    <h1>{% trans %}Hello World{% endtrans %}</h1>
    <p><a href="{{ path('index') }}">Home</a></p>
    <p><a href="{{ path('another_route') }}">Another route</a></p>
{% endblock %}

templates/home/another-route.html.twig :

Idem, une autre page de vérification de traduction.

{% extends 'base.html.twig' %}

{% block title %}Hello HomeController!{% endblock %}

{% block body %}
    <h1>{% trans %}Hello Another Route{% endtrans %}</h1>
    <p><a href="{{ path('index') }}">Home</a></p>
    <p><a href="{{ path('another_route') }}">Another route</a></p>
{% endblock %}

translations :

Générés via les commandes :

php bin/console translation:update --force fr

et

php bin/console translation:update --force en

Il suffit ensuite de modifier ces fichiers avec les traductions nécessaire.