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(5mo ago)03MITPHPPHP &gt;=7.3

Since Jan 3Pushed 5mo 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 today

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

29

—

LowBetter than 57% of packages

Maintenance70

Regular maintenance activity

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity32

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

169d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/131385452?v=4)[ahmed-rashad-alnaggar](/maintainers/ahmed-rashad-alnaggar)[@ahmed-rashad-alnaggar](https://github.com/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.1k8.4M96](/packages/mongodb-laravel-mongodb)[kirschbaum-development/eloquent-power-joins

The Laravel magic applied to joins.

1.6k32.6M46](/packages/kirschbaum-development-eloquent-power-joins)[spatie/laravel-sluggable

Generate slugs when saving Eloquent models

1.6k12.9M311](/packages/spatie-laravel-sluggable)[psalm/plugin-laravel

Psalm plugin for Laravel

3355.3M346](/packages/psalm-plugin-laravel)[yajra/laravel-oci8

Oracle DB driver for Laravel via OCI8

8793.2M25](/packages/yajra-laravel-oci8)[spiritix/lada-cache

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

592456.3k2](/packages/spiritix-lada-cache)

PHPackages © 2026

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