PHPackages                             emrane23/laravel-translatable - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Localization &amp; i18n](/categories/localization)
4. /
5. emrane23/laravel-translatable

ActiveLibrary[Localization &amp; i18n](/categories/localization)

emrane23/laravel-translatable
=============================

A simple, elegant and powerful translation package for Laravel

v1.0.9(3w ago)023↓100%MITPHPPHP ^8.1CI passing

Since May 16Pushed 3w agoCompare

[ Source](https://github.com/Emrane23/laravel-translatable)[ Packagist](https://packagist.org/packages/emrane23/laravel-translatable)[ Docs](https://github.com/Emrane23/laravel-translatable)[ RSS](/packages/emrane23-laravel-translatable/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (5)Versions (11)Used By (0)

laravel-translatable
====================

[](#laravel-translatable)

**Zero-config translations for Laravel — SPA, monolith, and everything in between.**

[![Latest Version on Packagist](https://camo.githubusercontent.com/26e06f75fc797cf8dfa51fa441cc12ea338f889db3a40a2e5dc6732d88adc490/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f656d72616e6532332f6c61726176656c2d7472616e736c617461626c652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/emrane23/laravel-translatable)[![Total Downloads](https://camo.githubusercontent.com/bd7832bbf5599f22f37b4705881d2806a8af676c7d0b1ff672aef6e69e08c78a/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f656d72616e6532332f6c61726176656c2d7472616e736c617461626c652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/emrane23/laravel-translatable)[![License](https://camo.githubusercontent.com/9c2b242d7a0e40245994115d11d60617b58967e2c9d2c42c74ba351ff1ed0152/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f656d72616e6532332f6c61726176656c2d7472616e736c617461626c652e7376673f7374796c653d666c61742d737175617265)](LICENSE.md)

---

Philosophy
----------

[](#philosophy)

Most translation packages store all languages in separate columns or JSON fields. This package takes a different approach:

- The **default language** lives directly in the model column — fast, native SQL, no joins
- **Other languages** live in a separate `translations` table — clean and scalable
- **Automatic fallback** — if a translation is missing, returns the default language value
- **Magic getter** — just call `$model->name`, it returns the right language automatically
- **Zero code change** in your controllers or views

---

Hybrid Laravel Support
----------------------

[](#hybrid-laravel-support)

This package works seamlessly across all Laravel architectures:

- SPA applications (Vue.js / React / Inertia.js)
- Classic Laravel monoliths (Blade + sessions)
- API-first architectures (mobile apps)
- Hybrid systems (mixed environments)

The middleware locale source is fully configurable — you pick the detection mechanism that fits your project.

---

Requirements
------------

[](#requirements)

- PHP 8.1+
- Laravel 10.x / 11.x / 12.x / 13.x
- Any database supported by Laravel (MySQL, PostgreSQL, SQLite)

---

Installation
------------

[](#installation)

```
composer require emrane23/laravel-translatable
```

Publish and run the migration:

```
php artisan vendor:publish --tag="translatable-migrations"
php artisan migrate
```

Optionally publish the config:

```
php artisan vendor:publish --tag="translatable-config"
```

---

Configuration
-------------

[](#configuration)

```
// config/translatable.php
return [
    'default_locale'    => env('APP_LOCALE', 'fr'),
    'fallback_locale'   => env('APP_FALLBACK_LOCALE', 'en'),
    'supported_locales' => ['fr', 'en', 'ar', 'es'],

    // Pick the one that matches your project — see Middleware section below
    'locale_source' => 'header',
];
```

```
APP_LOCALE=fr
APP_FALLBACK_LOCALE=en
```

---

Quick Start
-----------

[](#quick-start)

### 1. Add the trait to your model

[](#1-add-the-trait-to-your-model)

```
use Emrane23\Translatable\Traits\Translatable;

class Product extends Model
{
    use Translatable;

    protected $fillable = ['name', 'description', 'price'];

    protected $translatable = ['name', 'description'];
}
```

### 2. That's it.

[](#2-thats-it)

```
// App locale is 'fr' → returns "Ordinateur portable" (from column, no join)
// App locale is 'en' → returns "Laptop" (from translations table)
// App locale is 'ar' → returns "حاسوب محمول" (from translations table)
// App locale is 'de' → returns "Ordinateur portable" (fallback to default)

$product->name;
```

No controller changes. No view changes. It just works.

---

Middleware — Locale Detection
-----------------------------

[](#middleware--locale-detection)

The `TranslationMiddleware` detects the current locale automatically from the source you configure. An `InvalidArgumentException` is thrown if an invalid source value is provided.

### Laravel 11, 12, 13 — `bootstrap/app.php`

[](#laravel-11-12-13--bootstrapappphp)

```
use Emrane23\Translatable\Middleware\TranslationMiddleware;

->withMiddleware(function (Middleware $middleware) {
    $middleware->appendToGroup('api', TranslationMiddleware::class);
})
```

### Laravel 10 — `app/Http/Kernel.php`

[](#laravel-10--apphttpkernelphp)

```
use Emrane23\Translatable\Middleware\TranslationMiddleware;

protected $middlewareGroups = [
    'api' => [
        TranslationMiddleware::class,
    ],
];
```

### Available sources

[](#available-sources)

ValueHow it worksBest for`header`Reads `X-Locale` request headerSPA, API, mobile`query`Reads `?locale=` URL parameterDirect URLs, emails`session`Reads `session()->get('locale')`Classic monolith`cookie`Reads `cookie('locale')`Persistent preference`user`Reads from authenticated userPer-user preference### Configure for your architecture

[](#configure-for-your-architecture)

```
// config/translatable.php

'locale_source' => 'header',   // SPA / API
'locale_source' => 'session',  // Classic monolith
'locale_source' => 'cookie',   // Persistent preference
'locale_source' => 'query',    // URL parameter
'locale_source' => 'user',     // Per-user preference
```

### Frontend (Vue.js / React / any SPA)

[](#frontend-vuejs--react--any-spa)

```
axios.defaults.headers.common['X-Locale'] = 'ar';
```

### User locale — `preferredLocale()`

[](#user-locale--preferredlocale)

The `user` source uses `preferredLocale()` if available (Laravel's `HasLocalePreference` interface), then falls back to a direct `locale` attribute. You can adapt it to your own mechanism:

```
// Option 1 — HasLocalePreference (recommended)
use Illuminate\Contracts\Translation\HasLocalePreference;

class User extends Authenticatable implements HasLocalePreference
{
    public function preferredLocale(): string
    {
        return $this->locale ?? config('app.locale');
    }
}

// Option 2 — Custom attribute (adapt to your own mechanism)
public function getLocaleAttribute(): string
{
    return $this->settings['language'] ?? config('app.locale');
}
```

---

Email Locale
------------

[](#email-locale)

Implement `HasLocalePreference` on your `User` model to send emails in each user's preferred language:

```
use Illuminate\Contracts\Translation\HasLocalePreference;

class User extends Authenticatable implements HasLocalePreference
{
    public function preferredLocale(): string
    {
        return $this->locale ?? config('app.locale');
    }
}
```

Laravel will automatically use the user's locale when sending notifications.

---

Available Methods
-----------------

[](#available-methods)

### Reading translations

[](#reading-translations)

```
// Current app locale (automatic)
$product->name;

// Explicit locale
$product->getTranslatedAttribute('name', 'en');

// Without fallback
$product->getTranslatedAttribute('name', 'es', false);

// With full metadata — returns [value, locale_used, found]
[$value, $locale, $found] = $product->getTranslatedAttributeMeta('name', 'en');
```

### Writing translations

[](#writing-translations)

```
$product->setAttributeTranslations('name', [
    'fr' => 'Ordinateur portable', // saved to column directly
    'en' => 'Laptop',              // saved to translations table
    'ar' => 'حاسوب محمول',
    'es' => 'Portátil',
]);

// Save immediately
$product->setAttributeTranslations('name', ['en' => 'Laptop'], save: true);
```

### Eager loading (avoid N+1)

[](#eager-loading-avoid-n1)

```
Product::withTranslation()->get();            // current locale + fallback
Product::withTranslation('en')->get();        // specific locale
Product::withTranslation('en', false)->get(); // no fallback
```

For large applications with heavy traffic, eager load translations globally:

```
// In your model
protected $with = ['translations'];
```

### Deleting translations

[](#deleting-translations)

```
$product->deleteAttributeTranslation('name', 'en');
$product->deleteAttributeTranslation('name', ['en', 'es']);
$product->deleteAttributeTranslations(['name', 'description'], ['en', 'es']);
$product->deleteAttributeTranslations(['name', 'description']); // all locales
```

### Introspection

[](#introspection)

```
$product->translatable();               // true
$product->getTranslatableAttributes();  // ['name', 'description']
```

---

Seeder Pattern
--------------

[](#seeder-pattern)

### Method 1 — `bulkSeed`

[](#method-1--bulkseed)

The simplest way. One bulk query for all translations.

```
use Emrane23\Translatable\Helpers\TranslationSeeder;

class ProductSeeder extends Seeder
{
    public function run(): void
    {
        $p1 = Product::create(['name' => 'Ordinateur portable', 'price' => 999.99]);
        $p2 = Product::create(['name' => 'Souris sans fil', 'price' => 29.99]);

        TranslationSeeder::bulkSeed(Product::class, [
            [
                'id'          => $p1->id,
                'name'        => ['fr' => 'Ordinateur portable', 'en' => 'Laptop', 'ar' => 'حاسوب محمول', 'es' => 'Portátil'],
                'description' => ['fr' => 'Puissant et léger', 'en' => 'Powerful & light', 'ar' => 'قوي وخفيف', 'es' => 'Potente y ligero'],
            ],
            [
                'id'          => $p2->id,
                'name'        => ['fr' => 'Souris sans fil', 'en' => 'Wireless Mouse', 'ar' => 'فأرة لاسلكية', 'es' => 'Ratón inalámbrico'],
                'description' => ['fr' => 'Ergonomique', 'en' => 'Ergonomic', 'ar' => 'مريح', 'es' => 'Ergonómico'],
            ],
        ], ['name', 'description']);
    }
}
```

### Method 2 — `prepare` + `flush`

[](#method-2--prepare--flush)

Useful when seeding multiple models in one shot.

```
$translations = [];

foreach ($products as $product) {
    $translations = array_merge($translations,
        TranslationSeeder::prepare('products', $product->id, 'name', [
            'en' => 'Laptop',
            'ar' => 'حاسوب محمول',
        ])
    );
}

foreach ($rewards as $reward) {
    $translations = array_merge($translations,
        TranslationSeeder::prepare('rewards', $reward->id, 'name', [
            'en' => 'Gold Trophy',
            'ar' => 'كأس ذهبي',
        ])
    );
}

TranslationSeeder::flush($translations); // single query for everything
```

---

Database Structure
------------------

[](#database-structure)

```
┌──────────────────────┐     ┌──────────────────────────────────────┐
│       products       │     │           translations                │
├──────────────────────┤     ├──────────────────────────────────────┤
│ id       → 1         │────▶│ table_name  → products               │
│ name     → "Ordi..."  │     │ foreign_key → 1                      │
│ (default locale)     │     │ column_name → name                   │
└──────────────────────┘     │ locale      → en                     │
                             │ value       → "Laptop"               │
                             └──────────────────────────────────────┘

```

The default language is stored directly in the model column — no joins needed for the most common case. Other languages are fetched only when requested. One `translations` table serves all your models with no extra migrations needed.

Fallback chain: `requested locale → fallback locale → default column`

---

Advanced Usage
--------------

[](#advanced-usage)

All models share the same `translations` table:

```
class Product extends Model
{
    use Translatable;
    protected $translatable = ['name', 'description'];
}

class Reward extends Model
{
    use Translatable;
    protected $translatable = ['name', 'description'];
}

class SeasonChallenge extends Model
{
    use Translatable;
    protected $translatable = ['title', 'description'];
}
```

---

Contributing
------------

[](#contributing)

```
git clone https://github.com/Emrane23/laravel-translatable
cd laravel-translatable
composer install
composer test
```

---

Changelog
---------

[](#changelog)

See [CHANGELOG](CHANGELOG.md) for recent changes.

---

License
-------

[](#license)

MIT. See [LICENSE](LICENSE.md).

---

Author
------

[](#author)

**Emrane Klaai** — [@Emrane23](https://github.com/Emrane23) — Built from Tunisia

*"The best architecture is the one that solves real problems elegantly."*

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance95

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity48

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

10

Last Release

23d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/397dede5b8e2f0c6bab2de9b9ce7d1224778360d0215baa05381217d77819dd4?d=identicon)[emrane\_klaai](/maintainers/emrane_klaai)

---

Top Contributors

[![Emrane23](https://avatars.githubusercontent.com/u/88634484?v=4)](https://github.com/Emrane23 "Emrane23 (14 commits)")

---

Tags

laravellocalizationi18ntranslationtranslatable

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/emrane23-laravel-translatable/health.svg)

```
[![Health](https://phpackages.com/badges/emrane23-laravel-translatable/health.svg)](https://phpackages.com/packages/emrane23-laravel-translatable)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k51.0M7.4k](/packages/larastan-larastan)[api-platform/laravel

API Platform support for Laravel

59156.3k10](/packages/api-platform-laravel)[calebdw/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

15104.9k4](/packages/calebdw-larastan)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
