4

私はsymfonyアプリケーションに取り組んでおり、ユーザーがどのページにいてもページのロケールバージョンに移動することが目標です。

たとえば、ユーザーがホームページの「/」に移動すると、「/en/」にリダイレクトされます。

_localeそれらが「/admin」ページにある場合、プロパティがルートから設定されるように、「/en/admin」にリダイレクトされます。

また、ユーザーのブラウザから /admin にアクセスする場合はロケールを決定する必要があります。これは、ロケールが決定されていないため、リダイレクト先のページがわかるためです。

現在、テスト中のため、デフォルトのコントローラーは次のようになっています。開発モードとプロファイラーを使用して、翻訳が正しく機能していることをテストしています。

ここに画像の説明を入力

<?php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class DefaultController extends Controller
{
    /**
     * @Route("/", name="homepage")
     * @Route("/{_locale}/", name="homepage_locale")
     */
    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
        ]);
    }
}

この現在の方法では、ユーザーがそこに移動すると「/」に留まりますが、「/en/」にリダイレクトしたいと考えています。これは、/admin や /somepath/pathagain/article1 (/en/admin 、 /en/somepath/pathagain/article1) などの他のページでも機能するはずです。

どうすればいいですか?

私が読んだ参考文献は役に立ちませんでした:

Symfony2 ルーティングでデフォルトのロケールを使用する (1 つの言語に対して 1 つの URL)

ルーティングにおける Symfony2 のデフォルトロケール

::アップデート::

私は自分の問題を解決していませんが、より効率的にするためのいくつかのトリックを学びました。

DefaultController.php

<?php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class DefaultController extends Controller
{

    /**
     * @Route("/", name="home", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
     * @Route("/{_locale}/", name="home_locale", requirements={"_locale" = "%app.locales%"})
     */
    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", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
     * @Route("/{_locale}/admin", name="admin_locale", requirements={"_locale" = "%app.locales%"})
     */
    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
        ]);
    }
}
?>

構成.yml

imports:
    - { resource: parameters.yml }
    - { resource: security.yml }
    - { resource: services.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

framework:
    #esi:             ~
    translator:      { fallbacks: ["%locale%"] }
    secret:          "%secret%"
    router:
        resource: "%kernel.root_dir%/config/routing.yml"
        strict_requirements: ~
    form:            ~
    csrf_protection: ~
    validation:      { enable_annotations: true }
    #serializer:      { enable_annotations: true }
    templating:
        engines: ['twig']
        #assets_version: SomeVersionScheme
    default_locale:  "%locale%"
    trusted_hosts:   ~
    trusted_proxies: ~
    session:
        # handler_id set to null will use default session handler from php.ini
        handler_id:  ~
        save_path:   "%kernel.root_dir%/../var/sessions/%kernel.environment%"
    fragments:       ~
    http_method_override: true
    assets: ~

# Twig Configuration
twig:
    debug:            "%kernel.debug%"
    strict_variables: "%kernel.debug%"

# Doctrine Configuration
doctrine:
    dbal:
        driver:   pdo_mysql
        host:     "%database_host%"
        port:     "%database_port%"
        dbname:   "%database_name%"
        user:     "%database_user%"
        password: "%database_password%"
        charset:  UTF8
        # if using pdo_sqlite as your database driver:
        #   1. add the path in parameters.yml
        #     e.g. database_path: "%kernel.root_dir%/data/data.db3"
        #   2. Uncomment database_path in parameters.yml.dist
        #   3. Uncomment next line:
        #     path:     "%database_path%"

    orm:
        auto_generate_proxy_classes: "%kernel.debug%"
        naming_strategy: doctrine.orm.naming_strategy.underscore
        auto_mapping: true

# Swiftmailer Configuration
swiftmailer:
    transport: "%mailer_transport%"
    host:      "%mailer_host%"
    username:  "%mailer_user%"
    password:  "%mailer_password%"
    spool:     { type: memory }

parameters の下の value に注意してくださいapp.locales: en|es|zh。これは、今後さらに多くのロケールをサポートする予定がある場合に、ルートを作成するたびに参照できる値です。これらのルートは、好奇心旺盛な人のために、英語、スペイン語、中国語の順です。注釈の DefaultController では、"%app.locales%"構成パラメーターを参照する部分です。

私の現在の方法の問題は、/ admin に行くことです。たとえば、ユーザーを /{browsers locale}/admin にリダイレクトしません。これは、すべてを整理するためのよりエレガントなソリューションになります...しかし、少なくともルートは機能します。まだより良い解決策を探しています。

****アップデート****

Athlan の回答である下の回答 (ロケールと要件をすべてのルートに追加 - Symfony2 )として、ここで回答を見つけた可能性があると思います。彼の指示が私には十分に明確ではなかったため、symfony 3 でこれを実装する方法がわかりません。

この記事も役立つと思います ( http://symfony.com/doc/current/components/event_dispatcher/introduction.html )

4

4 に答える 4

11

正しい解決策にコメントを追加するのに十分な評判がありません。だから私は新しい答えを追加しています

次のように app/config/routing.yml に "prefix: /{_locale}" を追加できます。

app:
    resource: "@AppBundle/Controller/"
    type:     annotation
    prefix:   /{_locale}

したがって、すべてのアクションのすべてのルートに追加する必要はありません。次の手順。完璧ですありがとうございました。

于 2016-08-16T16:36:17.493 に答える
9

これを12時間調べた後、ようやく受け入れられる解決策を見つけました。より効率的にできる場合は、このソリューションの改訂版を投稿してください。

注意すべき点がいくつかありますが、私のソリューションは私のニーズに固有のものです。それが行うことは、URL が存在する場合、ローカライズされたバージョンに移動することを強制することです。

これには、ルートを作成するときにいくつかの規則に従う必要があります。

DefaultController.php

<?php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class DefaultController extends Controller
{

    /**
     * @Route("/{_locale}/", name="home_locale", requirements={"_locale" = "%app.locales%"})
     */
    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("/{_locale}/admin", name="admin_locale", requirements={"_locale" = "%app.locales%"})
     */
    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
        ]);
    }
}
?>

どちらのルートも常に「/{_locale}/」で始まることに注意してください。これが機能するには、プロジェクトのすべてのルートにこれが必要です。後で実際のルート名を入力するだけです。私にとって、私はこのシナリオで大丈夫でした。ニーズに合わせて私のソリューションを簡単に変更できます。

最初のステップは、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)),
        );
    }
}

最後に、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

parameters:
    locale: en
    app.locales: en|es|zh
    locale_supported: ['en','es','zh']

ロケールを定義する場所を 1 つだけにしたかったのですが、最終的には 2 つにしなければなりませんでした...しかし、少なくともそれらは同じ場所にあるため、簡単に変更できます。

app.locales はデフォルト コントローラー(requirements={"_locale" = "%app.locales%"})で使用され、locale_supported は LocaleRewriteListener で使用されます。リストにないロケールを検出すると、デフォルトのロケール (この場合は locale:en の値) にフォールバックします。

app.locales は、一致しないロケールに対して 404 が発生するため、requirements コマンドに適しています。

フォームを使用していてログインしている場合は、security.yml に対して次のことを行う必要があります。

Security.yml

# To get started with security, check out the documentation:
# http://symfony.com/doc/current/book/security.html
security:
    encoders:
        Symfony\Component\Security\Core\User\User:
            algorithm: bcrypt
            cost: 12
        AppBundle\Entity\User:
            algorithm: bcrypt
            cost: 12

    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

    providers:
        # http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
         database:
              entity: { class: AppBundle:User }
                #property: username
                # if you're using multiple entity managers
                # manager_name: customer

    firewalls:
        # disables authentication for assets and the profiler, adapt it according to your needs
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            pattern: ^/
            anonymous: true

            form_login:
                check_path: login_check
                login_path: login_route
                provider: database
                csrf_token_generator: security.csrf.token_manager

            remember_me:
                secret:   '%secret%'
                lifetime: 604800 # 1 week in seconds
                path:     /
                httponly: false
                #httponly false does make this vulnerable in XSS attack, but I will make sure that is not possible.
            logout:
                path:   /logout
                target: /

    access_control:
        # require ROLE_ADMIN for /admin*
        #- { path: ^/login, roles: ROLE_ADMIN }
        - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/(.*?)/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/, roles: ROLE_USER }

ここで注意すべき重要な変更点は、(.*?)/login匿名で認証されるため、ユーザーは引き続きログインできることです。これは、..dogdoghere/login のようなルートがトリガーされる可能性があることを意味しますが、ログイン ルートですぐに説明する要件により、これが妨げられ、404 エラーがスローされます。(.*?)en_US[a-z]{2}タイプのロケールを使用したい場合に備えて、このソリューションが気に入っています。

SecurityController.php

<?php
// src/AppBundle/Controller/SecurityController.php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

class SecurityController extends Controller
{
    /**
     * @Route("{_locale}/login", name="login_route", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
     */
    public function loginAction(Request $request)
    {
        $authenticationUtils = $this->get('security.authentication_utils');

        // get the login error if there is one
        $error = $authenticationUtils->getLastAuthenticationError();

        // last username entered by the user
        $lastUsername = $authenticationUtils->getLastUsername();

        return $this->render(
            'security/login.html.twig',
            array(
                // last username entered by the user
                'last_username' => $lastUsername,
                'error'         => $error,
            )
        );
    }

    /**
     * @Route("/{_locale}/login_check", name="login_check", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
     */
    public function loginCheckAction()
    {
        // this controller will not be executed,
        // as the route is handled by the Security system
    }

    /**
    * @Route("/logout", name="logout")
    */
    public function logoutAction()
    {
    }
}
?>

これらのパスでも、先頭に {_locale} を使用していることに注意してください。しかし、私はこれが好きなので、さまざまなロケール用のカスタム ログインを提供できます。それを覚えておいてください。ロケールを必要としない唯一のルートは logout です。これは、実際にはセキュリティ システムの唯一のインターセプト ルートであるため、問題なく機能します。また、config.yml から設定された要件を使用するため、プロジェクト全体のすべてのルートに対して 1 か所で編集するだけで済みます。

これが、私がしていたことをやろうとしている人に役立つことを願っています!

注:: これを簡単にテストするために、Google Chrome の「Quick Language Switcher」拡張機能を使用します。これにより、すべてのリクエストの accept-language ヘッダーが変更されます。

于 2016-01-11T11:38:47.243 に答える