Stop Hardcoding Translations in Laravel - Use Translatable

Stop Hardcoding Translations in Laravel - Use Translatable

Source: Dev.to

Stop Hardcoding Translations in Laravel - Use Translatable 🤔 The Problem
Building a multilingual Laravel app? You've probably written messy code like this: There's a much cleaner way.
✨ The Solution: Translatable Enums
Meet the Laravel Enum Translatable package by Osama Sadah.
Install It 🚀 Real Example: Blog API
The Model Perfect for multi-language frontends! 🌍
*💪 Advanced: Combine with Business Logic
* 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 🎓 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 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 Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK:
// ❌ The nightmare approach
switch($status) { case 'draft': return $locale === 'es' ? 'Borrador' : ($locale === 'fr' ? 'Brouillon' : 'Draft'); // ... more chaos
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
// ❌ The nightmare approach
switch($status) { case 'draft': return $locale === 'es' ? 'Borrador' : ($locale === 'fr' ? 'Brouillon' : 'Draft'); // ... more chaos
} CODE_BLOCK:
// ❌ The nightmare approach
switch($status) { case 'draft': return $locale === 'es' ? 'Borrador' : ($locale === 'fr' ? 'Brouillon' : 'Draft'); // ... more chaos
} CODE_BLOCK:
composer require osama/laravel-enum-translatable Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
composer require osama/laravel-enum-translatable CODE_BLOCK:
composer require osama/laravel-enum-translatable CODE_BLOCK:
<?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';
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
<?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';
} CODE_BLOCK:
<?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';
} COMMAND_BLOCK:
// 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é', ],
]; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
// 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é', ],
]; COMMAND_BLOCK:
// 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é', ],
]; COMMAND_BLOCK:
$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'] Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
$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'] COMMAND_BLOCK:
$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'] COMMAND_BLOCK:
use App\Enums\ArticleStatus;
use Illuminate\Database\Eloquent\Model; class Article extends Model
{ protected function casts(): array { return [ 'status' => ArticleStatus::class, ]; }
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
use App\Enums\ArticleStatus;
use Illuminate\Database\Eloquent\Model; class Article extends Model
{ protected function casts(): array { return [ 'status' => ArticleStatus::class, ]; }
} COMMAND_BLOCK:
use App\Enums\ArticleStatus;
use Illuminate\Database\Eloquent\Model; class Article extends Model
{ protected function casts(): array { return [ 'status' => ArticleStatus::class, ]; }
} COMMAND_BLOCK:
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(), ], ]);
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
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(), ], ]);
} COMMAND_BLOCK:
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(), ], ]);
} CODE_BLOCK:
{ "id": 1, "title": "Getting Started with Laravel", "status": { "value": "published", "label": "Published", "all_translations": { "en": "Published", "es": "Publicado", "fr": "Publié" } }
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
{ "id": 1, "title": "Getting Started with Laravel", "status": { "value": "published", "label": "Published", "all_translations": { "en": "Published", "es": "Publicado", "fr": "Publié" } }
} CODE_BLOCK:
{ "id": 1, "title": "Getting Started with Laravel", "status": { "value": "published", "label": "Published", "all_translations": { "en": "Published", "es": "Publicado", "fr": "Publié" } }
} COMMAND_BLOCK:
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]); }
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
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]); }
} COMMAND_BLOCK:
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]); }
} CODE_BLOCK:
<span class="badge badge-{{ $order->status->badge() }}"> {{ $order->status->trans() }}
</span> @if($order->status->canBeCancelled()) <button class="btn btn-danger"> {{ __('Cancel Order') }} </button>
@endif Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
<span class="badge badge-{{ $order->status->badge() }}"> {{ $order->status->trans() }}
</span> @if($order->status->canBeCancelled()) <button class="btn btn-danger"> {{ __('Cancel Order') }} </button>
@endif CODE_BLOCK:
<span class="badge badge-{{ $order->status->badge() }}"> {{ $order->status->trans() }}
</span> @if($order->status->canBeCancelled()) <button class="btn btn-danger"> {{ __('Cancel Order') }} </button>
@endif CODE_BLOCK:
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); } }
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
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); } }
} CODE_BLOCK:
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); } }
} COMMAND_BLOCK:
// 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]; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
// 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]; COMMAND_BLOCK:
// 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]; COMMAND_BLOCK:
// config/app.php
'fallback_locale' => 'en', Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
// config/app.php
'fallback_locale' => 'en', COMMAND_BLOCK:
// config/app.php
'fallback_locale' => 'en', COMMAND_BLOCK:
$options = collect(ArticleStatus::cases()) ->map(fn($s) => [ 'value' => $s->value, 'label' => $s->trans() ]); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
$options = collect(ArticleStatus::cases()) ->map(fn($s) => [ 'value' => $s->value, 'label' => $s->trans() ]); COMMAND_BLOCK:
$options = collect(ArticleStatus::cases()) ->map(fn($s) => [ 'value' => $s->value, 'label' => $s->trans() ]); CODE_BLOCK:
$locale = $user->locale ?? 'en';
$message = "Status changed to: " . $order->status->trans($locale); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
$locale = $user->locale ?? 'en';
$message = "Status changed to: " . $order->status->trans($locale); CODE_BLOCK:
$locale = $user->locale ?? 'en';
$message = "Status changed to: " . $order->status->trans($locale); - Always set a fallback: - Dynamic form selects: - Localized notifications: