liberusoftware/localization-core-filament
Filament admin panel integration for Localization Core. Manage locales, translation keys, and language files from your Filament dashboard, with resources and UI components for editing translations without leaving the admin area.
This is a presentation adapter, not a standalone localization package. It assumes liberusoftware/module-localization-core (the domain layer: models, contracts, services) is installed and supplies the actual persistence and business rules. Your job as a developer is almost never to touch this package's internals — it's to register the panel plugin, point your Filament panel at it, and let it render the resources the core exposes.
Minimal path to a running screen:
composer require liberusoftware/module-localization-core \
liberusoftware/module-localization-core-filament
Then, in order:
composer.json before anything else. Confirm the Filament major version constraint (filament/filament:^3.0 vs ^4.0) and the PHP/Laravel floor. Filament v3→v4 moved the form/table namespaces; if the constraint is v3 and your panel is v4, stop here and pick a different approach.database/migrations inside module-localization-core). The adapter should only contain Filament classes. If you see loadMigrationsFrom in the adapter provider, read it carefully before publishing twice.app/Providers/Filament/AdminPanelProvider.php). Liberu adapters typically ship either a Plugin class (->plugin(LocalizationCoreFilamentPlugin::make())) or a plain service provider listed in bootstrap/providers.php. Check src/ for which pattern this one uses — plugin is preferred because it scopes resources to a single panel.php artisan filament:optimize-clear && php artisan optimize:clear. Stale component caches are the #1 cause of "resource not showing up."Where to look first: src/Providers/ → src/Filament/Resources/ (the actual UI surface) → src/Filament/Pages/ (import/export, translation editor) → config/ in the core package (locale list, fallback behaviour, driver bindings). The adapter's public API is effectively "which resources and pages exist and what their names are."
First use case: create a locale record, add a translation key under a group, add values per locale, then visit the front end with ?lang= or App::setLocale() to confirm the read path resolves through the core. If the front end still reads lang/*.php files and ignores the DB rows, you haven't wired the core's translation loader — see Gotchas.
Treat the adapter as read-mostly and extend by subclassing. Don't fork the vendor resources. Instead:
namespace App\Filament\Resources;
use Liberusoftware\LocalizationCoreFilament\Filament\Resources\LocaleResource as BaseLocaleResource;
class LocaleResource extends BaseLocaleResource
{
public static function getNavigationGroup(): ?string
{
return 'Settings';
}
public static function getNavigationSort(): ?int
{
return 99;
}
}
Then hide the vendor resource (many Liberu adapters expose a ->resources([...]) or ->discoverResources() toggle on the plugin) and register your subclass. This survives composer update; a fork does not.
Keep domain logic in the core. The adapter's forms and actions should call core services/actions (CreateLocale, SyncTranslationsFromFiles, whatever the core exposes), never Locale::create() directly in a Filament ->action(). This keeps your Artisan commands, API endpoints, and admin UI behaviourally identical. If the core has no action for what you need, add it there and open a PR upstream rather than embedding it in a Filament closure.
Multi-panel and multi-tenant. If you run separate admin and tenant panels, register the plugin only where it belongs. For tenant-scoped translations, override resource queries with ->modifyQueryUsing(fn ($query) => $query->where('tenant_id', Filament::getTenant()?->id)) on the table and add the tenant column on create. Global keys (validation messages, mail subjects) usually need an explicit "global" flag in the core rather than a bypass in the UI.
Translation editor workflow. The pragmatic day-to-day pattern is: developers add keys in code, an admin edits values in Filament. Support that with a bulk action that runs the core's file-scan/sync, a TextColumn for "coverage" (values present / locales), and a filter for missing values. Avoid inline editable columns for large tables — they generate one Livewire request per keystroke commit and are painful at 10k+ keys.
Authorization. Map the adapter's resources to policies/permissions rather than relying on panel access alone. canViewAny, canCreate, plus a ->visible() guard on the navigation item. In a Liberu-style modular install, permissions usually come from the core's seeder — run it, don't hand-roll a policy that drifts.
Testing. Use Livewire resource tests against the real panel:
Livewire::actingAs($admin)
->test(ListLocales::class)
->assertCanSeeTableRecords($locales)
->callTableAction('create', data: ['code' => 'pt_BR', 'name' => 'Português (Brasil)'])
->assertHasNoTableActionErrors();
RefreshDatabase with the core's migrations is enough; you rarely need the adapter's own fixtures.
NOASSERTION. That is not a permissive license — it means the repo's LICENSE file is missing or unparseable by GitHub. Get legal sign-off before shipping this in a commercial product, and check composer.json for a license field that may contradict the repo."liberusoftware/module-localization-core-filament": "1.2.3", not ^1.0), and keep your subclasses in one directory so a breaking change is a single-file fix.bootstrap/providers.php ordering, and remember composer dump-autoload after moving entries.migrate. Publish once, from the core, into database/migrations and commit them.translations table does nothing for __('messages.welcome') unless the core registers a translation loader (typically a spatie/laravel-translation-loader-style driver) in config/app.php or via a service provider. Verify by changing a value in the UI and hitting a page that uses the key.php artisan cache:clear (or the core's dedicated translations:cache-flush). Bake it into the after-save hook of your import action, or admins will swear the UI is broken.config/app.php fallback_locale, and the DB locale table can all disagree. Pick the DB as the source of truth for available locales and app.fallback_locale for resolution, and assert that the fallback locale exists in the table at boot (a failing test is cheaper than a 500 in production).->with('values') in list tables, or use a SelectColumn/TextColumn backed by a single aggregate query. A naive values->firstWhere(...) per row is a per-row query on every render.AppServiceProvider::register), Filament render hooks (FilamentView::registerRenderHook) for extra panels on existing pages, and ->configureUsing() on resources for global tweaks. Avoid reflection or overriding protected methods; they're the first thing to break on minor releases.php artisan about (confirm Filament panel count), php artisan filament:optimize-clear, php artisan route:list --path=admin | grep -i local, then dump the registered plugin list. Nine times out of ten it's a stale component cache or the plugin registered on the wrong panel.How can I help you explore Laravel packages today?