【问题标题】:How to create multilingual translated routes in Laravel如何在 Laravel 中创建多语言翻译路线
【发布时间】:2014-09-24 18:29:53
【问题描述】:

我想根据所选语言创建具有许多翻译路线的应用程序。我曾经在3 methods of creating URLs in multilingual websites 描述过它。

在这种情况下,它应该是提到的主题中的第一个方法所以:

  1. 我有一种默认语言
  2. 我可以使用多种其他语言
  3. 当前语言应仅通过 URL 计算(不含 cookie/会话),以使其对搜索引擎也非常友好
  4. 对于默认语言,URL 中不应该有前缀,对于其他语言,应该是域后的语言前缀
  5. url的每一部分都应该按照当前语言翻译。

假设我设置了默认语言pl 和其他两种语言en 和fr。我只有 3 个页面 - 主页、联系页面和关于页面。

网站的网址应该是这样的:

/
/[about]
/[contact]
/en
/en/[about]
/en/[contact]
/fr
/fr/[about]
/fr/[contact]

而[about] 和[contact] 应根据所选语言进行翻译,例如英语应保留contact,但波兰语应保留kontakt 等等。

如何做到尽可能简单?

【问题讨论】:

标签: php laravel laravel-4 localization routing


【解决方案1】:

第一步:

转到app/lang 目录并在此处为每种语言的路线创建翻译。您需要创建 3 个 routes.php 文件 - 每个文件都在单独的语言目录 (pl/en/fr) 中,因为您想使用 3 种语言

波兰语:

<?php

// app/lang/pl/routes.php

return array(

    'contact' => 'kontakt',
    'about'   => 'o-nas'
);

英语:

<?php

// app/lang/en/routes.php

return array(
    'contact' => 'contact',
    'about'   => 'about-us'
);

法语:

<?php

// app/lang/fr/routes.php

return array(
    'contact' => 'contact-fr',
    'about'   => 'about-fr'
);

第二步:

转到app/config/app.php 文件。

你应该找到行:

'locale' => 'en',

并将其更改为您的主要网站语言(在您的情况下为波兰语):

'locale' => 'pl',

您还需要在此文件中添加以下行:

/**
 * List of alternative languages (not including the one specified as 'locale')
 */
'alt_langs' => array ('en', 'fr'),

/**
 *  Prefix of selected locale  - leave empty (set in runtime)
 */
'locale_prefix' => '',

在alt_langs 配置中,您设置替代语言(在您的情况下为en 和fr) - 它们应该与您在创建带有翻译的文件的第一步中的文件名相同。

locale_prefix 是您的语言环境的前缀。您希望默认语言环境没有前缀,因此将其设置为空字符串。如果选择默认语言以外的其他语言,则此配置将在运行时修改。

第三步

转到您的app/routes.php 文件并输入它们的内容(这是app/routes.php 文件的全部内容):

<?php

// app/routes.php

/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the Closure to execute when that URI is requested.
|
*/


/*
 *  Set up locale and locale_prefix if other language is selected
 */
if (in_array(Request::segment(1), Config::get('app.alt_langs'))) {

    App::setLocale(Request::segment(1));
    Config::set('app.locale_prefix', Request::segment(1));
}


/*
 * Set up route patterns - patterns will have to be the same as in translated route for current language
 */
foreach(Lang::get('routes') as $k => $v) {
    Route::pattern($k, $v);
}


Route::group(array('prefix' => Config::get('app.locale_prefix')), function()
{
    Route::get(
        '/',
        function () {
            return "main page - ".App::getLocale();
        }
    );


    Route::get(
        '/{contact}/',
        function () {
            return "contact page ".App::getLocale();
        }
    );



    Route::get(
        '/{about}/',
        function () {
            return "about page ".App::getLocale();

        }
    );

});

正如您首先看到的那样,您检查 url 的第一段是否与您的语言名称匹配 - 如果是,则更改区域设置和当前语言前缀。

然后在小循环中,您为所有路由名称设置要求(您提到您希望在 URL 中翻译 about 和 contact)所以在这里您将它们设置为与 routes.php 文件中定义的相同当前语言。

最后,您创建 Route 组,其前缀与您的语言相同(默认语言为空),在组内您只需创建路径,但将这些参数 about 和 contact 视为 @987654342 @ 所以你对它们使用 {about} 和 {contact} 语法。

您需要记住,在这种情况下,将检查所有路由中的 {contact} 是否与您在第一步中为当前语言定义的相同。如果您不想要这种效果并希望使用 where 为每条路线手动设置路线,可以使用不带循环的替代 app\routes.php 文件,您可以在其中为每条路线分别设置 contact 和 about:

<?php

// app/routes.php

/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the Closure to execute when that URI is requested.
|
*/

/*
 *  Set up locale and locale_prefix if other language is selected
 */
if (in_array(Request::segment(1), Config::get('app.alt_langs'))) {

    App::setLocale(Request::segment(1));
    Config::set('app.locale_prefix', Request::segment(1));
}


Route::group(array('prefix' => Config::get('app.locale_prefix')), function()
{
    Route::get(
        '/',
        function () {
            return "main page - ".App::getLocale();
        }
    );


    Route::get(
        '/{contact}/',
        function () {
            return "contact page ".App::getLocale();
        }
    )->where('contact', Lang::get('routes.contact'));



    Route::get(
        '/{about}/',
        function () {
            return "about page ".App::getLocale();

        }
    )->where('about', Lang::get('routes.about'));


});

第四步:

您还没有提到它,但是您可以考虑一件事。如果有人会使用 url /en/something 其中something 不是正确的路由,我认为进行重定向的最佳解决方案。但是你不应该重定向到/,因为它是默认语言,而是到/en。

所以现在您可以打开app/start/global.php 文件并在此处为未知网址创建301 重定向:

// app/start/global.php

App::missing(function()
{
   return Redirect::to(Config::get('app.locale_prefix'),301);
});

【讨论】:

  • 也许使用单个 routes.php 文件返回一个关联数组,该数组带有语言的 ISO 639-1 作为键,这会使事情变得更容易。 return array('en' =&gt; array(...), 'pl' =&gt; array(...) ...)
【解决方案2】:

Marcin Nabiałek 在他最初的回答中为我们提供的是路线定位问题的可靠解决方案。

小熊虫:

他的解决方案唯一真正的缺点是我们不能使用缓存路由,根据Laravel'sdocs,这有时会带来很大的好处:

如果您的应用程序专门使用基于控制器的路由,您 应该利用 Laravel 的路由缓存。使用路由缓存 将大大减少注册所有人所需的时间 您的应用程序的路线。在某些情况下,您的路线注册 甚至可能快 100 倍。要生成路由缓存,只需执行 route:cache Artisan 命令。


为什么我们不能缓存我们的路线?

由于Marcin Nabiałek's 方法会根据locale_prefix 动态生成新路由,因此在访问缓存时未存储在locale_prefix 变量中的任何前缀时,缓存它们会导致404 错误。


我们保留什么?

基础似乎很稳固,我们可以保留大部分!

我们当然可以保留各种本地化特定的路由文件:

<?php

// app/lang/pl/routes.php

return array(

    'contact' => 'kontakt',
    'about'   => 'o-nas'
);

我们还可以保留所有app/config/app.php 变量:

/**
* Default locale 
*/
'locale' => 'pl'

/**
 * List of alternative languages (not including the one specified as 'locale')
 */
'alt_langs' => array ('en', 'fr'),

/**
 *  Prefix of selected locale  - leave empty (set in runtime)
 */
'locale_prefix' => '',

 /**
 * Let's also add a all_langs array
 */
'all_langs' => array ('en', 'fr', 'pl'),

我们还需要检查路线段的代码。但由于这样做的目的是利用缓存,我们需要将其移出routes.php 文件。一旦我们缓存了路由,就不会再使用那个了。我们可以暂时将其移至app/Providers/AppServiceProver.php,例如:

public function boot(){
  /*
   *  Set up locale and locale_prefix if other language is selected
   */
   if (in_array(Request::segment(1), config('app.alt_langs'))) {
       App::setLocale(Request::segment(1));
       config([ 'app.locale_prefix' => Request::segment(1) ]);
   }
}

别忘了:

use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\App;

设置我们的路线:

我们的app/Http/routes.php 文件中将发生一些变化。

首先,我们必须创建一个包含所有alt_langs 以及默认locale_prefix 的新数组,这很可能是'':

$all_langs = config('app.all_langs');

为了能够缓存所有带有翻译路由参数的各种语言前缀,我们需要将它们全部注册。我们该怎么做?

*** Laravel aside 1: ***

我们来看看Lang::get(..)的定义:

public static function get($key, $replace = array(), $locale = null, $fallback = true){
      return \Illuminate\Translation\Translator::get($key, $replace, $locale, $fallback);
}

该函数的第三个参数是$locale 变量!太好了——我们当然可以利用它来发挥我们的优势!这个函数实际上让我们选择要从哪个语言环境中获取翻译!

接下来我们要做的是遍历$all_langs 数组并为每个语言前缀创建一个新的Route 组。不仅如此,我们还将摆脱我们之前需要的 where 链和 patterns,并且只注册具有正确翻译的路由(其他人将抛出 404 而无需再检查它):

/**
* Iterate over each language prefix 
*/
foreach( $all_langs as $prefix ){
   
   if ($prefix == 'pl') $prefix = '';

   /**
   * Register new route group with current prefix
   */
   Route::group(['prefix' => $prefix], function() use ($prefix) {

         // Now we need to make sure the default prefix points to default  lang folder.
         if ($prefix == '') $prefix = 'pl';

         /**
         * The following line will register:
         *
         * example.com/
         * example.com/en/
         */
         Route::get('/', 'MainController@getHome')->name('home');

         /**
         * The following line will register:
         *
         * example.com/kontakt
         * example.com/en/contact
         */
         Route::get(Lang::get('routes.contact',[], $prefix) , 'MainController@getContact')->name('contact');

         /**
         * “In another moment down went Alice after it, never once 
         * considering how in the world she was to get out again.”
         */
         Route::group(['prefix' => 'admin', 'middleware' => 'admin'], function () use ($prefix){

            /**
            * The following line will register:
            *
            * example.com/admin/uzivatelia
            * example.com/en/admin/users
            */
            Route::get(Lang::get('routes.admin.users',[], $prefix), 'AdminController@getUsers')
            ->name('admin-users');

         });
   });
}

/**
* There might be routes that we want to exclude from our language setup.
* For example these pesky ajax routes! Well let's just move them out of the `foreach` loop.
* I will get back to this later.
*/
Route::group(['middleware' => 'ajax', 'prefix' => 'api'], function () {
    /**
    * This will only register example.com/api/login
    */
    Route::post('login', 'AjaxController@login')->name('ajax-login');
});

休斯顿,我们有问题!

如您所见,我更喜欢使用命名路由(大多数人可能这样做):

Route::get('/', 'MainController@getHome')->name('home');

它们可以很容易地在刀片模板中使用:

{{route('home')}}

但到目前为止,我的解决方案存在一个问题:路由名称相互覆盖。上面的foreach 循环只会用它们的名字注册最后的前缀路由。

换句话说,只有example.com/ 会绑定到home 路由,因为locale_perfix 是$all_langs 数组中的最后一项。

我们可以通过在路由名称前加上语言$prefix 来解决这个问题。例如:

Route::get('/', 'MainController@getHome')->name($prefix.'_home');

我们必须为循环中的每条路线执行此操作。这又造成了一个小障碍。


但是我的大型项目快完成了!

您可能已经猜到了,您现在必须返回所有文件,并在每个 route 辅助函数调用前加上从 app 配置加载的当前 locale_prefix。

除非你不这样做!

*** Laravel aside 2: ***

让我们看看 Laravel 是如何实现它的 route 辅助方法的。

if (! function_exists('route')) {
    /**
     * Generate a URL to a named route.
     *
     * @param  string  $name
     * @param  array   $parameters
     * @param  bool    $absolute
     * @return string
     */
    function route($name, $parameters = [], $absolute = true)
    {
        return app('url')->route($name, $parameters, $absolute);
    }
}

如你所见,Laravel 将首先检查 route 函数是否已经存在。仅当另一个尚不存在时,它才会注册其route 函数!

这意味着我们可以非常轻松地解决我们的问题,而无需重写迄今为止在我们的 Blade 模板中进行的每一个 route 调用。

让我们快速创建一个app/helpers.php 文件。

让我们确保 Laravel 在加载其 helpers.php 之前加载文件,方法是将以下行放入 bootstrap/autoload.php

//Put this line here
require __DIR__ . '/../app/helpers.php';
//Right before this original line
require __DIR__.'/../vendor/autoload.php';

LARAVEL 7+ 更新

bootstrap/autoload.php 文件已不存在,您必须将上面的代码添加到public/index.php 文件中。

我们现在要做的就是在我们的app/helpers.php 文件中创建我们自己的route 函数。我们将以原始实现为基础:

<?php
//Same parameters and a new $lang parameter
use Illuminate\Support\Str;

function route($name, $parameters = [], $absolute = true, $lang = null)
{
    /*
    * Remember the ajax routes we wanted to exclude from our lang system?
    * Check if the name provided to the function is the one you want to
    * exclude. If it is we will just use the original implementation.
    **/
    if (Str::contains($name, ['ajax', 'autocomplete'])){
        return app('url')->route($name, $parameters, $absolute);
    }

   //Check if $lang is valid and make a route to chosen lang
   if ( $lang && in_array($lang, config('app.alt_langs')) ){
       return app('url')->route($lang . '_' . $name, $parameters, $absolute);
   }

    /**
    * For all other routes get the current locale_prefix and prefix the name.
    */
    $locale_prefix = config('app.locale_prefix');
    if ($locale_prefix == '') $locale_prefix = 'pl';
    return app('url')->route($locale_prefix . '_' . $name, $parameters, $absolute);
}

就是这样!

所以我们所做的基本上是注册所有可用的前缀组。创建翻译的每条路线,并为其名称加上前缀。然后某种覆盖了 Laravel 的 route 函数,以当前的 locale_prefix 为所有路由名称(除了一些)添加前缀,以便在我们的刀片模板中创建适当的 url,而无需输入 @987654382 @每一次。

哦,是的:

php artisan route:cache

只有在部署项目后才能真正完成缓存路由,因为在开发过程中很可能会弄乱它们。但是你可以随时清除缓存:

php artisan route:clear

再次感谢 Marcin Nabiałek 的原始回答。这对我真的很有帮助。

【讨论】:

  • 感谢您深思熟虑且解释清楚的答案。能够获得路由缓存的性能优势真是太好了。
  • 在新版本的 Laravel 中,没有 bootstrap/autoload.php ,我们现在如何编辑核心的 route 功能?我尝试在作曲家中使用自动加载,但它告诉我它无法重新声明路由功能。
  • 这是整个互联网上最有用的答案之一。我想知道为什么 Laravel 没有在他们的文档中提供更多关于本地化的信息。
  • 天哪!你救了我的星期天!感谢您的出色方法!
  • 仅供参考,我很久以前就写过这篇文章,老实说,当涉及到 Laravel 6、7 及更高版本时,其中一些信息可能已经过时(我没有检查过)。很高兴它有帮助,但请记住,可能需要进行一些修改以使其符合新版本的标准。
【解决方案3】:

使用更简单的方法可以应用相同的结果。虽然不完美,但确实提供了一种快速简便的解决方案。但是,在这种情况下,您必须编写每个路由,因此它可能不适用于大型网站。

Route::get('/contact-us', function () {
    return view('contactus');
})->name('rte_contact'); // DEFAULT

Route::get('/contactez-nous', function () {
    return view('contactus');
})->name('rte_contact_fr');

只需在本地化文件中定义路由名称:

# app/resources/lang/en.json
{ "rte_contact": "rte_contact" } //DEFAULT

// app/resources/lang/fr.json
{ "rte_contact": "rte_contact_fr" }

然后您可以使用生成的语言环境变量在刀片模板中使用它们,如下所示:

<a class="nav-link" href="{{ route(__('rte_contact')) }}"> {{ __('nav_contact') }}</a>

【讨论】:

  • __('rte_contact') 将在语言为“en”时转换为 'rte_contact',在语言为“fr”时转换为 'rte_contact_fr'
猜你喜欢
  • 2017-01-04
  • 2014-02-01
  • 2023-03-20
  • 2016-10-27
  • 2015-06-20
  • 2015-04-03
  • 1970-01-01
相关资源
最近更新 更多