PHPackages                             bouleluciole/db-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. bouleluciole/db-translatable

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

bouleluciole/db-translatable
============================

Package de facilitation d'internationalisation database

0.2.2(2mo ago)066MITPHPPHP ^8.2

Since Jan 14Pushed 3mo agoCompare

[ Source](https://github.com/Samy-Green/db-translatable)[ Packagist](https://packagist.org/packages/bouleluciole/db-translatable)[ RSS](/packages/bouleluciole-db-translatable/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (10)Versions (8)Used By (0)

DB-Translatable
===============

[](#db-translatable)

**DB-Translatable** est une solution robuste et performante pour gérer les traductions de vos modèles Eloquent dans des tables dédiées. Contrairement au stockage JSON, cette approche permet des recherches SQL natives, un typage strict et une intégrité référentielle totale.

Le package respecte strictement les conventions **snake\_case** et **lowercase** pour une compatibilité parfaite avec les outils comme Prisma ou les standards de bases de données modernes.

---

📋 Table des matières
--------------------

[](#-table-des-matières)

- [Installation](#-installation)
- [Configuration](#%EF%B8%8F-configuration)
    - [Tables et conventions](#tables-et-conventions)
    - [Gestion des locales](#gestion-des-locales)
    - [Cache](#cache)
    - [Comportement Eloquent](#comportement-eloquent)
- [Mise en place](#-mise-en-place)
    - [Définir un modèle traduisible](#1-d%C3%A9finir-un-mod%C3%A8le-traduisible)
    - [Générer l'infrastructure](#2-g%C3%A9n%C3%A9rer-linfrastructure)
    - [Configuration avancée du modèle](#3-configuration-avanc%C3%A9e-du-mod%C3%A8le)
- [Gestion de la langue](#-gestion-de-la-langue)
    - [Via le Helper](#via-le-helper)
    - [Via la Facade](#via-la-facade)
    - [Drivers disponibles](#drivers-disponibles)
- [Manipulation des données](#-manipulation-des-donn%C3%A9es)
    - [Accès direct](#acc%C3%A8s-direct-via-locale-courante)
    - [Locale forcée](#forcer-une-locale-temporairement)
    - [Accès explicite](#acc%C3%A8s-explicite-via-translationproxy)
    - [Exécuter un callback dans une locale](#ex%C3%A9cuter-un-callback-dans-une-locale)
    - [Récupérer toutes les traductions](#r%C3%A9cup%C3%A9rer-toutes-les-traductions)
- [Suppression de traductions](#-suppression-de-traductions)
- [Informations et utilitaires](#-informations-et-utilitaires)
- [Query Scopes](#-query-scopes)
    - [Scopes de base (T)](#scopes-de-base-t)
    - [Scopes stricts (StrictT)](#scopes-stricts-strictt)
    - [Scopes intelligents (Smart)](#scopes-intelligents-smart)
    - [Scopes de vérification](#scopes-de-v%C3%A9rification)
    - [Scopes de recherche](#scopes-de-recherche)
    - [Configuration des scopes](#configuration-des-scopes)
- [Migration des données existantes](#-migration-des-donn%C3%A9es-existantes)
- [Commandes Artisan](#%EF%B8%8F-commandes-artisan)
- [Fallbacks et cascade](#-fallbacks-et-cascade)
- [Gestion du cache](#%EF%B8%8F-gestion-du-cache)
- [Cas d'usage avancés](#-cas-dusage-avanc%C3%A9s)
- [Bonnes pratiques](#-bonnes-pratiques)
- [Tests](#-tests)

---

🚀 Installation
--------------

[](#-installation)

1. **Installer via Composer** :

    ```
    composer require bouleluciole/db-translatable
    ```
2. **Publier la configuration** :

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

> **Note :** Le package utilise le *Laravel Auto-Discovery*. Le Service Provider est enregistré automatiquement via les métadonnées du `composer.json`.

---

⚙️ Configuration
----------------

[](#️-configuration)

Le fichier `config/db-translatable.php` centralise le comportement du package :

### Tables et conventions

[](#tables-et-conventions)

```
'tables' => [
    'prefix' => '',                  // Préfixe des tables de traduction
    'suffix' => '_translations',     // Suffixe des tables de traduction
    'locale_column' => 'locale',     // Nom de la colonne locale
    'foreign_key' => null,           // Clé étrangère (null = auto)
],
```

**Exemple** : Pour un modèle `Post`, la table de traduction sera `post_translations`.

### Gestion des locales

[](#gestion-des-locales)

```
'locale' => [
    // Driver de résolution de la locale
    'driver' => 'cookie',  // 'static' | 'session' | 'cookie'

    // Locale par défaut
    'default' => config('app.locale', 'fr'),

    // Configuration par driver
    'drivers' => [
        'session' => [
            'key' => 'translatable.locale',
        ],
        'cookie' => [
            'key'    => 'translatable_locale',
            'minutes' => 60 * 24 * 365,  // 1 an
        ],
        'static' => [
            'value' => config('app.locale', 'fr'),
        ],
    ],

    // Fallbacks globaux
    'fallback' => ['en', 'fr'],

    // Fallbacks spécifiques par langue
    'map_fallbacks' => [
        'en' => ['fr'],
        'es' => 'fr',
    ],

    // Utilisation des fallbacks par mode
    'use_fallback' => [
        'auto' => true,      // $model->title
        'forced' => false,   // $model->useLocale('en')
        'explicit' => false, // $model->translation('en')
    ],

    // Mode strict (lève une exception si traduction manquante)
    'strict' => false,

    // Locales autorisées (null = toutes)
    'allowed' => null,  // ['fr', 'en', 'es']

    // Autoriser les locales non listées
    'allow_unknown' => false,

    // Valeur retournée si traduction manquante
    'missing_value' => null,

    // Empêcher l'écriture de locales inconnues
    'prevent_writing_unknown_locale' => false,
],
```

### Cache

[](#cache)

```
'cache' => [
    'enabled' => true,              // Activer le cache
    'store' => null,                // Store Laravel (null = défaut)
    'prefix' => 'db-translatable',  // Préfixe des clés
    'ttl' => 3600,                  // Durée de vie en secondes
],
```

### Comportement Eloquent

[](#comportement-eloquent)

```
'eloquent' => [
    // Chargement automatique des traductions
    'autoload' => true,

    // Nom de la relation
    'relation' => 'translations',

    // Accès dynamique aux attributs ($model->title)
    'dynamic_access' => true,

    // Utiliser les fallbacks dans les scopes
    'use_fallback_in_scopes' => false,

    // Colonnes à ignorer en lecture
    'skipped_getters' => [],

    // Colonnes à ignorer en écriture
    'skipped_setters' => ['created_at', 'updated_at', 'deleted_at'],
],
```

---

🏗️ Mise en place
----------------

[](#️-mise-en-place)

### 1. Définir un modèle traduisible

[](#1-définir-un-modèle-traduisible)

Ajoutez le trait `HasTranslations` et listez les attributs traduisibles :

```
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use DbTranslatable\Concerns\HasTranslations;

class Post extends Model
{
    use HasTranslations;

    public array $translatable = ['title', 'content', 'slug'];
}
```

### 2. Générer l'infrastructure

[](#2-générer-linfrastructure)

```
# Générer la migration de traduction
php artisan make:translation-migration Post

# Générer le modèle de traduction
php artisan make:translation-model Post

# Exécuter les migrations
php artisan migrate
```

Cela créera :

- La table `posts_translations` avec les colonnes traduisibles
- Le modèle `PostTranslation`

### 3. Configuration avancée du modèle

[](#3-configuration-avancée-du-modèle)

```
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use DbTranslatable\Concerns\HasTranslations;

class Post extends Model
{
    use HasTranslations, SoftDeletes;

    /**
     * Les attributs qui peuvent être traduits.
     * Ces champs doivent exister dans la table 'posts_translations'.
     */
    protected array $translatable = [
        'title',
        'content',
        'slug_translated',
    ];

    /**
     * OPTIONNEL : Définit explicitement le modèle de traduction.
     * Par défaut : NomDuModele + 'Translation' (ex: PostTranslation).
     */
    protected string $translationModel = PostTranslation::class;

    /**
     * OPTIONNEL : Liste des colonnes à ignorer lors de l'ACCÈS (getAttribute).
     * Ces colonnes seront toujours lues sur la table parente 'posts'.
     */
    protected array $skippedGetters = [
        'internal_reference',
    ];

    /**
     * OPTIONNEL : Liste des colonnes à ignorer lors de la MODIFICATION (setAttribute).
     * Utile si vous voulez que 'slug' soit géré par le modèle parent uniquement.
     */
    protected array $skippedSetters = [
        'slug',
        'user_id',
    ];

    /**
     * Attributs de base du modèle (table 'posts').
     */
    protected $fillable = [
        'slug',
        'user_id',
        'internal_reference',
        'is_published',
    ];
}
```

---

🌍 Gestion de la langue
----------------------

[](#-gestion-de-la-langue)

Le package permet de définir la langue de manière persistante ou temporaire via le `LocaleResolver`.

### Via le Helper

[](#via-le-helper)

Le helper `db_translatable()` est le moyen le plus rapide d'interagir avec la configuration.

```
// Définir la langue (sera stockée en cookie/session selon le driver)
db_translatable()->setCurrentLocale('en');

// Récupérer la langue active
echo db_translatable()->current();  // 'en'

// Récupérer la langue de fallback
echo db_translatable()->fallback();  // 'fr'

// Vérifier si les fallbacks sont activés
$useFallback = db_translatable()->useFallback();  // true/false

// Vérifier si le mode strict est activé
$strict = db_translatable()->strict();  // true/false
```

### Via la Facade

[](#via-la-facade)

```
use DbTranslatable\Facades\LocaleResolver;

LocaleResolver::setCurrentLocale('es');

echo LocaleResolver::current();  // 'es'
```

### Drivers disponibles

[](#drivers-disponibles)

#### Static

[](#static)

La locale est fixe, définie dans la config.

```
'driver' => 'static',
'drivers' => [
    'static' => [
        'value' => 'fr',
    ],
],
```

#### Session

[](#session)

La locale est stockée en session Laravel.

```
'driver' => 'session',
'drivers' => [
    'session' => [
        'key' => 'translatable.locale',
    ],
],
```

#### Cookie

[](#cookie)

La locale est stockée dans un cookie (recommandé pour les applications web).

```
'driver' => 'cookie',
'drivers' => [
    'cookie' => [
        'key'    => 'translatable_locale',
        'minutes' => 60 * 24 * 365,  // 1 an
    ],
],
```

---

📝 Manipulation des données
--------------------------

[](#-manipulation-des-données)

### Accès direct via locale courante

[](#accès-direct-via-locale-courante)

```
// Créer un post
$post = Post::create(['slug' => 'mon-post']);

// Définir la traduction pour la locale courante
$post->title = 'Titre FR';
$post->content = 'Contenu en français';
$post->save();

// Lire la traduction (locale courante)
echo $post->title;  // 'Titre FR'

// Changer de langue
db_translatable()->setCurrentLocale('en');

// Définir la traduction anglaise
$post->title = 'English Title';
$post->save();

echo $post->title;  // 'English Title'
```

### Forcer une locale temporairement

[](#forcer-une-locale-temporairement)

```
// Forcer une locale pour cette instance uniquement
$post->useLocale('en');
echo $post->title;  // 'English Title'

$post->useLocale('fr');
echo $post->title;  // 'Titre FR'

// Réinitialiser la locale forcée
$post->resetLocale();
echo $post->title;  // Retour à la locale courante
```

### Accès explicite via TranslationProxy

[](#accès-explicite-via-translationproxy)

Le `TranslationProxy` permet d'accéder directement à une traduction spécifique sans modifier la locale globale ou celle du modèle.

```
// Lecture
echo $post->translation('fr')->title;  // 'Titre FR'
echo $post->translation('en')->title;  // 'English Title'

// Écriture
$post->translation('es')->title = 'Título en español';
$post->translation('es')->content = 'Contenido en español';
$post->save();

// Appeler des méthodes sur la traduction
$post->translation('en')->update(['title' => 'Updated Title']);

// Chaînage fluent
$post->translation('de')
    ->fill(['title' => 'Deutscher Titel'])
    ->save();
```

### Exécuter un callback dans une locale

[](#exécuter-un-callback-dans-une-locale)

La méthode `withLocale` permet d'exécuter un callback en forçant temporairement une locale, puis restaure automatiquement l'état précédent.

```
$post->withLocale('en', function ($model) {
    $model->title = 'Hello World';
    $model->content = 'This is content in English';
});

// La locale précédente est automatiquement restaurée
echo $post->title;  // Utilise la locale courante

// Même en cas d'exception, la locale est restaurée
try {
    $post->withLocale('de', function ($model) {
        $model->title = 'Deutscher Titel';
        throw new Exception('Erreur !');
    });
} catch (Exception $e) {
    // La locale a été restaurée malgré l'exception
}
```

### Récupérer toutes les traductions

[](#récupérer-toutes-les-traductions)

```
// Récupérer toutes les traductions d'un champ
$translations = $post->getTranslations('title');
// ['fr' => 'Titre FR', 'en' => 'English Title', 'es' => 'Título en español']

// Récupérer toutes les traductions de tous les champs
$allTranslations = $post->getTranslations();
/*
[
    'fr' => ['title' => 'Titre FR', 'content' => '...', 'slug' => '...'],
    'en' => ['title' => 'English Title', 'content' => '...', 'slug' => '...'],
    'es' => ['title' => 'Título en español', 'content' => '...', 'slug' => '...'],
]
*/

// Accéder comme un attribut
$locales = $post->available_locales;
// ['fr', 'en', 'es']
```

---

🗑️ Suppression de traductions
-----------------------------

[](#️-suppression-de-traductions)

```
// Supprimer une traduction spécifique
$post->deleteTranslation('fr');

// Supprimer plusieurs traductions
$post->deleteTranslation(['fr', 'en']);

// Supprimer toutes les traductions sauf certaines
$post->deleteTranslationsExcept(['fr', 'en']);

// Supprimer le modèle et toutes ses traductions
$post->delete();

// Avec SoftDeletes : force delete
$post->forceDelete();
```

---

🔍 Informations et utilitaires
-----------------------------

[](#-informations-et-utilitaires)

```
// Liste des attributs traduisibles
$translatable = $post->getTranslatableAttributes();
// ['title', 'content', 'slug']

// Vérifier si un attribut est traduisible
if ($post->isTranslatableAttribute('title')) {
    // ...
}

// Locales disponibles pour cet enregistrement
$locales = $post->getAvailableLocales();
// ['fr', 'en', 'es']

// Ou via l'attribut
$locales = $post->available_locales;

// Vérifier si une traduction existe
if ($post->hasLocale('en')) {
    // La traduction anglaise existe
}

// Récupérer la locale actuellement utilisée
$locale = $post->getLocaleToUse();  // 'fr'

// Nom de la relation de traduction
$relationName = $post->getTranslationsRelationName();  // 'translations'

// Nom de la clé étrangère
$foreignKey = $post->getTranslationForeignKey();  // 'post_id'

// Classe du modèle de traduction
$translationClass = $post->getTranslationModelClass();  // 'App\Models\PostTranslation'
```

---

🎯 Query Scopes
--------------

[](#-query-scopes)

Le package offre une gamme complète de scopes pour interroger les traductions en base de données.

### Scopes de base (T)

[](#scopes-de-base-t)

Les scopes terminant par `T` utilisent la locale courante **avec fallback** (selon la configuration).

#### WHERE

[](#where)

```
// WHERE simple
Post::whereT('title', 'Bonjour')->get();
Post::whereT('title', 'LIKE', '%Laravel%')->get();

// OR WHERE
Post::whereT('title', 'Hello')
    ->orWhereT('title', 'Bonjour')
    ->get();

// WHERE NOT
Post::whereNotT('title', 'Test')->get();

// Callback (nested where)
Post::whereT(function($q) {
    $q->where('title', 'LIKE', '%Laravel%')
      ->orWhere('content', 'LIKE', '%PHP%');
})->get();
```

#### NULL / NOT NULL

[](#null--not-null)

```
Post::whereNullT('title')->get();
Post::whereNotNullT('title')->get();
Post::orWhereNullT('content')->get();
```

#### BETWEEN / NOT BETWEEN

[](#between--not-between)

```
Post::whereBetweenT('title', ['A', 'M'])->get();
Post::whereNotBetweenT('title', ['N', 'Z'])->get();
Post::orWhereBetweenT('title', ['A', 'F'])->get();
```

#### IN / NOT IN

[](#in--not-in)

```
Post::whereInT('title', ['Hello', 'Bonjour', 'Hola'])->get();
Post::whereNotInT('title', ['Test', 'Demo'])->get();
Post::orWhereInT('title', ['Hello', 'Hi'])->get();
```

#### LIKE

[](#like)

```
Post::whereLikeT('title', '%Laravel%')->get();
Post::orWhereLikeT('content', '%PHP%')->get();
```

#### ORDER BY

[](#order-by)

```
// Trier par colonne traduite
Post::orderByT('title', 'asc')->get();
Post::orderByT('title', 'desc')->get();

// Latest / Oldest
Post::latestT('created_at')->get();
Post::oldestT('created_at')->get();
```

#### GROUP BY

[](#group-by)

```
Post::groupByT('title')->get();
```

#### Locale spécifique

[](#locale-spécifique)

Tous les scopes `T` acceptent un paramètre `$locale` optionnel :

```
// Rechercher uniquement dans la locale 'en'
Post::whereT('title', 'Hello', locale: 'en')->get();

// Trier par titre français
Post::orderByT('title', 'asc', 'fr')->get();
```

### Scopes stricts (StrictT)

[](#scopes-stricts-strictt)

Les scopes terminant par `StrictT` recherchent **uniquement dans la locale spécifiée**, sans fallback.

```
// WHERE strict (sans fallback)
Post::whereStrictT('title', 'Hello', locale: 'en')->get();

// OR WHERE strict
Post::whereStrictT('title', 'Hello', locale: 'en')
    ->orWhereStrictT('title', 'Bonjour', locale: 'fr')
    ->get();

// WHERE NOT strict
Post::whereNotStrictT('title', 'Test', locale: 'en')->get();

// NULL / NOT NULL strict
Post::whereNullStrictT('title', 'en')->get();
Post::whereNotNullStrictT('content', 'fr')->get();

// BETWEEN strict
Post::whereBetweenStrictT('title', ['A', 'M'], 'en')->get();

// IN strict
Post::whereInStrictT('title', ['Hello', 'Hi'], 'en')->get();

// LIKE strict
Post::whereLikeStrictT('title', '%Laravel%', 'en')->get();

// SEARCH strict
Post::searchStrictT('title', 'Laravel', 'en')->get();

// ORDER BY strict
Post::orderByStrictT('title', 'asc', 'en')->get();

// GROUP BY strict
Post::groupByStrictT('title', 'en')->get();

// LATEST / OLDEST strict
Post::latestStrictT('created_at', 'en')->get();
Post::oldestStrictT('created_at', 'en')->get();
```

### Scopes intelligents (Smart)

[](#scopes-intelligents-smart)

Les scopes terminant par `Smart` détectent automatiquement si la colonne est traduisible ou non, et appliquent le scope approprié.

```
// WHERE smart : détecte automatiquement si 'title' est traduisible
Post::whereSmart('title', 'Hello')->get();  // Utilise whereT

// Si la colonne n'est pas traduisible, utilise le WHERE standard
Post::whereSmart('status', 'published')->get();  // Utilise where

// Combinaison de colonnes traduites et non traduites
Post::whereSmart('title', 'LIKE', '%Laravel%')
    ->whereSmart('status', 'published')
    ->whereSmart('is_featured', true)
    ->get();

// Tous les scopes ont une variante Smart
Post::whereNotSmart('title', 'Test')->get();
Post::orWhereSmart('content', 'LIKE', '%PHP%')->get();
Post::whereNullSmart('title')->get();
Post::whereNotNullSmart('content')->get();
Post::whereBetweenSmart('title', ['A', 'M'])->get();
Post::whereInSmart('title', ['Hello', 'Hi'])->get();
Post::whereLikeSmart('title', '%Laravel%')->get();
Post::orderBySmart('title', 'desc')->get();
Post::groupBySmart('title')->get();
Post::latestSmart('created_at')->get();
Post::oldestSmart('created_at')->get();

// Variantes strictes Smart
Post::whereStrictSmart('title', 'Hello', locale: 'en')->get();
Post::orderByStrictSmart('title', 'asc', 'en')->get();
```

### Scopes de vérification

[](#scopes-de-vérification)

#### Vérifier l'existence de traductions

[](#vérifier-lexistence-de-traductions)

```
// Posts ayant une traduction en anglais
Post::hasTranslation('en')->get();

// Posts n'ayant PAS de traduction en français
Post::doesntHaveTranslation('fr')->get();

// Posts ayant des traductions dans TOUTES les locales données
Post::hasTranslations(['fr', 'en', 'es'])->get();

// Posts ayant une traduction dans AU MOINS UNE des locales
Post::hasAnyTranslation(['fr', 'en'])->get();

// Posts n'ayant AUCUNE traduction dans les locales données
Post::doesntHaveAnyTranslation(['fr', 'en'])->get();
```

#### Vérifier la complétude des traductions

[](#vérifier-la-complétude-des-traductions)

```
// Posts dont TOUTES les colonnes traduites sont remplies pour la locale
Post::whereTranslationComplete('fr')->get();

// Posts ayant AU MOINS UNE colonne traduite remplie
Post::whereTranslationPartial('en')->get();

// Posts dont TOUTES les colonnes traduites sont vides
Post::whereTranslationEmpty('es')->get();

// Posts ayant des traductions complètes pour TOUTES les locales
Post::whereAllTranslationsComplete(['fr', 'en', 'es'])->get();
```

#### Vérifier des colonnes spécifiques

[](#vérifier-des-colonnes-spécifiques)

```
// Posts ayant le champ 'title' traduit en français
Post::whereColumnTranslated('title', 'fr')->get();

// Posts n'ayant PAS le champ 'content' traduit en anglais
Post::whereColumnNotTranslated('content', 'en')->get();

// Posts ayant TOUTES les colonnes spécifiées traduites
Post::whereColumnsTranslated(['title', 'content'], 'fr')->get();

// Posts ayant AU MOINS UNE des colonnes traduites
Post::whereAnyColumnTranslated(['title', 'content'], 'en')->get();
```

#### Compter les traductions

[](#compter-les-traductions)

```
// Ajouter le nombre de traductions à chaque post
$posts = Post::withTranslationCount()->get();
echo $posts->first()->translations_count;  // 3

// Posts ayant exactement N traductions
Post::hasExactlyTranslations(3)->get();

// Posts ayant au moins N traductions
Post::hasMinTranslations(2)->get();

// Posts ayant au maximum N traductions
Post::hasMaxTranslations(5)->get();
```

### Scopes de recherche

[](#scopes-de-recherche)

#### Recherche simple

[](#recherche-simple)

```
// Recherche fulltext sur une colonne traduite
Post::searchT('title', 'Laravel')->get();

// Équivalent à whereLikeT avec %...%
Post::searchT('title', 'Laravel')->get();
// WHERE title LIKE '%Laravel%'

// Recherche smart (détecte si traduisible)
Post::searchSmart('title', 'Laravel')->get();

// Recherche stricte (sans fallback)
Post::searchStrictT('title', 'Laravel', 'en')->get();
```

#### Recherche multi-colonnes

[](#recherche-multi-colonnes)

```
// Recherche dans PLUSIEURS colonnes traduites (OR)
Post::searchAnyT(['title', 'content'], 'Laravel')->get();

// Version Smart
Post::searchAnySmart(['title', 'content'], 'Laravel')->get();

// Version Stricte
Post::searchAnyStrictT(['title', 'content'], 'Laravel', 'en')->get();
```

#### Recherche avec WHERE ANY / ALL

[](#recherche-avec-where-any--all)

```
// WHERE ANY : au moins UNE des colonnes doit correspondre
Post::whereAnyT(['title', 'content'], 'LIKE', '%Laravel%')->get();

// WHERE ALL : TOUTES les colonnes doivent correspondre
Post::whereAllT(['title', 'content'], 'LIKE', '%PHP%')->get();

// Variantes Smart
Post::whereAnySmart(['title', 'content'], 'Hello')->get();
Post::whereAllSmart(['title', 'content'], 'Test')->get();

// Variantes Strictes
Post::whereAnyStrictT(['title', 'content'], 'Hello', locale: 'en')->get();
Post::whereAllStrictT(['title', 'content'], 'Test', locale: 'fr')->get();
```

### Configuration des scopes

[](#configuration-des-scopes)

#### Forcer une locale pour une requête

[](#forcer-une-locale-pour-une-requête)

```
// Utiliser une locale spécifique pour toute la requête
Post::forLocale('en', function($query) {
    $query->whereT('title', 'Hello')
          ->orderByT('title');
})->get();

// Ou sans callback (permanent pour cette requête)
Post::forLocale('en')
    ->whereT('title', 'Hello')
    ->get();

// Avec reset automatique
Post::forLocale('en', function($query) {
    $query->whereT('title', 'Hello');
}, reset: true)->get();
```

#### Désactiver les fallbacks pour une requête

[](#désactiver-les-fallbacks-pour-une-requête)

```
// Désactiver temporairement les fallbacks
Post::withoutFallbacks(function($query) {
    $query->whereT('title', 'Hello')
          ->orderByT('title');
})->get();

// Ou sans callback
Post::withoutFallbacks()
    ->whereT('title', 'Hello')
    ->get();
```

#### Activer les fallbacks pour une requête

[](#activer-les-fallbacks-pour-une-requête)

```
// Forcer les fallbacks (même si désactivés dans la config)
Post::withFallbacks(function($query) {
    $query->whereT('title', 'Hello');
})->get();

// Avec des locales de fallback personnalisées
Post::withFallbacks(['es', 'fr'], function($query) {
    $query->whereT('title', 'Hello');
})->get();
```

---

🔄 Migration des données existantes
----------------------------------

[](#-migration-des-données-existantes)

Si vous avez déjà des données dans vos tables principales et que vous souhaitez les migrer vers les tables de traductions :

```
# Migrer un modèle spécifique
php artisan translations:migrate Post

# Migrer tous les modèles utilisant HasTranslations
php artisan translations:migrate --all

# Spécifier la locale cible
php artisan translations:migrate Post --locale=fr

# Forcer sans confirmation
php artisan translations:migrate Post --force

# Combiner les options
php artisan translations:migrate --all --locale=en --force
```

**Comment ça fonctionne :**

1. La commande lit toutes les lignes de la table principale
2. Pour chaque ligne, elle crée une traduction dans la locale spécifiée
3. Les valeurs sont copiées depuis les colonnes traduisibles
4. Utilise `updateOrInsert` pour éviter les doublons si relancée

**Exemple :**

```
# Vous avez une table 'posts' avec title, content
# Vous voulez migrer vers 'posts_translations'
php artisan translations:migrate Post --locale=fr
```

**Note :** Après la migration, vous pouvez supprimer les colonnes traduisibles de la table principale si vous le souhaitez.

---

⌨️ Commandes Artisan
--------------------

[](#️-commandes-artisan)

CommandeUtilité`make:translation-migration {model}`Génère la migration SQL optimisée (BigInt/UUID/ULID).`make:translation-migration --all`Génère les migrations pour tous les modèles traduisibles.`make:translation-model {model}`Génère le modèle de traduction Eloquent.`make:translation-model --all`Génère les modèles pour tous les modèles traduisibles.`translations:migrate {model}`Migre les données existantes vers la table de traductions.`translations:migrate --all`Migre toutes les données de tous les modèles traduisibles.`db-translatable:info`Affiche l'état de santé et la config du package.---

🔀 Fallbacks et cascade
----------------------

[](#-fallbacks-et-cascade)

Le package supporte un système de fallback sophistiqué pour gérer les traductions manquantes.

### Configuration des fallbacks

[](#configuration-des-fallbacks)

```
// Fallbacks globaux (array ou string)
'fallback' => ['en', 'fr'],

// Fallbacks spécifiques par langue
'map_fallbacks' => [
    'en' => ['fr'],          // Si 'en' manque, chercher en 'fr'
    'es' => ['it', 'fr'],    // Si 'es' manque, chercher en 'it', puis 'fr'
    'de' => 'en',            // Si 'de' manque, chercher en 'en'
],
```

### Utilisation

[](#utilisation)

```
// Avec la configuration ci-dessus :
db_translatable()->setCurrentLocale('es');

// Si la traduction 'es' n'existe pas :
// 1. Cherche en 'it' (map_fallbacks['es'][0])
// 2. Si absent, cherche en 'fr' (map_fallbacks['es'][1])
// 3. Si absent, cherche en 'en' (fallback[0])
// 4. Si absent, cherche en 'fr' (fallback[1])
echo $post->title;
```

### Contrôler les fallbacks par mode

[](#contrôler-les-fallbacks-par-mode)

```
'use_fallback' => [
    'auto' => true,      // $model->title
    'forced' => false,   // $model->useLocale('en')->title
    'explicit' => false, // $model->translation('en')->title
],
```

**Exemple :**

```
// AUTO : utilise les fallbacks
$post->title;  // Cherche en 'es', puis fallbacks

// FORCED : pas de fallbacks (configuration ci-dessus)
$post->useLocale('es')->title;  // Uniquement 'es', retourne null si absent

// EXPLICIT : pas de fallbacks
$post->translation('es')->title;  // Uniquement 'es'
```

### Mode strict

[](#mode-strict)

```
// Lever une exception si la traduction est manquante
'strict' => true,

// Utilisation
$post->title;  // RuntimeException si manquant
```

---

🗄️ Gestion du cache
-------------------

[](#️-gestion-du-cache)

Le package utilise le cache Laravel pour améliorer les performances.

### Configuration

[](#configuration)

```
'cache' => [
    'enabled' => true,              // Activer/désactiver
    'store' => null,                // Store (null = défaut)
    'prefix' => 'db-translatable',  // Préfixe des clés
    'ttl' => 3600,                  // Durée de vie (secondes)
],
```

### Invalidation automatique

[](#invalidation-automatique)

Le cache est automatiquement invalidé lors de :

- Sauvegarde du modèle
- Suppression du modèle
- Suppression de traductions

### Invalidation manuelle

[](#invalidation-manuelle)

```
// Via le service
app(\DbTranslatable\Support\TranslationCache::class)->forget($post);

// Ou forcer le rechargement
$post->loadMissing($post->getTranslationsRelationName());
```

---

💡 Cas d'usage avancés
---------------------

[](#-cas-dusage-avancés)

### Formulaire multi-langues

[](#formulaire-multi-langues)

```
// Vue : afficher toutes les traductions
$translations = $post->getTranslations();

foreach ($translations as $locale => $data) {
    echo "$locale";
    echo "";
    echo "{$data['content']}";
}

// Controller : sauvegarder
foreach ($request->input('title') as $locale => $title) {
    $post->translation($locale)->title = $title;
    $post->translation($locale)->content = $request->input("content.$locale");
}
$post->save();
```

### API REST multilingue

[](#api-rest-multilingue)

```
// Endpoint : GET /api/posts/{id}?locale=fr
public function show(Post $post, Request $request)
{
    $locale = $request->input('locale', config('app.locale'));

    return [
        'id' => $post->id,
        'slug' => $post->slug,
        'title' => $post->translation($locale)->title,
        'content' => $post->translation($locale)->content,
        'created_at' => $post->created_at,
    ];
}

// Ou avec toutes les traductions
public function show(Post $post)
{
    return [
        'id' => $post->id,
        'slug' => $post->slug,
        'translations' => $post->getTranslations(),
        'available_locales' => $post->available_locales,
    ];
}
```

### Migration progressive

[](#migration-progressive)

```
// Étape 1 : Ajouter le trait et la propriété $translatable
class Post extends Model
{
    use HasTranslations;

    protected array $translatable = ['title', 'content'];
}

// Étape 2 : Créer la table de traductions
php artisan make:translation-migration Post
php artisan migrate

// Étape 3 : Migrer les données existantes
php artisan translations:migrate Post --locale=fr

// Étape 4 : (Optionnel) Supprimer les colonnes de la table principale
Schema::table('posts', function (Blueprint $table) {
    $table->dropColumn(['title', 'content']);
});
```

### Recherche multi-critères

[](#recherche-multi-critères)

```
// Recherche complexe avec colonnes traduites et non traduites
$posts = Post::whereSmart('title', 'LIKE', '%Laravel%')
    ->whereSmart('status', 'published')
    ->whereBetweenSmart('created_at', [$start, $end])
    ->whereNotNullSmart('content')
    ->orderBySmart('title', 'asc')
    ->paginate(20);
```

### Export de données

[](#export-de-données)

```
// Exporter toutes les traductions en CSV
$posts = Post::with('translations')->get();

$csv = [];
foreach ($posts as $post) {
    foreach ($post->getTranslations() as $locale => $data) {
        $csv[] = [
            'id' => $post->id,
            'locale' => $locale,
            'title' => $data['title'],
            'content' => $data['content'],
        ];
    }
}
```

### Localisation dynamique par utilisateur

[](#localisation-dynamique-par-utilisateur)

```
// Middleware
class SetUserLocale
{
    public function handle($request, Closure $next)
    {
        if (auth()->check()) {
            $locale = auth()->user()->preferred_locale ?? 'fr';
            db_translatable()->setCurrentLocale($locale);
        }

        return $next($request);
    }
}

// Utilisation
$post->title;  // Utilise automatiquement la locale de l'utilisateur
```

---

✅ Bonnes pratiques
------------------

[](#-bonnes-pratiques)

### 1. Définir explicitement les champs traduisibles

[](#1-définir-explicitement-les-champs-traduisibles)

```
// ✅ BON
protected array $translatable = ['title', 'content', 'description'];

// ❌ MAUVAIS
// Tout mettre sans réfléchir
```

### 2. Utiliser les scopes Smart pour les requêtes mixtes

[](#2-utiliser-les-scopes-smart-pour-les-requêtes-mixtes)

```
// ✅ BON : détection automatique
Post::whereSmart('title', 'LIKE', '%Laravel%')
    ->whereSmart('status', 'published')
    ->get();

// ❌ MOINS BON : spécifier manuellement
Post::whereT('title', 'LIKE', '%Laravel%')
    ->where('status', 'published')
    ->get();
```

### 3. Eager loading des traductions

[](#3-eager-loading-des-traductions)

```
// ✅ BON : évite N+1
$posts = Post::with('translations')->get();

// ❌ MAUVAIS : N+1 queries
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->title;  // Query par itération
}
```

### 4. Utiliser withLocale pour les modifications groupées

[](#4-utiliser-withlocale-pour-les-modifications-groupées)

```
// ✅ BON
$post->withLocale('en', function ($model) {
    $model->title = 'Title';
    $model->content = 'Content';
});

// ❌ MOINS BON
$post->useLocale('en');
$post->title = 'Title';
$post->content = 'Content';
$post->resetLocale();  // Oubli fréquent !
```

### 5. Configurer les fallbacks intelligemment

[](#5-configurer-les-fallbacks-intelligemment)

```
// ✅ BON : fallbacks logiques
'map_fallbacks' => [
    'en-GB' => ['en', 'fr'],
    'fr-CA' => ['fr', 'en'],
],

// ❌ MAUVAIS : boucles infinies
'map_fallbacks' => [
    'en' => ['fr'],
    'fr' => ['en'],  // Boucle !
],
```

### 6. Valider les locales en écriture

[](#6-valider-les-locales-en-écriture)

```
// Configuration
'allowed' => ['fr', 'en', 'es'],
'allow_unknown' => false,
'prevent_writing_unknown_locale' => true,

// Utilisation
try {
    $post->translation('de')->title = 'Test';  // Exception
} catch (InvalidArgumentException $e) {
    // Locale non autorisée
}
```

### 7. Utiliser les scopes de vérification

[](#7-utiliser-les-scopes-de-vérification)

```
// ✅ BON : vérifier avant d'afficher
$posts = Post::hasTranslation('fr')
    ->whereTranslationComplete('fr')
    ->get();

// ❌ MAUVAIS : afficher et espérer
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->title ?? 'N/A';  // Beaucoup de 'N/A'
}
```

---

🧪 Tests
-------

[](#-tests)

Le package utilise **Pest** + **Orchestra Testbench** :

```
composer test
```

### Tester vos modèles

[](#tester-vos-modèles)

```
use Tests\TestCase;

class PostTranslationTest extends TestCase
{
    /** @test */
    public function it_can_save_translations()
    {
        $post = Post::create(['slug' => 'test']);

        $post->translation('fr')->title = 'Titre FR';
        $post->translation('en')->title = 'Title EN';
        $post->save();

        $this->assertEquals('Titre FR', $post->translation('fr')->title);
        $this->assertEquals('Title EN', $post->translation('en')->title);
    }

    /** @test */
    public function it_uses_fallbacks()
    {
        config(['db-translatable.locale.fallback' => ['en', 'fr']]);

        $post = Post::create(['slug' => 'test']);
        $post->translation('en')->title = 'English Title';
        $post->save();

        db_translatable()->setCurrentLocale('de');

        // Doit retourner la version anglaise (fallback)
        $this->assertEquals('English Title', $post->title);
    }
}
```

---

📄 Licence
---------

[](#-licence)

Licence MIT. Développé par [Boule Luciole](mailto:bouleluciole@gmail.com).

---

🤝 Contribution
--------------

[](#-contribution)

Les contributions sont les bienvenues ! Merci de :

1. Forker le projet
2. Créer une branche (`git checkout -b feature/ma-fonctionnalite`)
3. Commiter vos changements (`git commit -m 'Ajout de ma fonctionnalité'`)
4. Pousser vers la branche (`git push origin feature/ma-fonctionnalite`)
5. Ouvrir une Pull Request

---

📚 Ressources supplémentaires
----------------------------

[](#-ressources-supplémentaires)

- [Documentation Laravel](https://laravel.com/docs)
- [Eloquent Relationships](https://laravel.com/docs/eloquent-relationships)
- [Query Scopes](https://laravel.com/docs/eloquent#query-scopes)

---

**DB-Translatable** - Une solution professionnelle pour gérer les traductions dans Laravel. 🚀

###  Health Score

37

—

LowBetter than 83% of packages

Maintenance82

Actively maintained with recent releases

Popularity11

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity41

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

Total

7

Last Release

68d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/b050733774fb93d80fea45ba3d5c63a31f4540c399836c6e7984c9b538b60426?d=identicon)[Samy-Green](/maintainers/Samy-Green)

---

Top Contributors

[![Samy-Green](https://avatars.githubusercontent.com/u/85443053?v=4)](https://github.com/Samy-Green "Samy-Green (18 commits)")

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/bouleluciole-db-translatable/health.svg)

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

###  Alternatives

[fumeapp/modeltyper

Generate TypeScript interfaces from Laravel Models

196277.9k](/packages/fumeapp-modeltyper)[stevebauman/translation

An easy automatic database driven translator for Laravel 5

7620.1k](/packages/stevebauman-translation)[longman/laravel-multilang

Package to integrate multi language (multi locale) functionality in Laravel 5.x

5514.4k1](/packages/longman-laravel-multilang)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)[kerigard/laravel-lang-ru

Ru lang for Laravel

2116.8k](/packages/kerigard-laravel-lang-ru)

PHPackages © 2026

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