Skip to main content

Translating Models: With No Packages

Premium
11 min read

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...

The Full Lesson is Only for Premium Members

Want to access all of our courses? (30 h 41 min)

You also get:

55 courses
Premium tutorials
Access to repositories
Private Discord
Get Premium for $129/year or $29/month

Already a member? Login here

Comments & Discussion

N
Nillos ✓ Link copied!

how can I check its uniqueness?

$rules += [ 'name.' . $locale => ['required', 'string', 'unique:posts', 'max:255'], ];

M
Modestas ✓ Link copied!

If you have user-entered SLUG, then it's just another field to add. Like:

$rules += [
'title.' . $locale => ['required', 'string'],
'post.' . $locale => ['required', 'string'],
'slug.' . $locale => ['required', 'string', 'unique:post_translations,slug'],
];

Otherwise, you can also build the Unique rule via Rule::unique() helper: https://laravel.com/docs/10.x/validation#rule-unique

We'd Love Your Feedback

Tell us what you like or what we can improve

Feel free to share anything you like or dislike about this page or the platform in general.