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

backtik-ch/filament-translatable

Filament plugin for managing translatable Eloquent fields in admin panels. Adds locale-aware form inputs and tabs so you can edit multilingual content alongside your resources, making translation workflows simpler for Filament apps.

View on GitHub
Deep Wiki
Context7

Getting Started

backtik-ch/filament-translatable adds translation-aware form fields and infolist entries to Filament panels, with optional AI-assisted translation. 0.1.0 is the first release, so treat the API as unstable and pin the exact version ("backtik-ch/filament-translatable": "0.1.0") until you have regression coverage.

Minimal setup:

  1. composer require backtik-ch/filament-translatable
  2. Register the panel plugin in your PanelProvider:
use Backtik\FilamentTranslatable\TranslatablePlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        ->plugin(
            TranslatablePlugin::make()
                ->languages(['en' => 'English', 'fr' => 'French', 'de' => 'German'])
                ->sourceLocale('en')
        );
}
  1. Replace a plain field with the translatable one:
use Backtik\FilamentTranslatable\Forms\Components\TranslatableInput;

TranslatableInput::make('title')
    ->required();

Where to look first, in this order:

  • The plugin class (TranslatablePlugin) — it is the single source of truth for languages, source locale, and AI provider/model. Everything else reads from it.
  • TranslatableInput / TranslatableEntry — check the actual chainable method names before writing a lot of code; the release notes describe modes, and the fluent helpers around them (inline(), modal(), lineClamp()) are the parts most likely to shift in 0.2.
  • Your model's storage strategy. The package translates values, it does not change how you persist them — you need a JSON column with a json/array cast or spatie/laravel-translatable.

First use case worth shipping: a single translatable title + slug pair on one Resource form and its infolist, deployed to a panel with exactly two locales. That validates storage, the modal UX, and the AI button in one small surface before you roll it across the app.

Implementation Patterns

Form fields — modal vs inline. Modal is the default and is the right choice when you have 3+ locales or the field is long-form (Textarea, RichEditor): it keeps the form column narrow and avoids a wall of tabs. Switch to inline when the field is short and users edit two locales constantly:

TranslatableInput::make('title')
    ->inline()          // side-by-side instead of a modal trigger
    ->required();

Keep the base field configuration (maxLength, rules, helperText) on the TranslatableInput itself so it applies to every locale, and only reach for per-locale customisation when the content genuinely differs.

Infolist entries. Use TranslatableEntry on view/detail pages instead of raw TextEntry, otherwise you render the raw JSON blob:

TranslatableEntry::make('description')
    ->inline()          // expandable in place
    ->lineClamp(3);     // truncate long copy in the collapsed state

Default (modal) display is better for long rich content where users occasionally need to peek at another locale; inline-expandable is better for short fields users scan across.

AI translation as a draft, not a commit. The single-call "translate all locales" feature is a huge time saver but should be wired as a prefill step: fill the other locales, mark them for human review, then save. Practical implementation:

  • Do not fire it on save(). Trigger it explicitly from the field's action so cost and latency are user-initiated.
  • Keep the source-locale value validated and non-empty before allowing the call — translating a blank field wastes a request and writes junk.
  • For anything user-facing at scale, wrap the call in a queued job and notify when done, rather than blocking the Livewire request for several seconds.

Per-panel configuration. The plugin being panel-scoped is the feature to lean on in multi-panel apps: the admin panel gets ['en','fr'] with the source locale en, while a customer-facing panel might get more locales and a cheaper model. Define a small helper or config file per panel rather than repeating arrays in three PanelProviders.

Consistent field definitions. Once you have five resources with translatable titles, extract the shared setup:

// app/Filament/Support/TranslatableFields.php
public static function title(): TranslatableInput
{
    return TranslatableInput::make('title')
        ->required()
        ->maxLength(255);
}

This is also the seam where you'd swap in your own subclass if you need to override behaviour, so extension stays in one file instead of N resources.

Integration tips.

  • Add the locale list and source locale to a config file and have the plugin read from it, so tests and seeders know the same set of locales.
  • In tests, Livewire::test() against the Resource's create/edit page and assert the JSON payload per locale — cheaper than asserting the modal DOM.
  • If you use spatie/laravel-translatable, its getTranslation()/fallback behaviour and this package's locale list must agree; a mismatch produces "missing" translations that are actually present.

Gotchas and Tips

Storage is your responsibility. The most common first-run failure is pointing TranslatableInput at a string column. You need a json column and an array cast (or a translatable trait). Migrate before you swap the field, and backfill existing rows ({"en": "..."}) or the modal opens empty on legacy records.

Source locale must exist in the language list. Setting sourceLocale('en') while languages() omits en yields confusing behaviour — validation may pass while the AI call has nothing to translate from. Treat the source locale as required configuration and assert it at boot.

Required rules apply per locale, not per field. ->required() on a translatable input typically means every locale is required, which is rarely what you want on day one (writers will block on a language they can't speak). Prefer required-on-source and nullable elsewhere, then enforce completeness in a separate publishing workflow. Verify exactly which semantics required() has in this version before relying on it.

AI translation is a network call inside a Livewire component. Expect: latency spikes on slow providers, rate-limit errors surfacing as raw exceptions in the modal, and cost per click. Catch provider exceptions and surface a Filament notification instead of a 500; log the request/response for debugging; make the button disable while in flight to prevent double-billing on double-click.

Filament 4 & 5 compatibility is declared, but test both. A package supporting two majors will occasionally diverge in schema/action APIs. Pin your Filament version in composer.json and run your form tests against the exact major you deploy — don't assume an upgrade path because the constraint allows it.

Modal state and dehydration. If a translated value looks like it "didn't save", check that the modal's inner fields are dehydrated and that the outer TranslatableInput isn't being filtered out by dehydrated(false) inherited from a parent, and that ->disabled()/->readOnly() is applied on the outer component (applying it per-locale inside the modal often doesn't propagate).

0.1.0 means no upgrade contract. Anything not documented is fair game to change in a minor release. Before adopting broadly: write one focused test per translatable field type you use (text, textarea, rich editor), so a composer update that breaks the API fails in CI rather than in production. Keep a note of the exact fluent methods you depend on.

Debugging checklist, in order: plugin actually registered on this panel → locale array non-empty and contains the source locale → column is JSON with a cast → field name matches the model attribute → clear caches (php artisan filament:optimize-clear, config:clear) after plugin config changes, since panel config is cached in production.

Extension points to reach for before forking: the panel plugin for configuration, per-field fluent overrides for behaviour, and Filament's view publishing/overriding for presentation (modal layout, line-clamp markup). Forking the components should be a last resort at 0.1.0 — you'd be signing up to maintain a fast-moving file.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
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