Подтвердить что ты не робот

Стандартная локализация 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. ? 

Я пытаюсь понять это, но пока безуспешно.

Спасибо.

4b9b3361

Ответ 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'

Интересно, что критики критики... С наилучшими пожеланиями, Грег

Ответ 12

Я думаю, вы могли бы просто добавить такой маршрут:

example: 
pattern:  /example 
defaults: { _controller: ExampleBundle:Example:index } 

Таким образом, локаль будет последней локалью, выбранной пользователем, или языковой стандарт по умолчанию, если пользовательский язык не установлен. Вы также можете добавить параметр "_locale" в "настройки по умолчанию" в конфигурации маршрутизации, если вы хотите установить конкретную локаль для примера:

example: 
pattern:  /example 
defaults: { _controller: ExampleBundle:Example:index, _locale: fr }

Я не знаю, есть ли лучший способ сделать это.