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 a HasTranslations trait to Eloquent models to store translations as JSON in the same table (no extra tables). Define translatable attributes via PHP attribute or $translatable, then set/get translations per locale and auto-resolve by app locale.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require spatie/laravel-translatable
    

    Publish the config (optional):

    php artisan vendor:publish --provider="Spatie\Translatable\TranslatableServiceProvider"
    
  2. Apply to a Model: Use either PHP 8 attributes (recommended) or a $translatable property:

    use Spatie\Translatable\HasTranslations;
    use Spatie\Translatable\Attributes\Translatable;
    
    #[Translatable(['title', 'content'])]
    class Post extends Model
    {
        use HasTranslations;
    }
    

    or

    class Post extends Model
    {
        use HasTranslations;
    
        public $translatable = ['title', 'content'];
    }
    
  3. First Translation:

    $post = new Post();
    $post->setTranslation('title', 'en', 'Hello World')->save();
    $post->setTranslation('title', 'es', 'Hola Mundo')->save();
    
    // Access translations dynamically (respects app locale)
    echo $post->title; // 'Hello World' if app locale is 'en'
    
  4. Querying Translations:

    // Get posts with English titles
    Post::whereLocale('title', 'en')->get();
    
    // Get posts with titles matching "Hello" in English
    Post::whereJsonContainsLocale('title', 'en', 'Hello')->get();
    

Implementation Patterns

Core Workflows

  1. Translation Management:

    • Setting Translations:
      $post->setTranslation('title', 'fr', 'Bonjour')->save();
      // Or bulk-set via array:
      $post->setTranslations([
          'title' => ['en' => 'Hello', 'es' => 'Hola'],
          'content' => ['en' => 'Body...']
      ]);
      
    • Clearing Translations:
      $post->clearTranslation('title', 'en'); // Clear single locale
      $post->clearTranslations('title');      // Clear all locales for a field
      
  2. Dynamic Access:

    • Magic Property Access:
      $post->title; // Falls back to app locale or first available
      
    • Explicit Locale Access:
      $post->getTranslation('title', 'es'); // 'Hola'
      
  3. Nested JSON Translations (v6.10+):

    #[Translatable(['meta->description'])]
    class Post extends Model { ... }
    
    $post->setTranslation('meta->description', 'en', 'Nested content')->save();
    $post->meta->description; // 'Nested content' (if app locale is 'en')
    
  4. Query Scopes:

    // Filter by locale presence
    Post::whereLocale('title', 'en')->get();
    
    // Filter by translation value (supports JSON operators)
    Post::whereJsonContainsLocale('title', 'en', 'Hello%')->get();
    

Integration Tips

  1. Form Handling:

    • Use spatie/laravel-translatable with Laravel Collective or Livewire for multi-language forms:
      // Livewire example
      public function mount()
      {
          $this->translations = [
              'title' => ['en' => '', 'es' => ''],
              'content' => ['en' => '', 'es' => '']
          ];
      }
      
      public function save()
      {
          $post->setTranslations($this->translations)->save();
      }
      
  2. Validation:

    • Validate translations per locale:
      $validator = Validator::make($request->all(), [
          'title.en' => 'required|string|max:255',
          'title.es' => 'required|string|max:255',
      ]);
      
  3. API Responses:

    • Serialize translations explicitly:
      return PostResource::make($post)->additional([
          'translations' => $post->getTranslations()
      ]);
      
  4. Caching:

    • Cache translations for performance-critical apps:
      Cache::remember("post.{$post->id}.translations", now()->addHours(1), function () use ($post) {
          return $post->getTranslations();
      });
      
  5. Testing:

    • Test translations with assertDatabaseHas:
      $this->assertDatabaseHas('posts', [
          'id' => $post->id,
          'translations' => json_encode(['title' => ['en' => 'Hello']])
      ]);
      

Gotchas and Tips

Pitfalls

  1. Null Handling:

    • By default, null values are stored as null in the JSON column. To allow empty strings, configure:
      'allow_null_for_translation' => false, // In config/translatable.php
      
    • Fix: Use setTranslation(..., null) explicitly if you need to clear a translation.
  2. JSON Column Constraints:

    • Ensure your database column supports JSON (e.g., json or longtext in MySQL). Avoid text for large translations.
  3. Locale Fallbacks:

    • The package respects Laravel’s locale but does not auto-fallback to other locales. Implement custom logic if needed:
      $post->title ?? $post->getTranslation('title', 'en') ?? 'Default';
      
  4. Nested Keys:

    • Nested keys (e.g., meta->description) require double hyphens (->) in the attribute name. Typos here will break access.
  5. Mass Assignment:

    • Translations are not automatically mass-assignable. Use fill() carefully:
      $post->fill(['title' => 'Hello']); // Won't work! Use setTranslation() instead.
      
  6. Query Performance:

    • whereJsonContainsLocale uses JSON operators, which can be slow on large datasets. Index the JSON column if needed:
      ALTER TABLE posts ADD INDEX translations_idx ((CAST(translations AS JSON)));
      

Debugging Tips

  1. Inspect Raw JSON:

    • Dump the raw translations column to debug:
      \Log::debug('Translations JSON:', [$post->getRawOriginal('translations')]);
      
  2. Check Config:

    • Verify config/translatable.php for custom settings like:
      'allow_null_for_translation' => true,
      'handle_empty_strings' => 'ignore', // 'ignore', 'store', or 'replace'
      
  3. Attribute Conflicts:

    • If using both #[Translatable] and $translatable, ensure no duplicates:
      // Merges to ['title', 'content', 'description']
      #[Translatable(['title'])]
      class Post { public $translatable = ['content', 'description']; }
      
  4. Locale-Specific Queries:

    • Use toSql() to debug query scopes:
      \Log::debug(Post::whereLocale('title', 'en')->toSql());
      

Extension Points

  1. Custom Storage:

    • Override getTranslationsAttribute to use a different storage mechanism:
      public function getTranslationsAttribute($value)
      {
          return Cache::remember("translations.{$this->id}", now()->addHours(1), function () {
              return json_decode($value, true);
          });
      }
      
  2. Event Hooks:

    • Listen for translation changes:
      $post->setTranslation('title', 'en', 'New Title');
      // Trigger custom logic via observers or events.
      
  3. Macros:

    • Extend the trait with custom methods:
      HasTranslations::macro('translateAll', function (array $locales) {
          foreach ($locales as $locale => $value) {
              $this->setTranslation('title', $locale, $value);
          }
          return $this;
      });
      
  4. Validation Rules:

    • Create reusable rules for translations:
      use Illuminate\Validation\Rule;
      
      Rule::macro('translation_exists', function ($field, $locale) {
          return function ($attribute, $value, $fail) {
              if (!$this->model()->whereJsonContainsLocale($field, $locale, $value)->exists()) {
                  $fail('The translation does not exist.');
              }
          };
      });
      
  5. Fallback Logic:

    • Implement a custom fallback resolver:
      class Post extends Model
      {
          public function getTitleAttribute($value)
          {
              return $value ?? $this->getTranslation('title', 'en') ?? 'Default Title';
          }
      }
      
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.
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope
anil/file-picker
broqit/fields-ai