Localized (i18n) Routes in Symfony: A Practical Guide

Updated: January 13, 2024 By: Guest Contributor Post a comment

Introduction

Translating routes in a Symfony application is crucial for creating a multilingual website that provides a localized user experience. Symfony’s Internationalization (i18n) feature allows developers to create routes that adapt based on the user’s locale, improving accessibility and SEO. In this tutorial, we’ll explore how to implement localized routes in your Symfony project from basics to advanced concepts.

Basic Localized Routing

To begin with, make sure you have the Symfony Translation component installed. Run the following Composer command if you’re unsure:

composer require symfony/translation

Let’s start by defining a simple localized route in the routes.yaml file:

app_hello:
    path:
        en: '/hello'
        fr: '/bonjour'
    controller: App\Controller\DefaultController::hello

This will create a route that responds to either /hello or /bonjour, depending on the locale. In your controller, you might have something like this:

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class DefaultController extends AbstractController
{
    public function hello()
    {
        // Your logic here
    }
}

When a user visits /hello, the route will match the English locale, while /bonjour will match the French locale.

Dynamic Locale Setting

In more complex applications, you often need to determine the locale dynamically. This can be done by configuring a listeners in the services.yaml:

services:
    App\EventListener\LocaleListener:
        tags:
            - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 20 }

Then, in your listener:

namespace App\EventListener;

use Symfony\Component\HttpKernel\Event\RequestEvent;

class LocaleListener
{
    public function onKernelRequest(RequestEvent $event)
    {
        $request = $event->getRequest();
        if ($request->hasPreviousSession()) {
            // Try to see if the locale has been set as a route parameter
            if ($locale = $request->attributes->get('_locale')) {
                $request->getSession()->set('_locale', $locale);
            } else {
                // If no explicit locale has been set on this request, use one from the session
                $request->setLocale($request->getSession()->get('_locale', $request->getDefaultLocale()));
            }
        }
    }
}

This listener will check the request for a ‘_locale’ parameter and set the session’s locale accordingly, which can then be used by subsequent requests to define the user’s preferred language.

Advanced Locale Routing

For even more control over your localized routes, Symfony allows you to use custom route loaders. Here’s an example:

namespace App\Routing;

use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Config\Loader\Loader;

class ExtraLoader extends Loader
{
    private $isLoaded = false;

    public function load($resource, $type = null)
    {
        if ($this->isLoaded) {
            throw new \Exception('Do not add this loader twice');
        }

        $routes = new RouteCollection();

        // Define your localized routes here
        $path = ['en' => '/about', 'fr' => '/a-propos'];
        $routes->add('app_about', new Route($path, ['_controller' => 'App\Controller\DefaultController::about']));

        $this->isLoaded = true;

        return $routes;
    }

    public function supports($resource, $type = null)
    {
        return 'extra' === $type;
    }
}

This extra loader lets you define routes in PHP, giving you more flexibility. It’s important to use this in combination with configuration to avoid issues with route loading:

app_extra_routes:
    resource: .
    type: extra

With ‘extra’ type now supported, Symfony can utilize this custom loader for more nuanced route localization.

Locale-Specific Requirements

Sometimes your routes might need patterns that are different for each locale. We can manage this by specifying localized route requirements like so:

app_contact:
    path:
        en: '/contact'
        fr: '/contactez'
    controller: App\Controller\ContactController::index
    requirements:
        _locale: 'en|fr'

This tells Symfony that for the ‘app_contact’ route, the locale must be either English or French. Additional requirements can be set up similarly, ensuring precise route matching based on locale.

Conclusion

Localized routing is a powerful feature in Symfony that enables the creation of multilingual applications. By defining routes per locale, dynamically setting the locale via event listeners, leveraging custom route loaders, and implementing locale-specific requirements, developers can offer a tailored user experience for users around the world. Through the use of these strategies, your Symfony application will become more accessible and user-friendly on a global scale.