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

Filament Localizable Resources Laravel Package

apility/filament-localizable-resources

A Filament plugin that makes resources localizable: translate resource labels, navigation, form and table fields per locale, and switch languages without duplicating resources. Integrates with spatie/laravel-translatable-style workflows to keep multilingual admin panels clean.

View on GitHub
Deep Wiki
Context7

Getting Started

The package layers locale-aware forms, tabs, and table columns onto a single Filament resource so one resource manages all translations instead of one resource per language. v1.0.2 is a patch release with only a compare link in the changelog, so treat it as a bugfix bump: no migration steps implied, no setup changes from the 1.0.x line.

Minimal steps, in order:

  1. Install with Filament pinned alongside it. Run composer require apility/filament-localizable-resources:^1.0 and immediately check the package's own composer.json (vendor/apility/filament-localizable-resources/composer.json) for its Filament constraint. These packages are tightly coupled to Filament's form/table schema API (v3 vs v4 differ substantially); pinning both together is the difference between a working install and a week of schema-method errors.
  2. Confirm your translation backend first. The package assumes your models expose translations — typically spatie/laravel-translatable with a $translatable array and a translations JSON column. Verify $model->getTranslation('title', 'nb') returns real values in php artisan tinker before touching Filament. Most "the package is broken" reports are actually misconfigured translatable models, and you'll debug the wrong layer for hours otherwise.
  3. Register locales as a single source of truth. Put ['en', 'nb', 'de'] in config (your own config file, or config/app.php referenced from the panel) and have forms, table columns, and the locale switcher all read from it. Hardcoded locale lists inside resources diverge within a sprint.
  4. Convert one small resource first. Pick a model with two translatable fields (title, body), apply the package's resource trait or extend its base resource — check vendor/apility/filament-localizable-resources/src for the exact class/trait name, since the release feed doesn't document it — and render the edit form. This is the fastest way to see whether the package produces tabs per locale or per-field locale sub-inputs; the two give very different editor UX and drive everything downstream.
  5. Read the package source once, end to end. It's a small surface area. Knowing exactly which method builds the schema and which reads the current locale is what makes the extension points in the last section usable instead of mysterious.

First real use case: a Page or Product resource with translatable title and body, edited by an admin who switches locale in the panel header and expects both the form fields and the table column to follow that switch.

Implementation Patterns

Panel-level locale switching, not per-field. Expose the editing locale as panel/session state via ->userMenuItems() or a custom render-hook header action, and let the package read it. One switcher controlling forms, tables, and infolists keeps behaviour predictable; per-field locale dropdowns scattered through a form become unusable past three locales and produce inconsistent table output.

Prefer the trait over the base class. Apply the localizable trait to each resource rather than extending a localizable base resource. Trait application composes with your existing base resource (tenancy, policies, navigation config) and avoids the "I need two base classes" dead end you hit the moment a second package also wants to be the parent.

Tabs per locale, one shared field builder. Shape the form as Tabs with one Tab::make($locale) per configured locale, each containing the same field set. Build that field set once in a private fields()/schema() method and map it into each tab, so validation rules, helper texts, and placeholders never drift between locales. Put the fallback locale tab first and consider ->badge() on tabs to flag empty translations.

Table columns: active locale with explicit fallback. Render the current locale's value and fall back when empty — $record->getTranslation('title', $locale) ?: $record->getTranslation('title', config('app.fallback_locale')). Make the fallback explicit in a helper rather than sprinkling it through column closures.

Required-per-locale as a closure. The common requirement is "fallback locale required, others optional." Encode it as a closure driven by the loop's locale variable (->required(fn () => $locale === config('app.fallback_locale'))) instead of duplicating rule sets per tab — it survives locale list changes for free.

Programmatic writes set all translations in one save. In seeders, imports, and console commands use $model->setTranslation('title', $locale, $value) per locale and then a single save(). Saving per locale multiplies model events and quietly breaks observers that send notifications or reindex search.

Keep slug and search logic in the model. If you generate slugs or index translatable content, do it from the fallback locale and keep a per-locale slug column when routes are localized. Put the logic in model accessors/mutators so API, console, and Filament paths behave identically.

Gotchas and Tips

  • The patch release is low-signal. v1.0.2 ships only a compare link, so treat it as a bugfix bump: no migration steps implied, but no guarantee either. Run the suite, and if a regression appears after upgrading, diff vendor/apility/filament-localizable-resources against the previous tag (composer diff or git log inside vendor).
  • Searchable/sortable translatable columns are the #1 bug source. ORDER BY title and WHERE title LIKE hit the raw JSON column and return cross-locale nonsense. Use generated/virtual columns per locale, json_extract expressions, or move search to Scout. "Localization works but sorting is wrong" is almost always this.
  • unique rules don't respect locales. A unique check against the JSON column fails or passes wrongly per locale. Use Rule::unique(...)->where(...) against a flattened column, or validate uniqueness in application code. Translate attribute names in lang/*/validation.php or error messages will mix languages.
  • Fallback is applied on read, not on write. Combining Filament's required() with translatable fallback produces "saved fine, displays empty" reports. Decide explicitly whether empty non-fallback locales store null or copy the fallback value, document it, and put it in a feature test — this is the most common support question for localizable resources.
  • Session state leaks across panels and tenants. If you store the editing locale in session, key it by panel ID (and tenant ID under multi-tenancy), or a locale chosen in one panel silently changes another's table output.
  • Watch for N+1 on table closures. getTranslation() in a column is cheap once and expensive across a page of rows if your accessors touch relations. ->with() whatever the accessors load.
  • Extend the schema builder, don't fork. Where the default tab/schema builder doesn't fit — say one locale needs extra fields like a per-language meta description — override the schema-building method in your own resource. Keep the override small and cover it with a feature test that renders the edit page, so a Filament minor bump failing that test tells you exactly where the break is.
  • Pin exact-ish in production (~1.0.2) and let CI catch Filament minors. With thin release notes and a small maintainer surface (3 stars, single 1.0.x line), Filament minor bumps — not this package's own releases — are the realistic breaking-change vector. Budget for reading vendor source when you upgrade, and keep an in-house feature test that renders one localizable edit page and asserts one table column; that single test catches most of what the changelog won't tell you.
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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle