PHPackages                             alnaggar/laravel-translatable-model - 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. [Database &amp; ORM](/categories/database)
4. /
5. alnaggar/laravel-translatable-model

ActiveLibrary[Database &amp; ORM](/categories/database)

alnaggar/laravel-translatable-model
===================================

A Laravel package to store model attribute translations in a separate database table.

1.2(3mo ago)03MITPHPPHP &gt;=7.3

Since Jan 3Pushed 3mo agoCompare

[ Source](https://github.com/ahmed-rashad-alnaggar/laravel-translatable-model)[ Packagist](https://packagist.org/packages/alnaggar/laravel-translatable-model)[ RSS](/packages/alnaggar-laravel-translatable-model/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (3)Dependencies (2)Versions (4)Used By (0)

Laravel Translatable Model
==========================

[](#laravel-translatable-model)

[![I Stand With Palestine Badge](./arts/PalestineBadge.svg)](./arts/PalestineBadge.svg)

[![I Stand With Palestine Banner](./arts/PalestineBanner.svg)](./arts/PalestineBanner.svg)

[![Latest Stable Version](https://camo.githubusercontent.com/13153539a356eb1479e0557b17dc0754cb6917681ed5246b1fc521ed88458e1b/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f616c6e61676761722f6c61726176656c2d7472616e736c617461626c652d6d6f64656c)](https://packagist.org/packages/alnaggar/laravel-translatable-model)[![Total Downloads](https://camo.githubusercontent.com/d11ecfa39c590c486a30ff5fe45a29725bda25b1a6ac3d255304e0bcc18448ce/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f616c6e61676761722f6c61726176656c2d7472616e736c617461626c652d6d6f64656c)](https://packagist.org/packages/alnaggar/laravel-translatable-model)[![License](https://camo.githubusercontent.com/688b9e2efc3ef3a4badb5cc7da0da9858eb35b5bd7fc9b7e72b57ae9a2dbd3e1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f616c6e61676761722f6c61726176656c2d7472616e736c617461626c652d6d6f64656c)](https://packagist.org/packages/alnaggar/laravel-translatable-model)

A small package that stores model attribute translations in a separate database table and provide a simple trait-based API to set/get translations per-locale, including support for nested (dot-notated) keys and [dynamic discovery](#dynamic-discovery).

Table of Contents
-----------------

[](#table-of-contents)

- [Requirements](#requirements)
- [Installation](#installation)
- [Configuration](#configuration)
- [Migration](#migration)
- [Usage](#usage)
- [Dynamic Discovery](#dynamic-discovery)
- [Implementation Notes](#implementation-notes)
- [Contributing](#contributing)
- [Credits](#credits)
- [License](#license)

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

[](#requirements)

- PHP 7.3+
- Laravel 8+

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

[](#installation)

1. Install the package using Composer:

    ```
    composer require alnaggar/laravel-translatable-model
    ```
2. Publish the configuration and migration files:

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

    ```
    php artisan vendor:publish --tag="translatable-model-migrations"
    ```
3. Run the migration:

    ```
    php artisan migrate
    ```

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

[](#configuration)

The published config file is `config/translatable-model.php` and exposes:

- `connection` (`string`|`null`): Database connection to use for the translations table. `null` uses the app default connection.
- `fallback_behavior` (`string` | `bool` | `null`): Controls how missing translations are handled **when no explicit fallback locale can be provided**, such as when accessing translatable attributes via:

    - `$model->attribute`
    - `$model['attribute']`
    - `$model->attributesToArray()`

    Supported values:

    - `string` (locale): Fallback to the specified locale.
    - `true` or `null`: Fallback to the application fallback locale.
    - `false`: Do not fallback to any locale (return `null`).
- `flush_translations_on_soft_delete` (`bool`): When `true`, translations will be flushed when a model is soft-deleted. When `false` (default), translations are only flushed on a force-delete.

Migration
---------

[](#migration)

The package publishes a migration that creates the `model_translations` table with the following columns:

- `translatable_type` (string)
- `translatable_id` (string) — supports numeric or string IDs
- `locale` (string)
- `key` (string) — attribute name (supports dot notation for nested values)
- `value` (text, nullable)
- `created_at`, `updated_at`

The table has a composite primary key on `translatable_type`, `translatable_id`, `locale`, `key`.

Usage
-----

[](#usage)

Add the `HasTranslations` trait to any Eloquent model and list translatable attributes.

```
use Alnaggar\TranslatableModel\HasTranslations;

class Post extends Model
{
    use HasTranslations;

    // flat or dot-notated keys
    protected $translatables = [
        'title',
        'body',
        'meta.title',
        'meta.description',
    ];
}
```

### Get a translation

[](#get-a-translation)

```
$titleAr = $post->getTranslation(
    key: 'title',
    locale: 'ar', // null for current locale
    fallback: 'en' // null for app fallback locale, false to return null when missing
);

// retrieves the translation in the current locale,
// using the configured (config/translatable-model.php) fallback behavior
$titleAr = $post->title;
$titleAr = $post['title'];
```

### Set translation(s)

[](#set-translations)

```
$post->setTranslation(
    key: 'title',
    value: 'Hello world',
    locale: 'en' // null for current locale
);

$post->setTranslation(
    key: 'title',
    value: ['en' => 'Hello', 'fr' => 'Bonjour']
);

// Laravel-style assignment for current locale
$post->title = 'Bonjour à tous';

// Translations are upserted when the model is saved
$post->save();
```

### Remove a translation

[](#remove-a-translation)

```
$post->removeTranslation(
    key: 'meta.description',
    locale: 'fr' // null for current locale
);

$post->save();
```

### Flush translations

[](#flush-translations)

```
// remove all French translations
$post->flushTranslations('fr');

// remove all translations for the model
$post->flushTranslations(null);

$post->save();
```

### Nested attributes

[](#nested-attributes)

When a translatable key targets nested data (dot notation), the trait will inject translations into the parent attribute when reading:

```
// Assuming 'meta.description' is translatable
$post->meta; // => ['author' => 'Ahmad', 'description' => 'Translated value']
```

You can set nested attributes in bulk and the trait will persist translatable parts:

```
$post->meta = [
    'author' => 'Ahmad',
    'description' => 'A translations management project',
];

$post->save();
```

### Persisting behavior

[](#persisting-behavior)

All translation operations are queued on the model instance and persisted when the model **is saved**.

**Deleting the model flushes all its translations automatically.** For models using SoftDeletes, by default translations are flushed only when the model is force deleted; to flush on soft delete set `flush_translations_on_soft_delete` to `true` in the package config.

### Checking translation existence

[](#checking-translation-existence)

```
if ($post->hasTranslation('content', 'ar')) {
    // Arabic translation exists
}
```

### Dynamic discovery

[](#dynamic-discovery)

Defining `translatables` is optional. If your model does not declare translatable keys, the trait will automatically discover existing translation keys stored for the model (for example, seeded or pre-stored translations). This is useful for models with dynamic or varied structures (e.g. a `Setting` model).

Example seeder that demonstrates dynamic discovery:

```
use App\Models\Setting;
use Alnaggar\TranslatableModel\HasTranslations;
use Illuminate\Database\Seeder;

class SettingSeeder extends Seeder
{
    public function run()
    {
        $generalSettings = Setting::create([
            'key' => 'general',
            'value' => [
                'app_name' => null,
                'app_timezone' => config('app.timezone'),
            ],
        ]);

        $generalSettings->setTranslation('value.app_name', 'My App', 'en');

        $generalSettings->save();
    }
}
```

Implementation notes
--------------------

[](#implementation-notes)

- The trait defers DB writes: translations are cached on the model and upserted/deleted when the model is saved.
- `null` translation values in an upsert are interpreted as removals for that key/locale.
- `$model->attributesToArray()` — will include translatable attributes (with nested values injected).
- **All translatable attribute columns MUST be nullable at the database level.**
    - The package sets these columns to `null` because their actual values are resolved dynamically from the translations store.
    - Nested translatable attributes are also persisted as `null`.

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

[](#contributing)

If you find any issues or have suggestions for improvements, feel free to open an issue or submit a pull request on the GitHub repository.

Credits
-------

[](#credits)

- Palestine banner and badge by [Safouene1](https://github.com/Safouene1/support-palestine-banner).

License
-------

[](#license)

**Laravel Translatable Model** is open-sourced software licensed under the [MIT license](LICENSE).

###  Health Score

31

—

LowBetter than 68% of packages

Maintenance78

Regular maintenance activity

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity31

Early-stage or recently created project

 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 ~6 days

Total

3

Last Release

116d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/deb82fef56c6147e5256320e88516da1909e25764a692938928eb1929de809db?d=identicon)[ahmed-rashad-alnaggar](/maintainers/ahmed-rashad-alnaggar)

---

Top Contributors

[![ahmed-rashad-alnaggar](https://avatars.githubusercontent.com/u/131385452?v=4)](https://github.com/ahmed-rashad-alnaggar "ahmed-rashad-alnaggar (5 commits)")

---

Tags

laraveltranslationmodeleloquenttranslatable

### Embed Badge

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

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

###  Alternatives

[mongodb/laravel-mongodb

A MongoDB based Eloquent model and Query builder for Laravel

7.1k7.2M71](/packages/mongodb-laravel-mongodb)[tucker-eric/eloquentfilter

An Eloquent way to filter Eloquent Models

1.8k4.8M26](/packages/tucker-eric-eloquentfilter)[dyrynda/laravel-model-uuid

This package allows you to easily work with UUIDs in your Laravel models.

4802.8M8](/packages/dyrynda-laravel-model-uuid)[spiritix/lada-cache

A Redis based, automated and scalable database caching layer for Laravel

591444.8k2](/packages/spiritix-lada-cache)[rinvex/laravel-categories

Rinvex Categories is a polymorphic Laravel package, for category management. You can categorize any eloquent model with ease, and utilize the power of Nested Sets, and the awesomeness of Sluggable, and Translatable models out of the box.

470161.6k3](/packages/rinvex-laravel-categories)[pdphilip/elasticsearch

An Elasticsearch implementation of Laravel's Eloquent ORM

145360.2k4](/packages/pdphilip-elasticsearch)

PHPackages © 2026

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