Can JMSI18nRoutingBundle use an Accept-Language HTTP array?

I am trying to make an internationalized website with a URL prefix for each translated language (e.g. /fr/my/page or /it/my/page ).

I tried JMSI18nRoutingBundle and it works very well, with almost no additional configuration. But I really want to automatically determine my preferred language. The languages ​​of the selected user are passed to the Accept-Language HTTP header, and I want to select the first language in which I have a translation.

Here is my JMSI18nRouting configuration:

 jms_i18n_routing: default_locale: en locales: [fr, en] strategy: prefix_except_default 

I need this type of behavior:

http://mywebsite.com/my/page automatically detect the language and then redirect to /xx/... (where xx is the user's favorite language) because the language is not specified in the URL. Currently, the default language is EN.

http://mywebsite.com/XX/my/page shows a page in XX - Currently works fine.

Any idea to do this? OK configuration?

Oh, and if anyone has the decision to do the same in pure Symfony (without the JMSI18nRoutingBundle), my ears are wide open.

EDIT / Found a way to have smart redirects with the JMSI18nRoutingBundle to respect the user's language or allow the user to force the language to display. See My answer .

+6
source share
3 answers

Finally, I answer my question.

I developed a small β€œpatch” that uses the JMSI18nRoutingBundle and determines the user's preferred language, and also allows the user to force the language to be used.

Create YourBundle/EventListener/LocaleListener.php

This listener will change the URL if the user prefers the locale is different from the language defined by Symfony or JMSI18nRoutingBundle. So you have two URLs for two different content in two different languages: this is SEO friendly.

You can also create a language selector consisting of hrefing links to ?setlang=xx , where xx is the language that the user wants to display. The listener will detect the setlang request and force it to display xx lang, including in the following requests.

Pay attention to the array $this->translatable = [... This allows you to determine which parts of your site are translated / translatable. Granularity can be determined from the supplier to the method of action. You can also create a node configuration to define your translatable providers / packages / controllers, I am not doing this due to performance reasons.

 <?php namespace YourVendor\YourBundle\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; class LocaleListener implements EventSubscriberInterface { private $defaultLocale; private $acceptedLocales; private $translatable; public function __construct($router, $defaultLocale, $acceptedLocales) { $this->router = $router; $this->defaultLocale = $defaultLocale; $this->acceptedLocales = $acceptedLocales; $this->translatable = [ 'Vendor1', 'Vendor2\Bundle1', 'Vendor2\Bundle2\Controller1', 'Vendor2\Bundle2\Controller2::myPageAction', ]; } public function onKernelRequest(GetResponseEvent $event) { $request = $event->getRequest(); $route = $request->get('_route'); if(!empty($newLocale = $request->query->get('setlang'))) { if(in_array($newLocale, $this->acceptedLocales)) { $cookie = new Cookie('force_lang', $newLocale, time() + 3600 * 24 * 7); $url = $this->router->generate($route, ['_locale' => $newLocale] + $request->attributes->get('_route_params')); $response = new RedirectResponse($url); $response->headers->setCookie($cookie); $event->setResponse($response); } } else if($this->translatable($request->attributes->get('_controller'))) { $preferred = empty($force = $request->cookies->get('force_lang')) ? $request->getPreferredLanguage($this->acceptedLocales) : $force; if($preferred && $request->attributes->get('_locale') != $preferred) { $url = $this->router->generate($route, ['_locale' => $preferred] + $request->attributes->get('_route_params')); $event->setResponse(new RedirectResponse($url)); } } } private function translatable($str) { foreach($this->translatable as $t) { if(strpos($str, $t) !== false) return true; } return false; } public static function getSubscribedEvents() { return [ KernelEvents::REQUEST => [['onKernelRequest', 200]] ]; } } 

Bind the listener to the HTTP core.

Modify the services.yml file.

 services: app.event_listener.locale_listener: class: YourVendor\YourBundle\EventListener\LocaleListener arguments: ["@router", "%kernel.default_locale%", "%jms_i18n_routing.locales%"] tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest } 

JMSI18nRoutingBundle Configuration

You have nothing to change. Example:

 # JMS i18n Routing Configuration jms_i18n_routing: default_locale: "%locale%" locales: [fr, en] strategy: prefix_except_default 
+4
source

Here is a way to do this using direct symfony. This may seem a bit hacky, because for each action you need to specify 2 routes, so if someone can think of a better way, I’m all ears.

First, I would define some configuration parameter for all valid locales and list the first one by default

parameters.yml.dist:

 parameters: accepted_locales: [en, es, fr] 

Then, make sure your controller routes match when _locale installed and not installed. Use the same route name for both except the suffix without _locale with a separator of type | :

 /** * @Route("/{_locale}/test/{var}", name="test") * @Route( "/test/{var}", name="test|") */ public function testAction(Request $request, $var, $_locale = null) { // whatever your controller action does } 

Next, determine the service that will listen to the Controller event and pass the received locales to you:

 <service id="kernel.listener.locale" class="My\Bundle\EventListener\LocaleListener"> <tag name="kernel.event_listener" event="kernel.controller" method="onKernelController" /> <argument>%accepted_locales%</argument> </service> 

Now use the service to determine if _locale is _locale on your route, and if not, determine the locale based on the HTTP_ACCEPT_LANGUAGE header and redirect the route containing it. Here is an example of a listener who will do this (I added comments to explain what I'm doing):

 namespace NAB\UtilityBundle\EventListener; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\Event\FilterControllerEvent; class ControllerListener { private $acceptedLocales; public function __construct(array $acceptedLocales) { $this->acceptedLocales = $acceptedLocales; } public function onKernelController(FilterControllerEvent $event) { if (HttpKernelInterface::MASTER_REQUEST != $event->getRequestType()) { return; } $controller = $event->getController(); if (!is_array($controller)) { return; } $request = $event->getRequest(); $params = $request->attributes->get('_route_params'); // return if _locale is already set on the route if ($request->attributes->get('_locale')) { return; } // if the user has accepted languages set, set the locale on the first match found $languages = $request->server->get('HTTP_ACCEPT_LANGUAGE'); if (!empty($languages)) { foreach (explode(',', $languages) as $language) { $splits = array(); $pattern = '/^(?P<primarytag>[a-zA-Z]{2,8})(?:-(?P<subtag>[a-zA-Z]{2,8}))?(?:(?:;q=)(?P<quantifier>\d\.\d))?$/'; // if the user locale matches the accepted locales, set _locale in the route params if (preg_match($pattern, $language, $splits) && in_array($splits['primarytag'], $this->acceptedLocales)) { $params['_locale'] = $splits['primarytag']; // stop checking once the first match is found break; } } } // if no locale was found, default to the first accepted locale if (!$params['_locale']) { $params['_locale'] = $this->acceptedLocales[0]; } // drop the '|' to get the appropriate route name list($localeRoute) = explode('|', $request->attributes->get('_route')); // attempt get the redirect URL but return if it could not be found try { $redirectUrl = $controller[0]->generateUrl($localeRoute, $params); } catch (\Exception $e) { return; } // set the controller response to redirect to the route we just created $event->setController(function() use ($redirectUrl) { return new RedirectResponse($redirectUrl); }); } } 

For more information about configuring the filter before the filter on the controller, check out the Symfony documentation here . If you use something like this, be very careful to correctly identify each route name.

+1
source

Another useful solution
Go to the I18nRoutingBundle provider and edit the listener

 /www/vendor/jms/i18n-routing-bundle/JMS/I18nRoutingBundle/EventListener 

Replace

 $locale = $this->localeResolver->resolveLocale($request, $this->locales) ?: $this->defaultLocale; 

by

 $locale = $this->localeResolver->resolveLocale($request, $this->locales) ?: $request->getPreferredLanguage($this->locales); 

(Cleaning is easier to listen to than directly editing providers)

0
source

Source: https://habr.com/ru/post/979817/


All Articles