Weave 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
translatable-development
spatie/laravel-translatable
claude-code
model
translate
eloquent
multilingual
spatie
laravel-translatable
i8n

Use when working with spatie/laravel-translatable. Trigger when the query mentions translatable models, translations, multi-language attributes, the HasTranslations trait, the #[Translatable] attribute, or storing translations as JSON on Eloquent models. Tasks include making a model translatable, writing migrations for translatable columns, setting translations for specific locales, getting translations (with or without fallbacks), querying by locale, handling missing translations, and testing translatable behavior.

Install
php artisan boost:add-skill spatie/laravel-translatable

Save this content to: .claude/skills/translatable-development/SKILL.md

# spatie/laravel-translatable

Translations are stored as JSON on the model itself. No extra tables, no pivot.

## Making a model translatable

Declare translatable attributes via the `#[Translatable]` PHP attribute (preferred) and add the `HasTranslations` trait.

```php
use Illuminate\Database\Eloquent\Model;
use Spatie\Translatable\Attributes\Translatable;
use Spatie\Translatable\HasTranslations;

#[Translatable('name', 'description')]
class NewsItem extends Model
{
    use HasTranslations;
}
```

The attribute accepts a variadic list of column names.

The `$translatable` property is also supported:

```php
class NewsItem extends Model
{
    use HasTranslations;

    public $translatable = ['name', 'description'];
}
```

When both are present, their values are merged and deduplicated.

## Migration

Translatable columns must be `json` (or `text` if the database does not support `json`). One column per translatable attribute — the column holds all locales as a JSON object.

```php
Schema::create('news_items', function (Blueprint $table) {
    $table->id();
    $table->json('name')->nullable();
    $table->json('description')->nullable();
    $table->timestamps();
});
```

## Setting translations

```php
// Current locale (via app()->getLocale())
$newsItem->name = 'Hello';

// Specific locale
$newsItem->setTranslation('name', 'en', 'Hello');
$newsItem->setTranslation('name', 'nl', 'Hallo');

// Multiple locales at once
$newsItem->setTranslations('name', ['en' => 'Hello', 'nl' => 'Hallo']);

// Mass assignment with a per-locale array
NewsItem::create([
    'name' => ['en' => 'Hello', 'nl' => 'Hallo'],
]);

$newsItem->save();
```

## Getting translations

```php
// Current locale
$newsItem->name;

// Specific locale (falls back to the configured fallback locale by default)
$newsItem->getTranslation('name', 'nl');

// Without fallback
$newsItem->getTranslationWithoutFallback('name', 'nl');

// All translations for one attribute
$newsItem->getTranslations('name'); // ['en' => 'Hello', 'nl' => 'Hallo']

// All translations for all translatable attributes
$newsItem->translations;

// All locales this model has translations in
$newsItem->locales();
```

## Querying by locale

```php
// Models that have a translation for the given locale
NewsItem::query()->whereLocale('name', 'en')->get();

// Models that have a translation in any of the given locales
NewsItem::query()->whereLocales('name', ['en', 'nl'])->get();

// Filter by translated value
NewsItem::query()->whereJsonContainsLocale('name', 'en', 'Hello')->get();
```

## Forgetting translations

```php
$newsItem->forgetTranslation('name', 'nl');      // one locale of one attribute
$newsItem->forgetTranslations('name');           // all locales of one attribute
$newsItem->forgetAllTranslations('nl');          // one locale across all translatable attributes
```

## Fallback behavior

By default, `getTranslation()` falls back to `config('app.fallback_locale')` when a translation is missing. Override per model:

```php
class NewsItem extends Model
{
    use HasTranslations;

    public $useFallbackLocale = false; // disable fallback for this model

    public function getFallbackLocale(): ?string
    {
        return 'en'; // custom fallback
    }
}
```

## Testing

```php
use Spatie\Translatable\Facades\Translatable;

it('stores a translation', function () {
    $newsItem = NewsItem::create(['name' => ['en' => 'Hello']]);

    expect($newsItem->getTranslation('name', 'en'))->toBe('Hello');
});

it('falls back when a translation is missing', function () {
    config()->set('app.fallback_locale', 'en');

    $newsItem = NewsItem::create(['name' => ['en' => 'Hello']]);

    expect($newsItem->getTranslation('name', 'nl'))->toBe('Hello');
});
```

## Common pitfalls

- Translatable columns must be `json` (or `text`) in the migration. A `string` column will throw on JSON casting.
- Setting a plain string on a translatable attribute only writes the current locale. Pass an associative array to set multiple locales at once.
- Do not register an `array` or `json` cast on translatable columns in `$casts`. `HasTranslations` handles the JSON encoding.
- The `$translatable` property and `#[Translatable]` attribute are class-level. Runtime mutations of `$this->translatable` work but are uncommon.
- `getTranslation()` returns the fallback locale's value when the requested locale is missing. Use `getTranslationWithoutFallback()` for a strict lookup.

spatie/laravel-translatable

Translations are stored as JSON on the model itself. No extra tables, no pivot.

Making a model translatable

Declare translatable attributes via the #[Translatable] PHP attribute (preferred) and add the HasTranslations trait.

use Illuminate\Database\Eloquent\Model;
use Spatie\Translatable\Attributes\Translatable;
use Spatie\Translatable\HasTranslations;

#[Translatable('name', 'description')]
class NewsItem extends Model
{
    use HasTranslations;
}

The attribute accepts a variadic list of column names.

The $translatable property is also supported:

class NewsItem extends Model
{
    use HasTranslations;

    public $translatable = ['name', 'description'];
}

When both are present, their values are merged and deduplicated.

Migration

Translatable columns must be json (or text if the database does not support json). One column per translatable attribute — the column holds all locales as a JSON object.

Schema::create('news_items', function (Blueprint $table) {
    $table->id();
    $table->json('name')->nullable();
    $table->json('description')->nullable();
    $table->timestamps();
});

Setting translations

// Current locale (via app()->getLocale())
$newsItem->name = 'Hello';

// Specific locale
$newsItem->setTranslation('name', 'en', 'Hello');
$newsItem->setTranslation('name', 'nl', 'Hallo');

// Multiple locales at once
$newsItem->setTranslations('name', ['en' => 'Hello', 'nl' => 'Hallo']);

// Mass assignment with a per-locale array
NewsItem::create([
    'name' => ['en' => 'Hello', 'nl' => 'Hallo'],
]);

$newsItem->save();

Getting translations

// Current locale
$newsItem->name;

// Specific locale (falls back to the configured fallback locale by default)
$newsItem->getTranslation('name', 'nl');

// Without fallback
$newsItem->getTranslationWithoutFallback('name', 'nl');

// All translations for one attribute
$newsItem->getTranslations('name'); // ['en' => 'Hello', 'nl' => 'Hallo']

// All translations for all translatable attributes
$newsItem->translations;

// All locales this model has translations in
$newsItem->locales();

Querying by locale

// Models that have a translation for the given locale
NewsItem::query()->whereLocale('name', 'en')->get();

// Models that have a translation in any of the given locales
NewsItem::query()->whereLocales('name', ['en', 'nl'])->get();

// Filter by translated value
NewsItem::query()->whereJsonContainsLocale('name', 'en', 'Hello')->get();

Forgetting translations

$newsItem->forgetTranslation('name', 'nl');      // one locale of one attribute
$newsItem->forgetTranslations('name');           // all locales of one attribute
$newsItem->forgetAllTranslations('nl');          // one locale across all translatable attributes

Fallback behavior

By default, getTranslation() falls back to config('app.fallback_locale') when a translation is missing. Override per model:

class NewsItem extends Model
{
    use HasTranslations;

    public $useFallbackLocale = false; // disable fallback for this model

    public function getFallbackLocale(): ?string
    {
        return 'en'; // custom fallback
    }
}

Testing

use Spatie\Translatable\Facades\Translatable;

it('stores a translation', function () {
    $newsItem = NewsItem::create(['name' => ['en' => 'Hello']]);

    expect($newsItem->getTranslation('name', 'en'))->toBe('Hello');
});

it('falls back when a translation is missing', function () {
    config()->set('app.fallback_locale', 'en');

    $newsItem = NewsItem::create(['name' => ['en' => 'Hello']]);

    expect($newsItem->getTranslation('name', 'nl'))->toBe('Hello');
});

Common pitfalls

  • Translatable columns must be json (or text) in the migration. A string column will throw on JSON casting.
  • Setting a plain string on a translatable attribute only writes the current locale. Pass an associative array to set multiple locales at once.
  • Do not register an array or json cast on translatable columns in $casts. HasTranslations handles the JSON encoding.
  • The $translatable property and #[Translatable] attribute are class-level. Runtime mutations of $this->translatable work but are uncommon.
  • getTranslation() returns the fallback locale's value when the requested locale is missing. Use getTranslationWithoutFallback() for a strict lookup.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource