Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Multilingual Laravel Package

guidocella/laravel-multilingual

Laravel package for building multilingual apps: defines per-locale routes and URLs, integrates language switching and detection, and helps translate paths for localized navigation. Lightweight setup for Laravel projects needing clean locale-aware routing.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require guidocella/laravel-multilingual
    

    Publish the config:

    php artisan vendor:publish --provider="Guidocella\Multilingual\MultilingualServiceProvider" --tag="config"
    
  2. Configure Default Locale Update config/multilingual.php:

    'default_locale' => 'en',
    'supported_locales' => ['en', 'fr', 'es'],
    
  3. First Multilingual Model Extend MultilingualModel and define translatable fields:

    use Guidocella\Multilingual\MultilingualModel;
    
    class Product extends MultilingualModel
    {
        public $translatable = ['name', 'description'];
    }
    
  4. Create a Translated Record

    $product = Product::create([
        'name' => ['en' => 'Laptop', 'fr' => 'Ordinateur'],
        'description' => ['en' => 'High-performance...', 'fr' => 'Haute performance...'],
    ]);
    
  5. Retrieve Translations

    $product->getTranslation('name', 'fr'); // Returns 'Ordinateur'
    

Implementation Patterns

Workflows

1. Dynamic Locale Handling

  • Request-based switching:

    use Guidocella\Multilingual\Facades\Multilingual;
    
    $locale = app()->getLocale(); // Or from request
    Multilingual::setLocale($locale);
    
    $product->name; // Automatically resolves to current locale
    
  • Fallback logic:

    $product->name ?? $product->getTranslation('name', 'en'); // Fallback to English
    

2. API Responses

  • Automatic localization in JSON:
    $product->toArray(); // Returns translations in current locale
    
    Or force a specific locale:
    $product->toArray(['locale' => 'fr']);
    

3. Eloquent Querying

  • Filter by translation:

    $products = Product::whereTranslation('name', 'like', '%Laptop%')->get();
    
  • Order by translation:

    $products = Product::orderByTranslation('name', 'asc')->get();
    

4. Form Handling (Livewire/Inertia)

  • Livewire example:

    public function mount()
    {
        $this->locale = app()->getLocale();
    }
    
    public function save()
    {
        $product->update([
            'name' => [$this->locale => $this->name],
        ]);
    }
    
  • Inertia props:

    return Inertia::render('ProductEdit', [
        'product' => $product->toArray(['locale' => $this->locale]),
    ]);
    

Integration Tips

1. Middleware for Locale Routing

Create middleware to set locale from URL:

public function handle($request, Closure $next)
{
    $locale = $request->route('locale');
    if (in_array($locale, config('multilingual.supported_locales'))) {
        app()->setLocale($locale);
    }
    return $next($request);
}

Register in routes/web.php:

Route::middleware('set-locale')->group(function () {
    Route::get('/{locale}/products', [ProductController::class, 'index']);
});

2. Database Optimization

  • Index translatable fields for large datasets:

    Schema::table('products', function (Blueprint $table) {
        $table->index('name->'.implode('|', config('multilingual.supported_locales')));
    });
    
  • Use json columns (default) or switch to separate tables for performance-critical apps:

    protected $translationsTable = 'product_translations'; // Custom table
    

3. Localization in Views

  • Blade directive:
    @lang($product->name) // Outputs translated value
    
  • Dynamic attributes:
    <h1>{{ $product->name }}</h1> <!-- Auto-resolves to current locale -->
    

4. Testing

  • Mock locales:

    $this->app->setLocale('fr');
    $product = Product::factory()->create(['name' => ['fr' => 'Test']]);
    $this->assertEquals('Test', $product->name);
    
  • Assert translations:

    $this->assertEquals('Laptop', $product->getTranslation('name', 'en'));
    

Gotchas and Tips

Pitfalls

1. JSON Column Limits

  • Issue: MySQL json columns have a 64KB limit. Exceeding this throws JSON document too deep. Fix: Use a custom table or split long translations (e.g., description into description_short/description_long).

2. Locale Fallback Misconfigurations

  • Issue: Missing translations return null, causing Undefined index errors. Fix: Always use getTranslation() with fallbacks:
    $name = $product->getTranslation('name', app()->getLocale(), 'en');
    

3. Caching Headaches

  • Issue: Cached queries may return stale translations if the locale isn’t accounted for. Fix: Exclude locale-sensitive queries from caching or use Cache::forget() after updates.

4. Mass Assignment Risks

  • Issue: fill() or create() may overwrite all translations unintentionally. Fix: Explicitly pass translations:
    $product->fill([
        'name' => ['en' => 'New Name'], // Only update English
    ]);
    

5. Migration Conflicts

  • Issue: Adding $translatable after table creation breaks migrations. Fix: Run:
    php artisan multilingual:install
    
    Or manually add the translations JSON column.

Debugging Tips

1. Dump Translations

dd($product->getTranslations()); // Full raw data

2. Check Locale

dd(app()->getLocale(), $product->getAvailableLocales());

3. Validate JSON

  • Symptom: JSON_ERROR_* errors on save.
  • Fix: Ensure translations are valid JSON:
    $translations = json_encode($request->input('name')); // Validate before save
    

4. Query Logs

Enable query logging to debug whereTranslation:

DB::enableQueryLog();
$products = Product::whereTranslation('name', 'like', '%Laptop%')->get();
dd(DB::getQueryLog());

Extension Points

1. Custom Storage Engines

Override the default JSON storage:

class Product extends MultilingualModel
{
    protected $translationsTable = 'custom_product_translations';

    public function getTranslationsAttribute()
    {
        return $this->translationsTable ? $this->getTranslationsFromTable() : parent::getTranslationsAttribute();
    }
}

2. Locale-Specific Validation

Extend validation rules:

use Guidocella\Multilingual\Rules\TranslationExists;

$request->validate([
    'name' => ['required', new TranslationExists('products', 'name', $locale)],
]);

3. API Resource Localization

Customize toArray() in a resource:

public function toArray($request)
{
    return [
        'id' => $this->id,
        'name' => $this->getTranslation('name', $request->locale),
        'translations' => $this->getTranslations(),
    ];
}

4. Event Listeners

Listen for translation updates:

public function handle($event)
{
    if ($event->model instanceof Product) {
        // Log or sync translations to external service
    }
}

5. Dynamic Locale Detection

Override locale resolution:

use Guidocella\Multilingual\Facades\Multilingual;

Multilingual::macro('detectLocale', function ($request) {
    return $request->header('X-Locale') ?? app()->getLocale();
});

Pro Tip: For large-scale apps, consider combining this package with Spatie’s Laravel Translation Manager for a more robust i18n workflow.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
nasirkhan/laravel-sharekit
directorytree/privacy-filter-classifier
directorytree/privacy-filter
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata
splash/openapi
splash/scopes
splash/toolkit
testo/output-teamcity
testo/bridge-symfony