tomatophp/filament-translations-google
A Filament plugin that extends Translation Manager with Google translation crawling to automatically translate your __() and trans() strings. Requires tomatophp/filament-translations; install via Composer and register the plugin in your panel provider.
This package is a plugin for tomatophp/filament-translations, not a standalone translator. It contributes a Google-powered translation action to the Translations Manager UI. If the parent package isn't already working in your panel, nothing here will work.
Prerequisite checklist (do this first):
tomatophp/filament-translations installed, migrations run, and the Translations Manager page reachable in your admin panel.lang/ directory (or wherever your __() / trans() keys live).create ability, so if your policy denies it, the button silently won't render for that user.Then install this add-on:
composer require tomatophp/filament-translations-google
php artisan filament-translations-google:install
Register the plugin on the panel you actually want it on:
// app/Providers/Filament/AdminPanelProvider.php
->plugin(\TomatoPHP\FilamentTranslationsGoogle\FilamentTranslationsGooglePlugin::make())
Publish the stubs so you can read what knobs exist instead of guessing:
php artisan vendor:publish --tag="filament-translations-google-config"
php artisan vendor:publish --tag="filament-translations-google-lang"
Pin your version line explicitly. The v5 line requires Filament v5, Livewire 4, Laravel 12/13, PHP 8.2+, and tomatophp/filament-translations ^5.0. On Filament v4, pin the v4 branch line for both this package and the parent. A loose constraint will resolve a mismatched major and you'll get a runtime failure inside the action rather than a clear composer conflict:
"tomatophp/filament-translations": "^5.0",
"tomatophp/filament-translations-google": "^5.0"
Where to look first, in order of value:
config/filament-translations.php (parent) — locales, scan paths, storage, policy. This is the real configuration surface; the Google package has very little of its own.config/filament-translations-google.php — confirm what locale mapping exists rather than assuming en_GB is accepted.vendor/tomatophp/filament-translations-google/src — the action class is the contract. Documentation is thin, so read how it selects keys, whether it skips keys that already have a value, and how it converts locale codes before hitting the translator.First use case — a deliberately tiny batch. Pick 5–10 keys that include at least one :placeholder and one pluralised string (apples|apple), scan, translate one target locale, then inspect the resulting language file by hand. This validates locale mapping, placeholder preservation, and whether existing values get clobbered — all before you point it at 2,000 keys.
The v5 release resolves the translator from the container. That is the single most useful architectural detail in the package — treat the Google binding as an implementation detail you own, not a given.
// app/Providers/AppServiceProvider.php
use Stichoza\GoogleTranslate\GoogleTranslate;
public function register(): void
{
$this->app->bind(GoogleTranslate::class, function () {
return (new GoogleTranslate())
->setClient(new \GuzzleHttp\Client([
'proxy' => config('services.translate.proxy'),
'timeout' => 15,
]));
});
}
Reasons to bind your own instance:
max_execution_time.Because the translator is container-resolved, CI never needs the network:
$this->app->bind(GoogleTranslate::class, fn () => new class extends GoogleTranslate {
public function translate(string $text): string
{
return "[es] {$text}";
}
});
This also makes seeded demo environments deterministic — you get obviously-fake translations instead of live machine output polluting screenshots or fixtures.
The realistic day-to-day loop for a Filament app:
__('invoices.mark_paid') in a resource.lang/ files in a PR titled something like i18n(es): machine-draft translations.Because steps 1–3 mutate files in the repo, keep the translation output reviewable: one PR per locale, so a reviewer reads lang/es.json as a diff rather than fighting a 400-line noise commit.
The action respects the create ability on the parent package's translation policy. Implement the gate there rather than hiding the page — it gives you a single, testable authorization point that both the UI and any future programmatic invocation share.
// app/Policies/TranslationPolicy.php
public function create(User $user): bool
{
return $user->hasAnyRole(['super_admin', 'translator']);
}
Treat "who can trigger machine translation" as a distinct permission from "who can edit translations" if your team has non-technical ops staff — you want a QA gate between drafts and shipping.
For a large initial backfill, don't click through the UI in one request. The package ships no documented queue or retry workflow, so the safe pattern is to chunk: trigger the action on 50–100 keys at a time, let each request complete, and commit between batches. If you need true background processing, resolve the bound translator yourself and drive it from a queued job that writes to the same language files — you lose the UI but gain retry and rate limiting.
composer test # PEST
composer format # php-cs-fixer
composer analyse # PHPStan
Run these on your own app too, not just when contributing upstream. analyse in particular catches locale arrays that have drifted out of shape after a machine-translation pass.
Stichoza\GoogleTranslate\GoogleTranslate hits an unofficial Google Translate endpoint. There is no SLA, no quota console, no documented rate limit, and no guarantee of ToS compliance for your jurisdiction and volume. Consequences you must plan for:
If any of these are unacceptable, bind a real Google Cloud Translation client instead — the container binding makes that a one-class change.
Laravel uses pt_BR and en_GB; Google wants pt and en. Check the published config for a mapping layer and confirm it covers every locale you configured in the parent. If a code isn't mapped, you'll either get an exception mid-batch or — worse — translations written into the wrong locale's file. Test with your weirdest locale first (the one with a region or script subtag), not with es.
Per the release notes, when a value has no English text the action translates the key itself. That's fine if your keys are English sentences ('Invoice marked as paid') and disastrous if they're dot-notation (invoices.status.mark_paid becomes "facturas.estado.marcar_pagado" in your Spanish file). Decide your key convention before adopting this, or filter those keys out of the batch.
:placeholdersThe release explicitly guarantees :placeholders survive untranslated. It does not promise anything about:
'apple|apples'trans_choice brace syntax: {0} No items|{1} One item{{ $name }}sprintf %s / %d tokensSpot-check every one of these categories on a throwaway locale before trusting a bulk pass. Pluralised strings are the highest-risk class — a mangled | separator produces a runtime InvalidArgumentException from trans_choice, not a bad translation.
Read the action source before your first real run and confirm the behaviour when a key already has a translation in the target locale. If it overwrites, you will silently destroy hand-corrected copy on the next bulk pass. Mitigation either way: commit (or branch) before every run so git diff is your undo, and never run the action in production against a populated lang/ directory without a backup.
The action runs synchronously in the request that fires the Filament action. With a proxy, retries, and 300 keys, you can blow past max_execution_time and leave a half-written language file. Symptoms: the panel spinner never resolves, or the file is valid PHP but only partially translated. Keep batches in the 50–100 range, and if you raise max_execution_time for this, raise it in the FPM pool, not with ini_set.
The action is registered once under Laravel Octane — that's a fix, not a feature, but it means you should not add your own Filament::register… calls in a loop or a service provider that runs per-request (e.g. inside a middleware or a booted callback on a request-scoped singleton). If the action appears twice or not at all after octane:reload, check for a duplicate registration before blaming the package.
If you use php artisan config:cache, your locale list is frozen at cache time — changing locales in the parent config won't be picked up by the action until you re-cache. Similarly, if your app caches compiled translations (lang:cache-style tooling, or a TranslationServiceProvider that preloads files), translated values won't appear in the UI until that cache is rebuilt. Standard symptom: the file on disk is correct, the site still shows English.
There is no built-in logging. When a string comes out wrong, your only signals are (a) the input string, (b) the target locale code the action passed, and (c) the raw response. Add a Guzzle middleware on your bound client that logs all three at debug level, gated behind an env flag. Without that, you're re-running the action and hoping to reproduce, which costs you another round-trip against a rate-limited endpoint.
Six stars, zero dependents, README-and-releases-only documentation. That is not a reason to avoid it, but it is a reason to: read the action class before trusting it, wrap the translator binding so you can swap it, keep the language files in git so you can revert, and be prepared to own the integration if upstream stalls. The MIT license and the small surface area (one plugin, one action, one binding) make that ownership cheap — as long as you don't spread its assumptions across the codebase.
How can I help you explore Laravel packages today?