tonsoo / filament-translatable
Simple translatable models and Filament form helpers for Laravel.
Requires
- php: ^8.3
- filament/filament: ^5.0
- illuminate/database: ^13.0
- illuminate/support: ^13.0
Requires (Dev)
- orchestra/testbench: ^11.0
- pestphp/pest: ^4.0
- phpunit/phpunit: ^12.5
README
Model and Filament page traits for runtime translations.
Locale discovery uses getTranslatableLocales() or the static $translatableLocales property when present, then app.available_locales (or app.locales), and finally the current app locale.
Fallback translation uses app.fallback_locale when set.
Supported Versions
- PHP:
^8.3 - Laravel:
^13.0 - Filament:
^5.0 - Livewire:
^4.1
Installation
composer require tonsoo/filament-translatable
Model Trait
use Illuminate\Database\Eloquent\Model; use Tonsoo\FilamentTranslatable\Concerns\HasTranslations; class Post extends Model { use HasTranslations; protected array $translatable = ['title', 'content']; }
Translatable attributes must be JSON columns.
Filament Page Trait
Use the page trait in your own create/edit pages:
use Filament\Resources\Resource; class ProductResource extends Resource { protected static ?string $model = Product::class; public static function getTranslatableLocales(): array // optional { return (array) config('app.available_locales', []); } }
InteractsWithTranslatableResourceData reads translatable attributes from the model.
Form inputs are scalar in the UI, and the trait converts translatable fields into locale maps for persistence.
Optional Runtime Locale Switcher
Add this resource trait to render a locale select beside the page heading:
use Tonsoo\FilamentTranslatable\Resources\Concerns\HasTranslatableLocaleSwitcher; class ProductResource extends Resource { use HasTranslatableLocaleSwitcher; protected static ?string $model = Product::class; public static function getTranslatableLocales(): array { return (array) config('app.available_locales', []); } protected static string $defaultTranslatableLocale = 'en'; // optional protected static array $translatableLocaleLabels = [ // optional 'en' => 'English', 'es' => 'Spanish', ]; }
You can also define the locale list with a static protected array $translatableLocales = [...] property instead of overriding getTranslatableLocales().
The locale labels array is optional; if you omit it, the switcher uses the locale code as the label.
protected static string $defaultTranslatableLocale = 'en'; is optional and is used only when it matches one of the configured locales.
use App\Filament\Resources\ProductResource; use Filament\Resources\Pages\CreateRecord; use Filament\Resources\Pages\EditRecord; use Tonsoo\FilamentTranslatable\Resources\Pages\Concerns\InteractsWithTranslatableResourceData; class CreateProduct extends CreateRecord { use InteractsWithTranslatableResourceData; protected static string $resource = ProductResource::class; } class EditProduct extends EditRecord { use InteractsWithTranslatableResourceData; protected static string $resource = ProductResource::class; }
This automatically wires:
mutateFormDataBeforeFill()on edit pagesmutateFormDataBeforeCreate()on create pagesmutateFormDataBeforeSave()on edit pages
Model API
$post->setTranslation('title', 'en', 'Hello'); $post->setTranslation('title', 'es', 'Hola'); $post->getTranslation('title', 'en'); $post->getTranslation('title', 'es'); // fallback uses app.fallback_locale $post->getTranslations('title'); $post->title = 'Hello'; // current locale $post->{'title.es'} = 'Hola'; // explicit locale
Locale-Aware Query Columns
When querying translatable attributes, the package maps plain translatable column names to the current locale JSON path.
app()->setLocale('pt_BR'); Post::query()->where('title', '=', 'Teste'); // same intent as querying: title->pt_BR = 'Teste'
You can also pass explicit locale dot notation:
Post::query()->where('title.pt_BR', '=', 'Teste');
Disable this behavior per query:
Post::query() ->withoutAutoTranslationsQuery() ->where('title->en', '=', 'Hello');
Re-enable later in the same chain:
Post::query() ->where('title', 'Teste') ->withoutAutoTranslationsQuery() ->where('title->en', 'Hello') ->withAutoTranslationsQuery() ->where('title', 'Teste');