asignua/filament-translatable-fields
Filament Translatable Fields adds locale-aware form fields and table columns to Filament resources, making it easy to edit, display, and manage multilingual content in your admin panel without writing custom UI for every language.
This package is a Filament-oriented translation layer: it gives you translatable form tabs, table columns, infolist entries, and model helpers for the same attribute across multiple locales. Treat v1.0.2 as a correctness-focused patch release: no advertised breaking API change, but several real-world bugs around rich editors, Repeaters/Builders, array state, and RelationManagers are fixed.
Minimal setup:
Look first at:
TranslatableColumn, and TranslatableEntry.HandlesTranslatableFields for locale helpers, translatableModel(), and requiredAny().First use case: a Post model with title and body translation maps. Create one tab per locale, make at least one locale required with requiredAny(), and use a rich editor for body. Add a table column and infolist entry for quick localized display. This gives immediate day-to-day value without building custom translation UI.
Use the package components instead of hand-rolling locale loops. They carry the record, locale list, and validation context for you.
Typical model setup:
use HandlesTranslatableFields;
class Post extends Model
{
use HandlesTranslatableFields;
protected $guarded = [];
}
Form pattern: bind fields to attribute.locale and let the translatable tabs component provide $record, locales(), and requiredAny() context.
// Shape: one schema callback per locale
fn (string $locale) => [
TextInput::make("title.$locale")
->requiredAny(),
RichEditor::make("body.$locale")
->requiredAny()
->columnSpanFull(),
]
Validation pattern: use requiredAny() when at least one locale must be filled. In v1.0.2 this now treats an empty Tiptap document as empty, so a post with every body translation empty fails validation as expected. Use normal per-field rules for stricter requirements, such as requiring the default locale.
Tables and infolists:
TranslatableColumn::make('title')
->locales(['en' => 'English', 'ru' => 'Русский']);
TranslatableEntry::make('title')
->locales(['en' => 'English', 'ru' => 'Русский']);
Repeater/Builder pattern: place translatable tabs inside the repeated schema and use the item's own context. v1.0.2 builds tabs from the injected component rather than a captured $this, so each item should use its own Get, $record, locales(), and requiredAny() context. Do not capture the outer form component in a closure when the field belongs to a repeated item.
RelationManagers: HandlesTranslatableFields::translatableModel() now returns the related model in a RelationManager / ManageRelatedRecords page. If the package cannot infer the model in a custom page, expect a clear exception; override or provide the model resolution explicitly.
Localized validation messages: the factory's own validationAttribute() remains the base of the language-aware message, e.g. заголовок (English). Customize labels on the field/factory if you want a different base name.
requiredAny(). Update tests and expectations accordingly.null or ''. The package now recognizes an empty Tiptap document as empty for requiredAny(). Do not add custom “is empty” checks unless you also account for the editor's empty document shape.$this or outer $record references.TranslatableEntry and TranslatableColumn now read the entry's own record or array state. For JSON RepeatableEntry items, the translation map should be the item's map, not the parent's attribute. When using ->state([...]) or array records, keep the state keyed by locale.translatableModel() is now safer in relation contexts, but it still needs a resolvable model. If you use a custom page or nonstandard relation manager, verify the related model and provide an explicit override if needed.locale()-style helper; check the actual source and README for available locale methods.$record, the component's locales(), and the raw field state per locale. In Repeaters, log inside the item schema callback rather than the parent form.How can I help you explore Laravel packages today?