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.phpassupportedLocales) - Create a
Postmodel that will have all the details about the post, except for the title and content - Create a
PostTranslationmodel 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
hasOnerelation and attribute mutators in ourPostmodel) - Validate each translated field for each supported locale
- Create a
Postand 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...
how can I check its uniqueness?
If you have user-entered SLUG, then it's just another field to add. Like:
Otherwise, you can also build the Unique rule via Rule::unique() helper: https://laravel.com/docs/10.x/validation#rule-unique