Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Gemini Translator Laravel Package

jayesh/laravel-gemini-translator

An interactive Artisan command and browser Translation Manager that scan Laravel projects for translation keys and generate language files with Gemini AI. Includes lang-pack support, concurrency, four sync modes, and script-fault highlighting. Review AI output before shipping.

View on GitHub
Deep Wiki
Context7

Getting Started

Prerequisites you'll hit before anything works: PHP 8.3+, Laravel 11/12/13, the tokenizer extension, a Gemini API key, and pcntl if you want parallelism. This is a dev-time tool, not a runtime dependency — install it with --dev and keep it out of your production autoload:

composer require --dev jayesh/laravel-gemini-translator
php artisan vendor:publish --provider="Jayesh\LaravelGeminiTranslator\TranslationServiceProvider"

Add GEMINI_API_KEY and GEMINI_REQUEST_TIMEOUT=600 to .env.

The one config step that breaks everyone: if config/gemini.php was already published by google-gemini-php/laravel, the timeout must be cast:

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

Without the cast you get Configuration value for key [gemini.request_timeout] must be an integer, string given and nothing runs.

Where to look first:

  1. config/gemini-translator.php (or whatever the publish tags produce) for defaults on chunk size, excluded dirs, extensions, retries.
  2. Your existing lang/ tree — the command reasons about what's already there, so understand your current shape before your first run.
  3. The docs site (jayeshmepani.github.io/laravel-gemini-translator) — the README is a feature list; the docs site has the actual option semantics.
  4. translation_extraction_log.json and failed_translation_keys.json — written to the project root, and your primary debugging surface.

First use case: you have a mature lang/en/ + lang/en.json and need a second locale. Always do a dry run first, on a clean git tree:

git add -A && git commit -m "pre-translation baseline"

php artisan translations:extract-and-generate \
  --langs=en,es \
  --driver=sync \
  --dry-run

Read the extraction log to confirm the keys it found match your mental model. Then run for real with --driver=sync before you touch concurrency — the sync driver is the reference implementation and gives you readable, sequential error output.

Implementation Patterns

Pick a mode per workflow, not a single mode for everything. The three modes map cleanly onto real project phases:

Situation Command
New locale from scratch default (full sync) with --langs=en,es
Ongoing feature work; some keys already translated --skip-existing
Copy changed / terminology shifted / quality was bad --refresh
Reviewing a diff before committing --dry-run layered on any of the above

--refresh and --skip-existing are mutually exclusive and the command hard-fails if you combine them. In practice, --skip-existing becomes your default and --refresh becomes the occasional cleanup pass after you tweak --context or fix terminology in en.json.

Wrap it in a composer script so nobody on the team has to remember flags, and so you can swap the tool later without rewriting docs:

"scripts": {
  "lang:new": "php artisan translations:extract-and-generate --driver=sync --skip-existing --exclude=vendor,node_modules,storage,public/build,dist",
  "lang:preview": "php artisan translations:extract-and-generate --driver=sync --dry-run"
}

Use --context deliberately. This is the highest-leverage flag for output quality. Dumping --context="B2B SaaS invoicing platform; 'bill' means invoice, not utility bill" prevents an entire class of semantic drift. Put your canonical context string in the composer script and treat it as source-controlled configuration.

Tune chunk size against key shape, not project size. Default is 25 keys/request. Validation attribute keys (validation.attributes.*) and long sentence keys are what blow up requests — v4.0.1 auto-drops to 3–5 keys/chunk when average key length exceeds 60–80 chars, but for a validation-heavy app set --chunk-size=10 preemptively.

Concurrency is a rate-limit budget problem. The free tier is 15 RPM / 1000 daily. Forking with --concurrency=10 does not make you faster; it makes you 429. A realistic starting point:

php artisan translations:extract-and-generate \
  --driver=fork --concurrency=3 \
  --max-retries=4 --retry-delay=15 \
  --skip-existing

Raise --concurrency only after you've confirmed a clean run at 3.

Module projects: if you use nwidart/laravel-modules, decide up front whether module translations live in Modules/*/lang or consolidate into root lang/. The --consolidate-modules flag is the one-way door — picking it after modules have their own translated files means a merge, not a move. Do it early.

Scope the file scan explicitly. --extensions=php,blade.php,vue,js,ts and --exclude=vendor,node_modules,storage cut runtime dramatically on a big repo. Do not let it walk public/build.

Keep a human review gate for anything customer-facing. The AI output is genuinely good for validation messages, pagination, auth strings, and admin UI. It is not a substitute for a native speaker on marketing copy or legal text. Route those files to manual review by keeping them out of the target dir, or by post-editing after --refresh.

Suggested day-to-day loop: developer adds __('invoices.paid') in a PR → CI job runs --dry-run --skip-existing and fails/warns if new keys are detected for configured locales → developer runs lang:new locally, reviews the lang/es/*.php diff, commits translations alongside the feature.

Gotchas and Tips

The scanner only sees literal keys. __('messages.' . $key) or __($dynamicKey) will not be extracted. Neither will keys built in config arrays and fed through trans() indirectly. Grep your codebase for dynamic translation patterns before trusting a green run — they're silent misses, and nothing in the output tells you.

--dry-run is not quota-free. It suppresses file writes, not the Gemini calls needed to build the preview. On a large project a "preview" can burn your entire free-tier daily allowance. Scope it with --langs and --skip-existing when previewing.

Atomic writes protect against corruption, not against bad translations. Path validation and temp-file-rename mean you won't get a half-written es.json; they do not mean the content is correct. Always run on a committed tree and review git diff lang/ like any other code change. This is your actual safety net.

Verify placeholder and pluralization preservation manually on the first locale. The package claims mismatch detection and plural-string handling, but the failure mode is a runtime :count that disappeared or a | separator that got translated into prose. Spot-check validation.php and any file using trans_choice immediately after your first real run.

Locale canonicalization will surprise you. en-US becomes en_US. That's correct for Laravel's directory convention, but check that whatever sets app()->setLocale() actually matches the directories produced — otherwise you'll have perfectly translated files that never load.

Framework file bootstrapping is opinionated. It publishes auth.php, pagination.php, passwords.php, validation.php into your locale dirs and merges vendor keys. Some teams want that (you can now edit them); some want them left in vendor. Decide and document it, because on the next run the behavior is not obvious from the diff. v4.0.2 fixed accidental PHP file creation during JSON-only runs — if you're on an older patch, JSON-only work may litter lang/{locale}/ with framework files.

Gitignore the artifacts. translation_extraction_log.json and failed_translation_keys.json belong in .gitignore or they'll end up in every diff.

Windows and CI containers: use --driver=sync. pcntl is generally absent from slim Docker images, and --concurrency is meaningless without the fork driver — it's silently ignored, which can look like the flag isn't working.

Pin the version. v5.0.0 is a hard break (PHP 8.3, declare(strict_types=1), strict comparisons), and v4.0.0 removed --source and --no-advanced. With 69 stars, zero dependents, and a single maintainer, treat this as a build-time scaffolder rather than infrastructure: pin an exact version, wrap the invocation in your own composer script or a thin custom Artisan command, and keep lang/ fully under version control so replacing the tool is a flag change, not a migration.

When a run fails, read the two JSON artifacts before re-running. The extraction log tells you what it tried to translate; the failed-keys file tells you what the API rejected. v4.0.1+ captures raw truncated Gemini responses on the final retry — look for Syntax error, which usually means malformed keys (empty strings are now filtered, but odd quoting isn't) or an oversized chunk.

Watch the timeout. Default 600s and it's a global request timeout, not per-key. On a large first-time run into three locales, either raise GEMINI_REQUEST_TIMEOUT or split by --langs and run once per locale so a single failure doesn't discard an hour of work.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core