如何在Laravel项目中快速设置使用Inertia和Vue的翻译系统。
一个示例Laravel项目可以在本Github仓库找到。更多内容请访问Capsules或X。
虽然Laravel框架提供了一个默认的本地化系统,但它需要一些额外的添加来实现使用Laravel Inertia和Vue技术的网页工具的适当功能。本文将讨论这个话题。
从基本的Laravel Inertia Vue Tailwind项目开始,它尚未适配国际化。这一点可以通过简单地看到缺失的lang
文件夹来证实。以下步骤为多语言工具奠定基础。
首先,将默认的Laravel lang
文件夹及其翻译文件添加到template
项目中。例如,Molière的语言
cd template
mkdir lang
lang/fr.json
{
"Hello world!" : "Bonjour le monde!",
"This is a translation" : "Ceci est une traduction",
"Maintenance mode activated" : "Le mode maintenance est activé"
}
有三种翻译可供访问。在Vue组件中可见的键为英文翻译,而法文翻译为值。
在文件config/app.php
中添加网站将包含的不同语言。本文中涉及en
和fr
。
config/app.php
/*
|--------------------------------------------------------------------------
| Application Available Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the available locales that will be used
| by the translation service provider. You are free to set this array
| to any of the locales which will be supported by the application.
|
*/
'available_locales' => [ 'en', 'fr' ],
- 此配置将在实现语言更改按钮时对我们很有用。
现在可以将新信息注入到Inertia的HandleInertiaRequest
中间件中的共享数据中。
app/Http/Middleware/HandleInertiaRequests.php
<?php
namespace App\Http\Middleware;
use Inertia\Middleware;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\File;
class HandleInertiaRequests extends Middleware
{
public function share( Request $request ) : array
{
$file = lang_path( App::currentLocale() . ".json" );
return array_merge( parent::share( $request ), [
'csrf' => csrf_token(),
'locale' => App::currentLocale(),
'locales' => config( 'app.available_locales' ),
'translations' => File::exists( $file ) ? File::json( $file ) : []
] );
}
}
-
locale
代表当前语言。 -
《locales》表示不同的可用的语言,如
config( 'app.available_locales' )
所示。 -
translations
将位于lang
目录中并与当前语言链接的 JSON 文件中的可用翻译进行分组。如果没有文件存在,则返回的翻译数组将为空。
以下是检查共享数据内容的客户端方法
routes/web.php
<?php
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
App::setLocale( 'fr' );
Route::get( '/', fn() => dd( Inertia::getShared() ) );
array:5 [▼ // routes/web.php:10
"errors" => Closure() {#307 ▶}
"csrf" => "QTGHRkM83KysIS7htTNEWfZ9sC6Cs7U20i6kSSeF"
"locale" => "fr"
"locales" => array:2 [▼
0 => "en"
1 => "fr"
]
"translations" => array:2 [▼
"Hello world!" => "Bonjour le monde!"
"This is a translation" => "Ceci est une traduction"
]
]
- 使用
App::setLocale('fr')
修改语言以识别不同的翻译。在这种情况下,其他可能性将为translations
返回一个空数组。
现在可以正确配置 web.php
文件了。
routes/web.php
<?php
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
App::setLocale( 'fr' );
Route::get( '/', fn() => Inertia::render( 'Welcome' ) )->name( 'welcome' );
在客户端,特别是 Vue 中,您需要设置一个 composable
,它考虑了当前 locale
以显示从服务器提供并位于翻译数组中的正确 translation
。
mkdir resources/js/composables
cd resources/js/composables
resources/js/composables/trans.js
import { usePage } from '@inertiajs/vue3';
export function useTrans( value )
{
const array = usePage().props.translations;
return array[ value ] != null ? array[ value ] : value;
}
-
useTrans
如果存在则返回翻译,否则返回默认英文短语。
现在可以在 Welcome.vue
文件中实施文章开始时添加的翻译,通过将 "Capsules Codes" 替换为 "Hello world!" 并导入 useTrans
来实现。
resources/js/pages/Welcome.vue
<script setup>
import { useTrans } from '/resources/js/composables/trans';
import logotype from '/public/assets/capsules-logotype.svg';
</script>
<template>
<div class="w-screen h-screen flex flex-col items-center justify-center text-center">
<img class="w-24 h-24" v-bind:src="logotype" v-bind:alt="'Capsules Codes Logotype'">
<h1 class="mt-4 text-6xl font-bold select-none header-mode" v-text="useTrans( 'Hello world!' )" />
</div>
</template>
现在是时候在 Welcome.vue
文件中实现导航栏了,直接列出不同的语言选项。
resources/js/pages/Welcome.vue
<script setup>
import { computed } from 'vue';
import { usePage } from '@inertiajs/vue3';
import { useTrans } from '/resources/js/composables/trans';
import logotype from '/public/assets/capsules-logotype.svg';
const locales = computed( () => usePage().props.locales );
const index = computed( () => locales.value.findIndex( value => value == usePage().props.locale ) + 1 );
const language = computed( () => locales.value[ index.value % locales.value.length ] );
</script>
<template>
<div class="absolute h-12 w-full flex items-center justify-center">
<a v-if=" locales.length > 1 " class="rounded-md outline-none hover:bg-slate-50 text-sm font-medium" v-bind:href="`/${language}`" v-text="`/ ${language}`" />
</div>
<div class="w-screen h-screen flex flex-col items-center justify-center text-center">
<img class="w-24 h-24" v-bind:src="logotype" v-bind:alt="'Capsules Codes Logotype'">
<h1 class="mt-4 text-6xl font-bold select-none header-mode" v-text="useTrans( 'Hello world!' )" />
</div>
</template>
- 计算常量
locales
通过 Inertia 返回可用的语言。 - 计算常量
index
代表当前locale
后的索引。 - 计算常量
language
代表当前语言后的语言。在这种情况下,如果我们有fr
,language
将代表en
。如果只有一个语言,则不显示。如果有三个语言,则每个语言将依次滚动。
顶部栏中显示的语言是,然后是不在页面中使用的语言。现在的目标是将该选择应用于服务器端的 locale
。然后 <a>
标签发送一个 GET 请求到 /fr
或 /en
,取决于 language
。
为了允许服务器理解这一点并通过此过程更改 locale
,需要必要的中间件:SetLocale
。
app/Http/Middleware/SetLocale.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\URL;
class SetLocale
{
public function handle( Request $request, Closure $next ) : Response
{
if( in_array( $request->segment( 1 ), config( 'app.available_locales' ) ) && $request->segment( 1 ) !== App::currentLocale() ) Session::put( 'locale', $request->segment( 1 ) );
App::setLocale( Session::get( 'locale', App::currentLocale() ) );
URL::defaults( [ 'locale' => App::currentLocale() ] );
return $next( $request );
}
}
- 第一个条件检查 locale 是否在可用 locale 中。
- 第二个条件检查给定的 locale 是否不同于当前 locale。
-
URL::defaults( [ 'locale' => App::currentLocale() ] );
允许添加 locale 到 URL。
《SetLocale》中间件的作用是初始化或更改 locale,并将它添加到 URL。
然后将此中间件添加到 Kernel
文件中。中间件的位置很重要,但这只取决于其用途。将其放在 PreventRequestsDuringMaintenance
维护中间件之前很有用,以便在维护期间也能从翻译中受益。
app/Http/Kernel.php
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
protected $middleware = [
...
\App\Http\Middleware\SetLocale::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
...
];
...
在 web.php
文件中,需要新的前缀、新的路由和回退。
routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
Route::prefix( '{locale}' )->where( [ 'locale' => '[a-zA-Z]{2}' ] )->group( function()
{
Route::get( '', fn() => redirect()->route( Route::getRoutes()->match( Request::create( URL::previous() ) )->getName() ) ?? 'welcome' );
Route::get( 'welcome', fn() => Inertia::render( 'Welcome' ) )->name( 'welcome' );
} );
Route::fallback( fn() => redirect()->route( 'welcome' ) );
-
如其名称所示,
Route::prefix( '{locale}' )
向每个路由添加前缀。这里,它将是 locale。 - 此 locale 必须遵循
where(['locale' => '[a-zA-Z]{2}'])
,这相当于两个介于a
和Z
之间的字母。 - 由于路由
''
和'/'
是相同的,因此需要将初始路由 welcome 重定向到'welcome'
。 -
现在可以删除
App::setLocale( 'fr' );
。 -
Route::fallback( fn() => redirect()->route('welcome') );
表示如果请求没有匹配到任何路由,将会重定向到'welcome'
路由。这是一种处理错误并避免在这种情况下出现404
页面的方法。 - 在更改语言环境时不要指定路由名称,否则可能会发生重定向的无限循环。
翻译系统现在已经可用。🎉
为了避免在每一个 href
引用中加入语言环境,等其它方法,可以向 trans.js
的可组合中添加另一个函数: useRoute
。
resources/js/composables/trans.js
import { usePage } from '@inertiajs/vue3';
...
export function useRoute( value = null )
{
return `/${usePage().props.lang}${value ?? ''}`;
}
import { useRoute, useTrans } from '~/composables/trans';
<a v-bind:href="useRoute( `/welcome` )"><span v-text="useTrans( 'Welcome' )" />
由于路由有了前缀,现在可以从它们的闭包中访问它们。
routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
Route::prefix( '{locale}' )->where( [ 'locale' => '[a-zA-Z]{2}' ] )->group( function()
{
...
Route::get( 'translate', fn( string $locale ) => dd( __( "This is a translation", [], $locale ) ) );
...
} );
...
"Ceci est une traduction" // routes/web.php:13
在维护期间,如前所述,虽然确实分配了语言环境,但由于在调用 HandleInertiaRequest
中间件之前将调用 PreventRequestDuringMaintenance
中间件,因此不会发送翻译。因此,您需要手动将它们注入到 Handler
中。
app/exceptions/handler.php
use Symfony\Component\HttpFoundation\Response;
use Inertia\Response as InertiaResponse;
use Inertia\Inertia;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\File;
public function render( $request, Throwable $exception ) : Response | InertiaResponse
{
$response = parent::render( $request, $exception );
if( $response->status() === 503 )
{
Inertia::share( 'locale', App::currentLocale() );
Inertia::share( 'translations', File::exists( lang_path( App::currentLocale() . ".json" ) ) ? File::json( lang_path( App::currentLocale() . ".json" ) ) : [] );
return Inertia::render( 'Error' )->toResponse( $request )->setStatusCode( $response->status() );
}
return $response;
}
resources/js/pages/Error.vue
<script setup>
import { useTrans } from '/resources/js/composables/trans';
</script>
<template>
<div class="w-screen h-screen flex items-center justify-center text-center space-y-8">
<h1 class="text-6xl font-bold select-none header-mode" v-text="useTrans( 'Maintenance mode activated' )" />
</div>
</template>
php artisan down
很高兴这有帮助。
driesvints, fanatp, antoniputra 喜欢这篇文章