Symfony JMSI18nRoutingBundle能否使用HTTP接受语言数组?

Symfony JMSI18nRoutingBundle能否使用HTTP接受语言数组?,symfony,internationalization,Symfony,Internationalization,我正在尝试创建一个国际化的网站,为我翻译的每种语言(例如/fr/my/page或/it/my/page)提供一个URL前缀 我尝试了JMSI18nRoutingBundle,它工作得非常好,几乎没有额外的配置。但我真的想自动确定用户喜欢的语言。 用户最喜欢的语言被传输到Accept LanguageHTTP头中,我想选择我有翻译的第一种语言 以下是我的JMSI18N路由配置: jms_i18n_routing: default_locale: en locales: [fr, e

我正在尝试创建一个国际化的网站,为我翻译的每种语言(例如
/fr/my/page
/it/my/page
)提供一个URL前缀

我尝试了JMSI18nRoutingBundle,它工作得非常好,几乎没有额外的配置。但我真的想自动确定用户喜欢的语言。 用户最喜欢的语言被传输到
Accept Language
HTTP头中,我想选择我有翻译的第一种语言

以下是我的JMSI18N路由配置:

jms_i18n_routing:
    default_locale: en
    locales: [fr, en]
    strategy: prefix_except_default
我想要这种行为:

http://mywebsite.com/my/page
执行自动语言检测,然后重定向到
/xx/..
(其中
xx
是用户最喜欢的语言),因为URL中未指定语言-目前默认语言为EN

http://mywebsite.com/XX/my/page
以XX语言显示页面-目前运行良好

有什么办法吗?配置是否正常

哦,而且,如果有人有办法在纯Symfony(没有JMSI18nRoutingBundle)中做同样的事情,我的耳朵是敞开的


编辑/找到一种使用JMSI18nRoutingBundle进行智能重定向的方法,以尊重用户喜爱的语言或让用户强制显示语言。请参阅我的。

这里有一个使用直接符号的方法。这可能会让人感觉有点不舒服,因为它要求每个动作指定2条路径,所以如果有人能想出更好的方法,我会全神贯注地听

首先,我将为所有可接受的地区定义某种配置参数,并将第一个列为默认值

parameters.yml.dist:

parameters:
    accepted_locales: [en, es, fr]
然后确保控制器路由与设置和未设置的
\u locale
相匹配。对这两种路由名称使用相同的路由名称,但在不带
\u locale
的路由名称后面加上类似
的分隔符:

/**
 * @Route("/{_locale}/test/{var}", name="test")
 * @Route(          "/test/{var}", name="test|")
 */
public function testAction(Request $request, $var, $_locale = null)
{
    // whatever your controller action does
}
接下来定义一个服务,该服务将侦听控制器事件并将您接受的区域设置传递给它:

<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>

有关在控制器上设置前置过滤器的详细说明,请查看。如果使用类似的方法,请非常小心,确保正确定义每个路由名称。

最后,我回答我的问题

我开发了一个小“补丁程序”使用JMSI18nRoutingBundle并检测用户的首选语言,还允许用户强制使用一种语言

创建监听器
YourBundle/EventListener/LocaleListener.php
如果用户的首选区域设置与Symfony或JMSI18nRoutingBundle定义的区域设置不同,则此侦听器将更改URL。这样,您就有了两个URL,用于两种不同语言的两种不同内容:它对SEO友好

您还可以创建一个由指向
?setlang=xx
的链接组成的语言选择器,其中
xx
是用户想要显示的语言。侦听器将检测
setlang
查询,并强制显示
xx
lang,包括在下一个请求中

注意
$this->translateable=[…
数组。它允许您定义站点的哪些部分是可翻译的。粒度可以从供应商定义到操作方法。 您还可以创建一个配置节点来定义可翻译的供应商/捆绑包/控制器,出于性能考虑,我没有这样做

<?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]] ];
    }
}
JMSI18nRoutingBundle的配置
你没有什么可以改变的。
例如:


另一个更有用的解决方案
转到I18nRoutingBundle的供应商,编辑侦听器

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

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

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

(覆盖监听器比直接编辑供应商更为简洁)

Hi Morgan,您是否考虑过使用事件监听器来确定用户的语言?您可以将其与每个路由提供2个选项(一个带区域设置,一个不带区域设置)结合起来,如果本地不存在,则使用您在侦听器中检测到的区域设置值重定向。感谢您的帮助!但是,我不想为每个方法定义twho路由。因此,根据您的建议,我编写了一个侦听器,设计用于JMSI18nRoutingBundle。我发布了一个带有解释和代码的答案。再次感谢您,很抱歉没有选择y我们的解决方案!不客气!我很高兴您找到了一个更简洁、更短的解决方案,每个控制器操作需要定义2条路径,因此感觉不那么麻烦。不要担心没有选择我的解决方案,我只是很高兴它激发了一种思维过程,让您到达了需要的地方。
/www/vendor/jms/i18n-routing-bundle/JMS/I18nRoutingBundle/EventListener
$locale = $this->localeResolver->resolveLocale($request, $this->locales) ?: $this->defaultLocale;
$locale = $this->localeResolver->resolveLocale($request, $this->locales) ?: $request->getPreferredLanguage($this->locales);