jayesh/laravel-gemini-translator
Interactive Artisan command to scan Laravel projects for translation keys, translate them via Google Gemini AI, and generate language files. Supports Blade/PHP/JS/Vue/TS, concurrency, safe atomic writes, and Laravel Modules integration with skip/refresh modes.
An interactive Artisan command that scans your Laravel project for translation keys, translates them using Google's Gemini AI, and generates the necessary language files with advanced safety and performance features.
AI translations are a best-effort draft. The package asks Gemini for the language you selected and rejects some mixed-script or leftover-English output, but it cannot guarantee a perfect, literal, or single-language result. Gemini can still mix writing systems, slip in another language, or return wording that is not what you expected (for example a Gujarati run that also contains Hindi, Kannada, or English). Review generated files before you ship them. In the Translation Manager, turn on Highlight script faults. Re-run
--refreshor--refresh-cleanfor bad rows.
/translations-manager to browse, edit, save, scan, and add languages. Optional Highlight script faults checkbox marks cells whose script does not match the localelang/app3/ and lang/web/ are handled separately from lang/ in both the CLI and the managerkernel32 menus + Symfony Process workers on Windowspcntl), Process driver, and a sequential sync fallback--skip-existing), refresh from file wording (--refresh), clean refresh from keys only (--refresh-clean)--model, GEMINI_MODEL / GEMINI_TRANSLATOR_MODEL, or config (default gemini-3.5-flash-lite)nwidart/laravel-modules with consolidation optionspcntl extension (for the fork driver on Linux/macOS)ffi extension (optional; native Windows menus via kernel32.dll)tokenizer PHP extension (for proper code parsing)composer require jayesh/laravel-gemini-translator
php artisan vendor:publish --tag=gemini-translator-config
The package registers the UI and JSON APIs for you. Open:
/translations-manager
Example: http://127.0.0.1:8000/translations-manager
Auth is automatic:
Optional publish (only if you want to edit the Blade or serve CSS/JS as files):
php artisan vendor:publish --tag=gemini-translator-manager
That copies views to resources/views/vendor/gemini-translator/ and CSS/JS to public/vendor/gemini-translator/. After publishing assets, pass $assetCss / $assetJs to use those files instead of the inlined CSS/JS.
If you already published and then upgrade the package, Laravel keeps your old copies. Overwrite them or remove them so the package files load again:
php artisan vendor:publish --tag=gemini-translator-manager --force
php artisan view:clear
# Or delete published views (there is no artisan unpublish)
rm -rf resources/views/vendor/gemini-translator
php artisan view:clear
--force on --tag=gemini-translator-config overwrites your local config edits. PHP from the package is never published; composer update always uses the new code.
Change the URL or turn the UI off in config/gemini-translator.php (publish with --tag=gemini-translator-config):
'manager' => [
'enabled' => true,
'prefix' => 'translations-manager',
'middleware' => ['web'],
],
Or .env:
GEMINI_TRANSLATOR_MANAGER=true
GEMINI_TRANSLATOR_MANAGER_PREFIX=translations-manager
GEMINI_TRANSLATOR_MANAGER_MIDDLEWARE=web
To drop the workspace into an existing layout:
@include('gemini-translator::partials.workspace')
The include picks up the registered /translations-manager/* endpoints automatically.
The table reads every lang tree Laravel knows about: lang/, Modules/*/lang, published resources/lang/modules/{name}, and any extra directory passed to loadJsonTranslationsFrom() / loadTranslationsFrom(). If a module (or the app) registers more than one folder — for example lang/, lang/app3/, and lang/web/ — each pack stays a separate set of keys. Select Module, then Pack, to work on one folder at a time.
Highlight script faults (checkbox next to “Show only missing”, off until you turn it on) uses the same writing-system map as the CLI. A Gujarati cell with Devanagari or Latin letters, or an English cell with Indic script, gets an amber outline and a tooltip of the unexpected scripts. Laravel placeholders (:name, {0}, [2,*]) are ignored. The choice is stored in localStorage as gemini-translator-malform-detector. If you published the manager views/assets, republish --tag=gemini-translator-manager to pick this up.
The Artisan command uses the same pack step: after you pick a module that has extra lang folders, it asks which packs to process (lang/, lang/app3/, lang/web/). Then it lists JSON and PHP files from those packs only. Modules with a single lang/ tree skip that prompt.
Add to your .env:
GEMINI_API_KEY="YOUR_GEMINI_API_KEY"
GEMINI_REQUEST_TIMEOUT=600
# Optional — any Gemini model id (free or paid). Default: gemini-3.5-flash-lite
GEMINI_MODEL="gemini-2.5-pro"
# or: GEMINI_TRANSLATOR_MODEL="gemini-3.5-flash"
Or one run at a time:
php artisan translations:extract-and-generate --model=gemini-2.5-pro --langs=en,hi
Precedence: --model → GEMINI_TRANSLATOR_MODEL / config/gemini-translator.php → config('gemini.model') → package default. Paid models are allowed; they are not free-tier RPM-capped unless you add them to the snapshot.
Get your API key from Google AI Studio.
⚠️ IMPORTANT: If you published the config/gemini.php file from the google-gemini-php/laravel package, make sure the request_timeout is cast to an integer:
// ✅ CORRECT
'request_timeout' => (int) env('GEMINI_REQUEST_TIMEOUT', 600),
// ❌ WRONG - Will cause "Configuration value must be an integer" error
'request_timeout' => env('GEMINI_REQUEST_TIMEOUT', 600),
# Linux/macOS (fastest with configurable concurrency)
php artisan translations:extract-and-generate --driver=fork --concurrency=10
# Windows (parallel via Symfony Process)
php artisan translations:extract-and-generate --driver=fork --concurrency=10
# Sequential (any OS)
php artisan translations:extract-and-generate --driver=sync
# Preview changes without writing files
php artisan translations:extract-and-generate --dry-run
# Refresh only existing translations (uses current file wording as source)
php artisan translations:extract-and-generate --refresh
# Clean-refresh existing keys from the key shape only (ignores stale/faulty file text)
php artisan translations:extract-and-generate --refresh-clean
# Add only missing translations (recommended for updates)
php artisan translations:extract-and-generate --skip-existing
For detailed documentation, step-by-step guides, and advanced usage examples, visit our comprehensive documentation:
# Custom languages (English is always used as source)
php artisan translations:extract-and-generate --langs=en,es,fr,de
# Skip existing translations (translate only missing keys)
php artisan translations:extract-and-generate --skip-existing
# Refresh existing translations (re-translate existing keys only)
php artisan translations:extract-and-generate --refresh
# Preview without writing files
php artisan translations:extract-and-generate --dry-run
# Custom chunk size for API requests
php artisan translations:extract-and-generate --chunk-size=50
# Custom concurrency (when using fork driver)
php artisan translations:extract-and-generate --concurrency=20
# Exclude directories
php artisan translations:extract-and-generate --exclude=vendor,node_modules
# Custom target directory
php artisan translations:extract-and-generate --target-dir=custom-lang
# Provide project context for better translations
php artisan translations:extract-and-generate --context="E-commerce platform with payment features"
# Concurrency driver (default, fork, process, sync)
php artisan translations:extract-and-generate --driver=fork
php artisan translations:extract-and-generate --driver=process
# Retry settings
php artisan translations:extract-and-generate --max-retries=3 --retry-delay=5
# Custom extensions
php artisan translations:extract-and-generate --extensions=php,blade.php,vue,js,ts,json
# Consolidate module translations
php artisan translations:extract-and-generate --consolidate-modules
# Get help
php artisan help translations:extract-and-generate
--refresh, --refresh-clean, and --skip-existing are mutually exclusive (the command will fail if more than one is used)--dry-run works with all other options to preview changes--concurrency affects the fork and process driverslang/
├── en/
│ ├── auth.php
│ ├── pagination.php
│ ├── passwords.php
│ └── validation.php
├── es/
│ ├── auth.php
│ ├── pagination.php
│ ├── passwords.php
│ └── validation.php
├── en.json
├── es.json
└── fr.json
.blade.php.php.vue, .js, .jsx, .ts, .tsx.json__(), trans(), trans_choice(), @lang(), @choice()Lang::get(), Lang::choice(), Lang::has()$t(), i18n.t()v-t, x-text, :v-t, :x-text, v-bind:v-t, v-bind:x-textSupports all quote types: single ('), double ("), and backtick (`).
en-US to en_US)LocaleHelper), including script tags such as pa-Arab, ms-latn, zgh-tfngLocaleHelper::malformReasons() (stricter than CLI: Latin is a fault in native-script locales)spatie/fork (pcntl)PHP_OS_FAMILY selects Laravel Prompts + fork on Unix, or kernel32 FFI prompts + Process on WindowsGoogle can change free-tier RPM/RPD, drop a model to 0, or introduce a higher-quota model at any time. The table below is a snapshot as of 2026-08-13, not an API contract.
Publish and edit config/gemini-translator.php when AI Studio disagrees with the snapshot:
php artisan vendor:publish --tag=gemini-translator-config
// Add a new model, raise/lower a row, or retire one with 0/0
'gemini-4-flash-lite' => ['rpm' => 60, 'rpd' => 2000],
'gemini-2.5-flash' => ['rpm' => 0, 'rpd' => 0],
| Model | In ListModels | RPM | RPD |
|---|---|---|---|
gemini-3.5-flash-lite (package default) |
yes | 15 | 500 |
gemini-3.1-flash-lite |
yes | 15 | 500 |
gemini-2.5-flash-lite |
yes | 10 | 20 |
gemini-2.5-flash |
yes | 5 | 20 |
gemini-3.5-flash |
yes | 5 | 20 |
gemini-3.6-flash |
yes | 5 | 20 |
--concurrency=15 is capped to a recorded positive RPM unless you pass --concurrency or set GEMINI_TRANSLATOR_APPLY_FREE_TIER_CAPS=false0 RPM/RPD is treated as “no free-tier budget” (sequential, with a warning) — not as a crash--concurrency explicitly, or turn caps off--retry-delay / --max-retries for 429 backoff--driver=fork --concurrency=N on Linux/macOS (pcntl) or Windows (Process) for best performance--chunk-size based on API limits (default: 25 keys per request)--concurrency carefully to avoid hitting rate limitsIf you see this error:
Configuration value for key [gemini.request_timeout] must be an integer, string given.
Fix: Edit config/gemini.php and cast the timeout to integer:
'request_timeout' => (int) env('GEMINI_REQUEST_TIMEOUT', 600),
--driver=fork maps to Symfony Process workers with the same concurrency, result order, and “cannot stop mid-process” UX as Linux pcntl fork. Native menus (ext-ffi) match Laravel Prompts (boxed list, arrows, space, enter). Without FFI the command falls back to Symfony choice() / confirm().www-data). Give that user write access on lang/ and any module lang trees.--tag=gemini-translator-manager --force or delete resources/views/vendor/gemini-translator.--refresh or --refresh-clean for those keys. The guard does not catch non-literal or off-tone wording.--chunk-size to avoid API timeoutsnwidart/laravel-modules is properly configured--dry-run to preview changes without writingtranslation_extraction_log.json for detailed code extractionfailed_translation_keys.json for failed translationsnwidart/laravel-modulesThe MIT License (MIT). Please see License File for more information.
⭐ Star this repo if you find it helpful! | 🐛 Report issues on GitHub | 📖 Read full docs at Here
How can I help you explore Laravel packages today?