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

wotz/filament-translatable-tabs

Filament Translatable Tabs adds locale tabs to Filament forms, letting you edit translatable fields for each language in a clean tabbed interface. Simplify multilingual content management in Laravel admin panels.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Confirm the dependency stack first. The package sits on top of spatie/laravel-translatable and Filament's form Tabs component. Before installing, verify:

    • Filament version (v3 vs v4 — the form/table API differs materially, and since v3.1.0 the shipped CSS is compiled for Tailwind v4, which aligns with Filament v4; on Filament v3/Tailwind v3 you'll likely need to build the plugin's CSS through your own theme rather than relying on the prebuilt file).
    • spatie/laravel-translatable is installed and your model uses HasTranslations.
    • Your model declares public array $translatable = ['title', 'body'].
  2. Install and publish.

    composer require wotz/filament-translatable-tabs
    php artisan vendor:publish --tag="filament-translatable-tabs-config"
    

    Read the published config before touching anything else — with a small package like this, the config file and the service provider are the real documentation.

  3. Wire up the styling. v3.1.0 renamed/migrated plugin.css to Tailwind v4. Add the package's view path to your panel theme so the locale-tab CSS is actually compiled into your bundle:

    /* resources/css/filament/admin/theme.css */
    @import '/../../../../vendor/wotz/filament-translatable-tabs/resources/css/plugin.css';
    

    Then register the theme on the panel with ->viteTheme('resources/css/filament/admin/theme.css'). If you skip this on Filament v4 you may get unstyled tab rails/badges; on Filament v3 the v4-compiled CSS may not apply at all.

  4. Register the plugin (if the package ships one). In your PanelProvider:

    ->plugin(TranslatableTabsPlugin::make())
    

    If there's no plugin class, skip this — the package is likely just a set of form/table components or a trait.

  5. First use case. Take a Post resource with translatable title and body. In PostResource::form():

    use Wotz\FilamentTranslatableTabs\Forms\Components\TranslatableTabs;
    
    public static function form(Form $form): Form
    {
        return $form->schema([
            TranslatableTabs::make()
                ->schema(fn (string $locale) => [
                    TextInput::make("title.{$locale}")
                        ->label('Title')
                        ->required($locale === config('app.fallback_locale')),
                    RichEditor::make("body.{$locale}")
                        ->label('Body'),
                ]),
        ]);
    }
    

    The exact factory signature is the thing to check first in src/ — the callback-per-locale pattern is the common shape, but confirm whether it's ->schema(), ->tabs(), or a ->locales() chain.

  6. Where to look first, in order: README.md → config/filament-translatable-tabs.php → the main form component class → resources/views/ (tab labels and locale badges live here) → resources/css/plugin.css → tests/. With one star and zero dependents, the test suite (if present) is the most reliable spec you'll get.

Implementation Patterns

Per-locale field builder. The dominant pattern is a single closure that receives a locale and returns a schema array. Keep the closure pure and derive labels from the locale code rather than hardcoding:

TranslatableTabs::make()->schema(fn (string $locale) => [
    TextInput::make("title.{$locale}")
        ->label(__('fields.title').' ('.strtoupper($locale).')'),
])

Lean on the built-in locale badges. Since v3.1.0 the locale badges are named, meaning tab headers render a readable locale label rather than a raw code. Don't reimplement a tab label yourself before checking whether ->locales() / the badge label helper already gives you what you want — and if you do override the Blade view, expect to re-apply v3.1.0's badge markup.

Extract to a shared method. If you have more than two translatable resources, put the tab block in a trait or static helper so locale handling stays consistent:

trait HasTranslatableContentTabs
{
    protected static function contentTabs(): array
    {
        return [TranslatableTabs::make()->schema(/* ... */)];
    }
}

Mix with non-translatable tabs. Wrap the package output inside a standard Filament Tabs when you need a "Settings" or "SEO" tab alongside the locale tabs:

Tabs::make()->tabs([
    Tab::make('Content')->schema([TranslatableTabs::make()->schema(...)]),
    Tab::make('Settings')->schema([Toggle::make('published')]),
])

Table columns. Reuse the same locale list for display. Most implementations read app()->getLocale(); for an admin table you usually want the fallback locale explicitly, or a ->formatStateUsing() that shows the fallback when the current locale is empty:

TextColumn::make('title')
    ->formatStateUsing(fn ($state, $record) => $record->getTranslation('title', app()->getLocale())
        ?: $record->getTranslation('title', config('app.fallback_locale')))

Locale sources. Decide once whether locales come from config('app.locales'), the package config, or a DB table — then pass them explicitly (->locales([...])) rather than relying on the package default. Explicit wins in multi-panel apps.

Integration with uploads. For translatable images, keep the file column non-translatable and store per-locale variants in a separate relation or JSON column; translatable file uploads inside tabs tend to fight with Filament's FileUpload state path and cleanup.

Gotchas and Tips

  • Tailwind v4 CSS is the v3.1.0 headline. Nothing in the PHP API changed between v3.0.0 and v3.1.0 — the release is a stylesheet migration plus badge naming. That means an upgrade shouldn't break behaviour, but it can break appearance: v4 CSS layered onto a v3/Tailwind-v3 panel may silently not apply, and a v3-compiled theme may not style the new badge markup. After upgrading, load the add/edit form for one resource and eyeball the tab rail before rolling out.
  • Build the CSS yourself if you use a custom theme. Filament panels with a custom viteTheme do not auto-load package CSS. Import plugin.css into your theme entrypoint (see Getting Started) and keep it imported across upgrades — a Filament theme rebuild after upgrading to v3.1.0 is the common "the badges disappeared" fix.
  • Dot-notation state paths are the whole trick. Fields inside the tabs must be named title.{locale} so Spatie's JSON column hydration works. If a field renders but saves nothing, the state path is almost always the culprit — check ->getStatePath() in a dd() inside the schema closure.
  • Validation duplication. Every locale tab duplicates required rules. Gate ->required() on the fallback locale only, otherwise editors cannot save partial translations. Same for ->maxLength() if you want per-locale limits.
  • Hidden tabs still dehydrate. Filament saves state from inactive tabs. Combined with per-locale fields this is usually desirable, but watch for ->visible() conditions that were meant to prevent saving.
  • Reactive fields across tabs break. ->live() / ->afterStateUpdated() dependencies between a field in the en tab and one in the fr tab won't re-render, because only the active tab is mounted. Keep cross-field reactivity within a single locale.
  • Sorting and searching JSON columns. Table sorting on title sorts the raw JSON string. Use a json_extract-based ->sortable(query: ...) or a generated/virtual column, otherwise ordering is meaningless.
  • Filament version drift. With a low-star package, the Filament constraint in composer.json may be loose (e.g. ^3.0) while the code — now including its CSS — targets a newer major. Pin Filament and this package ("wotz/filament-translatable-tabs": "3.1.0") in your own composer.json, and run the form in isolation before rolling out to every resource.
  • Cache clearing. After publishing config, changing your theme, or upgrading, run php artisan filament:clear-cached-components, php artisan view:clear and php artisan optimize:clear. Form component and view caching often masks config and CSS changes.
  • Debugging tip. Temporarily swap the component for a plain Tabs with Tab::make($locale)->schema([...]) written by hand. If that works and the package doesn't, the issue is the locale/state-path wiring, not your fields. If it renders but looks wrong, the issue is CSS, not PHP.
  • Extension points to look for in src/: a ->locales() setter, a ->fallbackLocale() setter, the Blade view (if the tabs render via a custom view, override it with ->view('vendor/...') — but note the badge markup changed in v3.1.0, so re-check your override against the new view), and any configureUsing() static. These are the seams you'll need if the default tab labels or ordering don't match your panel.
  • Maintenance risk. One star, zero dependents, and a release history that is thin mean you should assume you own the code. Fork it into packages/ with a path repository rather than depending on Packagist for anything production-critical, and add a smoke test that renders the form in each configured locale.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings