У меня проблема с маршрутизацией и интернационализацией моего сайта, созданного с помощью Symfony2.
Если я определю маршруты в файле routing.yml, например:
example:
pattern: /{_locale}/example
defaults: { _controller: ExampleBundle:Example:index, _locale: fr }
Он отлично работает с такими URL-адресами, как:
mysite.com/en/example
mysite.com/fr/example
Но не работает с
mysite.com/example
Может быть, это необязательные заполнители разрешены только в конце URL-адреса?
Если да, каким может быть возможное решение для отображения URL-адреса типа:
mysite.com/example
на языке по умолчанию или перенаправления пользователя на:
mysite.com/defaultlanguage/example
, когда он посещает:
mysite.com/example. ?
Я пытаюсь понять это, но пока безуспешно.
Спасибо.
Решение Джозефа Астрахана о LocalRewriteListener работает за исключением маршрута с параметрами из-за $routePath == "/{_locale}".$path)
Пример: $routePath = "/{_locale}/my/route/{foo}"
отличается от $path = "/{_locale}/my/route/bar"
, который мне пришлось использовать UrlMatcher (ссылка на Symfony 2.7 api doc) для сопоставления фактического маршрута с URL.
Я изменяю isLocaleSupported для использования локального кода браузера (например: fr -> fr_FR). Я использую локаль браузера в качестве ключа и локаль маршрута в качестве значения. У меня есть такой массив array(['fr_FR'] => ['fr'], ['en_GB'] => 'en'...)
(см. Файл параметров ниже для получения дополнительной информации)
Изменения:
Вот мой код. Работает для любого маршрута с или без параметров. Это добавляет языковой стандарт, только если {_local} установлен на маршруте.
Файл маршрутизации (в моем случае это файл в app / config)
app:
resource: "@AppBundle/Resources/config/routing.yml"
prefix: /{_locale}/
requirements:
_locale: '%app.locales%'
defaults: { _locale: %locale%}
Параметр в файле app / config / parameters.yml
locale: fr
app.locales: fr|gb|it|es
locale_supported:
fr_FR: fr
en_GB: gb
it_IT: it
es_ES: es
services.yml
app.eventListeners.localeRewriteListener:
class: AppBundle\EventListener\LocaleRewriteListener
arguments: ["@router", "%kernel.default_locale%", "%locale_supported%"]
tags:
- { name: kernel.event_subscriber }
LocaleRewriteListener.php
<?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;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
class LocaleRewriteListener implements EventSubscriberInterface
{
/**
* @var Symfony\Component\Routing\RouterInterface
*/
private $router;
/**
* @var routeCollection \Symfony\Component\Routing\RouteCollection
*/
private $routeCollection;
/**
* @var urlMatcher \Symfony\Component\Routing\Matcher\UrlMatcher;
*/
private $urlMatcher;
/**
* @var string
*/
private $defaultLocale;
/**
* @var array
*/
private $supportedLocales;
/**
* @var string
*/
private $localeRouteParam;
public function __construct(RouterInterface $router, $defaultLocale = 'fr', array $supportedLocales, $localeRouteParam = '_locale')
{
$this->router = $router;
$this->routeCollection = $router->getRouteCollection();
$this->defaultLocale = $defaultLocale;
$this->supportedLocales = $supportedLocales;
$this->localeRouteParam = $localeRouteParam;
$context = new RequestContext("/");
$this->matcher = new UrlMatcher($this->routeCollection, $context);
}
public function isLocaleSupported($locale)
{
return array_key_exists($locale, $this->supportedLocales);
}
public function onKernelRequest(GetResponseEvent $event)
{
//GOAL:
// Redirect all incoming requests to their /locale/route equivalent when exists.
// Do nothing if it already has /locale/ in the route to prevent redirect loops
// Do nothing if the route requested has no locale param
$request = $event->getRequest();
$baseUrl = $request->getBaseUrl();
$path = $request->getPathInfo();
//Get the locale from the users browser.
$locale = $request->getPreferredLanguage();
if ($this->isLocaleSupported($locale)) {
$locale = $this->supportedLocales[$locale];
} else if ($locale == ""){
$locale = $request->getDefaultLocale();
}
$pathLocale = "/".$locale.$path;
//We have to catch the ResourceNotFoundException
try {
//Try to match the path with the local prefix
$this->matcher->match($pathLocale);
$event->setResponse(new RedirectResponse($baseUrl.$pathLocale));
} catch (\Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {
} catch (\Symfony\Component\Routing\Exception\MethodNotAllowedException $e) {
}
}
public static function getSubscribedEvents()
{
return array(
// must be registered before the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest', 17)),
);
}
}
Если кому-то это интересно, мне удалось поставить префикс на моем routing.yml
без использования других пакетов.
Так что теперь, эти URL работают:
www.example.com/
www.example.com//home/
www.example.com/fr/home/
www.example.com/en/home/
Отредактируйте app/config/routing.yml
:
ex_example:
resource: "@ExExampleBundle/Resources/config/routing.yml"
prefix: /{_locale}
requirements:
_locale: |fr|en # put a pipe "|" first
Затем, в вас app/config/parameters.yml
, вы должны установить язык
parameters:
locale: en
Благодаря этому люди могут получить доступ к вашему веб-сайту без ввода конкретной локали.
Symfony3
app:
resource: "@AppBundle/Controller/"
type: annotation
prefix: /{_locale}
requirements:
_locale: en|bg| # put a pipe "|" last
У меня есть полное решение этого, которое я обнаружил после некоторых исследований. Мое решение предполагает, что вы хотите, чтобы у каждого маршрута была локаль перед ним, даже вход в систему. Это модифицировано для поддержки Symfony 3, но я верю, что оно все еще будет работать в 2.
В этой версии также предполагается, что вы хотите использовать локаль браузера в качестве локали по умолчанию, если они идут по маршруту, например / admin, но если они переходят в / en / admin, он будет знать, что нужно использовать en locale. Это тот случай, например, № 2 ниже.
Так, например:
1. User Navigates To -> "/" -> (redirects) -> "/en/"
2. User Navigates To -> "/admin" -> (redirects) -> "/en/admin"
3. User Navigates To -> "/en/admin" -> (no redirects) -> "/en/admin"
Во всех сценариях языковой стандарт будет настроен правильно, как вы хотите, чтобы он использовался во всей вашей программе.
Ниже вы можете посмотреть полное решение, которое включает в себя, как заставить его работать с логином и безопасностью, иначе короткая версия, вероятно, будет работать для вас:
Полная версия
Symfony 3 перенаправляет все маршруты в текущую версию локали
Короткая версия
Сделать так, чтобы в моем примере был возможен случай № 2 вам нужно сделать это с помощью httpKernal listner
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)),
);
}
}
Чтобы понять, как это работает, посмотрите интерфейс подписчика событий в документации Symfony.
Чтобы активировать список, вам нужно настроить его в 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
# 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
locale_supported: ['en','es','zh']
Наконец, вам нужно убедиться, что все ваши маршруты начинаются с / {locale}, пока , Пример этого приведен ниже в моем файле controller.php по умолчанию
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
/**
* @Route("/{_locale}", requirements={"_locale" = "%app.locales%"})
*/
class DefaultController extends Controller
{
/**
* @Route("/", name="home")
*/
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")
*/
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
]);
}
}
?>
Обратите внимание на требования requirements={"_locale" = "%app.locales%"}
, это ссылка на файл config.yml, поэтому вам нужно определить эти требования только в одном месте для всех маршрутов. .
Надеюсь, это кому-нибудь поможет:)