Язык по умолчанию Symfony2 в маршрутизации
У меня проблема с маршрутизацией и интернационализацией моего сайта, созданного с помощью 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. ?
Я пытаюсь понять это, но пока безуспешно.
Благодарю.
11 ответов
Если кому-то интересно, мне удалось поставить префикс на моем 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
Благодаря этому люди могут получить доступ к вашему веб-сайту без ввода конкретной локали.
Вы можете определить несколько шаблонов, как это:
example_default:
pattern: /example
defaults: { _controller: ExampleBundle:Example:index, _locale: fr }
example:
pattern: /{_locale}/example
defaults: { _controller: ExampleBundle:Example:index}
requirements:
_locale: fr|en
Вы должны быть в состоянии достичь того же самого с помощью аннотаций:
/**
* @Route("/example", defaults={"_locale"="fr"})
* @Route("/{_locale}/example", requirements={"_locale" = "fr|en"})
*/
Надеюсь, это поможет!
Это то, что я использую для автоматического определения и перенаправления локали, оно работает хорошо и не требует длинных аннотаций маршрутизации:
routing.yml
locale
route обрабатывает корень сайта, а затем каждое другое действие контроллера предшествует локали.
locale:
path: /
defaults: { _controller: AppCoreBundle:Core:locale }
main:
resource: "@AppCoreBundle/Controller"
prefix: /{_locale}
type: annotation
requirements:
_locale: en|fr
CoreController.php
Это определяет язык пользователя и перенаправляет на маршрут по вашему выбору. Я использую дом по умолчанию, так как это наиболее распространенный случай.
public function localeAction($route = 'home', $parameters = array())
{
$this->getRequest()->setLocale($this->getRequest()->getPreferredLanguage(array('en', 'fr')));
return $this->redirect($this->generateUrl($route, $parameters));
}
Тогда аннотации маршрута могут быть просто:
/**
* @Route("/", name="home")
*/
public function indexAction(Request $request)
{
// Do stuff
}
прут
LocaleAction может использоваться, чтобы позволить пользователю изменять локаль, не отходя от текущей страницы:
<a href="{{ path(app.request.get('_route'), app.request.get('_route_params')|merge({'_locale': targetLocale })) }}">{{ targetLanguage }}</a>
Чистый и простой!
Решение Джозефа Астрахана о 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'...)
(см. файл параметров ниже для получения дополнительной информации)
Перемены:
- Проверьте, поддерживается ли местное значение, указанное в запросе. Если нет, используйте локаль по умолчанию.
- Попробуйте сопоставить путь с коллекцией маршрутов приложения. Если не делать ничего (приложение выдает 404, если маршрут не существует). Если да, перенаправьте с правильным языком в параметре маршрута.
Вот мой код Работает для любого маршрута с или без параметров. Это добавляет локаль только тогда, когда в маршруте установлен {_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)),
);
}
}
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, поэтому вам нужно определить эти требования только в одном месте для всех маршрутов.
Надеюсь, это поможет кому-то:)
Есть мое решение, оно делает этот процесс быстрее!
контроллер:
/**
* @Route("/change/locale/{current}/{locale}/", name="locale_change")
*/
public function setLocaleAction($current, $locale)
{
$this->get('request')->setLocale($locale);
$referer = str_replace($current,$locale,$this->getRequest()->headers->get('referer'));
return $this->redirect($referer);
}
Twig:
<li {% if app.request.locale == language.locale %} class="selected" {% endif %}>
<a href="{{ path('locale_change', { 'current' : app.request.locale, 'locale' : language.locale } ) }}"> {{ language.locale }}</a>
</li>
Мы создали пользовательский RoutingLoader, который добавляет локализованную версию ко всем маршрутам. Вы вводите массив дополнительных локалей ['de', 'fr']
Загрузчик добавляет маршрут для каждой дополнительной локали. Основным преимуществом является то, что для вашей локали по умолчанию маршруты остаются прежними и перенаправление не требуется. Еще одним преимуществом является то, что дополнительные маршруты вводятся, поэтому они могут быть настроены по-разному для нескольких клиентов / сред и т. Д. И гораздо меньше конфигурации.
partial_data GET ANY ANY /partial/{code}
partial_data.de GET ANY ANY /de/partial/{code}
partial_data.fr GET ANY ANY /fr/partial/{code}
Вот загрузчик:
<?php
namespace App\Routing;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
class I18nRoutingLoader extends Loader
{
const NAME = 'i18n_annotation';
private $projectDir;
private $additionalLocales = [];
public function __construct(string $projectDir, array $additionalLocales)
{
$this->projectDir = $projectDir;
$this->additionalLocales = $additionalLocales;
}
public function load($resource, $type = null)
{
$collection = new RouteCollection();
$originalCollection = $this->getOriginalRouteCollection($resource);
$collection->addCollection($originalCollection);
foreach ($this->additionalLocales as $locale) {
$this->addI18nRouteCollection($collection, $originalCollection, $locale);
}
return $collection;
}
public function supports($resource, $type = null)
{
return self::NAME === $type;
}
private function getOriginalRouteCollection(string $resource): RouteCollection
{
$resource = realpath(sprintf('%s/src/Controller/%s', $this->projectDir, $resource));
$type = 'annotation';
return $this->import($resource, $type);
}
private function addI18nRouteCollection(RouteCollection $collection, RouteCollection $definedRoutes, string $locale): void
{
foreach ($definedRoutes as $name => $route) {
$collection->add(
$this->getI18nRouteName($name, $locale),
$this->getI18nRoute($route, $name, $locale)
);
}
}
private function getI18nRoute(Route $route, string $name, string $locale): Route
{
$i18nRoute = clone $route;
return $i18nRoute
->setDefault('_locale', $locale)
->setDefault('_canonical_route', $name)
->setPath(sprintf('/%s%s', $locale, $i18nRoute->getPath()));
}
private function getI18nRouteName(string $name, string $locale): string
{
return sprintf('%s.%s', $name, $locale);
}
}
Определение сервиса (SF4)
App\Routing\I18nRoutingLoader:
arguments:
$additionalLocales: "%additional_locales%"
tags: ['routing.loader']
Определение маршрутизации
frontend:
resource: ../../src/Controller/Frontend/
type: i18n_annotation #localized routes are added
api:
resource: ../../src/Controller/Api/
type: annotation #default loader, no routes are added
Я использую аннотации, и я буду делать
/**
* @Route("/{_locale}/example", defaults={"_locale"=""})
* @Route("/example", defaults={"_locale"="en"}, , requirements = {"_locale" = "fr|en|uk"})
*/
Но для yml попробуйте какой-нибудь эквивалент...
root:
pattern: /
defaults:
_controller: FrameworkBundle:Redirect:urlRedirect
path: /en
permanent: true
Как настроить перенаправление на другой маршрут без кастомного контроллера
Может быть, я решил это довольно простым способом:
example:
path: '/{_locale}{_S}example'
defaults: { _controller: 'AppBundle:Example:index' , _locale="de" , _S: "/" }
requirements:
_S: "/?"
_locale: '|de|en|fr'
Любопытно мнение судей... С наилучшими пожеланиями, Грег
Я думаю, что вы могли бы просто добавить маршрут как этот:
example:
pattern: /example
defaults: { _controller: ExampleBundle:Example:index }
Таким образом, языковой стандарт будет последним языковым стандартом, выбранным пользователем, или языковым стандартом по умолчанию, если пользовательский языковой стандарт не был установлен. Вы также можете добавить параметр "_locale" к "значениям по умолчанию" в вашей конфигурации маршрутизации, если вы хотите установить конкретную локаль для / example:
example:
pattern: /example
defaults: { _controller: ExampleBundle:Example:index, _locale: fr }
Я не знаю, есть ли лучший способ сделать это.