-1

I'm trying to change the user locale with Symfony from a field "locale" in the database. I read the Symfony manual (how to sticky a session for example), but nothing works in my application. Translator still gets the default locale...

I created listeners, subscribers... to dynamically change the locale, but as they are loaded before the firewall listener, I'm unable to change the current value.

I tried to change the priority subscriber, but I lost the user entity. I tried to set locale request in controllers, but I think it's too late.

I don't want to add locales in URLs.

Here my subscriber - listener - code:

public function onKernelRequest(RequestEvent $event)
{
   $user = $this->tokenStorage->getToken()->getUser();
   $request = $event->getRequest();
   $request->setLocale($user->getLocale());
}  

In subscribers, I added:

public static function getSubscribedEvents()
{
   return [
     KernelEvents::REQUEST => [['onKernelRequest', 0]],
   ];
}

Here, my full code:

framework.yml:

default_locale: fr

services.yml:

parameters:
    locale: 'fr'
    app_locales: fr|en|

translation.yml:

framework:
    default_locale: '%locale%'
    translator:
        paths:
            - '%kernel.project_dir%/translations'
        fallbacks:
            - '%locale%'

LocaleSubscriber.php:

namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class LocaleSubscriber implements EventSubscriberInterface
{
    private $defaultLocale;

    public function __construct($defaultLocale = 'en')
    {
        $this->defaultLocale = $defaultLocale;
    }

    public function onKernelRequest(RequestEvent $event)
    {
        $request = $event->getRequest();
        if (!$request->hasPreviousSession()) {
            return;
        }
        // try to see if the locale has been set as a _locale routing 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', $this->defaultLocale));
        }
    }

    public static function getSubscribedEvents()
    {
        return [
            // must be registered before (i.e. with a higher priority than) the default Locale listener
            KernelEvents::REQUEST => [['onKernelRequest', 20]],
        ];
    }
}

UserLocaleSubscriber.php

// src/EventSubscriber/UserLocaleSubscriber.php
namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;

/**
 * Stores the locale of the user in the session after the
 * login. This can be used by the LocaleSubscriber afterwards.
 */
class UserLocaleSubscriber implements EventSubscriberInterface
{
    private $session;

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

    public function onInteractiveLogin(InteractiveLoginEvent $event)
    {
        $user = $event->getAuthenticationToken()->getUser();

        if (null !== $user->getLocale()) {
            $this->session->set('_locale', $user->getLocale());
        }
    }

    public static function getSubscribedEvents()
    {
        return [
            SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin',
        ];
    }
}

Ex controller annotation:

/**
     * @Route("/user/locale", name="user_locale", requirements={"_locale" = "%app.locales%"})
     * @Route("/{_locale}/user/locale", name="user_locale_locale", requirements={"_locale" = "%app.locales%"})
     */
Alexander
  • 31
  • 1
  • 7
  • I lose the locale in the session. $request->getSession()->getLocale() is empty. In consequence, translator gets fallback locale. How can I solve this issue? – Alexander Jan 03 '20 at 15:01

2 Answers2

1
  1. Find the priority of the firewall listener using debug:event kernel.request.
  2. Make sure your UserLocaleSubscriber is executed right after the firewall listener.
  3. Autowire the TranslatorInterface and manually set the translator locale.
// src/EventSubscriber/UserLocaleSubscriber.php
namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
 * Stores the locale of the user in the session after the
 * login. This can be used by the LocaleSubscriber afterwards.
 */
class UserLocaleSubscriber implements EventSubscriberInterface
{
    private $session;

    private $translator;

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

    public function onInteractiveLogin(InteractiveLoginEvent $event)
    {
        $user = $event->getAuthenticationToken()->getUser();

        if (null !== $user->getLocale()) {
            $this->translator->setLocale($user->getLocale());
        }
    }

    public static function getSubscribedEvents()
    {
        return [
            SecurityEvents::INTERACTIVE_LOGIN => ['onInteractiveLogin', 7]
        ];
    }
}

-2

It's hard to help without seeing the full code you are using.

Symfony has it's own LocaleListener as well. Make sure your's is executed first.

    /**
     * @return array
     */
    public static function getSubscribedEvents()
    {
        return [
            // must be registered before (i.e. with a higher priority than) the default Locale listener
            KernelEvents::REQUEST => [['onKernelRequest', 20]],
        ];
    }
Nicodemuz
  • 3,539
  • 2
  • 26
  • 32
  • Thanks for your answer, I tried to set the priority to 20, 15 or 17 but the TokenStorage is empty. In the Symfony documentation, it's written: 'You might want to improve this technique even further and define the locale based on the user entity of the logged in user. However, since the LocaleSubscriber is called before the FirewallListener, which is responsible for handling authentication and setting the user token on the TokenStorage, you have no access to the user which is logged in.' Even if the locale is stored in the session, the defaultLocale is still applied. – Alexander Jan 02 '20 at 10:00
  • @Alexander Would an approach similar to this work: https://github.com/h4cc/Sweepo/blob/f97ec4afeda675584fde5c5646a376da80b5ef14/src/Sweepo/CoreBundle/Listener/LocaleListener.php https://github.com/h4cc/Sweepo/blob/f97ec4afeda675584fde5c5646a376da80b5ef14/src/Sweepo/CoreBundle/Resources/config/services.xml#L26 – Nicodemuz Jan 04 '20 at 02:30