Search by

laravel-tipi / translations

ika1224

Flexible Eloquent model translations for Laravel with dedicated-table, shared-table, and JSON storage.

Package info

github.com/laravel-tipi/translations

pkg:composer/laravel-tipi/translations

Statistics

Installs: 18

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.12 2026-10-06 23:59 UTC

This package is auto-updated.

Last update: 2026-10-07 02:09:09 UTC


README

Flexible Eloquent model translations for Laravel.

The package lets each translatable model choose the storage strategy that fits it:

  • Dedicated table — one translation table per model.
  • Shared table — one polymorphic translations table for many model types.
  • JSON columns — translations stored directly on the model.

It integrates with laravel-tipi/localization for supported, current, and default locales.

Requirements

  • PHP 8.5+
  • Laravel 13
  • laravel-tipi/localization ^0.1

Installation

Install the package with Composer:

composer require laravel-tipi/translations

Laravel discovers Tipi\Translations\TranslationServiceProvider automatically.

The package migration creates the shared translations table. Run your application migrations:

php artisan migrate

To publish the package configuration:

php artisan vendor:publish --tag=translation-config

Configuration

config/translation.php controls package-wide infrastructure:

return [
    'translations_table' => 'translations',
    'translation_model' => \Tipi\Translations\Models\TranslationModel::class,
    'locale_provider' => \Tipi\Translations\Providers\LocalizationLocaleProvider::class,
];

Storage strategy is selected by each model through its contract and trait; it is not a global driver setting.

Dedicated translation table

Use this strategy when a model should have its own normalized translation table.

use Illuminate\Database\Eloquent\Model;
use Tipi\Translations\Concerns\HasDedicatedTableTranslations;
use Tipi\Translations\Contracts\DedicatedTableTranslatableModel;

final class Article extends Model implements DedicatedTableTranslatableModel
{
    use HasDedicatedTableTranslations;

    protected static array $translatableAttributes = [
        'title',
        'description',
    ];
}

By convention the package resolves ArticleTranslation. The translation model uses IsTranslation:

use Illuminate\Database\Eloquent\Model;
use Tipi\Translations\Concerns\IsTranslation;
use Tipi\Translations\Contracts\TranslationModelContract;

final class ArticleTranslation extends Model implements TranslationModelContract
{
    use IsTranslation;
}

Your translation table should contain the parent foreign key, locale_code, translated columns, nullable outdated_at, and timestamps. Add a unique constraint for the parent foreign key plus locale_code.

Shared translation table

Use the shared polymorphic table when many models can use the same translation schema.

use Illuminate\Database\Eloquent\Model;
use Tipi\Translations\Concerns\HasSharedTableTranslations;
use Tipi\Translations\Contracts\SharedTableTranslatableModel;

final class Article extends Model implements SharedTableTranslatableModel
{
    use HasSharedTableTranslations;

    protected static array $translatableAttributes = [
        'title',
        'description',
    ];
}

The package migration stores one row per model and locale. Translated values are stored in the row's values JSON column.

JSON translations

Use JSON storage when translated values should live directly on the model table.

use Illuminate\Database\Eloquent\Model;
use Tipi\Translations\Concerns\HasJsonTranslations;
use Tipi\Translations\Contracts\JsonTranslatableModel;

final class Article extends Model implements JsonTranslatableModel
{
    use HasJsonTranslations;

    protected static array $translatableAttributes = [
        'title',
        'description',
    ];
}

Each translatable attribute must be backed by a JSON column.

JSON translations intentionally do not support outdated-translation tracking.

Reading translations

Translatable attributes resolve using the current locale:

$article->title;

You can explicitly choose a locale or provide a fallback value:

$article->translated(
    attribute: 'title',
    default: 'Untitled',
    localeCode: 'en',
);

Creating translations

Use the package actions for writes:

use Tipi\Translations\Actions\CreateTranslation;

$translation = resolve(CreateTranslation::class)->execute(
    translatable: $article,
    attributes: [
        'title' => 'ქართული სათაური',
        'description' => 'ქართული აღწერა',
    ],
    localeCode: 'ka',
);

If localeCode is omitted when creating a translation, the default locale is used.

Updating translations

use Tipi\Translations\Actions\UpdateTranslation;

$translation = resolve(UpdateTranslation::class)->execute(
    translatable: $article,
    attributes: [
        'title' => 'Updated title',
    ],
    localeCode: 'en',
);

Updates are partial: omitted attributes remain unchanged, while an explicitly supplied null clears that translated value.

For table-backed strategies, updating the default translation can mark the other translations as outdated:

resolve(UpdateTranslation::class)->execute(
    translatable: $article,
    attributes: ['title' => 'Updated default title'],
    localeCode: 'ka',
    markOthersAsOutdated: true,
);

Deleting translations

use Tipi\Translations\Actions\DeleteTranslation;

resolve(DeleteTranslation::class)->execute(
    translatable: $article,
    localeCode: 'en',
);

The default translation cannot be deleted independently.

Hard-deleting a table-backed translatable model deletes its translation records. Soft-deleting the parent keeps them; force-deleting removes them.

Transactions

Create, update, and delete actions use a database transaction by default and lock the translatable row before writing.

If you already own the surrounding transaction, you can disable the action's transaction wrapper:

resolve(UpdateTranslation::class)->execute(
    translatable: $article,
    attributes: ['title' => 'Updated title'],
    dbTransaction: false,
);

Development

Run the complete package check:

composer check

Or run the tools separately:

composer test
composer format:test

License

Laravel Tipi Translations is open-source software licensed under the MIT License.