Стандартная локализация 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. ?
Я пытаюсь понять это, но пока безуспешно.
Спасибо.
Ответы
Ответ 1
Если кто-то заинтересован, мне удалось поставить префикс на мой routing.yml
без использования других пакетов.
Итак, теперь работают URL-адреса thoses:
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
При этом люди могут получить доступ к вашему сайту без ввода определенного языка.
Ответ 2
Вы можете определить несколько шаблонов, например:
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"})
*/
Надеюсь, что это поможет!
Ответ 3
Это то, что я использую для автоматического обнаружения и переназначения локали, оно работает хорошо и не требует длительных аннотаций маршрутизации:
routing.yml
Маршрут locale
обрабатывает корень веб-сайта, а затем каждое другое действие контроллера добавляется к языку.
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
}
Twig
LocaleAction может использоваться, чтобы пользователь мог изменить языковой стандарт, не перемещаясь от текущей страницы:
<a href="{{ path(app.request.get('_route'), app.request.get('_route_params')|merge({'_locale': targetLocale })) }}">{{ targetLanguage }}</a>
Чисто и просто!
Ответ 4
Решение LocalRewriteListener Джозефа Астрахана работает за исключением маршрута с параметрами из-за $routePath == "/{_locale}".$path)
Ex: $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}.
Файл маршрутизации (в моем случае, тот, что в приложении /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)),
);
}
}
Ответ 5
Symfony3
app:
resource: "@AppBundle/Controller/"
type: annotation
prefix: /{_locale}
requirements:
_locale: en|bg| # put a pipe "|" last
Ответ 6
Есть мое решение, оно ускоряет этот процесс!
Контроллер:
/**
* @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>
Ответ 7
У меня есть полное решение этого, которое я обнаружил после некоторых исследований. Мое решение предполагает, что вы хотите, чтобы каждый маршрут имел локаль перед ним, даже для входа в систему. Это модифицировано для поддержки 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
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
# 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} на данный момент. Пример этого приведен ниже в моем контроллере по умолчанию. 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, поэтому вам нужно только определить эти требования в одном месте для всех маршрутов.
Надеюсь, это поможет кому-то:)
Ответ 8
Мы создали пользовательский 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();
// Import directly for Symfony < v4
// $originalCollection = $this->import($resource, 'annotation')
$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
Ответ 9
Я использую аннотации, и я сделаю
/**
* @Route("/{_locale}/example", defaults={"_locale"=""})
* @Route("/example", defaults={"_locale"="en"}, , requirements = {"_locale" = "fr|en|uk"})
*/
Но для yml, попробуйте какой-то эквивалент...
Ответ 10
Возможно, я решил это достаточно простым способом:
example:
path: '/{_locale}{_S}example'
defaults: { _controller: 'AppBundle:Example:index' , _locale="de" , _S: "/" }
requirements:
_S: "/?"
_locale: '|de|en|fr'
Интересно, что критики критики...
С наилучшими пожеланиями,
Грег
Ответ 11
root:
pattern: /
defaults:
_controller: FrameworkBundle:Redirect:urlRedirect
path: /en
permanent: true
Как настроить перенаправление на другой маршрут без настраиваемого контроллера
Ответ 12
Я думаю, вы могли бы просто добавить такой маршрут:
example:
pattern: /example
defaults: { _controller: ExampleBundle:Example:index }
Таким образом, локаль будет последней локалью, выбранной пользователем, или языковой стандарт по умолчанию, если пользовательский язык не установлен. Вы также можете добавить параметр "_locale" в "настройки по умолчанию" в конфигурации маршрутизации, если вы хотите установить конкретную локаль для примера:
example:
pattern: /example
defaults: { _controller: ExampleBundle:Example:index, _locale: fr }
Я не знаю, есть ли лучший способ сделать это.