Symfony 3 将所有路由重定向到当前语言环境版本
Symfony 3 Redirect All Routes To Current Locale Version
我正在开发一个 symfony 应用程序,我的目标是无论用户在哪个页面上,它都会导航到该页面的语言环境版本。
例如,如果用户导航到“/”主页,它将重定向到“/en/”
如果他们在“/admin”页面上,它将重定向到“/en/admin”,这样 _locale
属性 从路由中设置。
如果他们从用户浏览器访问 /admin,它还需要确定语言环境,因为没有确定语言环境,所以它知道要重定向到哪个页面。
目前我的默认控制器如下所示,因为我正在测试。我正在使用开发模式和分析器来测试翻译工作是否正确。
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* @Route("/", name="homepage")
* @Route("/{_locale}/", name="homepage_locale")
*/
public function indexAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
}
如果用户导航到“/”,此当前方法将使用户保持在“/”,但我想让它重定向到“/en/”。这也适用于其他页面,例如 /admin 或 /somepath/pathagain/article1 (/en/admin , /en/somepath/pathagain/article1)
我该怎么做?
我读过的参考文献没有帮助:
Symfony2 Use default locale in routing (one URL for one language)
Symfony2 default locale in routing
::更新::
我还没有解决我的问题,但我已经很接近了,并且学会了一些技巧来提高效率。
DefaultController.php
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* @Route("/", name="home", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
* @Route("/{_locale}/", name="home_locale", requirements={"_locale" = "%app.locales%"})
*/
public function indexAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
/**
* @Route("/admin", name="admin", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
* @Route("/{_locale}/admin", name="admin_locale", requirements={"_locale" = "%app.locales%"})
*/
public function adminAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
}
?>
Config.yml
imports:
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: services.yml }
# Put parameters here that don't need to change on each machine where the app is deployed
# http://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: en
app.locales: en|es|zh
framework:
#esi: ~
translator: { fallbacks: ["%locale%"] }
secret: "%secret%"
router:
resource: "%kernel.root_dir%/config/routing.yml"
strict_requirements: ~
form: ~
csrf_protection: ~
validation: { enable_annotations: true }
#serializer: { enable_annotations: true }
templating:
engines: ['twig']
#assets_version: SomeVersionScheme
default_locale: "%locale%"
trusted_hosts: ~
trusted_proxies: ~
session:
# handler_id set to null will use default session handler from php.ini
handler_id: ~
save_path: "%kernel.root_dir%/../var/sessions/%kernel.environment%"
fragments: ~
http_method_override: true
assets: ~
# Twig Configuration
twig:
debug: "%kernel.debug%"
strict_variables: "%kernel.debug%"
# Doctrine Configuration
doctrine:
dbal:
driver: pdo_mysql
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
# if using pdo_sqlite as your database driver:
# 1. add the path in parameters.yml
# e.g. database_path: "%kernel.root_dir%/data/data.db3"
# 2. Uncomment database_path in parameters.yml.dist
# 3. Uncomment next line:
# path: "%database_path%"
orm:
auto_generate_proxy_classes: "%kernel.debug%"
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
# Swiftmailer Configuration
swiftmailer:
transport: "%mailer_transport%"
host: "%mailer_host%"
username: "%mailer_user%"
password: "%mailer_password%"
spool: { type: memory }
注意参数下的值app.locales: en|es|zh
。现在,如果我计划在未来支持更多语言环境,那么每当我创建路线时,我都可以参考这个值。对于那些好奇的人,这些路线依次是英语、西班牙语、中文。在注释中的 DefaultController 中,"%app.locales%"
是引用配置参数的部分。
我当前方法的问题是 /admin 不会将用户重定向到 /{browsers locale}/admin,这将是保持一切井井有条的更优雅的解决方案......但至少路线工作。仍在寻找更好的解决方案。
****更新****
我想我可能在这里找到了答案,作为给出的底部答案(Add locale and requirements to all routes - Symfony2),Athlan 的答案。只是不确定如何在 symfony 3 中实现它,因为他的指示对我来说不够清楚。
我认为这篇文章也可能有所帮助 (http://symfony.com/doc/current/components/event_dispatcher/introduction.html)
经过 12 小时的调查,我终于找到了一个可以接受的解决方案。如果可以提高效率,请 post 修改此解决方案的版本。
有些事情需要注意,我的解决方案是针对我的需要的。它的作用是强制任何 URL 转到本地化版本(如果存在)。
创建路由时需要遵循一些约定。
DefaultController.php
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* @Route("/{_locale}/", name="home_locale", requirements={"_locale" = "%app.locales%"})
*/
public function indexAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
/**
* @Route("/{_locale}/admin", name="admin_locale", requirements={"_locale" = "%app.locales%"})
*/
public function adminAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
}
?>
请注意,两条路线始终以“/{_locale}/”开头。为此,您项目中的每条路线都需要有这个。您只需在后面输入真实的路线名称即可。对我来说,我可以接受这种情况。您可以轻松修改我的解决方案以满足您的需求。
第一步是在 httpKernal 上创建一个侦听器,以便在请求到达路由器进行渲染之前拦截请求。
LocaleRewriteListener.php
<?php
//src/AppBundle/EventListener/LocaleRewriteListener.php
namespace AppBundle\EventListener;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\RouteCollection;
class LocaleRewriteListener implements EventSubscriberInterface
{
/**
* @var Symfony\Component\Routing\RouterInterface
*/
private $router;
/**
* @var routeCollection \Symfony\Component\Routing\RouteCollection
*/
private $routeCollection;
/**
* @var string
*/
private $defaultLocale;
/**
* @var array
*/
private $supportedLocales;
/**
* @var string
*/
private $localeRouteParam;
public function __construct(RouterInterface $router, $defaultLocale = 'en', array $supportedLocales = array('en'), $localeRouteParam = '_locale')
{
$this->router = $router;
$this->routeCollection = $router->getRouteCollection();
$this->defaultLocale = $defaultLocale;
$this->supportedLocales = $supportedLocales;
$this->localeRouteParam = $localeRouteParam;
}
public function isLocaleSupported($locale)
{
return in_array($locale, $this->supportedLocales);
}
public function onKernelRequest(GetResponseEvent $event)
{
//GOAL:
// Redirect all incoming requests to their /locale/route equivlent as long as the route will exists when we do so.
// Do nothing if it already has /locale/ in the route to prevent redirect loops
$request = $event->getRequest();
$path = $request->getPathInfo();
$route_exists = false; //by default assume route does not exist.
foreach($this->routeCollection as $routeObject){
$routePath = $routeObject->getPath();
if($routePath == "/{_locale}".$path){
$route_exists = true;
break;
}
}
//If the route does indeed exist then lets redirect there.
if($route_exists == true){
//Get the locale from the users browser.
$locale = $request->getPreferredLanguage();
//If no locale from browser or locale not in list of known locales supported then set to defaultLocale set in config.yml
if($locale=="" || $this->isLocaleSupported($locale)==false){
$locale = $request->getDefaultLocale();
}
$event->setResponse(new RedirectResponse("/".$locale.$path));
}
//Otherwise do nothing and continue on~
}
public static function getSubscribedEvents()
{
return array(
// must be registered before the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest', 17)),
);
}
}
最后设置 services.yml 以启动侦听器。
Services.yml
# Learn more about services, parameters and containers at
# http://symfony.com/doc/current/book/service_container.html
parameters:
# parameter_name: value
services:
# service_name:
# class: AppBundle\Directory\ClassName
# arguments: ["@another_service_name", "plain_value", "%parameter_name%"]
appBundle.eventListeners.localeRewriteListener:
class: AppBundle\EventListener\LocaleRewriteListener
arguments: ["@router", "%kernel.default_locale%", "%locale_supported%"]
tags:
- { name: kernel.event_subscriber }
同样在 config.yml 中,您需要在参数下添加以下内容:
config.yml
parameters:
locale: en
app.locales: en|es|zh
locale_supported: ['en','es','zh']
我希望只有一个地方可以定义语言环境,但最后我不得不做 2 个...但至少它们在同一个地方,所以很容易改变。
app.locales用于默认控制器(requirements={"_locale" = "%app.locales%"})
,locale_supported用于LocaleRewriteListener。如果它检测到不在列表中的语言环境,它将回退到默认语言环境,在本例中为 locale:en.
的值
app.locales 非常适合 requirements 命令,因为对于任何不匹配的语言环境,它都会导致 404。
如果您使用的是表单并且有登录名,则需要对您的 security.yml
执行以下操作
Security.yml
# To get started with security, check out the documentation:
# http://symfony.com/doc/current/book/security.html
security:
encoders:
Symfony\Component\Security\Core\User\User:
algorithm: bcrypt
cost: 12
AppBundle\Entity\User:
algorithm: bcrypt
cost: 12
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
# http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
database:
entity: { class: AppBundle:User }
#property: username
# if you're using multiple entity managers
# manager_name: customer
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
anonymous: true
form_login:
check_path: login_check
login_path: login_route
provider: database
csrf_token_generator: security.csrf.token_manager
remember_me:
secret: '%secret%'
lifetime: 604800 # 1 week in seconds
path: /
httponly: false
#httponly false does make this vulnerable in XSS attack, but I will make sure that is not possible.
logout:
path: /logout
target: /
access_control:
# require ROLE_ADMIN for /admin*
#- { path: ^/login, roles: ROLE_ADMIN }
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/(.*?)/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER }
这里要注意的重要变化是 (.*?)/login
将进行匿名身份验证,因此您的用户仍然可以登录。这确实意味着像..dogdoghere/login 这样的路由可能会触发,但我稍后将在登录路由上向您展示的要求会阻止这种情况并会抛出 404 错误。我喜欢 (.*?)
与 [a-z]{2}
的解决方案,以防您想使用 en_US 类型的语言环境。
SecurityController.php
<?php
// src/AppBundle/Controller/SecurityController.php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class SecurityController extends Controller
{
/**
* @Route("{_locale}/login", name="login_route", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
*/
public function loginAction(Request $request)
{
$authenticationUtils = $this->get('security.authentication_utils');
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render(
'security/login.html.twig',
array(
// last username entered by the user
'last_username' => $lastUsername,
'error' => $error,
)
);
}
/**
* @Route("/{_locale}/login_check", name="login_check", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
*/
public function loginCheckAction()
{
// this controller will not be executed,
// as the route is handled by the Security system
}
/**
* @Route("/logout", name="logout")
*/
public function logoutAction()
{
}
}
?>
请注意,即使这些路径在前面也使用 {_locale}。但是我喜欢这样,所以我可以为不同的语言环境提供自定义登录。要时刻铭记在心。唯一不需要语言环境的路由是注销,它工作得很好,因为它实际上只是安全系统的拦截路由。另请注意,它使用了从 config.yml 设置的要求,因此您只需在一个地方为项目中的所有路由编辑它。
希望这对尝试做我正在做的事情的人有所帮助!
注意:: 为了轻松测试这一点,我对 Google Chrome 使用 'Quick Language Switcher' 扩展,这会更改所有请求的 accept-language header。
我没有足够的声誉来向正确的解决方案添加评论。所以我要添加一个新答案
您可以像这样在 app/config/routing.yml 添加 "prefix: /{_locale}":
app:
resource: "@AppBundle/Controller/"
type: annotation
prefix: /{_locale}
因此您无需将其添加到每个操作的每个路由中。对于以下步骤。非常感谢,非常完美。
Symfony 3.4 的小改进:
确保 getSubscribedEvents() 将在 RouterListener::onKernelRequest 和 LocaleListener::onKernelRequest 之前注册 LocaleRewriteListener。整数 17 必须大于 RouterListener::onKernelRequest 优先级。否则你会得到 404.
bin/console debug:event-调度员
services.yml 中的服务定义必须是(取决于 Symfony 配置):
AppBundle\EventListener\LocaleRewriteListener:
参数:['@router', '%kernel.default_locale%', '%locale_supported%']
标签:
- { 名称:kernel.event_subscriber,事件:kernel.request }
最终函数 smallResumeOfResearching($localeRewrite, $opinion = 'IMHO') :)
方法,由先生提供。 Joseph 在 /{route_name} 或 / 这样的路线上表现出色,但在 /article/slug/other.
这样的路线上表现不佳
如果我们使用修改后的mr.Joseph的方法,由提供,我们将在开发模式下丢失分析器和调试器。
如果我们想要更灵活的解决方案,onKernelRequest方法可以这样修改(感谢mr. Joseph,感谢):
public function onKernelRequest(GetResponseEvent $event)
{
$pathInfo = $event->getRequest()->getPathinfo();
$baseUrl = $event->getRequest()->getBaseUrl();
$checkLocale = explode('/', ltrim($pathInfo, '/'))[0];
//Or some other logic to detect/provide locale
if (($this->isLocaleSupported($checkLocale) == false) && ($this->defaultLocale !== $checkLocale)) {
if ($this->isProfilerRoute($checkLocale) == false) {
$locale = $this->defaultLocale;
$event->setResponse(new RedirectResponse($baseUrl . '/' . $locale . $pathInfo));
}
/* Or with matcher:
try {
//Try to match the path with the locale prefix
$this->matcher->match('/' . $locale . $pathInfo);
//$event->setResponse(new RedirectResponse($baseUrl . '/' . $locale . $pathInfo));
} catch (\Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {
} catch (\Symfony\Component\Routing\Exception\MethodNotAllowedException $e) {
}
*/
}
}
注意:$this->profilerRoutes = array('_profiler', '_wdt', '_error');
- 感谢 Susana Santos 指出简单的配置方法:)
我正在开发一个 symfony 应用程序,我的目标是无论用户在哪个页面上,它都会导航到该页面的语言环境版本。
例如,如果用户导航到“/”主页,它将重定向到“/en/”
如果他们在“/admin”页面上,它将重定向到“/en/admin”,这样 _locale
属性 从路由中设置。
如果他们从用户浏览器访问 /admin,它还需要确定语言环境,因为没有确定语言环境,所以它知道要重定向到哪个页面。
目前我的默认控制器如下所示,因为我正在测试。我正在使用开发模式和分析器来测试翻译工作是否正确。
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* @Route("/", name="homepage")
* @Route("/{_locale}/", name="homepage_locale")
*/
public function indexAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
}
如果用户导航到“/”,此当前方法将使用户保持在“/”,但我想让它重定向到“/en/”。这也适用于其他页面,例如 /admin 或 /somepath/pathagain/article1 (/en/admin , /en/somepath/pathagain/article1)
我该怎么做?
我读过的参考文献没有帮助:
Symfony2 Use default locale in routing (one URL for one language)
Symfony2 default locale in routing
::更新::
我还没有解决我的问题,但我已经很接近了,并且学会了一些技巧来提高效率。
DefaultController.php
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* @Route("/", name="home", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
* @Route("/{_locale}/", name="home_locale", requirements={"_locale" = "%app.locales%"})
*/
public function indexAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
/**
* @Route("/admin", name="admin", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
* @Route("/{_locale}/admin", name="admin_locale", requirements={"_locale" = "%app.locales%"})
*/
public function adminAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
}
?>
Config.yml
imports:
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: services.yml }
# Put parameters here that don't need to change on each machine where the app is deployed
# http://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: en
app.locales: en|es|zh
framework:
#esi: ~
translator: { fallbacks: ["%locale%"] }
secret: "%secret%"
router:
resource: "%kernel.root_dir%/config/routing.yml"
strict_requirements: ~
form: ~
csrf_protection: ~
validation: { enable_annotations: true }
#serializer: { enable_annotations: true }
templating:
engines: ['twig']
#assets_version: SomeVersionScheme
default_locale: "%locale%"
trusted_hosts: ~
trusted_proxies: ~
session:
# handler_id set to null will use default session handler from php.ini
handler_id: ~
save_path: "%kernel.root_dir%/../var/sessions/%kernel.environment%"
fragments: ~
http_method_override: true
assets: ~
# Twig Configuration
twig:
debug: "%kernel.debug%"
strict_variables: "%kernel.debug%"
# Doctrine Configuration
doctrine:
dbal:
driver: pdo_mysql
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
# if using pdo_sqlite as your database driver:
# 1. add the path in parameters.yml
# e.g. database_path: "%kernel.root_dir%/data/data.db3"
# 2. Uncomment database_path in parameters.yml.dist
# 3. Uncomment next line:
# path: "%database_path%"
orm:
auto_generate_proxy_classes: "%kernel.debug%"
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
# Swiftmailer Configuration
swiftmailer:
transport: "%mailer_transport%"
host: "%mailer_host%"
username: "%mailer_user%"
password: "%mailer_password%"
spool: { type: memory }
注意参数下的值app.locales: en|es|zh
。现在,如果我计划在未来支持更多语言环境,那么每当我创建路线时,我都可以参考这个值。对于那些好奇的人,这些路线依次是英语、西班牙语、中文。在注释中的 DefaultController 中,"%app.locales%"
是引用配置参数的部分。
我当前方法的问题是 /admin 不会将用户重定向到 /{browsers locale}/admin,这将是保持一切井井有条的更优雅的解决方案......但至少路线工作。仍在寻找更好的解决方案。
****更新****
我想我可能在这里找到了答案,作为给出的底部答案(Add locale and requirements to all routes - Symfony2),Athlan 的答案。只是不确定如何在 symfony 3 中实现它,因为他的指示对我来说不够清楚。
我认为这篇文章也可能有所帮助 (http://symfony.com/doc/current/components/event_dispatcher/introduction.html)
经过 12 小时的调查,我终于找到了一个可以接受的解决方案。如果可以提高效率,请 post 修改此解决方案的版本。
有些事情需要注意,我的解决方案是针对我的需要的。它的作用是强制任何 URL 转到本地化版本(如果存在)。
创建路由时需要遵循一些约定。
DefaultController.php
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* @Route("/{_locale}/", name="home_locale", requirements={"_locale" = "%app.locales%"})
*/
public function indexAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
/**
* @Route("/{_locale}/admin", name="admin_locale", requirements={"_locale" = "%app.locales%"})
*/
public function adminAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
}
?>
请注意,两条路线始终以“/{_locale}/”开头。为此,您项目中的每条路线都需要有这个。您只需在后面输入真实的路线名称即可。对我来说,我可以接受这种情况。您可以轻松修改我的解决方案以满足您的需求。
第一步是在 httpKernal 上创建一个侦听器,以便在请求到达路由器进行渲染之前拦截请求。
LocaleRewriteListener.php
<?php
//src/AppBundle/EventListener/LocaleRewriteListener.php
namespace AppBundle\EventListener;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\RouteCollection;
class LocaleRewriteListener implements EventSubscriberInterface
{
/**
* @var Symfony\Component\Routing\RouterInterface
*/
private $router;
/**
* @var routeCollection \Symfony\Component\Routing\RouteCollection
*/
private $routeCollection;
/**
* @var string
*/
private $defaultLocale;
/**
* @var array
*/
private $supportedLocales;
/**
* @var string
*/
private $localeRouteParam;
public function __construct(RouterInterface $router, $defaultLocale = 'en', array $supportedLocales = array('en'), $localeRouteParam = '_locale')
{
$this->router = $router;
$this->routeCollection = $router->getRouteCollection();
$this->defaultLocale = $defaultLocale;
$this->supportedLocales = $supportedLocales;
$this->localeRouteParam = $localeRouteParam;
}
public function isLocaleSupported($locale)
{
return in_array($locale, $this->supportedLocales);
}
public function onKernelRequest(GetResponseEvent $event)
{
//GOAL:
// Redirect all incoming requests to their /locale/route equivlent as long as the route will exists when we do so.
// Do nothing if it already has /locale/ in the route to prevent redirect loops
$request = $event->getRequest();
$path = $request->getPathInfo();
$route_exists = false; //by default assume route does not exist.
foreach($this->routeCollection as $routeObject){
$routePath = $routeObject->getPath();
if($routePath == "/{_locale}".$path){
$route_exists = true;
break;
}
}
//If the route does indeed exist then lets redirect there.
if($route_exists == true){
//Get the locale from the users browser.
$locale = $request->getPreferredLanguage();
//If no locale from browser or locale not in list of known locales supported then set to defaultLocale set in config.yml
if($locale=="" || $this->isLocaleSupported($locale)==false){
$locale = $request->getDefaultLocale();
}
$event->setResponse(new RedirectResponse("/".$locale.$path));
}
//Otherwise do nothing and continue on~
}
public static function getSubscribedEvents()
{
return array(
// must be registered before the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest', 17)),
);
}
}
最后设置 services.yml 以启动侦听器。
Services.yml
# Learn more about services, parameters and containers at
# http://symfony.com/doc/current/book/service_container.html
parameters:
# parameter_name: value
services:
# service_name:
# class: AppBundle\Directory\ClassName
# arguments: ["@another_service_name", "plain_value", "%parameter_name%"]
appBundle.eventListeners.localeRewriteListener:
class: AppBundle\EventListener\LocaleRewriteListener
arguments: ["@router", "%kernel.default_locale%", "%locale_supported%"]
tags:
- { name: kernel.event_subscriber }
同样在 config.yml 中,您需要在参数下添加以下内容:
config.yml
parameters:
locale: en
app.locales: en|es|zh
locale_supported: ['en','es','zh']
我希望只有一个地方可以定义语言环境,但最后我不得不做 2 个...但至少它们在同一个地方,所以很容易改变。
app.locales用于默认控制器(requirements={"_locale" = "%app.locales%"})
,locale_supported用于LocaleRewriteListener。如果它检测到不在列表中的语言环境,它将回退到默认语言环境,在本例中为 locale:en.
app.locales 非常适合 requirements 命令,因为对于任何不匹配的语言环境,它都会导致 404。
如果您使用的是表单并且有登录名,则需要对您的 security.yml
执行以下操作Security.yml
# To get started with security, check out the documentation:
# http://symfony.com/doc/current/book/security.html
security:
encoders:
Symfony\Component\Security\Core\User\User:
algorithm: bcrypt
cost: 12
AppBundle\Entity\User:
algorithm: bcrypt
cost: 12
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
# http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
database:
entity: { class: AppBundle:User }
#property: username
# if you're using multiple entity managers
# manager_name: customer
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
anonymous: true
form_login:
check_path: login_check
login_path: login_route
provider: database
csrf_token_generator: security.csrf.token_manager
remember_me:
secret: '%secret%'
lifetime: 604800 # 1 week in seconds
path: /
httponly: false
#httponly false does make this vulnerable in XSS attack, but I will make sure that is not possible.
logout:
path: /logout
target: /
access_control:
# require ROLE_ADMIN for /admin*
#- { path: ^/login, roles: ROLE_ADMIN }
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/(.*?)/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER }
这里要注意的重要变化是 (.*?)/login
将进行匿名身份验证,因此您的用户仍然可以登录。这确实意味着像..dogdoghere/login 这样的路由可能会触发,但我稍后将在登录路由上向您展示的要求会阻止这种情况并会抛出 404 错误。我喜欢 (.*?)
与 [a-z]{2}
的解决方案,以防您想使用 en_US 类型的语言环境。
SecurityController.php
<?php
// src/AppBundle/Controller/SecurityController.php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class SecurityController extends Controller
{
/**
* @Route("{_locale}/login", name="login_route", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
*/
public function loginAction(Request $request)
{
$authenticationUtils = $this->get('security.authentication_utils');
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render(
'security/login.html.twig',
array(
// last username entered by the user
'last_username' => $lastUsername,
'error' => $error,
)
);
}
/**
* @Route("/{_locale}/login_check", name="login_check", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
*/
public function loginCheckAction()
{
// this controller will not be executed,
// as the route is handled by the Security system
}
/**
* @Route("/logout", name="logout")
*/
public function logoutAction()
{
}
}
?>
请注意,即使这些路径在前面也使用 {_locale}。但是我喜欢这样,所以我可以为不同的语言环境提供自定义登录。要时刻铭记在心。唯一不需要语言环境的路由是注销,它工作得很好,因为它实际上只是安全系统的拦截路由。另请注意,它使用了从 config.yml 设置的要求,因此您只需在一个地方为项目中的所有路由编辑它。
希望这对尝试做我正在做的事情的人有所帮助!
注意:: 为了轻松测试这一点,我对 Google Chrome 使用 'Quick Language Switcher' 扩展,这会更改所有请求的 accept-language header。
我没有足够的声誉来向正确的解决方案添加评论。所以我要添加一个新答案
您可以像这样在 app/config/routing.yml 添加 "prefix: /{_locale}":
app:
resource: "@AppBundle/Controller/"
type: annotation
prefix: /{_locale}
因此您无需将其添加到每个操作的每个路由中。对于以下步骤。非常感谢,非常完美。
Symfony 3.4 的小改进:
确保 getSubscribedEvents() 将在 RouterListener::onKernelRequest 和 LocaleListener::onKernelRequest 之前注册 LocaleRewriteListener。整数 17 必须大于 RouterListener::onKernelRequest 优先级。否则你会得到 404.
bin/console debug:event-调度员
services.yml 中的服务定义必须是(取决于 Symfony 配置):
AppBundle\EventListener\LocaleRewriteListener: 参数:['@router', '%kernel.default_locale%', '%locale_supported%'] 标签: - { 名称:kernel.event_subscriber,事件:kernel.request }
最终函数 smallResumeOfResearching($localeRewrite, $opinion = 'IMHO') :)
方法,由先生提供。 Joseph 在 /{route_name} 或 / 这样的路线上表现出色,但在 /article/slug/other.
这样的路线上表现不佳
如果我们使用修改后的mr.Joseph的方法,由提供,我们将在开发模式下丢失分析器和调试器。
如果我们想要更灵活的解决方案,onKernelRequest方法可以这样修改(感谢mr. Joseph,感谢):
public function onKernelRequest(GetResponseEvent $event) { $pathInfo = $event->getRequest()->getPathinfo(); $baseUrl = $event->getRequest()->getBaseUrl(); $checkLocale = explode('/', ltrim($pathInfo, '/'))[0]; //Or some other logic to detect/provide locale if (($this->isLocaleSupported($checkLocale) == false) && ($this->defaultLocale !== $checkLocale)) { if ($this->isProfilerRoute($checkLocale) == false) { $locale = $this->defaultLocale; $event->setResponse(new RedirectResponse($baseUrl . '/' . $locale . $pathInfo)); } /* Or with matcher: try { //Try to match the path with the locale prefix $this->matcher->match('/' . $locale . $pathInfo); //$event->setResponse(new RedirectResponse($baseUrl . '/' . $locale . $pathInfo)); } catch (\Symfony\Component\Routing\Exception\ResourceNotFoundException $e) { } catch (\Symfony\Component\Routing\Exception\MethodNotAllowedException $e) { } */ } }
注意:$this->profilerRoutes = array('_profiler', '_wdt', '_error');
- 感谢 Susana Santos 指出简单的配置方法:)