【问题标题】:Symfony 3, detect browser languageSymfony 3,检测浏览器语言
【发布时间】:2018-02-11 19:36:46
【问题描述】:

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

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

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

感谢您的帮助

【问题讨论】:

  • 试试JMSI18nRoutingBundle。你也可以手动分析Accept-Language请求头。

标签: symfony browser lang


【解决方案1】:

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

对于您的情况,您想挂接到 kernel.request 事件。

典型用途:向请求添加更多信息、初始化系统的某些部分或在可能的情况下返回响应(例如拒绝访问的安全层)。

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

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 标头并使用 Negotiation\LanguageNegotiator 确定最佳语言环境。请注意,因为我没有添加 use 语句,但它们应该相当明显。

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

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

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

Symfony 文档还包含一个示例,如何读取语言环境并使用侦听器使其在会话中保持粘性:https://symfony.com/doc/current/session/locale_sticky_session.html 这个例子还展示了如何在你的 services.yml 中注册监听器。

【讨论】:

  • LanguageNegotiatorclass 我猜没有 Symfony Core 组件是什么命名空间? @dbrumann
  • 对不起,省略了这个。类的全名是\Negotiation\LanguageNegotiator。这不是 Symfony 的一部分,而是来自 3rd 方库。您可以使用 willdurand/negotiation(通过作曲家)与作曲家一起要求它。请参阅:packagist.org/packages/willdurand/negotiation 和 williamdurand.fr/Negotiation
  • 不错的答案!在 Symfony 4 中,GetResponseEvent 已弃用,请改用 RequestEvent。
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-30
    • 2011-04-15
    • 2019-02-17
    • 2022-01-13
    • 1970-01-01
    • 2017-05-31
    相关资源
    最近更新 更多