statikbe/laravel-filament-chained-translation-manager
A Filament plugin for Laravel that adds a translation manager to browse and edit your app's language files right in the admin panel. It supports chained machine translation via Google, DeepL, OpenAI and more, making it easy to keep every locale up to date.
Install the plugin alongside its underlying chained translator, publish what it needs, and migrate:
composer require statikbe/laravel-filament-chained-translation-manager
# publish config + migrations (pick the package's provider/tag from the prompt)
php artisan vendor:publish
php artisan migrate
The migrations create the language_lines table (the database translation store from spatie/laravel-translation-loader, which the chained translator layers on top of Laravel's file loader).
Register the plugin in the panel you want editors to use:
use Statikbe\FilamentChainedTranslationManager\ChainedTranslationManagerPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->id('admin')
// ...
->plugin(ChainedTranslationManagerPlugin::make());
}
First place to look: the published config file (typically config/filament-chained-translation-manager.php). That is where navigation group/label, the list of locales shown in the UI, and excluded translation groups live. After registering, log in and open the Translations page in the sidebar.
First real use case: a marketer fixes a typo in a Dutch CTA string and it goes live immediately — no PR, no deploy — while lang/nl/*.php stays the committed source of truth for developers.
File-first, DB-override. Keep keys defined in lang/ and let the manager write runtime overrides to the database. The chain resolves DB → files → fallback locale, so a missing DB row costs nothing and a DB row silently wins. This is the intended workflow: developers ship keys, editors ship copy.
Locale parity. The locale list in the plugin config must match what your app actually resolves (app()->getLocale(), your config/app.php locales, and Filament's panel locales). Add a locale in four places or don't add it at all — mismatched codes produce empty columns in the UI and untranslated pages in the app.
Access control. This is a write-capable admin tool that mutates live copy. Gate it at the panel level (canAccessPanel() on your User model) or through the plugin's authorization hook if your version exposes one. Don't rely on "it's hidden in the nav" — URLs are guessable.
Environment parity. DB translations don't travel with your code. Write a small artisan command that exports language_lines to JSON/YAML, commit it, and seed it on staging/production. Otherwise every environment has different copy and bug reports become unreproducible.
Bulk file → DB import. New keys don't appear in the manager until they exist in a lang file (or are inserted directly). Run php artisan list | grep -i translation to see whether an importer ships with your version; if not, a 30-line command over FileLoader + the Translation model is the standard workaround, and pairs nicely with a CI job.
Reverse sync for review. Because DB rows shadow files, "the translation lives in git" requires an export step. Export changed rows back into lang/*.php, commit, then delete those rows so the file regains authority. Do this before big refactors.
Per-panel scoping. Register the plugin only on the admin panel. For multi-brand/multi-tenant setups, subclass the page or use separate databases — there's no built-in per-tenant translation scoping.
language_lines first, then the file, then the cache.php artisan optimize:clear after any bulk change.__('checkout.recap') will never surface as "missing." Add the key to the file (or use a key-extraction tool) before expecting editors to find it.:name vs :Name, missing | separators, wrong {0}/[1,*] ranges — all render as literal text rather than throwing. Add a test asserting key translations with placeholders render correctly.(locale, group, key) can exceed limits on older MySQL/utf8mb4 setups. Use MySQL 8, or set Schema::defaultStringLength(191). Keys longer than 255 chars won't fit either — don't use sentences as keys.validation, pagination, auth, passwords, and filament:: namespaces are package-owned. Hide them in the config so editors don't "fix" strings that the next composer update overwrites.{!! !!}, you've given them a scripting surface. Prefer escaped output; sanitize where you can't.updated_by column. Add model events on the Translation model if compliance or "who broke the homepage" matters.migrate:fresh wipes editor work. Backup or seed language_lines in every non-production reset.statikbe/* in composer.json and upgrade deliberately.Translation model in config, and use excluded_groups for scoping. Everything else (navigation, labels) is config-driven — check the published config before subclassing.How can I help you explore Laravel packages today?