belaaredj / filament-localized
Multilingual infrastructure plugin for Filament 5.
Fund package maintenance!
Requires
- php: ^8.2
- filament/filament: ^5.0
- spatie/laravel-package-tools: ^1.15
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.0
- nunomaduro/collision: ^8.0
- orchestra/testbench: ^10.0
- pestphp/pest: ^3.7
- pestphp/pest-plugin-arch: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
- rector/rector: ^2.0
- spatie/laravel-ray: ^1.26
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Multilingual infrastructure for Filament 5, providing reusable localized form fields, relationship selects, table columns, infolist entries, multilingual search, and configurable translation fallbacks.
Features
- π Multi-language form tabs
- π Multilingual table search
- π Localized relationship selects
- π Multilingual relationship search
- π Localized table columns
- π Localized infolist entries
- βοΈ RTL/LTR locale support
- π Configurable translation fallback
- βοΈ Centralized locale configuration
- π§© Reusable API through
Localized - π§ͺ Tested with Filament 5
Requirements
- PHP
^8.2 - Filament
^5.0 - Laravel application compatible with Filament 5
Installation
Install the package with Composer:
composer require Belaaredj/filament-localized
Publish the package configuration:
php artisan vendor:publish --tag=filament-localized-config
The configuration file will be available at:
config/filament-localized.php
You can then customize the supported locales and fallback behavior.
Configuration
The package stores translations as JSON objects.
For example:
{
"ar": "Ψ§ΩΨ±ΩΨ¨ΩΨͺΨ§Ψͺ",
"fr": "Robotique",
"en": "Robotics"
}
The default configuration supports Arabic, French, and English:
'locales' => [ 'ar' => [ 'label' => 'Ψ§ΩΨΉΨ±Ψ¨ΩΨ©', 'short' => 'AR', 'direction' => 'rtl', ], 'fr' => [ 'label' => 'FranΓ§ais', 'short' => 'FR', 'direction' => 'ltr', ], 'en' => [ 'label' => 'English', 'short' => 'EN', 'direction' => 'ltr', ], ],
You can add or remove locales according to your application.
For example:
'locales' => [ 'ar' => [ 'label' => 'Ψ§ΩΨΉΨ±Ψ¨ΩΨ©', 'short' => 'AR', 'direction' => 'rtl', ], 'fr' => [ 'label' => 'FranΓ§ais', 'short' => 'FR', 'direction' => 'ltr', ], 'en' => [ 'label' => 'English', 'short' => 'EN', 'direction' => 'ltr', ], 'de' => [ 'label' => 'Deutsch', 'short' => 'DE', 'direction' => 'ltr', ], ],
Translation Fallback
The package resolves translations using the following order:
Current locale β fr β en β ar
For example, when the current locale is ar:
[
'fr' => 'Robotique',
'en' => 'Robotics',
]
The resolved value will be:
Robotique
If French is also unavailable:
[
'en' => 'Robotics',
]
the package will resolve:
Robotics
Configure the fallback order in:
'fallback_locales' => [ 'fr', 'en', 'ar', ],
The current application locale is always checked first.
Localized Form Tabs
Use Localized::tabs() to create language tabs for your form fields.
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\RichEditor; use Belaaredj\FilamentLocalized\Facades\Localized; Localized::tabs([ TextInput::make('name') ->label('Name'), RichEditor::make('description') ->label('Description'), ])
The package generates a field structure based on the configured locales.
For example:
name.ar
name.fr
name.en
description.ar
description.fr
description.en
The resulting state can be stored directly in a JSON column:
{
"ar": "Ψ§ΩΨ±ΩΨ¨ΩΨͺΨ§Ψͺ",
"fr": "Robotique",
"en": "Robotics"
}
Database columns
Your translation fields should normally use a JSON-compatible database column.
For Laravel migrations:
$table->json('name')->nullable(); $table->json('description')->nullable();
For MySQL, make sure the database supports JSON columns.
Localized Select
Use Localized::select() for localized relationship options.
use Belaaredj\FilamentLocalized\Facades\Localized; Localized::select('skill_id') ->relationship('skill') ->localizedTitle('name') ->localizedSearch() ->searchable();
The option label is resolved using the configured translation fallback.
For example:
{
"ar": "Ψ§ΩΨ±ΩΨ¨ΩΨͺΨ§Ψͺ",
"fr": "Robotique",
"en": "Robotics"
}
The displayed option automatically follows the current locale and fallback configuration.
Multiple relationships
The same API works with multiple relationships:
Localized::select('skills') ->multiple() ->relationship('skills') ->localizedTitle('name') ->localizedSearch() ->searchable();
Localized Relationship Search
When:
->localizedSearch()
is enabled, relationship searches are performed across the configured search locales.
For example:
Localized::select('skill_id') ->relationship('skill') ->localizedTitle('name') ->localizedSearch() ->searchable();
A search can match:
Ψ§ΩΨΉΨ±Ψ¨ΩΨ©
or:
Robotique
or:
Robotics
depending on the configured search locales.
By default:
'search_locales' => null,
means that all configured locales are searched.
You can restrict the locales:
'search_locales' => [ 'ar', 'fr', ],
Localized Table Columns
Use Localized::column() for translated JSON attributes in Filament tables.
use Belaaredj\FilamentLocalized\Facades\Localized; Localized::column('name') ->label('Name');
Enable multilingual searching with:
Localized::column('name') ->label('Name') ->localizedSearch() ->searchable();
The column automatically resolves the displayed translation using the configured fallback order.
Localized Infolist Entries
Use Localized::entry() for translated attributes in Filament infolists.
use Belaaredj\FilamentLocalized\Facades\Localized; Localized::entry('description') ->label('Description');
The displayed value follows the same locale and fallback rules used by the other package components.
Supported API
The package provides a centralized API:
| Method | Purpose |
|---|---|
Localized::tabs() |
Create multilingual form tabs |
Localized::select() |
Create localized relationship selects |
Localized::column() |
Display and search localized table columns |
Localized::entry() |
Display localized infolist values |
The underlying specialized components are also available:
LocalizedTabs
LocalizedSelect
LocalizedTextColumn
LocalizedTextEntry
Filament Panel Plugin
The package also provides a Filament plugin class:
use Belaaredj\FilamentLocalized\FilamentLocalizedPlugin; $panel ->plugin( FilamentLocalizedPlugin::make() );
The plugin is intentionally lightweight. The localized components can be used independently and do not require additional panel-specific configuration.
Locale Switcher
The plugin includes an enabled-by-default locale switcher in the Filament topbar. Configure it per panel:
use Filament\View\PanelsRenderHook; use Belaaredj\FilamentLocalized\FilamentLocalizedPlugin; $panel->plugin( FilamentLocalizedPlugin::make() ->localeSwitcher() ->localeSwitcherHook(PanelsRenderHook::TOPBAR_END) );
Disable it for a panel with ->localeSwitcher(false). The switcher validates locales, stores the selection in the session, and redirects back to the same-site referring page. Persistent panel middleware reapplies the locale before Filament renders each request, including Livewire requests.
Locale Configuration
The complete configuration is available in:
config/filament-localized.php
Example:
return [ 'locales' => [ 'ar' => [ 'label' => 'Ψ§ΩΨΉΨ±Ψ¨ΩΨ©', 'short' => 'AR', 'direction' => 'rtl', ], 'fr' => [ 'label' => 'FranΓ§ais', 'short' => 'FR', 'direction' => 'ltr', ], 'en' => [ 'label' => 'English', 'short' => 'EN', 'direction' => 'ltr', ], ], 'default_locale' => 'fr', 'fallback_locales' => [ 'fr', 'en', 'ar', ], 'search_locales' => null, 'locale_switcher' => [ 'enabled' => true, 'show_flag' => true, 'show_label' => true, 'show_short' => false, 'flag_fallback' => 'short', ], 'locale_persistence' => [ 'session' => true, 'user' => [ 'enabled' => false, 'attribute' => 'locale', ], 'browser' => [ 'enabled' => false, ], ], ];
Locale properties
Each locale supports:
| Property | Description |
|---|---|
label |
Full display name |
short |
Short locale label |
direction |
rtl or ltr |
flag |
Text, emoji, or image URL |
For example:
'ar' => [ 'label' => 'Ψ§ΩΨΉΨ±Ψ¨ΩΨ©', 'short' => 'AR', 'direction' => 'rtl', ],
Missing flags use flag_fallback (short, label, or none), so the switcher remains usable on systems whose fonts do not provide emoji flags. Locale resolution uses this priority: explicit valid request locale, session locale, optional authenticated-user attribute, optional browser language, then the configured default. User persistence and browser detection are disabled by default, so no user column or migration is required.
The locale direction is metadata for the selected language. The package does not force the entire Filament document into RTL or LTR by default. Apply LocaleManager::direction(app()->getLocale()) in an application layout only when the whole interface should follow that direction.
Troubleshooting
- Clear configuration cache after changing the config with
php artisan config:clear. - Confirm the locale code is a key in
locales; unsupported route values are rejected. - Ensure the panel uses
FilamentLocalizedPluginso persistent middleware runs on navigation and Livewire requests. - If a flag does not display, use an image URL or set
flag_fallbacktoshort.
Using the Components Directly
The facade is the recommended API for most applications.
However, the underlying components can also be imported directly.
Localized Select
use Belaaredj\FilamentLocalized\Components\Forms\LocalizedSelect; LocalizedSelect::make('skill_id') ->relationship('skill') ->localizedTitle('name') ->localizedSearch() ->searchable();
Localized Table Column
use Belaaredj\FilamentLocalized\Components\Tables\LocalizedTextColumn; LocalizedTextColumn::make('name') ->localizedSearch() ->searchable();
Localized Infolist Entry
use Belaaredj\FilamentLocalized\Components\Infolists\LocalizedTextEntry; LocalizedTextEntry::make('name');
Recommended Database Structure
A translated attribute should be stored as JSON.
Example migration:
Schema::create('skills', function (Blueprint $table) { $table->id(); $table->json('name'); $table->json('description')->nullable(); $table->boolean('is_active')->default(true); $table->timestamps(); });
Example Eloquent model:
class Skill extends Model { protected $fillable = [ 'name', 'description', 'is_active', ]; protected function casts(): array { return [ 'name' => 'array', 'description' => 'array', 'is_active' => 'boolean', ]; } }
The package does not require a translation-specific database table.
Testing
The package uses Pest for automated testing.
Run the test suite:
composer test
Run static analysis:
composer analyse
Run code formatting:
composer lint
Run the complete development checks:
composer test
composer analyse
composer lint
Architecture
The package is organized around a small set of reusable components:
Belaaredj\FilamentLocalized
β
βββ Components
β βββ Forms
β β βββ LocalizedTabs
β β βββ LocalizedSelect
β β
β βββ Infolists
β β βββ LocalizedTextEntry
β β
β βββ Tables
β βββ LocalizedTextColumn
β
βββ Support
β βββ LocaleManager
β βββ TranslationManager
β βββ TranslationQuery
β
βββ Facades
β βββ Localized
β
βββ FilamentLocalized
βββ FilamentLocalizedPlugin
βββ FilamentLocalizedServiceProvider
The package intentionally keeps translation resolution and multilingual querying separate from the UI components, making the underlying functionality reusable across forms, tables, infolists, and relationship fields.
Contributing
Contributions, bug reports, and feature requests are welcome.
Before submitting a pull request, please make sure the test suite and static analysis pass:
composer test
composer analyse
composer lint
For bugs and feature requests, please use the project's issue tracker.
License
The MIT License (MIT). Please see LICENSE for more information.
Author
Developed by Belaaredj Ahmed.
Filament Localized β reusable multilingual infrastructure for Filament 5.