Installation
composer require fevrok/laravel-translatable
For Laravel <5.5, add the service provider to config/app.php:
Fevrok\Translatable\TranslatableServiceProvider::class,
Publish Config & Migrations
php artisan vendor:publish --provider="Fevrok\Translatable\TranslatableServiceProvider"
php artisan migrate
Configure Defaults
Update config/translatable.php:
'enabled' => true,
'locale' => 'en', // Default language
Make a Model Translatable
Add the Translatable trait and define $translatable fields:
use Fevrok\Translatable\Translatable;
class Product extends Model
{
use Translatable;
protected $translatable = ['name', 'description'];
}
// Create a translatable record
$product = Product::create([
'name' => 'Base Name',
'description' => 'Base Description',
]);
// Set translations
$product->setTranslation('name', 'es', 'Nombre Traducido');
$product->setTranslation('description', 'es', 'Descripción Traducida');
// Retrieve translations
$esName = $product->getTranslation('name', 'es'); // 'Nombre Traducido'
Use setTranslation() and getTranslation() for runtime translations:
// In a controller or service
$product->setTranslation('name', request('locale'), request('translated_name'));
Override $fillable to include translatable fields:
protected $fillable = ['name', 'description'];
protected $translatable = ['name', 'description'];
// Mass assign with translations
Product::create([
'name' => 'Base',
'description' => 'Base Desc',
'translations' => [
'name' => ['es' => 'Español'],
'description' => ['es' => 'Descripción']
]
]);
Filter records by translation:
// In a model scope
public function scopeInLanguage($query, $locale)
{
return $query->whereHas('translations', function($q) use ($locale) {
$q->where('locale', $locale);
});
}
// Usage
Product::inLanguage('es')->get();
Use accessors to format translations in responses:
public function getTranslatedNameAttribute()
{
return $this->getTranslation('name', app()->getLocale());
}
Validate translations in a FormRequest:
public function rules()
{
return [
'name_en' => 'required|string',
'description_es' => 'required|string',
];
}
public function prepareForValidation()
{
$this->merge([
'name_en' => $this->input('name.translations.en'),
'description_es' => $this->input('description.translations.es'),
]);
}
Extend search to include translations:
use Laravel\Scout\Searchable;
class Product extends Model
{
use Translatable, Searchable;
public function toSearchableArray()
{
return [
'name' => $this->getTranslation('name', app()->getLocale()),
'description' => $this->getTranslation('description', app()->getLocale()),
];
}
}
Customize Nova fields for translations:
use Laravel\Nova\Fields\Text;
Text::make('Name Translation', 'name_translations')
->onlyOnDetail()
->asFunctionOf(function ($value) {
return $value->getTranslation('name', 'es');
});
Translate related models:
class Category extends Model
{
use Translatable;
protected $translatable = ['title'];
public function products()
{
return $this->hasMany(Product::class);
}
}
// Access translated category title
$product->category->getTranslation('title', 'fr');
Missing Migrations
php artisan migrate after publishing will break translation storage.Locale Mismatch
locale column in the translations table doesn’t match config/translatable.php.locale in config matches your app’s default and user-selected locales.Mass Assignment Risks
$fillable. Explicitly include them or use merge():
$product->merge([
'translations' => [
'name' => ['es' => 'Nombre']
]
]);
Caching Issues
fresh() or freshTimestamp() if translations aren’t updating:
$product = Product::find(1)->fresh();
Overwriting Translations
setTranslation() overwrites existing translations for the same field/locale. Use pushTranslation() (if available) for appending.Check the translations Table
Verify translations are stored:
SELECT * FROM translations WHERE translatable_id = 1;
Log Translation Calls Temporarily add logging to the trait:
trait Translatable {
public function setTranslation($field, $locale, $value)
{
\Log::debug("Setting {$field} to {$value} for locale {$locale}");
// ... rest of the method
}
}
Validate Config
Ensure config/translatable.php has:
'enabled' => true,
'locale' => 'en', // Must match your app's default locale
Custom Translation Storage
Override the getTranslationsTable() method in your model:
public function getTranslationsTable()
{
return 'custom_translations';
}
Add Translation Events Listen for translation changes:
use Fevrok\Translatable\Events\TranslationSaved;
TranslationSaved::listen(function ($model, $field, $locale, $value) {
\Log::info("Translation updated: {$field} ({$locale}) = {$value}");
});
Dynamic Locale Handling Extend the trait to support runtime locale switching:
public function setCurrentLocale($locale)
{
$this->currentLocale = $locale;
}
public function getTranslation($field, $locale = null)
{
$locale = $locale ?: $this->currentLocale;
// ... rest of the method
}
Fallback Locales
Implement fallback logic in getTranslation():
public function getTranslation($field, $locale)
{
$translation = $this->translations->firstWhere('field', $field)->where('locale', $locale);
if (!$translation) {
$translation = $this->translations->firstWhere('field', $field)->where('locale', config('translatable.fallback_locale'));
}
return $translation ? $translation->value : null;
}
API Resource Formatting Create a custom resource for translations:
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->getTranslation('name', $request->locale),
'description' => $this->getTranslation('description', $request->locale),
];
}
How can I help you explore Laravel packages today?