Laravel Gemini AI Translation Synchronization Tool Documentation

Overview

The Laravel Gemini AI Translation Synchronization Tool is an Artisan command and a browser Translation Manager for modern Laravel apps, including those built with nwidart/laravel-modules. It discovers keys from your code, existing language files, registered custom lang directories, and the framework, then synchronizes them across target languages with Gemini AI. Extra lang folders such as lang/app3/ and lang/web/ are handled as separate packs. Linux/macOS use Laravel Prompts and spatie/fork; Windows uses matching kernel32 menus and Symfony Process workers for parallel chunks.

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. 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 request 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 --refresh or --refresh-clean for bad rows. The AI-First Translation section explains what the guard catches and what it does not.
  • Full Module Support

    Scans stay isolated between the main app and modules. You can keep module files separate or consolidate them into the root lang/ directory. Packs such as lang/app3/ and lang/web/ are selected on their own.

  • AI-First Translation

    Prompts send source text and per-locale script rules. Namespaced keys and literal strings are both supported. Mixed-script or leftover-English output is reduced, not eliminated. Remaining AI mistakes still need a human review.

  • True Synchronization

    Discovers keys from your code and existing files to create a complete picture, ensuring no translation is left behind. Use --skip-existing to only add missing keys, --refresh to re-translate from file wording, or --refresh-clean for a key-only rebuild.

  • High Performance

    Unix uses the fork driver (pcntl + Spatie). Windows uses the process driver (Symfony Process workers) for the same kind of parallel chunks. sync is sequential on every OS. Non-interactive CI/CD skips prompts.

  • Production-Ready Safety

    Atomic file writes with path validation, framework translation bootstrapping, mixed-script checks that reduce (not eliminate) bad Gemini rows, and --dry-run previews.

  • Translation Manager

    Open /translations-manager to browse, edit, save, scan, and add languages. Filters cover type, module, pack, scope, PHP files, language, missing keys, and script faults. Auth follows the host app.

What's new in 5.1.x

Documentation updated for v5.1.1 (28 August 2026). The complete release history lives in the CHANGELOG.md.

v5.1.1 Modernization Update (2026-08-28)

  • PHP 8.3 & Laravel Refactoring: Integrated native #[Override] annotations and optimized exceptions using throw_if / throw_unless helpers.
  • Cross-Platform npm Tooling: Introduced the package's first JS quality tooling (package.json, eslint.config.js, Prettier) to format and lint Blade-embedded JS, utilizing a file crawler written in pure Node.js for native cross-platform Windows, macOS, and Linux support.
  • Robust Testing Environment: Bypassed Testbench dummy application paths in vendor scanning, added standard app key configurations to phpunit, and added fallback catalog resolution logic.

Added

  • Translation Manager at /translations-manager (prefix, enable flag, and middleware are configurable). Routes and JSON APIs register automatically. Guests must sign in when the host has login/register routes; otherwise the page is open. Publish tags: gemini-translator-config, gemini-translator-views, gemini-translator-assets, gemini-translator-manager. Filters: Type, Module, Pack, Scope, PHP files, Language, show-only-missing, Highlight script faults. Theme switch, sticky KEY column, pagination (default 5 rows, last two pages always shown).
  • Manager script-fault highlighter: optional checkbox (off by default, localStorage key gemini-translator-malform-detector). Marks cells whose letters do not belong to that locale, including leftover Latin in Gujarati or Hindi. Placeholders are ignored. Amber outline plus a tooltip of the unexpected scripts. Powered by LocaleHelper::malformReasons().
  • Lang packs in the manager and the CLI. Extra folders such as lang/app3/ and lang/web/ stay separate from lang/. Custom directories registered with loadJsonTranslationsFrom() / loadTranslationsFrom() and published resources/lang/modules/{name} are scanned. Pack PHP writes to lang/{pack}/{locale}/….
  • 249-language catalog with fallbacks such as zhzh_CN.
  • Multi-OS adapters: Unix Prompts + fork; Windows kernel32 menus + Symfony Process. --driver=process on every OS. Hidden translations:run-payload for child jobs.
  • --refresh-clean rebuilds existing keys from the key (official Laravel English for auth / pagination / passwords / validation).
  • Dated quota snapshot in config/gemini-translator.php (as of 2026-08-13), plus GEMINI_TRANSLATOR_APPLY_FREE_TIER_CAPS.
  • Configurable model: --model, GEMINI_MODEL / GEMINI_TRANSLATOR_MODEL, then config. Default gemini-3.5-flash-lite.
  • Per-locale writing-system map (Tibetan, N’Ko, Tifinagh, Ol Chiki, Meitei, extra Devanagari/Cyrillic/Arabic codes, and script tags such as _latn / _arab) plus checks that reduce mixed-script / leftover-English output. They do not eliminate it.

Changed

  • --refresh uses current file wording as the source.
  • On Windows, --driver=fork runs Process workers. FFI menus match Laravel Prompts.
  • JSON keeps dotted PHP-style keys. Blank literal JSON values fill from the key.
  • Command summary records processed_chunks.
  • CLI writing-system check reduces mixed-script Gemini output (for example Gujarati + Kannada ). It does not stop every mixed or leftover-English row.

Fixed (relative to 5.0.1 CLI)

  • JSON-only selection does not re-translate PHP file.subkey keys.
  • Selecting a single CLI file group returns that file.

Installation

Step 1: Install via Composer

composer require jayesh/laravel-gemini-translator

Step 2: Publish Configuration

php artisan vendor:publish --tag=gemini-translator-config

That publishes config/gemini-translator.php (model, quota snapshot, Translation Manager). The API client’s config/gemini.php comes from google-gemini-php/laravel, not this tag.

The manager is already registered at /translations-manager. You do not have to publish views for it to work. Publish only if you want to edit those files in the app.

Publishable tags

Tag Copies Required?
gemini-translator-config config/gemini-translator.php (model, quota snapshot, manager prefix / enable / middleware) No. Package config is merged without publishing. Publish to edit quotas or the manager URL.
gemini-translator-views resources/views/vendor/gemini-translator/ (manager.blade.php, partials/workspace.blade.php, errors/unauthenticated.blade.php) No. Laravel then prefers these copies over the package. Delete the folder to load the package views again.
gemini-translator-assets public/vendor/gemini-translator/ (css/manager.css, js/manager.js) No. CSS/JS are inlined from the package unless you pass $assetCss / $assetJs.
gemini-translator-manager Views and assets (both destinations above) No. Convenience tag for customizing the manager UI.
# Config only
php artisan vendor:publish --tag=gemini-translator-config

# Views only
php artisan vendor:publish --tag=gemini-translator-views

# Public CSS/JS only
php artisan vendor:publish --tag=gemini-translator-assets

# Views + CSS/JS
php artisan vendor:publish --tag=gemini-translator-manager

# Overwrite after a package upgrade
php artisan vendor:publish --tag=gemini-translator-manager --force
php artisan view:clear

config/gemini.php is published by google-gemini-php/laravel, not by these tags. PHP from this package is never published.

Step 3: Add API Key

Add your Gemini API key to your .env file. You can get a key from the Google AI Studio.

GEMINI_API_KEY="YOUR_GEMINI_API_KEY"
Offline Mode: If you run the command without a GEMINI_API_KEY, it will automatically switch to Offline Mode, generating placeholder files so your application doesn't break.

Configuration

Environment Variables

Add these variables to your .env file:

GEMINI_API_KEY="YOUR_GEMINI_API_KEY"
GEMINI_REQUEST_TIMEOUT=600

# Model (CLI --model wins, then these, then config)
GEMINI_MODEL="gemini-3.5-flash-lite"
# GEMINI_TRANSLATOR_MODEL="gemini-3.5-flash-lite"

# Free-tier snapshot caps (set false for paid keys)
# GEMINI_TRANSLATOR_APPLY_FREE_TIER_CAPS=true

# Translation Manager
# GEMINI_TRANSLATOR_MANAGER=true
# GEMINI_TRANSLATOR_MANAGER_PREFIX=translations-manager
# GEMINI_TRANSLATOR_MANAGER_MIDDLEWARE=web

Getting Your API Key

1. Visit Google AI Studio
2. Sign in with your Google account
3. Create a new API key
4. Copy and paste it into your .env file

Free-tier snapshot: RPM/RPD numbers live in config/gemini-translator.php (dated 2026-08-13). They are not an API contract. Publish the file and edit a row, add a model, or set 0 when Google changes quotas. Default concurrency follows a recorded positive RPM unless you pass --concurrency or set GEMINI_TRANSLATOR_APPLY_FREE_TIER_CAPS=false.

Configuration Files

config/gemini.php (from google-gemini-php/laravel) holds the API key and request timeout. Cast timeout to int:

'request_timeout' => (int) env('GEMINI_REQUEST_TIMEOUT', 600),

config/gemini-translator.php (this package) holds:

  • Default Gemini model (gemini-3.5-flash-lite)
  • Dated free-tier quota snapshot
  • Translation Manager enable flag, URL prefix, and middleware

Usage

Basic Command

Run the command to start the interactive process. It asks for targets, then packs (only if extra lang folders exist), then JSON/PHP files.

php artisan translations:extract-and-generate

Pick a model for one run with --model:

php artisan translations:extract-and-generate --model=gemini-2.5-pro --langs=en,hi

Platform-Specific Recommendations

For the best experience, specify a driver based on your operating system.

# Linux/macOS/WSL (Prompts + fork)
php artisan translations:extract-and-generate --driver=fork

# Windows (same flag; 5.1.x runs Symfony Process workers)
php artisan translations:extract-and-generate --driver=fork --concurrency=10

# Sequential on any OS
php artisan translations:extract-and-generate --driver=sync

Four Operational Modes

5.1.x offers four operational modes. --refresh, --refresh-clean, and --skip-existing cannot be combined.

1. Normal Mode (Default)

Translates all keys, both existing and new. This is the default behavior.

2. Skip-Existing Mode

To add new translations without overwriting existing ones, use the --skip-existing flag. This is the recommended workflow for ongoing projects.

# Find and translate only the keys that are missing in Spanish and German
php artisan translations:extract-and-generate --skip-existing --langs=es,de

3. Refresh Mode

Re-translate only existing keys, using the current file wording as the source. Does not add new keys.

php artisan translations:extract-and-generate --refresh --langs=es,de

4. Refresh-Clean Mode

Re-translate only existing keys, ignoring stale file text. Source is the key itself, except official Laravel English for auth, pagination, passwords, and validation.

php artisan translations:extract-and-generate --refresh-clean --langs=es,de

Translation Manager

The package registers the page and JSON APIs. Open:

/translations-manager

Example: http://127.0.0.1:8000/translations-manager

Auth

  • If the host already has login/register/sign-in routes, guests are redirected (JSON APIs return 401).
  • If the host has no auth routes, the manager is open.

What it scans

App lang/, Modules/*/lang, published resources/lang/modules/{name} (published wins), and extra directories registered with loadJsonTranslationsFrom() / loadTranslationsFrom().

Filters

Type → Module (when Type is Module) → Pack (only if that source has more than lang/) → Scope → PHP files (when Scope is PHP) → Language → show only missing → Highlight script faults. A selected language lists only keys that exist in that locale file.

Script faults

The highlighter is off until you check it. The choice is stored as gemini-translator-malform-detector. Rules come from LocaleHelper::detectorCatalog() (same 249-language map as the CLI). A cell is marked when it contains a writing system that locale does not use — for example Devanagari or Latin inside gu, or Gujarati inside en. Laravel placeholders (:name, {0}, [2,*]) and Common marks such as danda are ignored. Faulty editors use an amber outline and a tooltip of the unexpected scripts. This is review-only; it does not rewrite files. The CLI hasDisallowedScript() check still allows Latin tokens in native scripts; the highlighter is stricter.

UI

Light/dark theme (stored as gemini-translator-theme), sticky KEY column, pagination (default 5 rows, last two pages always shown), Add Languages, Sync, and Save. CSS is semantic component CSS, not Tailwind or Bootstrap utilities.

Optional publish (only if you want to edit Blade/CSS/JS):

php artisan vendor:publish --tag=gemini-translator-manager

After a package upgrade, either overwrite those copies or go back to the package files:

# Overwrite published views + assets
php artisan vendor:publish --tag=gemini-translator-manager --force
php artisan view:clear

# Or delete the published views so Laravel loads the package copies again
rm -rf resources/views/vendor/gemini-translator
php artisan view:clear

There is no unpublish command. PHP from the package is never published; composer update always uses the new code. Stale UI almost always means an old published Blade.

Or drop the workspace into an existing layout:

@include('gemini-translator::partials.workspace')

Module Support (nwidart/laravel-modules)

The tool is designed from the ground up to support modular applications. Scans are now correctly isolated, so selecting the "Main Application" will not incorrectly scan `Modules/` directories, preventing key contamination.

1. Select Scan Targets

First, you will be prompted to choose which parts of your application to scan.

 ┌ Which parts of the application would you like to scan and process? ┐
 │ Module: Settings                                                   │
 └────────────────────────────────────────────────────────────────────┘

2. Language packs

If the selected module (or the main app) has extra lang folders such as lang/app3/ or lang/web/, the next prompt is a multi-select for those packs. The file list then only includes JSON and PHP from the packs you chose. Modules with only lang/ skip this step. The Translation Manager uses the same pack filter.

 ┌ Which language packs would you like to process? ┐
 │ lang/                                           │
 │ lang/app3/                                      │
 │ lang/web/                                       │
 └─────────────────────────────────────────────────┘

3. Consolidating Module Translations

If you select any modules, you are asked whether to consolidate writes into the main lang/ directory.

 ┌ Consolidate all module translations into the main application's `lang` directory? ┐
 │ No                                                                                │
 └───────────────────────────────────────────────────────────────────────────────────┘
  • Default (No): Translations are kept separate. A key from the `Settings` module is written to `Modules/Settings/lang/en.json`.
  • Consolidate (Yes): All translations, regardless of origin, are written to the main application's `lang` directory (e.g., `lang/en.json`).

You can bypass this prompt for automated scripts using the --consolidate-modules flag.

Advanced Usage & Output

Fork Driver Output (Linux/macOS)

The fork driver (pcntl + Spatie) shows a real-time progress bar. Cooperative stop (--stop-key, default q) does not interrupt an in-flight fork or Process batch. Use sync if you need to stop between chunks.

✅ Key discovery complete! Found 451 unique keys from all sources combined.
📊 Total keys needing translation: 7
📦 Total chunks to process: 2
⚡ Using 'fork' driver for high-performance concurrency.
🚀 7/7 [============================] 100% -- ✅ Chunk 2/2 - SUCCESS (5 keys) ⏱️     3 s
        
╔═ 💾 Phase 3: Writing Language Files ════════════════════════════════════╗
        
 ✅ Updated: lang/en/auth.php (8 total keys)
 ✅ Updated: lang/ru/auth.php (8 total keys)
 ✅ Updated: lang/uz/auth.php (8 total keys)
        

Process Driver (Windows parallel)

On Windows, --driver=fork and --driver=process run Symfony Process workers. That is not Spatie fork (pcntl is Unix-only). You still get parallel chunks, ordered results, inherited env, and the same “cannot stop mid-flight” behaviour as Unix fork. Menus use kernel32 FFI when ext-ffi is loaded; otherwise Symfony choice() / confirm().

php artisan translations:extract-and-generate --driver=fork --concurrency=10 --langs=gu,hi

Sync Driver Output (sequential)

The sync driver processes chunks one after another on any OS. Use it when you want cooperative stop, or when Process/fork is unavailable.

✅ Key discovery complete! Found 222 unique keys from all sources combined.
        
🐌 Running in synchronous mode - this will be slower but more stable!
        
-> Processing chunk 1/3... ✓ Done
-> Processing chunk 2/3... ✓ Done
-> Processing chunk 3/3... ✗ Failed
Error: File: __MAIN__::__JSON__, Keys: An unknown error occurred.,... - Error: Syntax error
        
╔═ 💾 Phase 3: Writing Language Files ═══════════════════════════════════════╗
        
 ✅ Wrote: lang/es.json (222 total keys)
 ✅ Wrote: lang/fr.json (222 total keys)
        

Example 1: Updating a Single Module (Separate)

This output shows the workflow for updating only the `Settings` module and keeping its translations separate.

Scanning 1 target(s): Module: Settings

 ┌ Which language packs would you like to process? ┐
 │ lang/web/                                       │
 └─────────────────────────────────────────────────┘
        
 ┌ Consolidate all module translations into the main application's `lang` directory? ┐
 │ No                                                                                │
 └───────────────────────────────────────────────────────────────────────────────────┘
Found missing translations needing synchronization:
  File: Module: Settings -> JSON File (web/*.json)
-> Language 'en' is missing 36 keys.
...
⚡ Using 'fork' driver for high-performance concurrency.
🚀 36/36 [============================] 100% -- ✅ Chunk 1/1 - SUCCESS (36 keys) ⏱️    12 s

╔═ 💾 Phase 3: Writing Language Files ════════════════════════════════════╗

 ✅ Updated: /.../Modules/Settings/lang/web/en.json (104 total keys)
 ✅ Updated: /.../Modules/Settings/lang/web/ru.json (104 total keys)
 ✅ Updated: /.../Modules/Settings/lang/web/uz.json (104 total keys)
        

Example 2: Updating Only the Main Application

Notice how the scan is now correctly isolated to the main application, even though modules exist in the project.

Scanning 1 target(s): Main Application
Reading existing language files from selected targets...
...
Found missing translations needing synchronization:
  File: Main Application -> JSON File (web/*.json)
-> Language 'en' is missing 34 keys.
...
🚀 34/34 [============================] 100% -- ✅ Chunk 1/1 - SUCCESS (34 keys) ⏱️    12 s

╔═ 💾 Phase 3: Writing Language Files ════════════════════════════════════╗

 ✅ Updated: /.../lang/web/en.json (485 total keys)
 ✅ Updated: /.../lang/web/ru.json (485 total keys)
 ✅ Updated: /.../lang/web/uz.json (485 total keys)
        

Supported Functions & Patterns

The scanner is pre-configured to find keys used in the most common Laravel and JavaScript localization functions.

Pattern Example Typical Context
__(), trans(), @lang() __('messages.welcome') PHP & Blade
trans_choice(), @choice() trans_choice('messages.apples', 5) PHP & Blade (Pluralization)
$t(), i18n.t() $t('user.profile') Vue.js, JavaScript (i18n Libraries)
v-t, x-text, :v-t, :x-text <span v-t="'buttons.submit'"> Vue.js & Alpine.js (including bound attributes)
Lang::get(), Lang::choice() Lang::get('messages.welcome') Laravel facade

Logging & Debugging

For transparency and debugging, the command creates two log files in your project root:

  • translation_extraction_log.json: A detailed log of every unique key found in your *code* and the file paths where it was located.
  • failed_translation_keys.json: A list of any keys that failed to be translated by the AI, so you can easily retry them.

AI-First Translation

This tool adopts an "AI-First" philosophy. Instead of trying to pre-process translation keys into human-readable text (which can be buggy), it trusts the AI to correctly interpret the keys based on Laravel conventions.

Notice: Gemini is a generative model. This package tries to honor the requested language as far as prompts and checks allow. It can still return mixed scripts, another language inside the same string, or a translation that is not literal or not the wording you wanted. Treat every generated file as a draft until a person reviews it.

How the AI Interprets Keys

The prompt includes source text and script rules. Gemini is asked to handle two kinds of keys:

Key Type Example Key AI's Interpretation
Namespaced (PHP-style) auth.failed "Based on Laravel conventions, this means an authentication failure. I will generate 'These credentials do not match our records.'"
Literal (JSON-style) Save Changes "This is a literal string. I will translate 'Save Changes' directly."

Writing-system guard

Each locale is mapped to one Unicode writing system (sc=, so shared marks such as danda are Common). Gemini output that mixes neighbor scripts (for example Gujarati plus Kannada ) or leaves English sentences in an Indic locale is sometimes rejected and the source is kept. The check reduces how often that happens; it does not catch every mixed or leftover-English row. The prompt also sends source text and script rules. In the Translation Manager, Highlight script faults uses the same map (plus leftover Latin in native-script locales) to outline bad cells while you edit.

The guard only looks at writing systems. It does not judge meaning. A Gujarati string that is grammatically odd, too free, or mixes Hindi words in Gujarati script will usually pass. Brand names, leftover Latin in a native-script locale, and mixed neighbor scripts are the cases the manager highlighter is meant to surface. Human review is still required for production copy.

The catalog covers 249 language codes, including zh_CN / zh_TW, pt_BR / pt_PT, and Punjabi Gurmukhi/Shahmukhi. Short codes such as zh resolve to the primary variant.

Intelligent Fallbacks

The tool no longer generates useless placeholders like "NEEDS TRANSLATION". If the AI fails to provide a translation for a key, the system now uses this intelligent fallback logic:

  1. It first looks for an existing English translation for that key in your language files.
  2. If none is found, it uses the key itself as the placeholder text.

This ensures your generated files are always usable and immediately show you the source text that needs attention, dramatically speeding up the manual review process.

Placeholder Mismatch Detection

Added robust placeholder validation with count checking instead of just presence. This ensures:

  • Source text placeholders like :attribute, :count, {0}, {name} are preserved in translations
  • Count mismatches are detected and the source text is used as fallback to prevent runtime errors
  • Translation quality is maintained across all language families

Improving Accuracy with the --context Flag

For domain-specific terms, you can guide the AI with the --context flag. This is crucial for avoiding ambiguity.

# For a financial trading app, this context ensures accuracy.
php artisan translations:extract-and-generate \
--context="A SaaS platform for financial trading. 'Position' and 'security' refer to financial assets."

Command Options

Option Description
--langs Comma-separated language codes to translate to (e.g., es,fr,de). Default is en (used as source).
--driver Concurrency driver: fork (Unix Prompts + pcntl; on Windows 5.1.x maps to Process), process (Symfony Process workers), or sync (sequential, any OS).
--skip-existing A flag to only find and translate keys that are missing from target language files. Ideal for updates.
--context Provide a project-specific description to Gemini for more accurate, domain-aware translations.
--chunk-size Number of keys to send to the AI in a single request. Default: 25.
--concurrency Number of concurrent workers for the fork and process drivers. Default: 15 (capped to a recorded free-tier RPM unless you override or turn caps off).
--refresh Re-translate only existing keys, using current file wording as the source. Does not add new keys.
--refresh-clean Like --refresh, but ignores stale file wording. Official Laravel English is used for auth, pagination, passwords, and validation.
--model Gemini model id. Overrides GEMINI_MODEL / config. Default: gemini-3.5-flash-lite.
--dry-run Run full extraction + mapping but show what files would be modified without writing anything.
--stop-key Key that requests a graceful stop (default q). Honoured between chunks on sync. Fork and Process do not stop a batch that is already running.
--consolidate-modules Consolidate all module translations into the main application's lang directory, skipping the interactive prompt.
--target-dir Root directory for the translation files within the app/modules. Default: "lang".
--exclude Comma-separated directories to exclude from the scan.
--extensions Comma-separated file extensions to search within.
--max-retries Maximum number of retries for a failed API call. Default: 5.
--retry-delay Base delay in seconds between retries (uses exponential backoff). Default: 3.

Usage Examples

Fresh Translation for a Modular Project

# Generate all translations for the main app and all modules
php artisan translations:extract-and-generate --driver=fork --langs=es,fr

Synchronize a Single Module (Separate)

# Interactively select only the 'Settings' module.
# If it has lang/app3 or lang/web, pick those packs next.
# When prompted, choose NOT to consolidate.
php artisan translations:extract-and-generate --driver=fork --skip-existing

Translate All Modules into Main `lang` Directory

# Scan all targets (main app + all modules).
# The --consolidate-modules flag ensures all translations land in the root lang/ dir.
php artisan translations:extract-and-generate --driver=fork --consolidate-modules

Update Only the Main Application (Ignoring Modules)

# Interactively select ONLY the "Main Application".
# The scanner will now correctly ignore the Modules/ directory.
php artisan translations:extract-and-generate --driver=fork --skip-existing

High-Performance Synchronization

# Use fork driver with smaller chunks to find and fill only missing keys
php artisan translations:extract-and-generate --driver=fork --chunk-size=25 --skip-existing

Context-Aware Update for a Specific Module

# Select the 'ECommerce' module, then provide context for its specific terminology
php artisan translations:extract-and-generate --skip-existing \
--context="An e-commerce module. 'Shipment' refers to a package delivery."

Automated Run in a CI/CD Pipeline

# This command runs non-interactively, great for a GitHub Action.
# It skips prompts, consolidates modules, and only syncs missing Spanish keys.
php artisan translations:extract-and-generate --langs=es --skip-existing --consolidate-modules

Open the Translation Manager

# No extra route registration. Visit:
# http://127.0.0.1:8000/translations-manager

Dry Run to Preview Changes

# Preview what would be changed without writing any files
php artisan translations:extract-and-generate --dry-run --langs=es,fr

Refresh Only Existing Translations

# Current file wording is the source
php artisan translations:extract-and-generate --refresh --langs=es,fr

# Key-only rebuild (official Laravel English for auth/pagination/passwords/validation)
php artisan translations:extract-and-generate --refresh-clean --langs=es,fr

Custom Concurrency

# Unix fork, or Windows Process (same flag)
php artisan translations:extract-and-generate --driver=fork --concurrency=20

# Explicit Process driver
php artisan translations:extract-and-generate --driver=process --concurrency=10 --langs=gu,hi

# One-off model
php artisan translations:extract-and-generate --model=gemini-2.5-pro --langs=en,hi

Troubleshooting

Error / Issue Common Cause Solution
"Invalid API key" The GEMINI_API_KEY in your .env file is incorrect or missing. Verify the key is correct and has no extra spaces. Run php artisan config:cache after changes.
"Configuration value must be an integer, string given" The config/gemini.php file has incorrect type casting for request_timeout. Edit config/gemini.php and change:
'request_timeout' => (int) env('GEMINI_REQUEST_TIMEOUT', 600),
Not: env('GEMINI_REQUEST_TIMEOUT', 600) (without cast)
"Syntax error" from Gemini / Low success rate The AI's safety filters may have been triggered by a batch of keys, or the model returned a malformed response. Reduce the chunk size significantly. Try --chunk-size=20 or lower and re-run. Check `failed_translation_keys.json` for problematic keys.
"Quota exceeded" or "Rate limit" errors The free tier API limit has been reached. Lower the chunk size with --chunk-size=25 and/or wait a few minutes before retrying. The tool has built-in exponential backoff.
"Call to undefined function pcntl_fork()" The pcntl extension is missing. In 5.1.x, --driver=fork on Windows uses Process workers instead of pcntl. On Windows, use --driver=fork or --driver=process. On Unix without pcntl, use --driver=sync or --driver=process.
Script hangs or runs very slowly Processing a very large number of keys with the sync driver. Use --driver=fork on Linux/macOS, or the same flag (Process workers) on Windows. Use sync only when you need sequential runs.
Keys from a module appear in the main app's files. You selected to consolidate translations either via the prompt or the --consolidate-modules flag. This is intended behavior when consolidation is active. If you want separate files, re-run the command and choose "No" when prompted about consolidation.
The command ran automatically without asking me any questions. You are running in a non-interactive environment (like a CI/CD pipeline, Docker, or Git Bash on Windows). This is the new intended behavior for automation. The tool automatically proceeds without prompts. If you need to limit the scope, use flags like --skip-existing.
All my PHP translation arrays were reordered after running the command. This is an intentional, one-time change introduced in v4.0.1 (retained in v4.0.2 and v4.0.3) The tool now performs a recursive sort on all PHP arrays to ensure a stable, deterministic order. This makes future code reviews (diffs) much cleaner. You should commit this one-time change.
"You cannot use --refresh and --skip-existing together" You combined --refresh, --refresh-clean, and/or --skip-existing. Those three flags are mutually exclusive. Pick one mode per run.
Manager redirects to login / JSON 401 The host app already has login or register routes. Sign in with the host app. If the app has no auth routes, the manager is open.
Published manager Blade is missing Pack / theme / rows 5 Laravel prefers resources/views/vendor/gemini-translator over the package views. Republish with --force: php artisan vendor:publish --tag=gemini-translator-manager --force and php artisan view:clear. Or delete resources/views/vendor/gemini-translator so the package views load again. Laravel has no unpublish command.
No “Highlight script faults” checkbox Published manager Blade/JS is older than the package. Republish --tag=gemini-translator-manager or delete resources/views/vendor/gemini-translator.
Mixed-script, other language, or unexpected wording Gemini is a generative model. It can mix neighbor scripts, include another language, or return wording that is not literal. The writing-system guard rejects some mixed-script rows and keeps the source; it does not catch meaning or tone. Treat generated files as a draft. In the manager, turn on Highlight script faults. Re-run --refresh or --refresh-clean for that language. Use --context for domain terms.
“Cannot write … PHP process cannot create files” The user that runs PHP (php-fpm, Apache, nginx, or artisan serve) cannot write that path. That user is not always www-data. The error names the process user when POSIX is available. Give that user write access on lang/ (and module lang trees), for example sudo chown -R USER:USER lang && sudo chmod -R u+w lang.

Frequently Asked Questions

How does this handle translations in subdirectories like lang/web/en.json?
Question
Those folders are packs. After you pick a module (or the main app) that has lang/, lang/app3/, and lang/web/, the CLI asks which packs to process, then lists JSON/PHP from those packs only. The manager Pack filter does the same. PHP under a pack writes to lang/web/{locale}/messages.php, not lang/{locale}/web/messages.php.
Can I use this with existing translation files?
Question
Yes. The tool reads existing language files to build a complete key map. With --skip-existing, it only fills missing entries, so your existing translations remain intact.
What are the different operation modes available?
Question
Four modes:
  • Normal: translates all keys (new and existing).
  • Skip Existing: translates only missing keys (--skip-existing).
  • Refresh: re-translates existing keys from current file wording (--refresh).
  • Refresh-clean: re-translates existing keys from the key only (--refresh-clean).

--refresh, --refresh-clean, and --skip-existing are mutually exclusive.

How do I pick up package UI changes after I already published the views?
Question
Laravel keeps your copies in resources/views/vendor/gemini-translator. Overwrite them with php artisan vendor:publish --tag=gemini-translator-manager --force and php artisan view:clear, or delete that folder so the package views load. There is no unpublish command. PHP is never published; composer update always uses the new package code.
How do I open the Translation Manager?
Question
Visit /translations-manager. You do not register routes yourself. If the app has a login route, sign in first. Publish the Blade only if you want to customize it: php artisan vendor:publish --tag=gemini-translator-manager.
Does Windows run translations in parallel?
Question
Yes. Windows does not use Spatie fork (pcntl). --driver=fork or --driver=process starts Symfony Process workers so chunks run in parallel, similar to Unix fork. Menus use kernel32 when FFI is available.
How accurate are the AI translations?
Question
Gemini translations are generally high quality. The package sends source text, per-locale script rules, and a writing-system check so most output matches the language you asked for. Gemini can still mix writing systems, slip in another language, or return wording that is not literal or not what you expected (for example Gujarati mixed with Hindi, Kannada, or English). The check reduces those cases; it does not catch every mistake. Human review is still recommended for production-critical text. Turn on Highlight script faults in the manager, re-run --refresh or --refresh-clean for leftover faults, and use --context for domain-specific terminology.
What is dry-run mode?
Question
--dry-run simulates the full process and previews changes without writing files.
What happens if AI translation fails for a chunk?
Question
The tool applies intelligent fallback logic by using existing English source text, or the key itself if no source text exists. Persistently failing keys are logged to failed_translation_keys.json.

Contributing & Support

This project is open-source and maintained by Jayesh Mepani. Contributions are welcome!

How to Contribute

Support

If you encounter issues, please check the troubleshooting section first. If the problem persists, create a new issue on the GitHub repository with detailed information, including your OS, PHP version, and the exact command used.

Security: If you discover a security vulnerability, please email the maintainer directly rather than creating a public issue.