01/01/2015

Reading the current locale in Symfony

The locale lives on the Request. In a controller, ask for the request as an argument and read it:

use Symfony\Component\HttpFoundation\Request;

public function index(Request $request): Response
{
    $locale = $request->getLocale();   // 'en', 'fr_FR', ...
}

In a Twig template it is on the app global, with no plumbing at all:

{{ app.request.locale }}

In a service, which has no request argument to take, inject RequestStack:

use Symfony\Component\HttpFoundation\RequestStack;

public function __construct(private RequestStack $requestStack)
{
}

public function currentLocale(): string
{
    return $this->requestStack->getCurrentRequest()?->getLocale() ?? 'en';
}

The null-safe call matters: a service can also run from a console command or a message handler, where there is no current request at all, and getCurrentRequest() returns null rather than inventing one.

Why the old two-liner does not run

$request = $this->get('request');
$locale = $request->getLocale();

$this->get('request') fetched the request as a service. That was deprecated in Symfony 2.4 and removed in 3.0, and the reason is worth knowing rather than just the fact: one shared request object is wrong the moment a sub-request exists. Render a controller from inside a template and there are two requests in flight; a single service can only be one of them. RequestStack replaced it precisely because a stack is what the situation actually is.

$this->get() itself is gone too, removed in Symfony 6.0. A controller does not reach into the container any more; it declares what it needs as an argument or in its constructor.

getLocale() was never the problem and has not changed.

Where the locale gets set

Reading it is the easy half. It is set, in this order, by:

  • framework.default_locale in config/packages/framework.yaml, the fallback when nothing else says otherwise;

  • a _locale route parameter, which Symfony recognizes specially:

    # config/routes.yaml
    article_show:
        path: /{_locale}/articles/{slug}
        requirements:
            _locale: en|fr|de
    
  • $request->setLocale(), usually from a listener that reads a stored preference off the user or a cookie.

A common mistake is calling setLocale() inside a controller and wondering why the translator ignored it — by then the translator has already been handed a locale. Set it in a listener on kernel.request, before the controller runs.

Questions this keeps raising

What replaced $this->get('request')?

Take Request as a controller argument, or inject RequestStack in a service and call getCurrentRequest(). The request service was deprecated in Symfony 2.4 and removed in 3.0 because a single shared request cannot represent a sub-request, which is a normal thing to have when one controller renders another.

How do I read the locale in a service?

Inject RequestStack and call getCurrentRequest()?->getLocale(). Keep the null-safe operator and a fallback: the same service may run in a console command or a message handler where there is no request, and getCurrentRequest() returns null there.

Why does setLocale in my controller not change translations?

Because it runs too late. The translator has already been given a locale by the time the controller executes. Set it in a listener on the kernel.request event instead, which is where the _locale route parameter is applied too.

How do I get the locale in a Twig template?

app.request.locale. The app variable is available in every template with no configuration, and app.request is the current Request object, so anything the controller can read off it a template can too.