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 Translatable Laravel Package

spatie/laravel-translatable

Adds HasTranslations to Eloquent models to store translations in JSON columns—no extra tables. Define translatable attributes via PHP 8 attribute or $translatable property, then set/get per-locale values while model accessors return the current app locale.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require spatie/laravel-translatable
    

    No additional configuration is needed beyond requiring the package.

  2. Mark Model as Translatable: Use either PHP 8 attributes (recommended) or a $translatable property:

    use Spatie\Translatable\Attributes\Translatable;
    use Spatie\Translatable\HasTranslations;
    
    #[Translatable(['name', 'description'])]
    class Product extends Model
    {
        use HasTranslations;
    }
    

    Or:

    class Product extends Model
    {
        use HasTranslations;
    
        public $translatable = ['name', 'description'];
    }
    
  3. First Use Case: Set translations for a model instance:

    $product = new Product();
    $product->setTranslation('name', 'en', 'Wireless Headphones')
            ->setTranslation('description', 'en', 'Noise-cancelling wireless headphones')
            ->save();
    

Implementation Patterns

Core Workflows

  1. Setting Translations:

    • Dynamic Locale: Use setTranslation() with explicit locale:
      $product->setTranslation('name', 'es', 'Auriculares Inalámbricos');
      
    • Current Locale: Assign directly to the attribute (auto-saves on save()):
      $product->name = 'Wireless Headphones'; // Uses app()->getLocale()
      
  2. Retrieving Translations:

    • Current Locale: Access attribute directly:
      echo $product->name; // 'Wireless Headphones' (if 'en' is current locale)
      
    • Specific Locale: Use getTranslation():
      echo $product->getTranslation('name', 'es'); // 'Auriculares Inalámbricos'
      
    • All Translations: Fetch as an array:
      $translations = $product->getTranslations('name');
      
  3. Nested JSON Translations: Define nested keys in $translatable (e.g., 'specs->weight'), then access via:

    $product->setTranslation('specs->weight', 'en', '250g');
    echo $product->{'specs->weight'}; // '250g'
    
  4. Bulk Operations:

    • Replace All Translations:
      $product->replaceTranslations('name', ['en' => 'Updated Name', 'es' => 'Nombre Actualizado']);
      
    • Remove Translations:
      $product->forgetTranslation('name', 'es'); // Remove Spanish translation
      $product->forgetAllTranslations('es');    // Remove all Spanish translations
      
  5. Querying:

    • Locale-Specific Queries:
      Product::whereLocale('name', 'en')->get(); // Products with English names
      Product::whereLocales('name', ['en', 'es'])->get(); // English or Spanish names
      
    • Value-Based Queries:
      Product::whereJsonContainsLocale('name', 'en', 'Wireless')->get();
      
  6. Fallback Handling: Configure fallbacks in a service provider:

    Translatable::fallback(
        fallbackLocale: 'en',
        fallbackAny: true,
        missingKeyCallback: fn($model, $key, $locale, $fallback, $fallbackLocale) => log("Missing: {$key}")
    );
    

Integration Tips

  • Form Requests: Use setTranslation() in form handlers to avoid magic strings:
    $request->validate(['name_en' => 'required']);
    $product->setTranslation('name', 'en', $request->name_en);
    
  • API Responses: Normalize translations in API resources:
    public function toArray($request)
    {
        return [
            'name' => $this->getTranslations('name'),
            'description' => $this->getTranslations('description'),
        ];
    }
    
  • Admin Panels: Use getTranslations() to populate multilingual fields in admin interfaces (e.g., Nova, Filament).

Gotchas and Tips

Pitfalls

  1. Locale Sensitivity:

    • Direct attribute access ($product->name) uses the current app locale. Always use getTranslation() for explicit locales.
    • Fix: Cache the current locale or pass it explicitly:
      $currentLocale = app()->getLocale();
      $name = $product->getTranslation('name', $currentLocale);
      
  2. JSON Column Constraints:

    • MySQL/MariaDB JSON columns have a 16MB limit. Avoid storing large translations (e.g., entire documents) in a single field.
    • Fix: Use separate columns for large content or split into multiple translatable fields.
  3. Fallback Overrides:

    • Fallbacks apply globally unless overridden per-model ($useFallbackLocale = false).
    • Fix: Test fallback behavior early:
      $product->setTranslation('name', 'en', 'Original');
      app()->setLocale('es');
      echo $product->name; // Falls back to 'en' if not set
      
  4. Nested Key Quirks:

    • Nested keys (e.g., meta->title) must include the -> separator. Omitting it causes silent failures.
    • Fix: Validate keys before use:
      if (!str_contains($key, '->')) {
          throw new \InvalidArgumentException("Nested keys must use '->' separator");
      }
      
  5. Query Performance:

    • whereJsonContainsLocale() uses JSON_CONTAINS under the hood, which can be slow on large datasets.
    • Fix: Add indexes to JSON columns (MySQL 8.0+):
      ALTER TABLE products ADD INDEX idx_name_translations ON name;
      
  6. Mass Assignment:

    • Translatable attributes are not automatically mass-assignable. Use $fillable explicitly:
      protected $fillable = ['name', 'description'];
      
    • Fix: Whitelist translatable fields in $fillable or use fill():
      $product->fill(['name' => 'New Name']); // Only fills whitelisted fields
      

Debugging Tips

  1. Inspect Stored JSON: Use getRawOriginal('name') to debug stored translations:

    dd($product->getRawOriginal('name')); // Raw JSON string
    
  2. Check Fallback Logic: Override getTranslation() temporarily to log fallback behavior:

    $product->setTranslation('name', 'en', 'Test');
    app()->setLocale('fr');
    dd($product->getTranslation('name', 'fr')); // Debug fallback
    
  3. Validate Locale Keys: Ensure locales are valid (e.g., en, es) and not empty strings. Invalid locales may cause silent failures.

  4. Test Edge Cases:

    • Empty translations: $product->setTranslation('name', 'en', '').
    • Non-string values: $product->setTranslation('price', 'en', 99.99).
    • Special characters: $product->setTranslation('name', 'en', 'Café').

Extension Points

  1. Custom Accessors: Override getTranslation() to add logic (e.g., caching, sanitization):

    public function getTranslation($key, $locale = null)
    {
        $translation = parent::getTranslation($key, $locale);
        return htmlspecialchars($translation); // Sanitize output
    }
    
  2. Event Listeners: Listen for TranslationHasBeenSetEvent to log or validate translations:

    $product->setTranslation('name', 'en', 'New Name');
    // Triggered after save()
    
  3. Dynamic Fallbacks: Use getFallbackLocale() to implement dynamic fallbacks (e.g., based on user preferences):

    public function getFallbackLocale()
    {
        return auth()->user()->preferred_locale ?? 'en';
    }
    
  4. Custom Query Scopes: Extend query methods for domain-specific needs:

    public function scopeWithEnglishName($query, $name)
    {
        return $query->whereJsonContainsLocale('name', 'en', $name);
    }
    
  5. Migration Helpers: Use Spatie\Translatable\Database\TranslatableJson for custom migrations:

    Schema::table('products', function (Blueprint $table) {
        $table->translatableJson('name')->nullable();
    });
    
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.
codraw/entity-migrator
codraw/doctrine-extra
codraw/aws-tool-kit
codraw/validator
codraw/workflow
codraw/open-api
codraw/cron-job
codraw/process
codraw/log
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony