diff --git a/UPGRADE-4.3.md b/UPGRADE-4.3.md
index ba8ab75cb2..110e58ed65 100644
--- a/UPGRADE-4.3.md
+++ b/UPGRADE-4.3.md
@@ -86,6 +86,7 @@ HttpKernel
* Renamed `GetResponseForControllerResultEvent` to `ViewEvent`
* Renamed `GetResponseForExceptionEvent` to `ExceptionEvent`
* Renamed `PostResponseEvent` to `TerminateEvent`
+ * Deprecated `TranslatorListener` in favor of `LocaleAwareListener`
Messenger
---------
diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md
index 00a819543d..7c4a38aba2 100644
--- a/UPGRADE-5.0.md
+++ b/UPGRADE-5.0.md
@@ -232,6 +232,7 @@ HttpKernel
* Removed `GetResponseForControllerResultEvent`, use `ViewEvent` instead
* Removed `GetResponseForExceptionEvent`, use `ExceptionEvent` instead
* Removed `PostResponseEvent`, use `TerminateEvent` instead
+ * Removed `TranslatorListener` in favor of `LocaleAwareListener`
Messenger
---------
diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
index 3d19141469..d38ddc7e6d 100644
--- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
+++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
@@ -4,13 +4,14 @@ CHANGELOG
4.3.0
-----
- * added `WebTestAssertions` trait (included by default in `WebTestCase`)
- * renamed `Client` to `KernelBrowser`
+ * Added `WebTestAssertionsTrait` (included by default in `WebTestCase`)
+ * Renamed `Client` to `KernelBrowser`
* Not passing the project directory to the constructor of the `AssetsInstallCommand` is deprecated. This argument will
be mandatory in 5.0.
* Deprecated the "Psr\SimpleCache\CacheInterface" / "cache.app.simple" service, use "Symfony\Contracts\Cache\CacheInterface" / "cache.app" instead
* Added the ability to specify a custom `serializer` option for each
transport under`framework.messenger.transports`.
+ * Added the `RegisterLocaleAwareServicesPass` and configured the `LocaleAwareListener`
* [BC Break] When using Messenger, the default transport changed from
using Symfony's serializer service to use `PhpSerializer`, which uses
PHP's native `serialize()` and `unserialize()` functions. To use the
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php
index 2e433cdc6c..efeafad5f0 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php
@@ -40,6 +40,7 @@ class UnusedTagsPass implements CompilerPassInterface
'kernel.event_listener',
'kernel.event_subscriber',
'kernel.fragment_renderer',
+ 'kernel.locale_aware',
'messenger.bus',
'messenger.receiver',
'messenger.message_handler',
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 4831af368e..f3491d3569 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -119,6 +119,7 @@ use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\Service\ResetInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
+use Symfony\Contracts\Translation\LocaleAwareInterface;
/**
* FrameworkExtension.
@@ -370,6 +371,8 @@ class FrameworkExtension extends Extension
->addTag('kernel.cache_warmer');
$container->registerForAutoconfiguration(EventSubscriberInterface::class)
->addTag('kernel.event_subscriber');
+ $container->registerForAutoconfiguration(LocaleAwareInterface::class)
+ ->addTag('kernel.locale_aware');
$container->registerForAutoconfiguration(ResetInterface::class)
->addTag('kernel.reset', ['method' => 'reset']);
diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
index c7b37222ae..97b4ef28a6 100644
--- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
+++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
@@ -41,6 +41,7 @@ use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueReso
use Symfony\Component\HttpKernel\DependencyInjection\FragmentRendererPass;
use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass;
use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass;
+use Symfony\Component\HttpKernel\DependencyInjection\RegisterLocaleAwareServicesPass;
use Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass;
use Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass;
use Symfony\Component\HttpKernel\KernelEvents;
@@ -121,6 +122,7 @@ class FrameworkBundle extends Bundle
$this->addCompilerPassIfExists($container, FormPass::class);
$container->addCompilerPass(new WorkflowGuardListenerPass());
$container->addCompilerPass(new ResettableServicePass());
+ $container->addCompilerPass(new RegisterLocaleAwareServicesPass());
$container->addCompilerPass(new TestServiceContainerWeakRefPass(), PassConfig::TYPE_BEFORE_REMOVING, -32);
$container->addCompilerPass(new TestServiceContainerRealRefPass(), PassConfig::TYPE_AFTER_REMOVING);
$this->addCompilerPassIfExists($container, AddMimeTypeGuesserPass::class);
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml
index 7f4f5890fa..7455ae8833 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml
@@ -112,5 +112,11 @@
+
+
+
+
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml
index 8853914f65..a9d5a85d20 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml
@@ -19,6 +19,7 @@
+
@@ -140,11 +141,5 @@
-
-
-
-
-
-
diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md
index 310f9818d8..06193a00b3 100644
--- a/src/Symfony/Component/HttpKernel/CHANGELOG.md
+++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md
@@ -8,7 +8,9 @@ CHANGELOG
* `KernelInterface` doesn't extend `Serializable` anymore
* deprecated the `Kernel::serialize()` and `unserialize()` methods
* increased the priority of `Symfony\Component\HttpKernel\EventListener\AddRequestFormatsListener`
- * made `Symfony\Component\HttpKernel\EventListenerLocaleListener` set the default locale early
+ * made `Symfony\Component\HttpKernel\EventListener\LocaleListener` set the default locale early
+ * deprecated `TranslatorListener` in favor of `LocaleAwareListener`
+ * added the registration of all `LocaleAwareInterface` implementations into the `LocaleAwareListener`
* made `FileLinkFormatter` final and not implement `Serializable` anymore
* the base `DataCollector` doesn't implement `Serializable` anymore, you should
store all the serialized state in the data property instead
diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterLocaleAwareServicesPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterLocaleAwareServicesPass.php
new file mode 100644
index 0000000000..0efb164b72
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterLocaleAwareServicesPass.php
@@ -0,0 +1,58 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Register all services that have the "kernel.locale_aware" tag into the listener.
+ *
+ * @author Pierre Bobiet
+ */
+class RegisterLocaleAwareServicesPass implements CompilerPassInterface
+{
+ private $listenerServiceId;
+ private $localeAwareTag;
+
+ public function __construct(string $listenerServiceId = 'locale_aware_listener', string $localeAwareTag = 'kernel.locale_aware')
+ {
+ $this->listenerServiceId = $listenerServiceId;
+ $this->localeAwareTag = $localeAwareTag;
+ }
+
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition($this->listenerServiceId)) {
+ return;
+ }
+
+ $services = [];
+
+ foreach ($container->findTaggedServiceIds($this->localeAwareTag) as $id => $tags) {
+ $services[] = new Reference($id);
+ }
+
+ if (!$services) {
+ $container->removeDefinition($this->listenerServiceId);
+
+ return;
+ }
+
+ $container
+ ->getDefinition($this->listenerServiceId)
+ ->setArgument(0, new IteratorArgument($services))
+ ;
+ }
+}
diff --git a/src/Symfony/Component/HttpKernel/EventListener/LocaleAwareListener.php b/src/Symfony/Component/HttpKernel/EventListener/LocaleAwareListener.php
new file mode 100644
index 0000000000..325b8cbc0d
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/EventListener/LocaleAwareListener.php
@@ -0,0 +1,76 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\EventListener;
+
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
+use Symfony\Component\HttpKernel\Event\RequestEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Contracts\Translation\LocaleAwareInterface;
+
+/**
+ * Pass the current locale to the provided services.
+ *
+ * @author Pierre Bobiet
+ */
+class LocaleAwareListener implements EventSubscriberInterface
+{
+ private $localeAwareServices;
+ private $requestStack;
+
+ /**
+ * @param LocaleAwareInterface[] $localeAwareServices
+ */
+ public function __construct(iterable $localeAwareServices, RequestStack $requestStack)
+ {
+ $this->localeAwareServices = $localeAwareServices;
+ $this->requestStack = $requestStack;
+ }
+
+ public function onKernelRequest(RequestEvent $event): void
+ {
+ $this->setLocale($event->getRequest()->getLocale(), $event->getRequest()->getDefaultLocale());
+ }
+
+ public function onKernelFinishRequest(FinishRequestEvent $event): void
+ {
+ if (null === $parentRequest = $this->requestStack->getParentRequest()) {
+ $this->setLocale($event->getRequest()->getDefaultLocale());
+
+ return;
+ }
+
+ $this->setLocale($parentRequest->getLocale(), $parentRequest->getDefaultLocale());
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return [
+ // must be registered after the Locale listener
+ KernelEvents::REQUEST => [['onKernelRequest', 15]],
+ KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', -15]],
+ ];
+ }
+
+ private function setLocale(string $locale, ?string $defaultLocale = null): void
+ {
+ foreach ($this->localeAwareServices as $service) {
+ try {
+ $service->setLocale($locale);
+ } catch (\InvalidArgumentException $e) {
+ $service->setLocale($defaultLocale);
+ }
+ }
+ }
+}
diff --git a/src/Symfony/Component/HttpKernel/EventListener/TranslatorListener.php b/src/Symfony/Component/HttpKernel/EventListener/TranslatorListener.php
index 4d022f750c..d28eee2b1a 100644
--- a/src/Symfony/Component/HttpKernel/EventListener/TranslatorListener.php
+++ b/src/Symfony/Component/HttpKernel/EventListener/TranslatorListener.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\HttpKernel\EventListener;
+@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3 and will be removed in 5.0, use LocaleAwareListener instead.', TranslatorListener::class), E_USER_DEPRECATED);
+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
@@ -25,7 +27,7 @@ use Symfony\Contracts\Translation\LocaleAwareInterface;
*
* @author Fabien Potencier
*
- * @final since Symfony 4.3
+ * @deprecated since Symfony 4.3, use LocaleAwareListener instead
*/
class TranslatorListener implements EventSubscriberInterface
{
diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterLocaleAwareServicesPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterLocaleAwareServicesPassTest.php
new file mode 100644
index 0000000000..aa3c6aa0c4
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterLocaleAwareServicesPassTest.php
@@ -0,0 +1,59 @@
+register('locale_aware_listener', LocaleAwareListener::class)
+ ->setPublic(true)
+ ->setArguments([null, null]);
+
+ $container->register('some_locale_aware_service', LocaleAwareInterface::class)
+ ->setPublic(true)
+ ->addTag('kernel.locale_aware');
+
+ $container->register('another_locale_aware_service', LocaleAwareInterface::class)
+ ->setPublic(true)
+ ->addTag('kernel.locale_aware');
+
+ $container->addCompilerPass(new RegisterLocaleAwareServicesPass());
+ $container->compile();
+
+ $this->assertEquals(
+ [
+ new IteratorArgument([
+ 0 => new Reference('some_locale_aware_service'),
+ 1 => new Reference('another_locale_aware_service'),
+ ]),
+ null,
+ ],
+ $container->getDefinition('locale_aware_listener')->getArguments()
+ );
+ }
+
+ public function testListenerUnregisteredWhenNoLocaleAwareServices()
+ {
+ $container = new ContainerBuilder();
+
+ $container->register('locale_aware_listener', LocaleAwareListener::class)
+ ->setPublic(true)
+ ->setArguments([null, null]);
+
+ $container->addCompilerPass(new RegisterLocaleAwareServicesPass());
+ $container->compile();
+
+ $this->assertFalse($container->hasDefinition('locale_aware_listener'));
+ }
+}
diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleAwareListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleAwareListenerTest.php
new file mode 100644
index 0000000000..489b02151c
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleAwareListenerTest.php
@@ -0,0 +1,119 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\Tests\EventListener;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
+use Symfony\Component\HttpKernel\Event\RequestEvent;
+use Symfony\Component\HttpKernel\EventListener\LocaleAwareListener;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Contracts\Translation\LocaleAwareInterface;
+
+class LocaleAwareListenerTest extends TestCase
+{
+ private $listener;
+ private $localeAwareService;
+ private $requestStack;
+
+ protected function setUp()
+ {
+ $this->localeAwareService = $this->getMockBuilder(LocaleAwareInterface::class)->getMock();
+ $this->requestStack = new RequestStack();
+ $this->listener = new LocaleAwareListener(new \ArrayIterator([$this->localeAwareService]), $this->requestStack);
+ }
+
+ public function testLocaleIsSetInOnKernelRequest()
+ {
+ $this->localeAwareService
+ ->expects($this->once())
+ ->method('setLocale')
+ ->with($this->equalTo('fr'));
+
+ $event = new RequestEvent($this->createHttpKernel(), $this->createRequest('fr'), HttpKernelInterface::MASTER_REQUEST);
+ $this->listener->onKernelRequest($event);
+ }
+
+ public function testDefaultLocaleIsUsedOnExceptionsInOnKernelRequest()
+ {
+ $this->localeAwareService
+ ->expects($this->at(0))
+ ->method('setLocale')
+ ->will($this->throwException(new \InvalidArgumentException()));
+ $this->localeAwareService
+ ->expects($this->at(1))
+ ->method('setLocale')
+ ->with($this->equalTo('en'));
+
+ $event = new RequestEvent($this->createHttpKernel(), $this->createRequest('fr'), HttpKernelInterface::MASTER_REQUEST);
+ $this->listener->onKernelRequest($event);
+ }
+
+ public function testLocaleIsSetInOnKernelFinishRequestWhenParentRequestExists()
+ {
+ $this->localeAwareService
+ ->expects($this->once())
+ ->method('setLocale')
+ ->with($this->equalTo('fr'));
+
+ $this->requestStack->push($this->createRequest('fr'));
+ $this->requestStack->push($subRequest = $this->createRequest('de'));
+
+ $event = new FinishRequestEvent($this->createHttpKernel(), $subRequest, HttpKernelInterface::SUB_REQUEST);
+ $this->listener->onKernelFinishRequest($event);
+ }
+
+ public function testLocaleIsSetToDefaultOnKernelFinishRequestWhenParentRequestDoesNotExist()
+ {
+ $this->localeAwareService
+ ->expects($this->once())
+ ->method('setLocale')
+ ->with($this->equalTo('en'));
+
+ $this->requestStack->push($subRequest = $this->createRequest('de'));
+
+ $event = new FinishRequestEvent($this->createHttpKernel(), $subRequest, HttpKernelInterface::SUB_REQUEST);
+ $this->listener->onKernelFinishRequest($event);
+ }
+
+ public function testDefaultLocaleIsUsedOnExceptionsInOnKernelFinishRequest()
+ {
+ $this->localeAwareService
+ ->expects($this->at(0))
+ ->method('setLocale')
+ ->will($this->throwException(new \InvalidArgumentException()));
+ $this->localeAwareService
+ ->expects($this->at(1))
+ ->method('setLocale')
+ ->with($this->equalTo('en'));
+
+ $this->requestStack->push($this->createRequest('fr'));
+ $this->requestStack->push($subRequest = $this->createRequest('de'));
+
+ $event = new FinishRequestEvent($this->createHttpKernel(), $subRequest, HttpKernelInterface::SUB_REQUEST);
+ $this->listener->onKernelFinishRequest($event);
+ }
+
+ private function createHttpKernel()
+ {
+ return $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock();
+ }
+
+ private function createRequest($locale)
+ {
+ $request = new Request();
+ $request->setLocale($locale);
+
+ return $request;
+ }
+}
diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/TranslatorListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/TranslatorListenerTest.php
index 3fecf9aab3..1627a2b191 100644
--- a/src/Symfony/Component/HttpKernel/Tests/EventListener/TranslatorListenerTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/TranslatorListenerTest.php
@@ -19,6 +19,9 @@ use Symfony\Component\HttpKernel\EventListener\TranslatorListener;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Contracts\Translation\LocaleAwareInterface;
+/**
+ * @group legacy
+ */
class TranslatorListenerTest extends TestCase
{
private $listener;