Symfony2路由中的默认语言环境

时间:2011-10-26 13:41:59

标签: symfony

我的路由问题以及使用Symfony2构建的网站的国际化 如果我在routing.yml文件中定义路由,如下所示:

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

它适用于以下网址:

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

但不适用于

mysite.com/example 

是否只允许在URL结尾处使用可选占位符? 如果是,那么可能是显示网址的可能解决方案:

mysite.com/example  

使用默认语言或将用户重定向到:

mysite.com/defaultlanguage/example 

他访问时:

mysite.com/example. ? 

我想弄清楚但到目前为止还没有成功。

感谢。

12 个答案:

答案 0 :(得分:20)

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

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

答案 1 :(得分:12)

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

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

希望有所帮助!

答案 2 :(得分:7)

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

<强>的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
}

<强>枝条

localeAction可用于允许用户更改语言环境而无需导航离开当前页面:

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

清洁&amp;简单!

答案 3 :(得分:3)

由于$routePath == "/{_locale}".$path)

,Joseph Astrahan的LocalRewriteListener解决方案除了包含params的路由外有效

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

不同

我必须使用UrlMatcher(链接到Symfony 2.7 api doc)来匹配实际路由和网址。

我更改 isLocaleSupported 以使用浏览器本地代码(例如:fr - &gt; fr_FR)。我使用浏览器区域设置作为键,将路径区域设置用作值。我有一个像array(['fr_FR'] => ['fr'], ['en_GB'] => 'en'...)这样的数组(有关更多信息,请参阅下面的参数文件)

变化:

  • 检查请求中的本地是否被支持。如果没有,请使用默认语言环境。
  • 尝试将路径与app路径集合相匹配。如果不做任何事情(如果路线不存在,应用程序将抛出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)),
        );
    }
}

答案 4 :(得分:2)

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

控制器:

/**
 * @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>

答案 5 :(得分:2)

Symfony3

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

答案 6 :(得分:1)

我有一个完整的解决方案,我在一些研究后发现。我的解决方案假设您希望每个路由都有一个区域设置,甚至登录。这被修改为支持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 Redirect All Routes To Current Locale Version

短版

为了使我的例子中的案例#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

中进行设置

<强> 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文件,因此您只需在一个地方为所有路由定义这些要求。

希望这有助于某人:)

答案 7 :(得分:0)

我使用注释,我会做

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

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

答案 8 :(得分:0)

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

1

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

答案 9 :(得分:0)

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

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

答案 10 :(得分:0)

我们创建了一个自定义的RoutingLoader,它向所有路由添加了本地化版本。您注入了一个附加语言环境['de', 'fr']的数组,并且Loader为每个附加语言环境添加了一条路由。主要优点是,对于您的默认语言环境,路由保持不变,并且不需要重定向。另一个优点是,注入了附加路由,因此可以针对多个客户端/环境等对它们进行不同的配置。而且配置要少得多。

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

答案 11 :(得分:-3)

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

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

这样,语言环境将是用户选择的最后一个语言环境,或者如果尚未设置用户语言环境,则为默认语言环境。如果要为/ example设置特定的语言环境,也可以在路由配置中将“_locale”参数添加到“defaults”:

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

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