Courses

Multi-Language Laravel 11: All You Need to Know

We've covered translations for static text and routes, but what about the Models? What if we have a Blog Post that we want to store in a multi-language format?

There are quite a few ways to do this, but first, let's look at the simplest way without any packages:


Let's Get Started

For this demo, we will create Posts in multiple languages. The idea here will be:

  • Make sure that we have a list of supported locales somewhere (for example in config/app.php as supportedLocales)
  • Create a Post model that will have all the details about the post, except for the title and content
  • Create a PostTranslation model that will have the title and content of the post in a specific language
  • Automatically load the current locale translation of the post when we retrieve the post (with hasOne relation and attribute mutators in our Post model)
  • Validate each translated field for each supported locale
  • Create a Post and its translations in one go

Here's the database schema for our Post and its translations in PostTranslation:

Migration

Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->dateTime('publish_date')->nullable();
$table->foreignId('user_id')->constrained();
$table->softDeletes();
$table->timestamps();
});
 
Schema::create('post_translations', function (Blueprint $table) {
$table->id();
$table->foreignId('post_id')->constrained()->cascadeOnDelete();
$table->string('locale');
$table->string('title');
$table->longText('post');
$table->softDeletes();
$table->timestamps();
});

Our Post model, which will contain a title and post attributes to always have the current locale translation of the text:

app/Models/Post.php

use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\SoftDeletes;
 
class Post extends Model
{
use SoftDeletes;
 
protected $fillable = [
'publish_date',
'user_id',
];
 
protected $casts = [
'publish_date' => 'datetime',
];
 
// Preloading current locale translation at all times
protected $with = [
'defaultTranslation'
];
 
public function title(): Attribute
{
return new Attribute(
// Always making sure that we have current locale title
get: fn() => $this->defaultTranslation->title,
);
}
 
public function post(): Attribute
{
return new Attribute(
// Always making sure that we have current locale post model
get: fn() => $this->defaultTranslation->post,
);
}
 
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
 
public function translations(): HasMany
{
return $this->hasMany(PostTranslation::class);
}
 
public function defaultTranslation(): HasOne
{
// Making sure that we always retrieve current locale information
return $this->translations()->one()->where('locale', app()->getLocale());
}
}

And our PostTranslation model:

app/Models/PostTranslation.php

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
 
class PostTranslation extends Model
{
use SoftDeletes;
 
protected $fillable = [
'post_id',
'locale',
'title',
'post',
];
 
public function post(): BelongsTo
{
return $this->belongsTo(Post::class);
}
}

Once we have our Models, we can look into our PostController and see how we can create a new post with translations...

This lesson is only for Premium Members.
Want to access all lessons of this course?

You also get:

  • 68 courses (1188 lessons, 43 h 18 min total)
  • Premium tutorials
  • Access to repositories
  • Private Discord