欢迎各位兄弟 发布技术文章
这里的技术是共享的
嘿! 可以使用本教程的新版本!在新的,我使用Spatie的可翻译包,我提供更清洁和更好的实现!转到新教程!!
本文已更新,以匹配Laravel 5.4
有一天,我在这个博客上写了一篇关于用非持久的Laravel 5 App SetLocale解决问题的文章。令我惊讶的是,这篇文章变得非常受欢迎,并且每月获得或多或少的1000次独立访问(对我来说,这很多)。
这篇博客文章中有很多评论,其中一些评论提供了更好的方法来制作多语言应用程序,以便更加“SEO友好”。即使它超出了文章本身的范围,它也给了我很多关于撰写关于这一主题的新文章的灵感和动力。
因此,在本新教程的范围内,我们将设置逻辑和体系结构,使我们能够构建100%多语言,SEO友好和可扩展的应用程序。
像往常一样,我将使用一个博客网站的例子,它易于理解,没有太多的功能,所以我很容易写这篇文章,并让你了解实际的结果。
首先,你可以在这个github repo上找到我将要描述的实例。只需克隆repo,composer install,artisan migrate,db:seed,你就能看到它在现实生活中的演示:)
我想要的功能必须:
在URL中使用语言代码
强制语言代码显示在URL的开头
写下更少的代码来做到这一点
拥有本地化模型,意思是:您可以用多种语言编写相同的博客文章。
能够通过配置文件添加任意数量的语言
翻译博客文章的slu ..
拥有静态页面和Eloquent模型的本地化URL(感谢slug)
切换语言时即时更新页面的slug
我们需要两个软件包来实现我们所需要的:一个用于根据帖子标题构建slug,一个用于存储我们帖子的翻译。
快速说明:自从我开始研究这个问题以来,我搜索了大量用于管理Eloquent模型转换的软件包,并且可以与缓存软件包并行工作。经过数月的研究,我得出的结论是dimsav / laravel-translatable是最适合我们需求的包装。
可口的套餐
将包添加到您的应用: composer require cviebrock/eloquent-sluggable:^4.1
现在将提供程序添加到config / app.php文件中。
'providers' => [
// ...
Cviebrock\EloquentSluggable\ServiceProvider::class,
];
可翻译套餐
将包添加到您的应用: composer require dimsav/laravel-translatable
现在将提供程序添加到config / app.php文件中
'providers' => [
// ...
Dimsav\Translatable\TranslatableServiceProvider::class,
];
使用生成配置文件 php artisan vendor:publish
使用新的Laravel安装,您已经拥有了User模型,但我们不会在这里使用它,所以让我们创建将用于查询博客文章的Post模型。您必须知道,包含可翻译内容的每个应用程序模型都将拥有自己的翻译模型和自己的翻译数据库表。
php artisan make:model Post -m
(在命令中添加“-m”将生成一个迁移文件)。我们不需要更改刚刚生成的迁移文件,因为帖子的属性(标题,段塞,内容文本)将存储在另一个专用于翻译的表中。
快速编辑app / Post.php文件并从Translatable包中添加一些代码:
// app/Post.php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Dimsav\Translatable\Translatable;
class Post extends Model
{
use Translatable;
public $translatedAttributes = ['title', 'slug', 'content'];
protected $fillable = ['title', 'slug', 'content'];
}
对于Post模型,使用该命令创建另一个专用于Post Translations的模型,php artisan make:model PostTranslation -m
并设置如下内容:
// app/PostTranslation.php
namespace App;
use Cviebrock\EloquentSluggable\Sluggable;
use Illuminate\Database\Eloquent\Model;
class PostTranslation extends Model
{
use Sluggable;
public $timestamps = false;
protected $fillable = ['title', 'slug', 'content'];
/**
* Return the sluggable configuration array for this model.
*
* @return array
*/
public function sluggable()
{
return [
'slug' => [
'source' => 'title'
]
];
}
}
和迁移文件:
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('post_translations', function (Blueprint $table) {
$table->increments('id');
$table->integer('post_id')->unsigned();
$table->string('locale')->index();
// The actual fields to store the content of your entity. You can add whatever you need.
$table->string('title');
$table->string('slug')->unique();
$table->text('content');
$table->unique(['post_id', 'locale']);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('post_translations');
}
你现在可以运行php artisan migrate
:)
请注意,我们要求将PostTranslation模型设为slugged。这实际上是有道理的,因为slug是与模型的标题一起生成的,由于我们的翻译逻辑,它存储在post_translations表中。
好 !我们有一个工作逻辑来翻译内容。但是我们仍然需要设置一些逻辑来以正确的语言显示应用程序视图,构建语言切换器并管理路由。
可翻译包提供了一个新的配置文件:config / translatable.php,您可以在其中列出应用程序的不同区域设置。更新locales
数组:
'locales' => [
'en' => 'English',
'fr' => 'Français',
],
这是我上一篇文章的重点,问题是在URL中没有语言代码的情况下设置语言环境。现在,我们想要完全相反!我们希望在URL的开头有语言代码,并使用它来设置Laravel语言环境。
首先,我们将创建一个新的中间件,如果需要,将对所有请求进行分析和修改。中间件的目标是强制语言显示为URL的第一部分。创建一个新文件:app / Http / Middleware / Language.php
// app/Http/Middleware/Language.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Foundation\Application;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
class Language
{
public function handle(Request $request, Closure $next)
{
// Check if the first segment matches a language code
if (!array_key_exists($request->segment(1), config('translatable.locales')) ) {
// Store segments in array
$segments = $request->segments();
// Set the default language code as the first segment
$segments = array_prepend($segments, config('app.fallback_locale'));
// Redirect to the correct url
return redirect()->to(implode('/', $segments));
}
return $next($request);
}
}
正如您在上面的代码中看到的那样,如果缺少语言代码,我们将重新创建整个URL路径。这样,您将始终定义一种语言。
不要忘记将中间件添加到Kernel.php文件中:
// app/Http/Kernel.php
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\Language::class, // Here it is
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
为了做到这一点,我们需要让Laravel 使用URL的第一段中的代码setLocale()。没有比AppServiceProvider更好的地方了。编辑AppServiceProvider.php并在引导方法中添加:
// app/Providers/AppServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Http\Request;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot(Request $request)
{
// Set the app locale according to the URL
app()->setLocale($request->segment(1));
}
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
}
根据URL的指定,Laravel现在可以使用正确的语言了。
如果你现在访问你的应用程序,你将看不到任何东西,因为你没有设置任何路由,除了Laravel样板模板的默认路由。
难道您不认为在routes / web.php文件中编辑所有路由以获得语言变量会很麻烦吗?并在HTML中的所有链接中指定此变量?是的,我也是,这就是RouteServiceProvider的原因!前往app / Providers / RouteServiceProvider.php!
//app/Providers/RouteServiceProvider.php
// ...
use Request;
class RouteServiceProvider extends ServiceProvider
{
// ...
protected function mapWebRoutes()
{
$locale = Request::segment(1);
Route::group([
'middleware' => 'web',
'namespace' => $this->namespace,
'prefix' => $locale
], function ($router) {
require base_path('routes/web.php');
});
}
// ...
}
基本上......就是这样!您可以尝试访问您的应用程序!
我知道,我知道,我们仍然需要构建一些东西,我将解释如何使用这种架构
由于应用程序区域设置是从AppServiceProvider设置的,因此您可以直接在routes / web.php文件中使用翻译。想象一下,您想要翻译静态页面:
www.mysite.com/en/about
www.mysite.com/fr/a-propos
两个不同的URL使用不同语言的相同视图?简单 !!您首先需要以硬编码的laravel方式创建一个语言文件来存储翻译。
我将路径名存储在语言文件资源/ lang / en / routes.php和resources / lang / fr / routes.php中
当然,您的应用中每种语言都有一个文件。
您现在可以在路径文件中执行以下操作:
Route::get(trans('routes.about'), ['as' => 'about', 'uses' => 'PageController@getAboutPage']);
从您的语言文件:
// resources/lang/en/routes.php
return [
'about' => 'about',
// other routes name
];
// resources/lang/fr/routes.php
return [
'about' => 'a-propos',
// other routes name
];
猜测您在页眉或页脚中有“关于”链接,使用路线名称创建正确的链接非常容易!
// layout.blade.php
{{ route('about') }}
可能比那更简单吗?:D显然不是。
我们看到了如何显示带有硬编码内容和带有编码的URL的静态页面,但是我们的博客帖子呢?首先,您需要在数据库中使用一些!看看这个命令类:https://github.com/mydnic/Laravel-Multilingual-SEO-Example/blob/master/app/Console/Commands/InsertDummyPosts.php 在你的视图中,实际上没什么特别的。
列表显示:
// routes/web.php
Route::get('/', ['as' => 'home', 'uses' => 'PostController@index']);
// app/Http/Controllers/PostController.php
class PostController extends Controller
{
public function index()
{
$posts = Post::latest()->get();
return view('blog')
->with('posts', $posts);
}
}
// resources/views/blog.blade.php
@foreach ($posts as $post)
<p class="panel panel-default">
<p class="panel-heading">
<a href="{{ route('post.show', $post->slug) }}">{{ $post->title }}</a>
</p>
<p class="panel-body">
{{ $post->content }}
</p>
<p class="panel-footer text-right">
<a href="{{ route('post.show', $post->slug) }}">
{{ trans('app.view_more') }}
</a>
</p>
</p>
@endforeach
如您所见,您可以像平常一样显示博客帖子列表。这是一件非常好的事情,因为将我们的多语言解决方案集成到现有应用程序中非常容易。
显示单个帖子有点复杂。
显示帖子
// routes/web.php
Route::get('post/{slug}', ['as' => 'post.show', 'uses' => 'PostController@show']);
// app/Http/Controllers/PostController.php
class PostController extends Controller
{
// ...
public function show($slug)
{
// This is the only difference you need be aware of
$post = Post::whereTranslation('slug', $slug)->firstOrFail();
return view('post')
->with('post', $post);
}
}
// resources/views/post.blade.php
<h1>{{ $post->title }}</h1>
<div class="panel panel-default">
<div class="panel-body">
{{ $post->content }}
</div>
</div>
当我们将slu存储在另一个表中时,您不能简单地使用该->where()
方法,而是使用->whereTranslation()
实际上有意义的方法!
我们还需要做一件事:语言切换器!
我将使用我在之前的博客文章中构建的相同内容。
添加新路线。
// routes/web.php
Route::get('lang/{language}', ['as' => 'lang.switch', 'uses' => 'LanguageController@switchLang']);
并创建一个新的控制器: php artisan make:controller LanguageController
奇迹发生的地方:
// app/Http/Controllers/LanguageController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Lang;
class LanguageController extends Controller
{
public function switchLang(Request $request, $lang)
{
// Store the URL on which the user was
$previous_url = url()->previous();
// Transform it into a correct request instance
$previous_request = app('request')->create($previous_url);
// Get Query Parameters if applicable
$query = $previous_request->query();
// In case the route name was translated
$route_name = app('router')->getRoutes()->match($previous_request)->getName();
// Store the segments of the last request as an array
$segments = $previous_request->segments();
// Check if the first segment matches a language code
if (array_key_exists($lang, config('translatable.locales'))) {
// If it was indeed a translated route name
if ($route_name && Lang::has('routes.' . $route_name, $lang)) {
// Translate the route name to get the correct URI in the required language, and redirect to that URL.
if (count($query)) {
return redirect()->to($lang . '/' . trans('routes.' . $route_name, [], $lang) . '?' . http_build_query($query));
}
return redirect()->to($lang . '/' . trans('routes.' . $route_name, [], $lang));
}
// Replace the first segment by the new language code
$segments[0] = $lang;
// Redirect to the required URL
if (count($query)) {
return redirect()->to(implode('/', $segments) . '?' . http_build_query($query));
}
return redirect()->to(implode('/', $segments));
}
return redirect()->back();
}
}
最后,在你的布局HTML中:
// resources/views/layouts/app.blade.php
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
{{ app()->getLocale() }} <i class="fa fa-caret-down"></i>
</a>
<ul class="dropdown-menu">
@foreach (config('translatable.locales') as $lang => $language)
@if ($lang != app()->getLocale())
<li>
<a href="{{ route('lang.switch', $lang) }}">
{{ $language }}
</a>
</li>
@endif
@endforeach
</ul>
</li>
这就是它
哇,这是一个很长的教程...我希望它能帮到你!
实际上......还有一件事我们需要做......
如果您现在测试您的应用程序,并且您访问博客帖子页面,如果您切换lang,URL中的语言代码会更新,内容也会更新,但实际的slug仍然是以前的语言。不好!
这是我们需要在PostController级别添加的逻辑。
需要三个简单的附加线。
// app/Http/Controllers/PostController.php
class PostController extends Controller
{
// ...
public function show($slug)
{
$post = Post::whereTranslation('slug', $slug)->firstOrFail();
// New Code
if ($post->translate()->where('slug', $slug)->first()->locale != app()->getLocale()) {
return redirect()->route('post.show', $post->translate()->slug);
}
return view('post')
->with('post', $post);
}
}
因此,您可以将此重定向逻辑添加到可以使用此“动态”重定向的任何方法。
这对我来说!如果您有任何评论或者我犯了一些错误,请在下面发表评论......不要忘记我在这里提供的解释工作示例。拉请求欢迎!
再见 !