Symfony2 路由中的默认语言环境

发布于 2024-12-12 01:44:23 字数 682 浏览 0 评论 0原文

我在使用 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 末尾使用可选占位符?
如果是,那么可能的解决方案是什么,可以用

mysite.com/example  

默认语言显示 : 之类的 url,或者

mysite.com/defaultlanguage/example 

当用户访问 : 时将用户重定向到 :

mysite.com/example. ? 

我正在尝试解决这个问题,但到目前为止还没有成功。

谢谢。

I have a problem with routing and the internationalization of my site built with Symfony2.
If I define routes in the routing.yml file, like this:

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

It works fine with URLs like:

mysite.com/en/example 
mysite.com/fr/example 

But doesn't work with

mysite.com/example 

Could it be that optional placeholders are permitted only at the end of an URL ?
If yes, what could be a possible solution for displaying an url like :

mysite.com/example  

in a default language or redirecting the user to :

mysite.com/defaultlanguage/example 

when he visits :

mysite.com/example. ? 

I'm trying to figure it out but without success so far.

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

给我一枪 2024-12-19 01:44:23

如果有人感兴趣,我成功地在我的 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

这样,人们就可以访问您的网站,而无需输入特定的区域设置。

If someone is interested in, I succeeded to put a prefix on my routing.yml without using other bundles.

So now, thoses URLs work :

www.example.com/
www.example.com//home/
www.example.com/fr/home/
www.example.com/en/home/

Edit your app/config/routing.yml:

ex_example:
    resource: "@ExExampleBundle/Resources/config/routing.yml"
    prefix:   /{_locale}
    requirements:
        _locale: |fr|en # put a pipe "|" first

Then, in you app/config/parameters.yml, you have to set up a locale

parameters:
    locale: en

With this, people can access to your website without enter a specific locale.

森林很绿却致人迷途 2024-12-19 01:44:23

您可以像这样定义多个模式:

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"})
 */

希望有帮助!

You can define multiple patterns like this:

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

example:
  pattern:   /{_locale}/example
  defaults:  { _controller: ExampleBundle:Example:index}
  requirements:
      _locale:  fr|en

You should be able to achieve the same sort of thing with annotations:

/**
 * @Route("/example", defaults={"_locale"="fr"})
 * @Route("/{_locale}/example", requirements={"_locale" = "fr|en"})
 */

Hope that helps!

笑脸一如从前 2024-12-19 01:44:23

这就是我用于自动区域设置检测和重定向的方法,它运行良好并且不需要冗长的路由注释:

routing.yml

locale 路由处理网站的根目录,然后每个其他控制器操作都以区域设置为前缀。

locale:
  path: /
  defaults:  { _controller: AppCoreBundle:Core:locale }

main:
  resource: "@AppCoreBundle/Controller"
  prefix: /{_locale}
  type: annotation
  requirements:
    _locale: en|fr

CoreController.php

这会检测用户的语言并重定向到您选择的路线。我使用 home 作为默认值,因为这是最常见的情况。

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>

Clean &简单的!

This is what I use for automatic locale detection and redirection, it works well and doesn't require lengthy routing annotations:

routing.yml

The locale route handles the website's root and then every other controller action is prepended with the locale.

locale:
  path: /
  defaults:  { _controller: AppCoreBundle:Core:locale }

main:
  resource: "@AppCoreBundle/Controller"
  prefix: /{_locale}
  type: annotation
  requirements:
    _locale: en|fr

CoreController.php

This detects the user's language and redirects to the route of your choice. I use home as a default as that it the most common case.

public function localeAction($route = 'home', $parameters = array())
{
    $this->getRequest()->setLocale($this->getRequest()->getPreferredLanguage(array('en', 'fr')));

    return $this->redirect($this->generateUrl($route, $parameters));
}

Then, the route annotations can simply be:

/**
 * @Route("/", name="home")
 */
public function indexAction(Request $request)
{
    // Do stuff
}

Twig

The localeAction can be used to allow the user to change the locale without navigating away from the current page:

<a href="{{ path(app.request.get('_route'), app.request.get('_route_params')|merge({'_locale': targetLocale })) }}">{{ targetLanguage }}</a>

Clean & simple!

忘东忘西忘不掉你 2024-12-19 01:44:23

Joseph Astrahan 的 LocalRewriteListener 解决方案除了带参数的路由外都有效,因为 $routePath == "/{_locale}".$path)

例如: $routePath = "/{_locale}/my /route/{foo}"$path = "/{_locale}/my/route/bar" 不同,

我必须使用 UrlMatcher (链接到 Symfony 2.7 api 文档)用于匹配实际带有 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)),
        );
    }
}

The Joseph Astrahan's solution of LocalRewriteListener works except for route with params because of $routePath == "/{_locale}".$path)

Ex : $routePath = "/{_locale}/my/route/{foo}" is different of $path = "/{_locale}/my/route/bar"

I had to use UrlMatcher (link to Symfony 2.7 api doc) for matching the actual route with the url.

I change the isLocaleSupported for using browser local code (ex : fr -> fr_FR). I use the browser locale as key and the route locale as value. I have an array like this array(['fr_FR'] => ['fr'], ['en_GB'] => 'en'...) (see the parameters file below for more information)

The changes :

  • Check if the local given in request is suported. If not, use the default locale.
  • Try to match the path with the app route collection. If not do nothing (the app throw a 404 if route doesn't exist). If yes, redirect with the right locale in route param.

Here is my code. Works for any route with or without param. This add the locale only when {_local} is set in the route.

Routing file (in my case, the one in app/config)

app:
    resource: "@AppBundle/Resources/config/routing.yml"
    prefix:   /{_locale}/
    requirements:
        _locale: '%app.locales%'
    defaults: { _locale: %locale%}

The parameter in app/config/parameters.yml file

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)),
        );
    }
}
爱你是孤单的心事 2024-12-19 01:44:23

交响乐3

app:
resource: "@AppBundle/Controller/"
type:     annotation
prefix: /{_locale}
requirements:
    _locale: en|bg|  # put a pipe "|" last

Symfony3

app:
resource: "@AppBundle/Controller/"
type:     annotation
prefix: /{_locale}
requirements:
    _locale: en|bg|  # put a pipe "|" last
美人骨 2024-12-19 01:44:23

这是我的解决方案,它使这个过程更快!

控制器:

/**
 * @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);
}

树枝:

<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>

There is my Solution, it makes this process faster!

Controller:

/**
 * @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>
清眉祭 2024-12-19 01:44:23

经过一番研究后我发现了一个完整的解决方案。我的解决方案假设您希望每条路线前面都有一个区域设置,甚至是登录。对此进行了修改以支持 Symfony 3,但我相信它在 2 中仍然可以工作。

此版本还假设您希望使用浏览器区域设置作为默认区域设置,如果它们转到 /admin 之类的路径,但如果它们转到 /en /admin 它会知道使用语言环境。下面的示例#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文件,因此您只需在一个地方为所有路线定义这些要求。

希望这对某人有帮助:)

I have a full solution to this that I discovered after some research. My solution assumes that you want every route to have a locale in front of it, even login. This is modified to support Symfony 3, but I believe it will still work in 2.

This version also assumes you want to use the browsers locale as the default locale if they go to a route like /admin, but if they go to /en/admin it will know to use en locale. This is the case for example #2 below.

So for example:

1. User Navigates To ->  "/"         -> (redirects)    -> "/en/"
2. User Navigates To ->  "/admin"    -> (redirects)    -> "/en/admin"
3. User Navigates To ->  "/en/admin" -> (no redirects) -> "/en/admin"

In all scenarios the locale will be set correctly how you want it for use throughout your program.

You can view the full solution below which includes how to make it work with login and security, otherwise the Short Version will probably work for you:

Full Version

Symfony 3 Redirect All Routes To Current Locale Version

Short Version

To make it so that case #2 in my examples is possible you need to do so using a 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)),
        );
    }
}

To understand how that is working look up the event subscriber interface on symfony documentation.

To activate the listner you need to set it up in your 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 }

Finally this refers to variables that need to be defined in your 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']

Finally, you need to make sure all your routes start with /{locale} for now on. A sample of this is below in my default 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
        ]);
    }
}
?>

Note the requirements requirements={"_locale" = "%app.locales%"}, this is referencing the config.yml file so you only have to define those requirements in one place for all routes.

Hope this helps someone :)

空城旧梦 2024-12-19 01:44:23

我们创建了一个自定义 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

We created a custom RoutingLoader that adds a localized version to all routes. You inject an array of additional locales ['de', 'fr'] and the Loader adds a route for each additional locale. The main advantage is, that for your default locale, the routes stay the same and no redirect is needed. Another advantage is, that the additionalRoutes are injected, so they can be configured differently for multiple clients/environments, etc. And much less configuration.

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}

Here is the loader:

<?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);
}
}

Service definition (SF4)

App\Routing\I18nRoutingLoader:
    arguments:
        $additionalLocales: "%additional_locales%"
    tags: ['routing.loader']

Routing definition

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
念三年u 2024-12-19 01:44:23

我使用注释,我会做

/**
 * @Route("/{_locale}/example", defaults={"_locale"=""})
 * @Route("/example", defaults={"_locale"="en"}, , requirements = {"_locale" = "fr|en|uk"})
 */

但是对于 yml 方式,尝试一些等效的...

I use annotations, and i will do

/**
 * @Route("/{_locale}/example", defaults={"_locale"=""})
 * @Route("/example", defaults={"_locale"="en"}, , requirements = {"_locale" = "fr|en|uk"})
 */

But for yml way, try some equivalent...

八巷 2024-12-19 01:44:23

也许我用一种相当简单的方式解决了这个问题:

example:
    path:      '/{_locale}{_S}example'
    defaults:  { _controller: 'AppBundle:Example:index' , _locale="de" , _S: "/" }
    requirements:
        _S: "/?"
        _locale: '|de|en|fr'

对批评者的判断感到好奇......
最好的祝愿,
格雷格

Maybe I solved this in a reasonably simple way:

example:
    path:      '/{_locale}{_S}example'
    defaults:  { _controller: 'AppBundle:Example:index' , _locale="de" , _S: "/" }
    requirements:
        _S: "/?"
        _locale: '|de|en|fr'

Curious about the judgement of the critics ...
Best wishes,
Greg

情话墙 2024-12-19 01:44:23
root:
    pattern: /
    defaults:
        _controller: FrameworkBundle:Redirect:urlRedirect
        path: /en
        permanent: true

如何在不使用自定义控制器的情况下配置重定向到另一条路由

root:
    pattern: /
    defaults:
        _controller: FrameworkBundle:Redirect:urlRedirect
        path: /en
        permanent: true

How to configure a redirect to another route without a custom controller

何必那么矫情 2024-12-19 01:44:23

我认为您可以简单地添加这样的路由:

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

这样,区域设置将是用户选择的最后一个区域设置,或者如果尚未设置用户区域设置,则为默认区域设置。如果您想为 /example 设置特定的区域设置,您还可以将“_locale”参数添加到路由配置中的“默认值”中:

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

我不知道是否有更好的方法来做到这一点。

I think you could simply add a route like this:

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

This way, the locale would be the last locale selected by the user, or the default locale if user locale has not been set. You might also add the "_locale" parameter to the "defaults" in your routing config if you want to set a specific locale for /example:

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

I don't know if there's a better way to do this.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文