flobbos/laravel-translatable-db
Store model translations in a dedicated database table with Laravel. flobbos/laravel-translatable-db adds a simple translatable trait, helpers, and query support so you can persist and retrieve localized fields cleanly without JSON columns.
Installation
composer require flobbos/laravel-translatable-db
Publish the config and migrations:
php artisan vendor:publish --provider="Flobbos\LaravelTranslatable\LaravelTranslatableServiceProvider"
php artisan migrate
Basic Model Setup
Extend your model with Flobbos\LaravelTranslatable\Translatable trait and define translatable fields in $translatable:
use Flobbos\LaravelTranslatable\Translatable;
class Post extends Model
{
use Translatable;
protected $translatable = ['title', 'content'];
}
First Usage Create a post with translations:
$post = Post::create([
'title' => 'English Title',
'content' => 'English Content',
]);
$post->translate('es', [
'title' => 'Título en Español',
'content' => 'Contenido en Español',
]);
Retrieving Translations Fetch a translation by locale:
$post->getTranslation('es')->title; // "Título en Español"
Or use the localize() helper:
$post->localize('es')->title; // Same result
Dynamic Translation Handling
Use translatable() to dynamically add/remove translatable fields:
$model->translatable(['new_field']); // Add
$model->translatable(null); // Reset
Locale Fallbacks
Configure fallbacks in config/translatable.php:
'fallback_locale' => 'en',
'fallbacks' => [
'es' => 'en',
'fr' => 'en',
],
Then fetch translations with fallback:
$post->localize('es')->title; // Falls back to 'en' if 'es' missing
Scopes for Localized Queries
Use the translated() scope to filter by translations:
Post::translated('title', 'like', '%English%')->get();
API Responses Serialize translations in API responses:
return Post::withTranslations()->get();
Or manually:
return $post->toArray(['translations']);
Form Requests Validate and bind translations in form requests:
public function rules()
{
return [
'title' => 'required',
'translations.es.title' => 'required',
];
}
public function prepareForValidation()
{
$this->merge([
'translations' => json_decode($this->input('translations'), true),
]);
}
toSearchableArray() to include translations.withTranslations() in queries for admin panels.translatable.* rules for nested validation.Cache::rememberForever().Migration Conflicts
translations table, ensure it matches the package’s schema:
Schema::create('translations', function (Blueprint $table) {
$table->id();
$table->string('locale')->index();
$table->json('attributes');
$table->foreignId('translatable_id')->constrained()->onDelete('cascade');
$table->unique(['translatable_id', 'locale']);
});
Mass Assignment Risks
$fillable or use $guarded.protected $fillable = ['title', 'content'];
Locale-Specific Queries
Translation::where(...)). Use the provided scopes instead.JSON Field Limitations
attributes column uses JSON, which may cause issues with very large translations. Consider splitting into separate columns if needed.Model Events
savingTranslation, savedTranslation, etc. Listen carefully to avoid infinite loops:
$model->setAttribute('title', 'New Title');
$model->save(); // Triggers translation events
Check for Missing Translations
Use hasTranslation('es') to verify:
if (!$post->hasTranslation('es')) {
// Handle missing translation
}
Inspect Raw Translations
Dump the translations relationship:
dd($post->translations);
Enable Query Logging
Temporarily enable debug mode in config/translatable.php:
'debug' => true,
Clear Cached Compiled Views If translations aren’t updating, run:
php artisan view:clear
Custom Storage
Override getTranslationsTable() in your model:
protected function getTranslationsTable()
{
return 'custom_translations';
}
Custom Attribute Handling Extend the trait to modify how attributes are stored:
use Flobbos\LaravelTranslatable\Translatable as BaseTranslatable;
trait CustomTranslatable
{
use BaseTranslatable;
protected function getTranslatableAttributes()
{
return array_merge($this->$translatable, ['custom_field']);
}
}
Add Scopes Extend the query builder in your model:
public function scopeActiveTranslations($query)
{
return $query->whereHas('translations', function ($q) {
$q->where('locale', app()->getLocale());
});
}
Custom Serialization
Override toArray() or toJson() to format translations:
public function toArray()
{
return array_merge(parent::toArray(), [
'translations' => $this->translations->pluck('attributes', 'locale'),
]);
}
withTranslations() when fetching models with translations.locale and translatable_id are indexed in the translations table.updateTranslations() for bulk updates:
$posts->each->updateTranslations('es', ['title' => 'Updated']);
How can I help you explore Laravel packages today?