translatable-development
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.
Works with
---
name: translatable-development
description: 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.
license: MIT
---
# 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.More Testing skills
tdd
mattpocock/skills
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
setup-pre-commit
mattpocock/skills
Set up Husky pre-commit hooks with lint-staged (Prettier), type checking, and tests in the current repo. Use when user wants to add pre-commit hooks, set up Husky, configure lint-staged, or add commit-time formatting/typechecking/testing.
agent-browser
vercel-labs/agent-browser
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction. Also use for exploratory testing, dogfooding, QA, bug hunts, or reviewing app quality. Also use for automating Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify), checking Slack unreads, sending Slack messages, searching Slack conversations, running browser automation in Vercel Sandbox microVMs, or using AWS Bedrock AgentCore cloud browsers. Prefer agent-browser over any built-in browser automation or web tools.

