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

astrotomic/laravel-translatable

Laravel package for translatable Eloquent models. Store model translations in the database and automatically fetch/save the correct locale with minimal code. Simplifies retrieving and persisting multilingual attributes across your app.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:
    composer require astrotomic/laravel-translatable
    php artisan vendor:publish --tag=translatable
    
  2. Configure Locales in config/translatable.php:
    'locales' => ['en', 'fr', 'de'],
    
  3. Create Migration for translations (e.g., post_translations):
    Schema::create('post_translations', function (Blueprint $table) {
        $table->id();
        $table->foreignId('post_id')->constrained()->onDelete('cascade');
        $table->string('locale')->index();
        $table->string('title');
        $table->text('content');
        $table->unique(['post_id', 'locale']);
    });
    
  4. Define Model:
    use Astrotomic\Translatable\Translatable;
    
    class Post extends Model
    {
        use Translatable;
        public $translatedAttributes = ['title', 'content'];
        protected $fillable = ['author'];
    }
    

First Use Case: Fetching Translations

$post = Post::find(1);
echo $post->translate('en')->title; // English title
App::setLocale('fr');
echo $post->title; // French title (auto-fallback)

Implementation Patterns

1. Translation Workflow

  • Create with Translations:
    $post = Post::create([
        'author' => 'John Doe',
        'en' => ['title' => 'Hello'],
        'fr' => ['title' => 'Bonjour'],
    ]);
    
  • Update Translations:
    $post->translate('en')->title = 'Updated Title';
    $post->save();
    
  • Bulk Update:
    $post->fillTranslations([
        'en' => ['title' => 'New Title'],
        'fr' => ['title' => 'Nouveau Titre'],
    ]);
    $post->save();
    

2. Dynamic Locale Handling

  • Fallback Logic:
    $post->translate('it'); // Returns null if 'it' missing
    $post->translate('it', true); // Falls back to default locale
    
  • Default Locale:
    $post->setDefaultLocale('fr');
    $post->translate()->title; // Uses 'fr' by default
    

3. Querying with Translations

  • Filter by Translation:
    $posts = Post::whereTranslation('title', 'like', '%Hello%')->get();
    
  • Join with Translations:
    $posts = Post::withTranslation('en')->get();
    

4. Replication & Cloning

  • Clone with Translations:
    $clone = $post->replicateWithTranslations();
    $clone->save();
    

5. API/JSON Responses

  • Serialize Translations:
    $post->toArray(); // Includes translations in nested structure
    
  • Custom JSON Structure:
    $post->getTranslationsArray(); // Returns flat array
    

Gotchas and Tips

Pitfalls

  1. Missing Locale Config:

    • Ensure all locales are defined in config/translatable.php. Undefined locales will cause errors.
    • Fix: Add missing locales or handle fallback explicitly:
      $post->translate('es', true); // Force fallback
      
  2. Translation Not Saved:

    • Forgetting to call $model->save() after updating translations.
    • Fix: Use fillTranslations() + save() or leverage model events.
  3. STI (Single Table Inheritance) Issues:

    • Custom foreign keys (e.g., child_post_id) require explicit configuration:
      protected $translationForeignKey = 'post_id';
      
  4. Mass Assignment Risks:

    • Translated attributes may bypass $fillable if not guarded.
    • Fix: Whitelist translated attributes in $translatedAttributes.
  5. Performance with Large Datasets:

    • Eager-loading translations (withTranslation()) avoids N+1 queries but increases memory usage.
    • Tip: Use with(['translation' => function($query) { ... }]) for scoped queries.

Debugging Tips

  1. Check Translation Existence:

    if (!$post->hasTranslation('es')) {
        // Handle missing translation
    }
    
  2. Inspect Translation Data:

    dd($post->getTranslationsArray());
    
  3. Log Fallback Behavior:

    $post->translate('it', true); // Logs fallback to default locale
    
  4. Validate Locale Format:

    • Ensure locales match the config (e.g., 'es_MX' vs. 'es'). Use config('translatable.locales') to verify.

Extension Points

  1. Custom Translation Models:

    • Override the default PostTranslation class by setting $translationModel in the model:
      protected $translationModel = CustomPostTranslation::class;
      
  2. Dynamic Attribute Handling:

    • Extend Translatable trait to add custom logic for specific attributes:
      use Astrotomic\Translatable\Translatable as BaseTranslatable;
      
      class Post extends Model
      {
          use BaseTranslatable {
              translate as private baseTranslate;
          }
      
          public function translate($locale = null)
          {
              $translation = $this->baseTranslate($locale);
              if ($translation && $locale === 'en') {
                  $translation->title = strtoupper($translation->title);
              }
              return $translation;
          }
      }
      
  3. Event Hooks:

    • Listen for translatable.saving or translatable.saved events to modify translations before/after save:
      Event::listen('translatable.saving', function ($model) {
          $model->translate('en')->title = 'Prefix: ' . $model->translate('en')->title;
      });
      
  4. Custom Query Scopes:

    • Add scopes to filter translations:
      public function scopeWithTitleLike($query, $search)
      {
          return $query->whereHas('translation', function($q) use ($search) {
              $q->where('title', 'like', "%{$search}%");
          });
      }
      
  5. Localization Middleware:

    • Set the default locale dynamically based on user input:
      public function handle($request, Closure $next)
      {
          $locale = $request->input('locale', config('app.locale'));
          App::setLocale($locale);
          return $next($request);
      }
      

Configuration Quirks

  1. translations_wrapper:

    • Useful for nested API payloads:
      'translations_wrapper' => 'i18n', // Accepts `i18n.en.title`
      
    • Note: Requires fill() to recognize the wrapper.
  2. fallback_locale:

    • Set in config or per-model:
      protected $fallbackLocale = 'en';
      
    • Warning: Overly aggressive fallback may hide bugs in missing translations.
  3. disableOriginalAttributeSync:

    • Disable syncing original attributes (e.g., title) when translations are updated:
      'disableOriginalAttributeSync' => true,
      
    • Use Case: Avoid overwriting non-translated fields during save.
  4. disableDefaultLocaleSync:

    • Prevent automatic sync of the default locale:
      'disableDefaultLocaleSync' => true,
      
    • Tip: Useful for models where the default locale is managed separately.

Performance Optimizations

  1. Cache Translations:

    • Cache getTranslationsArray() for read-heavy applications:
      $translations = Cache::remember("post.{$post->id}.translations", now()->addHours(1), function() use ($post) {
          return $post->getTranslationsArray();
      });
      
  2. Selective Loading:

    • Load only needed translations:
      $post->loadTranslation('en', ['title']); // Loads only 'title' for 'en'
      
  3. Database Indexes:

    • Ensure locale and post_id are indexed in the translations table for faster lookups.
  4. Batch Operations:

    • Use updateTranslations() for bulk updates:
      Post::where('author', 'John')->updateTranslations([
          'en' => ['title' => 'Batch Updated'],
      ]);
      
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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport
twbs/bootstrap4