novius/laravel-translatable
Make Laravel Eloquent models translatable using locale and locale_parent_id fields. Provides migration macro, Translatable trait with translations relations (incl. soft-deleted), translate/getTranslation helpers, and withLocale query scope. Supports Laravel 10–13, PHP 8.2–8.5.
composer require novius/laravel-translatable
translatable() macro to your migration:
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->translatable(); // Adds `locale` and `locale_parent_id` columns
$table->string('title');
$table->text('content');
$table->timestamps();
});
use Novius\LaravelTranslatable\Traits\Translatable;
class Post extends Model {
use Translatable;
}
$post = new Post(['title' => 'Titre Français']);
$post->save();
$post->translate('en', ['title' => 'English Title']);
$englishPost = $post->getTranslation('en');
withLocale() scope to fetch posts in a specific language:
$posts = Post::withLocale('es')->get();
getTranslation().$post->translate('de', ['title' => 'Deutscher Titel']);
$post->translate('ja', [
'title' => '日本語のタイトル',
'content' => '日本語のコンテンツ...'
]);
translateAttributes() to transform data before saving:
protected function translateAttributes($parent): void {
$this->slug = Str::slug($parent->title . ' ' . $this->locale);
}
$translation = $post->getTranslation('fr');
$posts = Post::withLocale('pt')->get();
$translation = $post->getTranslation('en', true); // Includes soft-deleted
$translation = $post->getTranslation($locale) ?: $post->getTranslation('en');
translatableConfig():
public function translatableConfig(): TranslatableModelConfig {
return new TranslatableModelConfig(
['en', 'fr', 'es'], // Only these locales
'locale',
'locale_parent_id'
);
}
translations relation to list all translations in a dropdown:
$translations = $post->translations->pluck('locale', 'id');
Route::get('/posts/{post}/translations/{locale}', function (Post $post, $locale) {
return $post->getTranslation($locale) ?: abort(404);
});
withLocale() in API queries to filter results by language.fetch(`/posts/${postId}/translations/${userLocale}`)
.then(response => response.json())
.then(data => renderPost(data));
$translation = Cache::remember(
"post.{$post->id}.locale.{$locale}",
now()->addHours(1),
fn() => $post->getTranslation($locale)
);
Orphaned Translations on Soft Delete
SoftDeletes, translations may become orphaned. Mitigate by:
translationsWithDeleted to fetch them.deleted_at column to translations or cascading deletes.Performance with Many Locales
translations for models with 50+ locales can be slow. Optimize with:
$post->translations()->where('locale', $desiredLocale)->first();
locale and locale_parent_id:
Schema::table('posts_translations', function (Blueprint $table) {
$table->index(['locale', 'locale_parent_id']);
});
Missing Fallback Logic
public function getTranslationOrFallback(string $locale): ?Model {
return $this->getTranslation($locale) ?: $this->getTranslation('en');
}
No Translation History
versions table for history.audit packages (e.g., owen-it/auditing) for tracking.AGPL License Restrictions
spatie/laravel-translatable (MIT) if proprietary code is involved.Check for Orphaned Records
SELECT * FROM posts_translations WHERE locale_parent_id NOT IN (SELECT id FROM posts);
Verify Locale Configuration
translatableConfig() is correctly set if you override it. Test with:
$post->translate('invalid_locale', [...]); // Should fail if restricted
Debug Soft Deletes
translationsWithDeleted is used.deleted_at column exists on the translations table.Query Performance
DB::enableQueryLog() to analyze slow queries:
$post->translations; // Check the generated SQL
Add Translation Events
$post->translations()->created(function ($translation) {
// Log or notify
});
Custom Validation
protected function translateAttributes($parent): void {
$this->validate([
'title' => ['required', 'max:255'],
'content' => ['required', 'max:10000'],
]);
}
Translation Scopes
withLocale scope for complex queries:
public function scopeWithLocaleActive($query, $locale) {
return $query->withLocale($locale)->where('published', true);
}
Bulk Translation Tools
$posts = Post::where('locale', 'fr')->get();
foreach ($posts as $post) {
$post->translate('en', ['title' => __($post->title)]);
}
Integration with Localization Packages
laravel-localization for route/locale switching:
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;
$locale = LaravelLocalization::getCurrentLocale();
$post = Post::withLocale($locale)->find($id);
How can I help you explore Laravel packages today?