DEV Community

Laravel Mastery
Laravel Mastery

Posted on

Stop Hardcoding Translations in Laravel - Use Translatable

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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';
}
Enter fullscreen mode Exit fullscreen mode

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é',
    ],
];
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

🚀 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,
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

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(),
        ],
    ]);
}
Enter fullscreen mode Exit fullscreen mode

The Response

{
  "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

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]);
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

📦 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];
Enter fullscreen mode Exit fullscreen mode

🎓 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

  1. Always set a fallback:
// config/app.php
'fallback_locale' => 'en',
Enter fullscreen mode Exit fullscreen mode
  1. Dynamic form selects:
$options = collect(ArticleStatus::cases())
    ->map(fn($s) => [
        'value' => $s->value,
        'label' => $s->trans()
    ]);
Enter fullscreen mode Exit fullscreen mode
  1. Localized notifications:
$locale = $user->locale ?? 'en';
$message = "Status changed to: " . $order->status->trans($locale);
Enter fullscreen mode Exit fullscreen mode

🌟 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)

Collapse
 
xwero profile image
david duymelinck • Edited

Why is the first solution you go to a third party package, instead of using the build-in functionality?

$status = ArticleStatus::DRAFT;
$label = $status->trans(); 

// versus
__('status.article.'.$status); // can come from the database, an enum, a hardcoded string

// status.php
return [
    'article' => [
      'draft' => 'Daft',
  ],
];
Enter fullscreen mode Exit fullscreen mode

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.

$message = "Status changed to: " . $order->status->trans($locale);

// versus

$message = __('messages.status.changed', ['status' => __('status.article.'.$status)]);

// messages.php
return [
   'status' => [
      'changed' => 'Status changed to :status',
   ],
];
Enter fullscreen mode Exit fullscreen mode

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.

Collapse
 
stas_7702602173bf3ccef914 profile image
Stas

Yep, that's true.

$locale = $user->locale ?? 'en';
$message = "Status changed to: " . $order->status->trans($locale);
Enter fullscreen mode Exit fullscreen mode

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.