我正在尝试建立一个国际化网站,我翻译的每种语言都有一个网址前缀(例如/fr/my/page
或/it/my/page
)。
我尝试了JMSI18nRoutingBundle,它几乎没有其他配置,效果很好。但我真的想自动确定用户首选语言。
用户最喜欢的语言被传输到Accept-Language
HTTP标头,我想选择我翻译的第一种语言。
这是我的JMSI18nRouting配置:
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进行智能重定向,以尊重用户最喜欢的语言或让用户强制显示语言。查看我的answer。
答案 0 :(得分:4)
最后,我回答了我的问题。
我开发了一个小“补丁”,使用 JMSI18nRoutingBundle并检测用户的首选语言,并让用户强制使用语言。
YourBundle/EventListener/LocaleListener.php
如果用户的首选语言环境与Symfony或JMSI18nRoutingBundle定义的语言环境不同,则此侦听器将更改URL。通过这种方式,您可以使用两种不同语言的两种不同内容的URL:它是SEO友好的。
您还可以创建一个由 hrefing 到?setlang=xx
的链接组成的语言选择器,其中xx
是用户想要显示的语言。侦听器将检测setlang
查询并强制显示xx
lang,包括在下一个请求中。
注意$this->translatable = [...
数组。它可以让您定义网站的哪些部分可以翻译/翻译。可以从供应商到操作方法定义粒度。
您还可以创建一个配置节点来定义可翻译的供应商/软件包/控制器,由于性能方面的考虑,我没有这样做。
<?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]] ];
}
}
修改您的services.yml
文件。
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
你无需改变。 例如:
# JMS i18n Routing Configuration
jms_i18n_routing:
default_locale: "%locale%"
locales: [fr, en]
strategy: prefix_except_default
答案 1 :(得分:1)
这是使用直接Symfony进行此操作的方法。它可能会觉得有点hacky因为它需要为每个动作指定2条路线,所以如果有人能想出更好的方式,我就会全力以赴。
首先,我将为所有可接受的语言环境定义某种配置参数,并将第一个列为默认语言
<强> parameters.yml.dist:强>
parameters:
accepted_locales: [en, es, fr]
然后确保您的Controller路由匹配时_locale
已设置且未设置。对两者使用相同的路由名称,但没有_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
}
接下来定义一个服务,该服务将侦听Controller事件并将接受的语言环境传递给它:
<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>
现在使用该服务检测路由中是否设置了_locale
,如果没有,则根据HTTP_ACCEPT_LANGUAGE
标头确定区域设置并重定向到包含它的路由。这是一个示例监听器,它将执行此操作(我添加了注释来解释我在做什么):
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's 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);
});
}
}
有关在Controller上设置前置过滤器的进一步说明,请查看the Symfony documentation here。如果你使用这样的东西,要非常小心,正确定义每个路径名称。
答案 2 :(得分:0)
另一种更有用的解决方案
转到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);
(覆盖监听器比直接编辑供应商更清晰)