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 multilingual attributes based on locale, reducing boilerplate when working with multi-language content.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require astrotomic/laravel-translatable
    php artisan vendor:publish --tag=translatable
    

    Configure locales in config/translatable.php (e.g., ['en', 'fr']).

  2. Database Migration: Create a translation table (e.g., post_translations) with:

    • id, post_id (foreign key), locale, and translated fields (e.g., title).
    • Add a unique constraint on [post_id, locale].
  3. Model Setup:

    // Post.php
    use Astrotomic\Translatable\Translatable;
    
    class Post extends Model
    {
        use Translatable;
        public $translatedAttributes = ['title', 'content'];
        protected $fillable = ['author'];
    }
    
    // PostTranslation.php
    class PostTranslation extends Model
    {
        public $timestamps = false;
        protected $fillable = ['title', 'content'];
    }
    

First Use Case

Retrieve and update translations dynamically:

// Fetch a post and access translations
$post = Post::find(1);
echo $post->translate('en')->title; // English title

// Update a translation
$post->translate('en')->title = 'Updated Title';
$post->save();

// Create with translations
$post = Post::create([
    'author' => 'John Doe',
    'en' => ['title' => 'Hello'],
    'fr' => ['title' => 'Bonjour'],
]);

Implementation Patterns

1. Locale-Aware Workflows

  • Dynamic Locale Switching: Use App::setLocale() to switch contexts:
    App::setLocale('fr');
    echo $post->title; // Automatically fetches French translation
    
  • Fallback Handling: Configure fallback_locale in config/translatable.php (e.g., 'en') to default to English if a translation is missing.

2. Bulk Operations

  • Batch Creation: Use the translations_wrapper config (e.g., 'translations_wrapper' => 'translations') to nest translations:
    $post = Post::create([
        'author' => 'Jane Doe',
        'translations' => [
            'en' => ['title' => 'Hello'],
            'fr' => ['title' => 'Bonjour'],
        ],
    ]);
    
  • Update All Translations:
    $post->translate('en')->title = 'New Title';
    $post->translate('fr')->title = 'Nouveau Titre';
    $post->save(); // Saves all translations at once
    

3. Querying with Translations

  • Filter by Translation: Use whereHas with translate():
    $posts = Post::whereHas('translate', function ($query) {
        $query->where('title', 'like', '%Hello%');
    })->get();
    
  • Eager Load Translations:
    $posts = Post::with(['translate' => function ($query) {
        $query->where('locale', 'en');
    }])->get();
    

4. Replication and Cloning

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

5. API/JSON Responses

  • Serialize Translations: Use getTranslationsArray() for API responses:
    return response()->json($post->getTranslationsArray());
    

Gotchas and Tips

Pitfalls

  1. Missing Fallback Locale:

    • If fallback_locale is not set, translate('it') returns null instead of falling back to en.
    • Fix: Configure fallback_locale in config/translatable.php.
  2. Translation Not Saved:

    • Forgetting to call $post->save() after updating translations.
    • Fix: Always save the parent model to persist translations.
  3. Locale Mismatch:

    • Using a locale not defined in config/translatable.php (e.g., 'it' when only ['en', 'fr'] are configured).
    • Fix: Validate locales against the config array.
  4. Single Table Inheritance (STI):

    • If using STI (e.g., ChildPost), explicitly set $translationForeignKey to avoid ambiguity:
      protected $translationForeignKey = 'post_id';
      
  5. Mass Assignment Risks:

    • Translated attributes are not included in $fillable by default. Explicitly whitelist them in PostTranslation:
      protected $fillable = ['title', 'content'];
      

Debugging Tips

  1. Check Translation Existence: Use hasTranslation() to verify:

    if (!$post->hasTranslation('fr')) {
        // Create or handle missing translation
    }
    
  2. Inspect Translation Data: Dump the translations array:

    dd($post->getTranslationsArray());
    
  3. Query Debugging: Add ->toSql() to debug whereHas queries:

    $query = Post::whereHas('translate', function ($q) {
        return $q->where('title', 'like', '%Hello%')->toSql();
    });
    

Extension Points

  1. Custom Translation Model: Override the default PostTranslation by setting $translationModel in the parent model:

    protected $translationModel = CustomPostTranslation::class;
    
  2. Dynamic Translated Attributes: Use a getter to dynamically define translated attributes:

    public function getTranslatedAttributes()
    {
        return ['title', 'content', 'description'];
    }
    
  3. Event Hooks: Listen for translatable.saving or translatable.saved events to log or validate translations:

    Event::listen('translatable.saving', function ($model) {
        // Custom logic before save
    });
    
  4. Custom Storage: Extend the package to support non-database storage (e.g., Redis) by overriding the getTranslation() method.

Performance Tips

  1. Eager Loading: Always eager load translations to avoid N+1 queries:

    $posts = Post::with(['translate' => function ($query) {
        $query->whereIn('locale', ['en', 'fr']);
    }])->get();
    
  2. Indexing: Ensure locale and post_id are indexed in the translations table for faster lookups.

  3. Caching: Cache translations for read-heavy applications:

    $translation = Cache::remember("post.{$post->id}.{$locale}", now()->addHours(1), function () use ($post, $locale) {
        return $post->translate($locale);
    });
    

Configuration Quirks

  1. Default Locale: The default_locale in config/translatable.php is per-model. Override it dynamically:

    $post->setDefaultLocale('fr');
    
  2. Translations Wrapper: If using translations_wrapper, ensure the input array matches the expected structure:

    // Correct:
    'translations' => ['en' => ['title' => 'Hello']]
    // Incorrect:
    'en' => ['title' => 'Hello'] // Only works without wrapper
    
  3. Locale Format: The package supports custom locale formats (e.g., 'eng' instead of 'en'), but stick to one format across the app to avoid inconsistencies.

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