Stop Hardcoding Translations in Laravel - Use Translatable
🤔 The Problem
Building a multilingual Laravel app? You've probably written messy code like this:
// ❌ The nightmare approach
switch($status) {
case 'draft':
return $locale === 'es' ? 'Borrador' :
($locale === 'fr' ? 'Brouillon' : 'Draft');
// ... more chaos
}
There's a much cleaner way.
✨ The Solution: Translatable Enums
Meet the Laravel Enum Translatable package by Osama Sadah.
Install It
composer require osama/laravel-enum-translatable
Create Your Enum
<?php
namespace App\Enums;
use Osama\LaravelEnums\Concerns\EnumTranslatable;
enum ArticleStatus: string
{
use EnumTranslatable; // ← Magic happens here
case DRAFT = 'draft';
case PUBLISHED = 'published';
case REJECTED = 'rejected';
}
Add Translations
// lang/en/enums.php
return [
'article_statuses' => [
'draft' => 'Draft',
'published' => 'Published',
'rejected' => 'Rejected',
],
];
// lang/es/enums.php
return [
'article_statuses' => [
'draft' => 'Borrador',
'published' => 'Publicado',
'rejected' => 'Rechazado',
],
];
// lang/fr/enums.php
return [
'article_statuses' => [
'draft' => 'Brouillon',
'published' => 'Publié',
'rejected' => 'Rejeté',
],
];
Use It Everywhere
$status = ArticleStatus::DRAFT;
// Current locale
$label = $status->trans();
// "Draft" (en) or "Borrador" (es) or "Brouillon" (fr)
// Specific locale
$spanish = $status->trans('es'); // "Borrador"
// All translations at once
$all = $status->allTrans();
// ['en' => 'Draft', 'es' => 'Borrador', 'fr' => 'Brouillon']
🚀 Real Example: Blog API
The Model
use App\Enums\ArticleStatus;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
protected function casts(): array
{
return [
'status' => ArticleStatus::class,
];
}
}
The Controller
public function show(Article $article)
{
return response()->json([
'id' => $article->id,
'title' => $article->title,
'status' => [
'value' => $article->status->value,
'label' => $article->status->trans(),
'all_translations' => $article->status->allTrans(),
],
]);
}
The Response
{
"id": 1,
"title": "Getting Started with Laravel",
"status": {
"value": "published",
"label": "Published",
"all_translations": {
"en": "Published",
"es": "Publicado",
"fr": "Publié"
}
}
}
Perfect for multi-language frontends! 🌍
*💪 Advanced: Combine with Business Logic
*
enum OrderStatus: string
{
use EnumTranslatable;
case PENDING = 'pending';
case PAID = 'paid';
case SHIPPED = 'shipped';
case DELIVERED = 'delivered';
public function badge(): string
{
return match($this) {
self::PENDING => 'warning',
self::PAID => 'info',
self::SHIPPED => 'primary',
self::DELIVERED => 'success',
};
}
public function canBeCancelled(): bool
{
return in_array($this, [self::PENDING, self::PAID]);
}
}
In Your Blade View
<span class="badge badge-{{ $order->status->badge() }}">
{{ $order->status->trans() }}
</span>
@if($order->status->canBeCancelled())
<button class="btn btn-danger">
{{ __('Cancel Order') }}
</button>
@endif
Clean, readable, maintainable! ✨
🎯 Why This Rocks
✅ Type-Safe - PHP 8.1+ enum power
✅ DRY - Write translation once, use everywhere
✅ Clean Code - No conditionals scattered around
✅ Scalable - Add languages without code changes
✅ Testable - Easy to unit test
🧪 Quick Test Example
public function test_translations_exist_for_all_locales()
{
$locales = ['en', 'es', 'fr'];
foreach (ArticleStatus::cases() as $status) {
foreach ($locales as $locale) {
$translation = $status->trans($locale);
$this->assertNotNull($translation);
$this->assertNotEmpty($translation);
}
}
}
📦 Quick Reference
// Get current locale translation
$status->trans()
// Get specific locale
$status->trans('es')
// Get all translations
$status->allTrans()
// In models (automatic casting)
protected $casts = ['status' => ArticleStatus::class];
🎓 Want More?
This is a quick introduction. I've written a comprehensive guide covering:
🏗️ Multi-tenant applications
🔧 Custom translation structures
⚡ Performance optimization
🚀 Production deployment strategies
🧪 Complete testing approaches
📊 Real-world case studies
👉 Read the Full Guide on Medium →
💡 Pro Tips
- Always set a fallback:
// config/app.php
'fallback_locale' => 'en',
- Dynamic form selects:
$options = collect(ArticleStatus::cases())
->map(fn($s) => [
'value' => $s->value,
'label' => $s->trans()
]);
- Localized notifications:
$locale = $user->locale ?? 'en';
$message = "Status changed to: " . $order->status->trans($locale);
🌟 Common Use Cases
E-commerce: Order statuses, payment statuses
CMS: Article statuses, content types
SaaS: Subscription tiers, user roles
Task Management: Priority levels, task states
Multi-tenant Apps: Per-tenant translations
Top comments (2)
Why is the first solution you go to a third party package, instead of using the build-in functionality?
I love enums, but even for me this is going too far.
With the package you need to maintain the translation files and the enums, that is just extra work you gave yourself.
Also the localized notification message example it very limiting.
It looks to me you seen a new toy and you can't wait to test it out, forgetting the solution that already is working.
Yep, that's true.
Also, what a nice example of a so-called "translated" string, haha :) Just imagine seeing this on your website: `Status changed to: Доставлено"
P.S. It's a shame I can't downvote posts... this one deserves it.