zaber-dev/laravel-localization

Application-level multilingual content management and provider-agnostic translation orchestration for Laravel.

Maintainers

Package info

github.com/zaber-dev/laravel-localization

pkg:composer/zaber-dev/laravel-localization

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-08-12 16:51 UTC

This package is auto-updated.

Last update: 2026-08-12 16:56:56 UTC


README

Laravel Localize Banner

Laravel Localization

Latest Version on Packagist Run Tests Total Downloads PHP Version Require License

Supports: Laravel 11, 12 & 13+ • PHP 8.2+ • Database

Application-level multilingual content management and provider-agnostic translation orchestration for Laravel.

Manage localizations using stateful entity lifecycles, attribute-level content fingerprinting, AI-ready translation pipelines, Scout-compatible search indexing, and transparent Eloquent model proxies — all through a clean, expressive API.

Language is an application concern, not just a presentation concern. Modern applications generate content with AI, send translations to human editors for review, publish across multi-region clusters, and need to know exactly which attributes became outdated when source models change.

Quick Example

use ZaberDev\Localization\Facades\Localization;
use ZaberDev\Localization\Enums\LocalizationSource;

// 1. Transparent Model Proxying (reads active application locale seamlessly)
app()->setLocale('fr');
echo $post->title; // Automatically returns French title!

// 2. Explicit Locale Access
echo $post->localize('fr')->title;

// 3. Storing AI/Human Translations with Rich Metadata
Localization::model($post)->store('fr', [
    'title'   => 'Bonjour le monde',
    'content' => 'Contenu du billet...',
], LocalizationSource::AI, [
    'provider' => 'openai',
    'model'    => 'gpt-4o',
    'tokens'   => 1200,
    'cost'     => 0.04,
]);

// 4. Stale Detection via Attribute-Level Hashing
$post->update(['title' => 'Updated English Title']);

$info = Localization::model($post)->get('fr');
$info->isStale();            // true
$info->staleAttributes();    // ['title'] (content is still valid!)

Common Use Cases

Laravel Localization is ideal for:

  • AI-Ready Translation Pipelines: Dispatch structured localization requests containing only the attributes that require translation, allowing your application to connect OpenAI, Anthropic, DeepL, or custom translation services.
  • Multilingual SaaS & E-Commerce: Manage translated product titles, descriptions, and SEO metadata with draft/published state control.
  • Content Review Workflows: Transition translations through DraftGeneratedNeedsReviewApprovedPublished states.
  • Stale Translation Auditing: Identify exactly which localized attributes require re-translation after source model updates.
  • Scout-Compatible Search Indexing: Flatten localized attributes into searchable fields for Scout-compatible engines such as Meilisearch, Typesense, and Algolia.

Why not Spatie Laravel Translatable or Raw JSON Columns?

Spatie's package is excellent for simple string storage. However, enterprise applications require lifecycle awareness, AI pipeline integration, and granular staleness detection.

Feature Spatie Translatable Raw JSON Columns Laravel Localization
Primary Purpose Simple attribute storage Loose JSON storage Application-Level Multilingual Lifecycle
Transparent Model Proxy ⚠️ Partial ❌ Manual Native (HasLocalizations)
Stateful Lifecycle Workflows ❌ None ❌ None Draft, Generated, NeedsReview, Approved, Published
Attribute-Level Content Fingerprinting ❌ None ❌ None Attribute-Level Content Fingerprinting
Partial Staleness Detection ❌ All or nothing ❌ None staleAttributes() identifies exact dirty keys
AI Metadata Tracking ❌ None ❌ None Provider, Model, Tokens, Cost & Prompt Version
AI Pipeline Batching ❌ Manual ❌ Manual Localization::batch([...])->dispatch()
Fallback Chains ⚠️ Global Config ❌ Manual Per-Request & Per-Locale Fallback Chains
Laravel Scout Indexing ❌ Manual ❌ Complex Native (toLocalizedSearchArray())
CLI Artisan Diagnostics ❌ None ❌ None localization:missing & localization:stale

Features

  • Transparent Model Proxy ⭐: Access localized attributes as if they were standard model properties ($post->title or $post->localize('fr')->title).
  • Attribute-Level Content Fingerprinting ⭐: Tracks individual content fingerprints for each translatable field. When you update the title, only the title is marked stale — preventing expensive AI re-generation of unchanged content.
  • Stateful Localization Lifecycles: Manage translation approval using formal state machine transitions (Draft, Generated, NeedsReview, Approved, Published).
  • AI Batching & Metadata: Dispatch batch translation events (LocalizationRequested) while recording provider names, AI model versions, token counts, and cost telemetry.
  • Enterprise Fallback Chains: Define granular fallback paths per locale (e.g., fr-CA -> fr -> en).
  • Laravel Scout Search Integration: Flatten all active translations into indexed search fields via toLocalizedSearchArray().
  • Artisan CLI Diagnostics: Audit un-translated models and stale localizations straight from your terminal.

Documentation

Installation

Install the package via Composer:

composer require zaber-dev/laravel-localization

Publish the configuration file and database migrations:

php artisan vendor:publish --provider="ZaberDev\Localization\LocalizationServiceProvider"

Run database migrations to create the localizations table:

php artisan migrate

Configuration

The configuration file config/localization.php allows you to set default locales, database table names, and fallback chains:

return [
    /*
    |--------------------------------------------------------------------------
    | Default Locale
    |--------------------------------------------------------------------------
    */
    'default_locale' => 'en',

    /*
    |--------------------------------------------------------------------------
    | Default Storage Driver
    |--------------------------------------------------------------------------
    */
    'default' => 'database',

    /*
    |--------------------------------------------------------------------------
    | Database Table Configuration
    |--------------------------------------------------------------------------
    */
    'database' => [
        'table' => 'localizations',
    ],

    /*
    |--------------------------------------------------------------------------
    | Fallback Chains
    |--------------------------------------------------------------------------
    */
    'fallbacks' => [
        'default' => ['en'],
        'fr-CA'   => ['fr', 'en'],
        'es-MX'   => ['es', 'en'],
    ],
];

Usage Guide

1. Preparing Eloquent Models

Implement Localizable contract and add the HasLocalizations trait to any Eloquent model:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use ZaberDev\Localization\Contracts\Localizable;
use ZaberDev\Localization\Traits\HasLocalizations;

class Post extends Model implements Localizable
{
    use HasLocalizations;

    public function getTranslatableAttributes(): array
    {
        return ['title', 'content', 'seo_description'];
    }
}

2. Transparent Model Proxy

Accessing translatable attributes automatically inspects the active application locale (app()->getLocale()). If a translation payload exists, it is returned transparently:

app()->setLocale('fr');

// Automatically returns the French title
echo $post->title;

// Access a specific locale explicitly without altering global app state
echo $post->localize('es')->title;
echo $post->localize('es')->seo_description;

3. Reading DTOs (LocalizationInfo)

To inspect metadata, state, or completion metrics, retrieve the strongly-typed LocalizationInfo Data Transfer Object:

use ZaberDev\Localization\Facades\Localization;

$info = Localization::model($post)->get('fr');

if ($info) {
    echo $info->locale;        // 'fr'
    echo $info->state->value;  // 'published'
    echo $info->source->value; // 'ai'
    echo $info->completion;    // 100 (% of translatable fields populated)
    echo $info->provider;      // 'openai'
    echo $info->tokens;        // 1200
    echo $info->cost;          // 0.04
}

4. Attribute-Level Content Fingerprinting & Stale Detection

Laravel Localization calculates a content fingerprint (MD5 hash) for each translatable attribute when localizations are saved. When the source model changes, only modified fields are flagged:

// Update source model title
$post->update(['title' => 'New Product Title']);

$info = Localization::model($post)->get('fr');

if ($info->isStale()) {
    // Returns array of changed attributes needing update: ['title']
    $staleFields = $info->staleAttributes();
    
    // Only re-translate $staleFields instead of regenerating everything!
}

5. Storing Translations & AI Metadata

Store translation payloads manually or through automated background jobs, passing rich execution telemetry:

use ZaberDev\Localization\Enums\LocalizationSource;

Localization::model($post)->store(
    locale: 'fr',
    payload: [
        'title'   => 'Nouveau titre du produit',
        'content' => 'Contenu inchangé...',
    ],
    source: LocalizationSource::AI,
    metadata: [
        'source_locale'  => 'en',
        'provider'       => 'openai',
        'model'          => 'gpt-4o',
        'prompt_version' => 'v2.1',
        'tokens'         => 850,
        'cost'           => 0.025,
    ]
);

6. AI Batching & Generation Pipelines

Trigger batch translation pipelines across multiple models and locales:

use ZaberDev\Localization\Facades\Localization;

// Dispatches LocalizationRequested events for background consumption
Localization::batch([$post1, $post2, $product])
    ->from('en')
    ->to('de')
    ->dispatch();

Listen to LocalizationRequested events in your application listener:

namespace App\Listeners;

use ZaberDev\Localization\Events\LocalizationRequested;

class ProcessAiTranslation
{
    public function handle(LocalizationRequested $event): void
    {
        // $event->model
        // $event->sourceLocale
        // $event->targetLocale
        // $event->changedAttributes (only attributes requiring translation!)
    }
}

7. Stateful Lifecycle Workflows

Transition translations through formal publication stages:

use ZaberDev\Localization\Enums\LocalizationState;

// Move to published state (dispatches LocalizationPublished event)
$post->localization('fr')->publish();

// Transition to custom state
Localization::model($post)
    ->to('fr')
    ->transitionTo(LocalizationState::NeedsReview);

8. Querying & Scopes

Query models based on translation presence, missing locales, or lifecycle states:

use ZaberDev\Localization\Enums\LocalizationState;

// Models having AT LEAST ONE translation
$posts = Post::translated()->get();

// Models missing a French translation
$untranslated = Post::missingTranslations('fr')->get();

// Models with a Published French localization
$publishedFrench = Post::whereLocale('fr')
    ->whereLocalizationState(LocalizationState::Published)
    ->get();

9. Laravel Scout Search Indexing

Flatten all localized content for full-text search indexing engines:

class Post extends Model implements Localizable
{
    use HasLocalizations;

    public function toSearchableArray(): array
    {
        return array_merge(
            $this->toArray(),
            $this->toLocalizedSearchArray()
        );
    }
}

Produces indexed fields like title_fr, content_fr, title_de, content_de.

10. CLI Diagnostics Commands

Audit missing and stale translations directly from Artisan:

# List missing localizations for a model class
php artisan localization:missing "App\Models\Post"

# List stale localizations requiring re-translation
php artisan localization:stale "App\Models\Post"

Testing

Run the test suite using PHPUnit:

composer test

License

The MIT License (MIT). Please see License File for more information.