PHPackages                             syriable/filament-translator - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Localization &amp; i18n](/categories/localization)
4. /
5. syriable/filament-translator

ActiveLibrary[Localization &amp; i18n](/categories/localization)

syriable/filament-translator
============================

Convention-based automatic translations for Filament forms, tables, actions, and pages.

1.1.2(1mo ago)0796↑40%[1 PRs](https://github.com/syriable/filament-translator/pulls)1MITPHPPHP ^8.3CI passing

Since Jun 3Pushed 1mo agoCompare

[ Source](https://github.com/syriable/filament-translator)[ Packagist](https://packagist.org/packages/syriable/filament-translator)[ Docs](https://github.com/syriable/filament-translator)[ RSS](/packages/syriable-filament-translator/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (5)Dependencies (20)Versions (8)Used By (1)

[![Syriable Filament Translator](art/header-img.svg)](art/header-img.svg)

Syriable Filament Translator
============================

[](#syriable-filament-translator)

[![Latest Version on Packagist](https://camo.githubusercontent.com/2e01b7727a6f81aa7af79b89df193b48b2cc7a80638a2ec47212ec3fcf657dd0/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7379726961626c652f66696c616d656e742d7472616e736c61746f722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/syriable/filament-translator)[![GitHub Tests Action Status](https://github.com/syriable/filament-translator/actions/workflows/run-tests.yml/badge.svg)](https://github.com/syriable/filament-translator/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://github.com/syriable/filament-translator/actions/workflows/fix-php-code-style-issues.yml/badge.svg)](https://github.com/syriable/filament-translator/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)[![Software License](https://camo.githubusercontent.com/55c0218c8f8009f06ad4ddae837ddd05301481fcf0dff8e0ed9dadda8780713e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265)](LICENSE.md)

Convention-based automatic translations for [Filament](https://filamentphp.com) panels — forms, tables, actions, infolists, resources, pages, widgets, importers, and exporters.

**Syriable Filament Translator** derives translation keys from your PHP class names and Filament component names so UI code stays free of hard-coded copy. The package registers lazy label resolvers at boot; when a lang entry is missing, Filament’s default label is preserved.

Features
--------

[](#features)

- **Convention-based labels** — keys are derived from the owner class and component name; no hard-coded copy. ([what gets translated](#what-gets-translated))
- **Broad coverage** — forms, infolists, tables, columns, filters, summarizers, grouping, actions, importers/exporters, plus page/resource/cluster/widget metadata.
- **Path aliases** — map namespaces outside Filament’s defaults, e.g. `App\Livewire` → `livewire`. ([docs](#path-aliases))
- **Component macros** — override or pin a component’s key, including absolute keys. ([docs](#component-macros))
- **Automatic key creation** — scaffold missing required keys into your lang files as you browse, during local development. ([docs](#automatic-key-creation-local-development))
- **Configurable required attributes** — choose which attributes must be translated via config. ([docs](#configuration))
- **Custom schema components** — register your own components for convention resolution; Filament’s `Text` is supported out of the box. ([docs](#custom-schema-components))
- **Hint-icon tooltips** — translate `hintIconTooltip` via the `hint_icon_tooltip` key. ([docs](#hint-icon-tooltips))
- **Translatable base classes &amp; traits** — drop-in bases/traits for pages, resources, clusters, widgets, importers, and more. ([docs](#base-classes))
- **Graceful fallback** — any key you omit falls back to Filament’s native label.

Requirements
------------

[](#requirements)

- PHP 8.3+
- Laravel 11, 12, or 13
- Filament 4 or 5

Installation
------------

[](#installation)

You can install the package via Composer:

```
composer require syriable/filament-translator
```

Register the plugin on your Filament panel:

```
use Filament\Panel;
use Syriable\Filament\Plugins\Translator\TranslatorPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        ->plugins([
            TranslatorPlugin::make()
                ->pathAliases([
                    'App\\Livewire' => 'livewire',
                ]),
        ]);
}
```

The `TranslatorServiceProvider` is auto-discovered and registers `conventionKey()` macros on Filament schema components.

Usage
-----

[](#usage)

### Panel plugin

[](#panel-plugin)

`TranslatorPlugin` boots the convention registry when the panel starts. Register it on every panel that should resolve labels automatically.

### Path aliases

[](#path-aliases)

Map namespace fragments to lang file paths when your classes live outside Filament’s default directory structure:

```
TranslatorPlugin::make()
    ->pathAliases([
        'App\\Livewire' => 'livewire',
    ]);

// Replace all aliases instead of merging:
TranslatorPlugin::make()
    ->pathAliases(['App\\Livewire' => 'livewire'], merge: false);
```

### Automatic key creation (local development)

[](#automatic-key-creation-local-development)

Hand-writing every lang key while building an interface is tedious. Enable `createMissingTranslationKeys()` and the package scaffolds any **missing required label** into the correct lang file as you load pages — creating the file, the nested array path, and a humanised default value — so you’re left with a ready-to-translate stub instead of a raw key on screen.

```
TranslatorPlugin::make()
    ->createMissingTranslationKeys();
```

Requesting `livewire/auth/login.form.components.actions.forgot-password.label`, for example, writes `lang/{locale}/livewire/auth/login.php`:

```
return [
    'form' => [
        'components' => [
            'actions' => [
                'forgot-password' => [
                    'label' => 'Forgot Password',
                ],
            ],
        ],
    ],
];
```

- **Local only.** Writes are always skipped in production regardless of the flag — lang files are never written on live requests.
- **Required labels only.** Optional attributes (placeholder, helper text, tooltip, …) are skipped to keep lang files lean, and existing values are never overwritten.

Customise the seeded value, or gate activation behind a condition, with the method arguments:

```
// Seed every new key with an empty string instead of a humanised guess:
TranslatorPlugin::make()->createMissingTranslationKeys(using: fn (string $key) => '');

// Enable only in the local environment:
TranslatorPlugin::make()->createMissingTranslationKeys(fn () => app()->isLocal());
```

### Plugin helpers

[](#plugin-helpers)

```
TranslatorPlugin::get();      // plugin instance on the current panel
TranslatorPlugin::isActive(); // whether the plugin is registered
```

### Standalone Livewire pages

[](#standalone-livewire-pages)

Filament schemas on guest routes still need the registry booted once:

```
use Syriable\Filament\Plugins\Translator\ConventionRegistry;

app(ConventionRegistry::class)->registerDefaults();
```

Register `TranslatorPlugin` on a panel with `pathAliases()` when you need namespace remapping; aliases are read from the active plugin during label resolution. When the plugin is not registered on the active panel, resolution falls back to empty path aliases instead of throwing, so guest routes keep working.

#### Boot behaviour &amp; double `registerDefaults()`

[](#boot-behaviour--double-registerdefaults)

`TranslatorPlugin::boot()` calls `registerDefaults()` automatically for every panel the plugin is registered on, so **panel pages need no manual boot**. Standalone Livewire components rendered *outside* a panel (guest/auth routes such as `/login`) are not covered by panel boot, so the host app must call `registerDefaults()` itself — typically once from a service provider:

```
// app/Providers/AppServiceProvider.php
public function boot(): void
{
    app(ConventionRegistry::class)->registerDefaults();
}
```

It is safe to use **both** patterns at once (panel plugin + manual boot). `registerDefaults()` is **idempotent**: it tracks the active Filament component manager and a signature of the current configuration, so repeated calls within the same request — and repeated worker boots under [Laravel Octane](https://laravel.com/docs/octane) — skip re-wiring instead of stacking duplicate `configureUsing` hooks. Re-registration only happens when the component manager instance changes (a fresh app) or when the relevant configuration (`required`, custom `components`, path aliases) changes.

> **Boot cost trade-off.** Wiring is registered **eagerly** for all supported component families in a single pass rather than lazily per component type. The pass is cheap (it only registers `configureUsing` closures; no resolution happens until a component is rendered) and the idempotency guard above ensures it runs at most once per app instance. Eager-but-idempotent wiring keeps the boot path simple and predictable; lazy per-type registration is intentionally **not** used because its complexity is not justified once duplicate boots are already elided.

#### Path aliases without an active panel

[](#path-aliases-without-an-active-panel)

Path aliases live on the `TranslatorPlugin` instance and are read from the **active panel** during resolution. On a standalone Livewire route there may be no panel plugin active, in which case `TranslatorPlugin::get()` returns a plugin with **empty path aliases** and resolution falls back to the default `Filament`-prefixed namespace derivation (it never throws). If a guest route needs custom namespace remapping, register `TranslatorPlugin` (with your `pathAliases()`) on the default panel so the aliases are available even when that panel is not the one rendering the page.

ScenarioPath aliasesBoot requirementPanel page (plugin registered)From the active panelAutomatic via `TranslatorPlugin::boot()`Standalone Livewire, plugin on any panelFrom that panel's pluginManual `registerDefaults()` in a providerStandalone Livewire, no plugin anywhereEmpty (default derivation)Manual `registerDefaults()` in a providerConfiguration
-------------

[](#configuration)

By default only the primary attributes (`label`, section `heading`, placeholder `content`, model labels, …) are **required** — a missing translation surfaces the convention key — while attributes such as `placeholder`, `tooltip`, `helperText`, and `hint` are optional and fall back to `null`.

Publish the config file to change which attributes are required:

```
php artisan vendor:publish --tag="filament-translator-config"
```

`config/filament-translator.php` exposes a `required` map keyed by attribute (method) name. `true` makes the attribute required, `false` makes it optional; anything not listed keeps the default. Overrides apply across every context where the attribute appears (forms, tables, columns, filters, actions, summarizers):

```
return [
    'required' => [
        'placeholder' => true, // require placeholders everywhere
        'tooltip' => true,     // require tooltips
        'label' => false,      // make labels optional
    ],
];
```

Required attributes also participate in [automatic key creation](#automatic-key-creation-local-development) when that feature is enabled.

### Custom schema components

[](#custom-schema-components)

Register your own schema components (extending `Filament\Schemas\Components\Component`) so their attributes resolve through the same convention pipeline as first-party fields. Map each component class to an `attribute => allowNull` list (`false` = required, `true` = optional), in the same shape as the built-in attribute maps:

```
// config/filament-translator.php
'components' => [
    \App\Filament\Schemas\Components\Separator::class => [
        'text' => false,
    ],
],
```

```
// Used unchanged in a form…
Separator::make('or'),

// …resolved from lang/{locale}/livewire/auth/login.php:
'form' => ['components' => ['or' => ['text' => 'Or']]],
```

The `required` overrides above apply to registered attributes too, and they participate in automatic key creation when enabled.

Non-schema components are supported as well — table columns (extending `Filament\Tables\Columns\Column`), filters, summarizers, and actions are classified from the class hierarchy and wired with the matching resolver. A bare class-string entry (no attribute map) adopts that category's default attributes:

```
'components' => [
    // Adopts the column default map (label, tooltip, description, …):
    \App\Filament\Tables\Columns\ProgressColumn::class,

    // Or pin specific attributes:
    \App\Filament\Tables\Filters\StatusFilter::class => [
        'label' => false,
    ],
],
```

Filament’s first-party `Filament\Schemas\Components\Text` (static schema copy) is supported out of the box. Address it via `key()` so the content resolves from lang; explicit content stays literal:

```
Text::make(null)->key('or'); // resolves {namespace}.form.components.or.content
Text::make('Or');            // literal — no lang lookup
```

### Hint-icon tooltips

[](#hint-icon-tooltips)

Hint-icon tooltips are translated from the `hint_icon_tooltip` key. Set the icon in PHP with a **single argument** and put the copy in lang:

```
TextInput::make('password')
    ->hintIcon('heroicon-o-question-mark-circle'); // one argument — tooltip comes from lang
```

```
'form' => ['components' => ['password' => ['hint_icon_tooltip' => 'Minimum 8 characters.']]],
```

Passing a second argument — `->hintIcon($icon, $tooltip)` — sets the tooltip explicitly and skips the translation. This needs a Filament version that guards `hintIcon()` with `func_num_args()`; older releases clear the tooltip even with a single argument.

#### Filament version compatibility

[](#filament-version-compatibility)

Single-argument `->hintIcon($icon)` tooltip preservation depends on Filament guarding `hintIcon()`with `func_num_args()` so a single call does not null out a previously wired tooltip:

FilamentSingle-arg `hintIcon()` preserves the wired tooltip4.x / 5.x (with the guard)YesOlder releases without the guardNo — the tooltip is cleared even with one argumentThe package does not hard-pin this behaviour; instead the suite probes it at runtime via `preservesSingleArgHintIconTooltip()` (`tests/Feature/SchemaResolverTest.php`) and skips the relevant assertion when the running Filament version lacks the guard. Mirror that probe in your own CI if you support a wide Filament range, and watch this section's matrix when upgrading.

### Infolist scope (reserved API)

[](#infolist-scope-reserved-api)

The `Enums\InfolistScope` enum is a **reserved/future API placeholder**. Infolist entries are fully translated today, but they resolve through the shared schema pipeline under the `form` / `infolist` context rather than a dedicated infolist scope — so unlike `ActionScope`, `SchemaScope`, and `TableScope`, `InfolistScope` is intentionally **not** wired into a resolution path yet. It is kept as a stable marker for a possible infolist-specific scope; treat it as reserved and do not depend on additional cases until that work ships.

What gets translated
--------------------

[](#what-gets-translated)

`ConventionRegistry` wires lazy resolvers through Filament’s `configureUsing` hooks. Missing translations fall back to Filament’s native labels.

AreaTranslated attributes**Actions**Label, tooltip, badge, modal heading/description, submit/cancel labels, success/failure notification titles**Forms &amp; infolists**Field labels, placeholders, helper text, hints, hint-icon tooltips, prefixes/suffixes, validation attributes, section headings/descriptions, tabs, wizard steps, `Text` content, repeater action labels, select create/edit modal headings, loading messages**Tables**Search placeholder, model labels, heading/description, default sort label, empty state heading/description, actions column label**Columns**Label, description, tooltip, prefix/suffix, placeholder, default value, validation attribute**Filters**Label, indicator, placeholder, true/false labels, constraint labels**Summarizers &amp; grouping**Label, prefix, suffix, grouping labels**Importers &amp; exporters**Column and action labels> **Monitored column types.** Automatic column-label resolution is wired for `TextColumn`, `IconColumn`, `ColorColumn`, `ToggleColumn`, and `SelectColumn`. Custom or other column types are not translated automatically — set their key explicitly with the [`conventionKey()` macro](#component-macros).

Static metadata on pages, resources, clusters, widgets, relation managers, and resource pages is resolved through the `Resolves*` traits (see below).

Translation key convention
--------------------------

[](#translation-key-convention)

```
{owner-path}.{context}.{component-name}.{attribute}

```

Examples:

UI sourceLang key`UserResource` form field `name``filament/resources/user-resource.form.name.label`Login page action `login``livewire/auth/login.actions.login.label`Field inside a tab`livewire/auth/login.form.components.tabs.tab.{tab}.schema.{field}.label`Page title`livewire/auth/login.title`Relation manager table`filament/resources/user-resource.relation_managers.posts.table.heading`Place strings under `lang/{locale}/` using nested arrays or dot keys.

### Example lang file

[](#example-lang-file)

For a `UserResource` form with `name` and `email` fields, create `lang/en/filament/resources/user-resource.php`:

```
