如何在 Laravel 中创建多语言翻译路由

新手上路,请多包涵

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

在这种情况下,它应该是 上述主题中的第一种方法, 因此:

  1. 我有一种默认语言
  2. 我可以有许多其他语言
  3. 当前语言应该只通过 URL(没有 cookies/session)来计算,以使其对搜索引擎也非常友好
  4. 对于默认语言,URL 中不应有前缀,对于其他语言,域后应为语言前缀
  5. url 的每一部分都应该根据当前语言进行翻译。

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

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

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

whereas [about] and [contact] should be translated according to selected language, for example in English it should be left contact but for Polish it should be kontakt 等等。

如何做到尽可能简单?

原文由 Marcin Nabiałek 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 715
2 个回答

第一步:

转到 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 配置中设置替代语言(在你的情况下 enfr ) - 它们应该与你创建文件的第一步中的文件名相同翻译。

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 的第一段是否与您的语言名称匹配 - 如果是,则更改区域设置和当前语言前缀。

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

最后,您创建的路由组的前缀与您的语言相同(对于默认语言,它将为空),在组内您只需创建路径,但这些参数 aboutcontact 你对待 variables 所以你使用 {about}{contact} 语法。

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

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

原文由 Marcin Nabiałek 发布,翻译遵循 CC BY-SA 3.0 许可协议

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

小熊地精:

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

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


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

因为 Marcin Nabiałek 的 方法基于 locale_prefix 动态生成新路由,缓存它们将导致 404 访问任何未存储在 locale_prefix 变量中的前缀时出错缓存的时间。


我们保留什么?

地基看起来真的很牢固,我们可以保留大部分!

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

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

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

*** 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 循环只会用它们的名称注册最后的前缀路由。

In other words only example.com/ would be bound to the home route as locale_perfix was the last item in the $all_langs array.

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

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

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


但我的大型项目即将完成!

正如您可能猜到的,您现在必须返回到所有文件并为每个文件加上前缀 route 辅助函数调用,当前 locale_prefixapp 48a6 加载.

除了你不!

*** 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,而无需键入 config('app.locale_prefix') 每一次。

哦耶:

 php artisan route:cache

缓存路由只应在您部署项目后真正完成,因为您可能会在开发过程中弄乱它们。但您始终可以清除缓存:

 php artisan route:clear

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

原文由 PeterTheLobster 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏