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 Translatable Field Laravel Package

34ml/filament-translatable-field

A Filament form field for editing translatable model attributes across multiple locales. Add locale-aware inputs to Laravel Filament resources to manage translations alongside other fields, with configurable locales and support for Spatie Laravel Translatable in a clean, native UI.

View on GitHub
Deep Wiki
Context7

Getting Started

What it is: a Filament form component that wraps one or more native Filament fields and renders them once per locale, reading/writing a single JSON-backed translatable attribute on your model. It's a UI layer — it does not handle translation storage semantics itself, so your model still needs an array/json cast or spatie/laravel-translatable.

Version alignment (v3.0 matters here):

Your stack Use
Filament v5 + Laravel 13 ^3.0
Filament v4, or Laravel ≤12 ^2.1

v3.0 is a compatibility release only — two PRs (Filament v5, Laravel 13), no new features or documented deprecations. The major bump signals the Filament v5 API boundary, not new capability. If you're not moving to Filament 5, there is no reason to take this upgrade.

Minimal steps:

composer require 34ml/filament-translatable-field:^3.0
  1. Confirm the model attribute is castable to array:
    protected function casts(): array { return ['title' => 'array']; }
    
    (or add HasTranslations + $translatable = ['title'] if using Spatie.)
  2. Publish/define the locale list — typically a config entry or passed inline via the field's ->locales([...]).
  3. Wrap your first field in the resource form:
    use 34ML\FilamentTranslatableField\Translatable;
    
    Translatable::make('title')
        ->locales(['en', 'ar'])
        ->schema([
            TextInput::make('title')->required(),
        ]),
    

Look first at: the package's config (locale source of truth) and the field class constructor signature — these are the two things that changed shape across major Filament versions, and the release notes don't spell out the diffs.

Best first use case: a resource with exactly one short translatable text field (title). Get create/edit/read working end-to-end before wrapping RichEditor/body or adding slugs — those add validation and table-column complexity you don't want to debug simultaneously.

Implementation Patterns

One wrapper per attribute, multiple fields inside. The wrapper owns the locale loop; put every per-locale input for that attribute in its schema(). Don't create two wrappers for the same attribute name — you'll get Livewire state-key collisions.

Locale source of truth in config. Hardcoding ->locales(['en','ar','fr']) in ten resources drifts. Define locales once (config('app.locales') or a dedicated config key) and pass it in; then adding a locale is one edit, and you can gate it per-panel (->locales(array_keys(Filament::getCurrentPanel()->getLocales()))).

Tabbed vs stacked. Tabs are better for many locales but hide failures — a required() on a hidden tab still validates, and users won't see which tab is failing. For ≤3 locales, stacked sections with visible ->label("$label (".strtoupper($locale).")") reduce support tickets.

Table columns and search. The wrapper is a form component; the table column is still a plain TextColumn on a JSON attribute. Handle it explicitly:

TextColumn::make('title')
    ->getStateUsing(fn ($record) => $record->getTranslation('title', app()->getLocale()))
    ->searchable(query: fn ($q, $s) => $q->where('title->'.app()->getLocale(), 'like', "%{$s}%")),

Decide early whether search hits one locale or all of them — JSON path queries are fine on MySQL/Postgres but the multi-locale orWhere variant needs an index on each path.

Per-locale slug generation. Generate the slug from the default locale value on create, and treat secondary-locale slugs as manually editable. Auto-slugging every locale on every keystroke is a reliable source of overwriting user edits.

Wrap, don't fork. When you need custom behaviour (RTL ->extraInputAttributes(['dir' => 'rtl']), a character counter, translated validation messages), compose around the wrapper or use Filament's field customization API rather than subclassing internals — v3.0 just demonstrated how quickly internals follow Filament majors.

Gotchas and Tips

  • required() semantics. Inside the wrapper, ->required() applies on every locale. Usually what you want is required on the default locale only, soft-required elsewhere — express that with ->required(fn () => $locale === config('app.fallback_locale')) or a Rule::requiredIf. This is the single most common source of "form won't save" bug reports.
  • Hidden tabs still validate. Combined with ->reactive() fields, this can produce validation errors on an invisible tab. Consider ->validationAttribute() per locale so messages read "Title (AR) is required."
  • Casts are the failure point. Attempt to assign property ... on string or an array-to-string conversion on save almost always means the model attribute lacks an array cast, or the value arrived as a plain string from a legacy column. Dump $form->getState() before saving to confirm shape.
  • Upgrading v2 → v3. Expect Filament v5's own breaking changes to surface through this package (method signatures, schema/component API, possibly Livewire 4). Any code you wrote that extends or macros into the wrapper's internals is at risk. Upgrade Filament and this package in the same PR, never separately, and re-run form tests across all locales — including one with a fallback locale and missing translation.
  • Thin release notes. No migration guide was published with v3.0. Read the diff of the two merged PRs against your integration points, and pin exact versions (^3.0 in composer.json is fine; but check the resolved lock for the Filament minor).
  • PHP baseline. Filament v5 pushes the PHP floor up (8.2+). Verify your CI matrix and any shared hosting before adopting — Laravel 13 alone will usually force this anyway.
  • Debugging. ->dehydrated(false) temporarily to inspect raw Livewire state; Translatable fields are ordinary Filament fields, so ->dump() / ->dd() chaining works and is faster than dd() in the callback.
  • Extension points worth knowing: ->locales(), per-field ->rules(), custom ->label(), and Filament's ->afterStateHydrated() / ->dehydrateStateUsing() hooks on the inner fields. Prefer these over editing the package.
  • RTL and layout. For Arabic/Hebrew, set direction at the field or panel level, not with CSS hacks — Filament v5's layout primitives changed and CSS selectors from a v4 panel will silently stop matching.
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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo