laravel-localization maintained by zaber-dev
Laravel Localization
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
Draft→Generated→NeedsReview→Approved→Publishedstates. - 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->titleor$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.