Symfony 3,检测浏览器语言

Symfony 3, detect browser language

我使用 Symfony 3。 我的网站有两种语言,法语和英语,人们可以通过 select 表格进行切换。 默认语言是法语。 主要 URL 是: example.com/fr 法文版和 example.com/en 英文版

好吧,现在,我希望当用户到达网站时检测他的浏览器语言并自动重定向到正确的语言。 例如,如果浏览器是法语的,他将被重定向到法语版本:example.com/fr 否则他将被重定向到英文版:example.com/en

有没有办法正确地做到这一点?

感谢您的帮助

如果你不想依赖其他包,比如 JMSI18nRoutingBundle 你必须让自己熟悉 Symfony 的事件系统,例如通过阅读 HttpKernel.

对于你的情况,你想挂钩到 kernel.request 事件。

Typical Purposes: To add more information to the Request, initialize parts of the system, or return a Response if possible (e.g. a security layer that denies access).

在您的自定义 EventListener 中,您可以监听该事件,将信息添加到路由器中使用的 Request-object。它可能看起来像这样:

class LanguageListener implements EventSubscriberInterface
{
    private $supportedLanguages;

    public function __construct(array $supportedLanguages)
    {
        if (empty($supportedLanguages)) {
            throw new \InvalidArgumentException('At least one supported language must be given.');
        }

        $this->supportedLanguages = $supportedLanguages;
    }

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

    public function redirectToLocalizedHomepage(GetResponseEvent $event)
    {
        // Do not modify sub-requests
        if (KernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
            return;
        }
        // Assume all routes except the frontpage use the _locale parameter
        if ($event->getRequest()->getPathInfo() !== '/') {
            return;
        }

        $language = $this->supportedLanguages[0];
        if (null !== $acceptLanguage = $event->getRequest()->headers->get('Accept-Language')) {
            $negotiator = new LanguageNegotiator();
            $best       = $negotiator->getBest(
                $event->getRequest()->headers->get('Accept-Language'),
                $this->supportedLanguages
            );

            if (null !== $best) {
                $language = $best->getType();
            }
        }

        $response = new RedirectResponse('/' . $language);
        $event->setResponse($response);
    }
}

此侦听器将检查请求的 Accept-Language header 并使用 Negotiation\LanguageNegotiator 确定最佳语言环境。请注意,我没有添加 use 语句,但它们应该相当明显。

对于更高级的版本,您可以从 JMSI18nRoutingBundle 中阅读 LocaleChoosingListener 的源代码。

通常只有首页才需要这样做,这就是为什么我发布的示例和 JMSBundle 中的示例都排除了所有其他路径。对于那些你可以只使用文档中描述的特殊参数 _locale

https://symfony.com/doc/current/translation/locale.html#the-locale-and-the-url

Symfony 文档还包含一个示例,说明如何使用侦听器读取语言环境并使其粘附在 session 中:https://symfony.com/doc/current/session/locale_sticky_session.html 此示例还展示了如何在 services.yml.

中注册监听器

对@dbrumann 的回答进行细微更改以处理我的用例和设置:

可用语言环境列表在 services.yml 文件中定义:

parameters:
  available_locales:
    - nl
    - en
    - cs

我想确定网站任何登陆页面上的语言环境。如果解析失败,它会回退到 _locale 参数或默认参数。

class LocaleDetermineSubscriber implements EventSubscriberInterface
{
    private $defaultLocale;
    private $parameterBag;
    private $logger;

    public function __construct(ParameterBagInterface $parameterBag,
       LoggerInterface $logger, 
       $defaultLocale = 'en')
    {
        $this->defaultLocale = $defaultLocale;
        $this->parameterBag = $parameterBag;
        $this->logger = $logger;
    }

    public function onKernelRequest(RequestEvent $event)
    {
        $request = $event->getRequest();
        //do this on first request only
        if ($request->hasPreviousSession()) {
            return;
        }

        $allowedLocales = $this->parameterBag->get('available_locales'); //defined in services.yml
        $determinedLocale = null;

        // use locale from the user preference header
        $acceptLanguage = $event->getRequest()->headers->get('Accept-Language');
        if ($acceptLanguage != null) {
            $negotiator = new LanguageNegotiator();
            try {
                $best = $negotiator->getBest($acceptLanguage, $allowedLocales);

                if ($best != null) {
                    $language = $best->getType();
                    $request->setLocale($language);
                    $determinedLocale = $language;
                }
            } catch (Exception $e) {
                $this->logger->warning("Failed to determine language from Accept-Language header " . $e);
            }
        }

        //check if locale is set with _locale parameter if user preference header parsing not happened
        if($determinedLocale == null) {
            if ($locale = $request->attributes->get('_locale')) {
                if(in_array($locale, $allowedLocales)) {
                    $request->getSession()->set('_locale', $locale);
                } else {
                    $request->setLocale($request->getSession()->get('_locale', $this->defaultLocale));
                }
            } else {
                //fallback to default
                $request->setLocale($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', 25]],
        ];
    }
}

它使用willdurand/negotiation包,所以需要先安装:

composer require willdurand/negotiation

https://packagist.org/packages/willdurand/negotiation