astrotomic/laravel-translatable
Laravel package for translatable Eloquent models. Store model translations in the database and automatically fetch/save multilingual attributes based on locale, reducing boilerplate when working with multi-language content.
Installation
composer require astrotomic/laravel-translatable
php artisan vendor:publish --tag=translatable
Configure locales in config/translatable.php (e.g., ['en', 'fr', 'es']).
Create Migration
php artisan make:migration create_posts_table
php artisan make:migration create_post_translations_table
Define tables for the main model (e.g., posts) and translations (e.g., post_translations).
Define Models
Post.php):
use Astrotomic\Translatable\Translatable;
class Post extends Model
{
use Translatable;
public $translatedAttributes = ['title', 'content'];
protected $fillable = ['author'];
}
PostTranslation.php):
class PostTranslation extends Model
{
public $timestamps = false;
protected $fillable = ['title', 'content'];
}
First Use Case
// Create a post with translations
$post = Post::create([
'author' => 'John Doe',
'en' => ['title' => 'Hello World'],
'fr' => ['title' => 'Bonjour le monde'],
]);
// Access translations
echo $post->translate('en')->title; // "Hello World"
echo $post->translate('fr')->title; // "Bonjour le monde"
Dynamic Locale Switching
Use App::setLocale() or middleware to switch locales globally:
App::setLocale('fr');
echo $post->title; // Automatically fetches French translation
Mass Assignment with Translations
$data = [
'author' => 'Jane Doe',
'translations' => [
'en' => ['title' => 'Updated Title'],
'es' => ['title' => 'Título Actualizado'],
],
];
Post::create($data);
Fallback Handling
// Force fallback to 'en' if translation is missing
$post->translate('it', true)->title; // Falls back to English
Querying with Translations
// Get posts with English translations
$posts = Post::withTranslations('en')->get();
Replicating with Translations
$clone = $post->replicateWithTranslations();
getTranslationsArray() to serialize translations:
return response()->json($post->getTranslationsArray());
translateOrNew() to preload translations:
$translation = $post->translateOrNew('de');
translatedAttributes for dynamic form fields (e.g., QuickAdminPanel, Nova).public function scopeInLocale($query, $locale)
{
return $query->whereHas('translations', function($q) use ($locale) {
$q->where('locale', $locale);
});
}
Locale Mismatches
config/translatable.php match those used in code.if (!in_array($locale, config('translatable.locales'))) {
throw new \InvalidArgumentException("Locale {$locale} not configured.");
}
Missing Translations
translate() returns null for missing locales. Use translateOrDefault() or translateOrNew() to avoid NullPointerException.fallback_locale in config to auto-fallback:
'fallback_locale' => 'en',
Circular References in Serialization
toArray() in collections).getTranslationsArray() or exclude translations:
protected $hidden = ['translations'];
STI (Single Table Inheritance) Issues
translationForeignKey if using STI:
protected $translationForeignKey = 'parent_id';
Performance with Large Datasets
$posts = Post::with(['translations' => function($query) {
$query->where('locale', app()->getLocale());
}])->get();
if (!$post->hasTranslation('fr')) {
// Handle missing translation
}
dd($post->getTranslationsArray());
\DB::enableQueryLog();
$post->translate('en');
dd(\DB::getQueryLog());
Custom Translation Models
Override the default Translation model by setting translationModel() in the trait:
use Astrotomic\Translatable\Translatable;
class Post extends Model
{
use Translatable;
protected function translationModel()
{
return CustomPostTranslation::class;
}
}
Dynamic Translated Attributes Use accessors/mutators for dynamic fields:
public function getDescriptionAttribute()
{
return $this->translate()->description ?? 'No description';
}
Validation Rules Extend validation to ensure translations exist:
use Astrotomic\Translatable\Validation\TranslatableRule;
$validator = Validator::make($data, [
'translations.en.title' => ['required', new TranslatableRule('Post', 'en', 'title')],
]);
Events Listen for translation-related events:
\Event::listen('translatable.saving', function ($model, $locale, $attributes) {
// Log translation changes
});
Caching Cache translations for read-heavy apps:
$translation = Cache::remember("post_{$post->id}_{$locale}", now()->addHours(1), function() use ($post, $locale) {
return $post->translate($locale);
});
translations_wrapper:
Useful for nested payloads (e.g., API requests):
'translations_wrapper' => 'i18n',
$post = Post::create([
'i18n' => [
'en' => ['title' => 'Hello'],
'fr' => ['title' => 'Bonjour'],
],
]);
fallback_locale:
Set to null to disable fallback entirely. Defaults to the first locale in locales.
default_locale:
Override per-model:
class Post extends Model
{
use Translatable;
protected $defaultLocale = 'fr';
}
How can I help you explore Laravel packages today?