laravel-lang/models
Adds localized names and attributes for Laravel Eloquent models via Laravel Lang. Plug-and-play translations for model labels across multiple languages to improve UI, validation messages, and admin panels. Install with composer and follow the docs for setup.
Installation:
composer require laravel-lang/models
Publish the package assets (if needed):
php artisan vendor:publish --provider="LaravelLang\Models\ServiceProvider"
Generate a Translatable Model:
php artisan make:model-translation Post -t en,es,fr
This creates:
Post (parent model) with HasTranslations trait.PostTranslation (translation model) with locale, title, body, etc.First Use Case:
// Create a post with translations
$post = Post::create([
'title' => 'Hello World', // Default locale (e.g., 'en')
'translations' => [
'es' => ['title' => 'Hola Mundo'],
'fr' => ['title' => 'Bonjour le Monde']
]
]);
// Retrieve translations
$post->getTranslation('es')->title; // "Hola Mundo"
HasTranslations (core functionality).orderByTranslation, whereTranslation (query filtering).make:model-translation (model generation).Define Translatable Fields:
Use the translatable array in your model to specify fields for translation:
protected $translatable = ['title', 'description'];
Create/Update Translations:
// Mass assignment with translations
$post = Post::create([
'title' => 'Default Title',
'translations' => [
'es' => ['title' => 'Título en Español'],
'fr' => ['title' => 'Titre en Français']
]
]);
// Update a single translation
$post->setTranslation('es', ['title' => 'Nuevo Título']);
Querying Translations:
// Get a specific locale's translation
$post->getTranslation('es')->title;
// Query with translation scopes
Post::whereTranslation('title', 'like', '%Hola%')->get();
Post::orderByTranslation('title', 'asc')->get();
Eager Loading:
// Load translations for multiple posts
$posts = Post::withTranslations(['en', 'es'])->get();
Fallback Logic:
Use getTranslation() with a fallback locale:
$post->getTranslation('es')->title ?? $post->getTranslation('en')->title;
Dynamic Locales: Set the current locale dynamically (e.g., via middleware):
app()->setLocale(request()->header('Accept-Language') ?? 'en');
API Responses: Serialize translations in API responses:
return PostResource::collection($posts)->additional([
'translations' => $posts->map->getTranslations()
]);
Admin Panels: Integrate with tools like Nova or Filament for translation management:
// Nova Tool Example
Nova::serving(function () {
Nova::resources([
new PostResource(),
]);
});
Testing:
Use the LocaleData type for type-safe tests:
use LaravelLang\Models\LocaleData;
public function test_translations()
{
$post = Post::create([
'translations' => [
'es' => ['title' => 'Test']
] as LocaleData
]);
}
Locale Column Mismatch:
PostTranslation model’s locale column matches the expected format (e.g., en, es). The package defaults to locale, but custom names require configuration:
protected $localeColumn = 'language_code';
Eager Loading Quirks:
withTranslations() method is called before get():
// Wrong: No translations loaded
$posts = Post::all()->withTranslations(['en']);
// Correct
$posts = Post::withTranslations(['en'])->get();
Mass Assignment Risks:
fill() method may not handle nested translations correctly. Use create() or update() with explicit arrays:
// Avoid:
$post->fill(['translations' => ['es' => ['title' => 'Test']]]);
// Prefer:
$post->setTranslation('es', ['title' => 'Test']);
Migration Conflicts:
PostTranslation, ensure the locale column is defined as a string (not enum or integer).Caching Issues:
php artisan config:clear
Check Translation Existence:
if ($post->hasTranslation('es')) {
// Translation exists
}
Log Translation Queries: Enable Eloquent logging to debug queries:
DB::enableQueryLog();
$post->getTranslation('es');
dd(DB::getQueryLog());
Validate Model Structure:
Use the lang:models-helper command to generate IDE helpers:
php artisan lang:models-helper
Custom Translation Storage:
Override the translationModelName() method to use a custom translation model:
public function translationModelName()
{
return 'CustomPostTranslation';
}
Add Translation Scopes:
Extend the HasTranslations trait to add custom scopes:
use LaravelLang\Models\Scopes\TranslationScope;
public function scopeActiveTranslations($query)
{
return $query->whereHas('translations', function ($q) {
$q->where('active', true);
});
}
Modify Fallback Logic:
Override getTranslation() to implement custom fallback behavior:
public function getTranslation($locale, $fallback = null)
{
$translation = parent::getTranslation($locale);
return $translation ?: $this->getTranslation($fallback ?? 'en');
}
Locale-Specific Validation:
Use the LocaleData type in validation rules:
use LaravelLang\Models\LocaleData;
public function rules()
{
return [
'translations.es.title' => 'required|string',
'translations.fr.title' => 'nullable|string',
];
}
Batch Translation Updates:
Use updateTranslations() for bulk updates:
$posts->each(function ($post) {
$post->updateTranslations([
'es' => ['title' => 'Updated Title']
]);
});
Query Optimization:
Limit loaded translations with withTranslations() to avoid N+1 queries:
$posts = Post::withTranslations(['en'])->limit(100)->get();
Caching Translations: Cache frequently accessed translations (e.g., product names):
$title = Cache::remember("post:{$post->id}:title:en", now()->addHours(1), function () {
return $post->getTranslation('en')->title;
});
How can I help you explore Laravel packages today?