pxlrbt/filament-translate-action
Adds a configurable Translate action to Filament resources, letting editors translate translatable model fields between locales from a modal without leaving the admin panel. Integrates with Filament tables and forms for quick multilingual content workflows.
Install and confirm your Filament major first. The v1.2.0 release is the compatibility line for Filament v5; if you are still on Filament v4, you can stay on ^1.0 — the constraint is the only real difference between the branches, so pick the version that matches your panel:
composer require pxlrbt/filament-translate-action
Check composer show pxlrbt/filament-translate-action after install and verify the resolved version (1.2.x for Filament v5). If your filament/filament constraint is still ^4.0, Composer will keep you on 1.x, which is fine but will block a later Filament upgrade if left unpinned.
Where to look first. The package's README is the source of truth for the action API, but the fastest orientation is the published config: publish it (php artisan vendor:publish --tag="filament-translate-action-config", tag name per README) and read the translator/driver section. That file answers the two questions that block first use: which translation backend is used, and which credentials it needs in .env.
First use case — translating a translatable model from a table row. The canonical setup assumes spatie/laravel-translatable (or a JSON-cast attribute map) on the model. Add a row action to the resource table:
use pxlrbt\FilamentTranslateAction\Actions\TranslateAction;
->actions([
TranslateAction::make(),
])
Select a row, pick target locales, and the configured translator writes the translated values into the model's translatable attributes. That single loop — open a record, translate, eyeball the result, save — is the workflow to validate before automating anything.
Then move it to the Edit page. The same action placed in the getHeaderActions() of an EditRecord page is where day-to-day editing actually happens; the table row action is best kept for batch/backfill work.
Custom action with explicit fields and locales. The default instance guesses at translatable attributes. In practice you configure it — target locales, the field list, and the source locale — so the action cannot translate things it should not (IDs, prices, SKUs, enum-backed selects). Build one configured action and reference it from both the table and the edit page so behaviour never diverges.
Two placements, two intents.
->visible() gating so non-editors never see it.Bulk translate with a queue. A bulk action over 50 records × 6 locales × 3 long text fields is dozens of HTTP calls and will hit the PHP execution limit before it finishes. Either restrict bulk to a handful of selected rows, or wrap the translation for each record in a job. The action itself is synchronous — do not assume it queues.
Provider configuration is the real integration surface. The interesting decision is the translator, not the action. Put the credential in .env (DEEPL_API_KEY, Google key, etc. — per the published config), set the driver in config/filament-translate-action.php, and commit the config with a safe default. For multi-tenant or per-team billing, resolve the key at runtime rather than hardcoding it in config.
Gate it like any write action. Translation writes to the model and, depending on setup, may overwrite manual edits. Combine a policy check with a confirmation modal on bulk paths, and require an explicit "overwrite existing" acknowledgement if the action offers one.
Test without burning API credits. In the test suite, fake outbound HTTP (Http::fake()) or swap the driver to a stub via the config binding, and assert on the persisted translated attributes. Never let a feature test reach a real provider — it is slow, costs money, and makes CI flaky. Also assert the action is absent/hidden for users without permission.
Relation managers and nested records. If you translate a child record, register the action on the relation manager's table, not the parent's — the parent's action operates on the parent model and will not touch related rows.
Filament v5 is not a drop-in for hand-written action code. v1.2.0 adds v5 support, but Filament v5 reworked the action/form APIs (schema vs. form namespaces, action class signatures). If you subclassed or wrapped TranslateAction in your own action in v1.0, re-verify your imports and method signatures after the upgrade — your code, not the package, is the likely breakage point. Plain TranslateAction::make() in a table ->actions([...]) is the path least likely to need changes.
Silent no-ops are the most common failure. A missing API key, an empty source-locale value, or a field that is not actually in the translatable set all produce "nothing happened" rather than a loud error. When translation returns nothing, check in this order: credentials present in the running process (config:cache will freeze an old env), source field non-empty, target attribute listed as translatable, provider quota not exhausted.
Locale codes must match the provider, not your app. en vs en_US vs en-GB matter — the provider will reject or mistranslate. Keep a mapping between your app's locale keys and the provider's codes; do not assume config('app.locales') keys are accepted verbatim.
HTML and Markdown get mangled. Translating a WYSIWYG field round-trips tags through a text translator: attributes get reflowed, entities get rewritten, inline code breaks. Exclude rich-text fields from the default field list, translate them with an HTML-aware mode if supported, or translate the plain-text portions and re-render. Always diff a translated rich-text field before trusting it on a page.
Do not translate slugs or other URL-bearing fields. A translated slug silently changes the public URL and breaks inbound links and any route() lookups. Exclude slug fields; generate localised slugs deliberately if you need them.
Overwrites are destructive by design. The action writes into the target locale. On an established site this clobbers hand-polished copy. Standard practice: enable it only for locales still in draft, or rely on the confirm step and take a database snapshot before the first bulk run.
Long text and request limits. Provider APIs cap request size (DeepL's is 128 KiB per request) and the package does not chunk for you. A long description field plus several locales can exceed that; split the field or translate per-field for heavy content.
Cost and rate limits show up as timeouts. Bulk runs hit provider QPS limits and per-character billing. Expect 429s on wide bulk actions; add retry/backoff in your own job wrapper rather than retrying the synchronous action.
Clear config after changing env. Publishing the config, then editing .env without php artisan config:clear, is the classic "my key is set but it says not configured" trap — especially with config:cache in production.
Extension points worth knowing. The configured action can be wrapped in a standard Filament Action to add ->label(), ->icon(), ->color(), ->requiresConfirmation(), ->visible(), and ->authorize() without touching package code; that is the intended customisation route. Anything deeper — a different translator backend, custom field handling — belongs in your own service invoked from the action rather than a fork.
How can I help you explore Laravel packages today?