tonydev/lara-glot
Composer 安装命令:
composer require tonydev/lara-glot
包简介
Auto-translation for Laravel models, language files, and Filament. Drivers for Anthropic Claude, OpenAI, DeepL, Google, and Ollama, with glossary locking and aggressive caching.
关键字:
README 文档
README
Automatic translation for Laravel. LaraGlot translates Eloquent models with JSON translatable attributes, nested page-builder content, and static PHP language files — and it goes to great lengths to never pay for the same translation twice.
Five drivers ship out of the box: Anthropic Claude, OpenAI, DeepL, Google Translate, and Ollama for local models. Laravel placeholders, URLs, HTML tags, and your brand terms survive translation intact.
Table of Contents
- Requirements
- Installation
- Configuration
- Drivers
- Keeping Costs Down
- Glossary and Brand Terms
- Quality Review Pass
- Placeholder Protection
- Model Setup
- Language File Translation
- Artisan Commands
- Queue Setup
- Filament Integration
- Custom Drivers
- Custom Prompts
- Failure Behaviour
- Upgrading to 2.1
- Upgrading from 1.x
- License
Requirements
| Requirement | Version |
|---|---|
| PHP | ^8.3 |
| Laravel | ^11.0 | ^12.0 | ^13.0 |
| spatie/laravel-translatable | ^6.0 (for model translation) |
| filament/filament | ^4.0 | ^5.0 (for the admin UI) |
Installation
composer require tonydev/lara-glot
The service provider is auto-discovered. Then publish the config:
php artisan vendor:publish --tag=lara-glot-config
Two drivers need an extra package; the rest use Laravel's HTTP client directly:
# Google (unofficial endpoint, no API key required) composer require stichoza/google-translate-php:^5.3 # Ollama (local models) composer require cloudstudio/ollama-laravel:^2.0
If you use queued model translation, make sure job batching is migrated:
php artisan queue:batches-table php artisan migrate
Running php artisan migrate also creates the lara_glot_translation_retries table, which tracks values that fell back to source text so laraglot:retry can re-attempt them. It's optional — without it, translation still works and LaraGlot logs a single notice instead of tracking fallbacks.
Configuration
Pick a driver and set its key in .env:
LARAGLOT_DRIVER=anthropic ANTHROPIC_API_KEY=sk-ant-... # or LARAGLOT_DRIVER=openai OPENAI_API_KEY=sk-... # or LARAGLOT_DRIVER=deepl DEEPL_API_KEY=...
Every driver accepts the same tuning knobs in config/lara-glot.php: chunk size, retries, retry delay, concurrency, HTTP timeout, and cache TTL. The defaults are sensible; you rarely need to touch them.
For slow reasoning models (e.g. DeepSeek-class models behind an OpenAI-compatible endpoint), raise the timeout and/or lower the chunk size:
LARAGLOT_OPENAI_TIMEOUT=180 LARAGLOT_OPENAI_CHUNK_SIZE=10
When a chunk still times out (or gets rate-limited), the LLM drivers automatically retry it in smaller sub-chunks before falling back, so one slow request no longer drops a whole chunk of strings to source text.
Fallback detection
When a driver fails on a string, that string falls back to the source text. LaraGlot refuses to let that go unnoticed:
laraglot:files --syncprints a per-filetranslated / identical-to-sourcecount, warns when the fallback share exceedslara-glot.fallback.warn_ratio(default0.5), and exits non-zero on failures.- When every translatable string in a file comes back identical to the source — the signature of a failed run — the file is not written and the job fails so it can retry, instead of an untranslated English file masquerading as done. Disable via
LARAGLOT_FAIL_ON_FULL_FALLBACK=falseif a locale is legitimately identical to the source.
Protected values
Values that are references rather than prose — 'link' => 'route:contact', bare URLs, mailto:/tel: links — are passed through untranslated. Patterns are configurable in lara-glot.protected_value_patterns.
Drivers
| Driver | Needs a key | Best for |
|---|---|---|
anthropic |
yes | Best translation quality. Defaults to claude-haiku-4-5 (fast, cheap); set claude-sonnet-5 when nuance matters most. |
openai |
yes | Works with any OpenAI-compatible endpoint — OpenAI, Azure, Groq, Together AI. Defaults to gpt-5-mini. |
deepl |
yes | Strong European-language quality at a flat per-character price. Free-tier keys (ending :fx) are detected automatically. |
google |
no | Prototyping and low-volume work. Uses the unofficial web endpoint — no SLA, so don't build production on it. Long strings are split on sentence boundaries automatically (LARAGLOT_GOOGLE_MAX_LENGTH, default 1500 chars) since the free endpoint 500s on long payloads, and every response is verified to have preserved placeholder tokens before being accepted. |
ollama |
no | Fully local and free. Point it at any model your hardware can run. Defaults to llama3.2. |
To use another OpenAI-compatible provider, change the base URL — no code changes:
# Groq LARAGLOT_OPENAI_BASE_URL=https://api.groq.com/openai/v1 LARAGLOT_OPENAI_MODEL=llama-3.3-70b-versatile # Azure OpenAI LARAGLOT_OPENAI_BASE_URL=https://your-deployment.openai.azure.com/v1
Keeping Costs Down
This is where LaraGlot earns its keep. A translation API call is made only when a string has genuinely never been translated before:
- In-process cache. Within one request or job, a repeated string costs a RAM lookup, nothing more.
- Persistent cache. Every translation is stored in your Laravel cache (30 days by default), keyed per string, per language pair, per driver. Re-running a command, re-saving a model, or rebuilding language files re-uses everything already translated.
- In-batch deduplication. If "Save" appears forty times across your language files, it is sent to the API once and fanned back out to all forty keys.
- Change detection. Model translation tracks a hash of the source content and skips fields that haven't changed since the last run.
- Per-string caching, not per-chunk. Editing one string in a file invalidates that string only — the other forty-nine stay cached.
In practice a second full run over an unchanged project costs zero API calls. You can verify this yourself: every driver exposes getStats() with cache hits, misses, and the actual API call count.
Glossary and Brand Terms
Two tools to keep terminology under control:
'glossary' => [ // Never translated — byte-identical in every language. // Works with all five drivers. 'protected_terms' => ['LaraGlot', 'Acme Cloud'], // Forced translations per target locale. // Applied by the LLM drivers (anthropic, openai, ollama). 'terms' => [ 'checkout' => ['de' => 'Kasse', 'fr' => 'paiement'], ], ],
protected_terms are shielded with the same token mechanism used for URLs, so even Google and DeepL can't mangle them. Matching is case-sensitive and whole-word: LaraGlot is protected, laraglots is not.
Changing the glossary automatically invalidates the affected cached translations — you never serve stale wording after a terminology decision.
Quality Review Pass
Machine translation is accurate but often stiff. With the review pass enabled, each freshly translated chunk gets a second call where the model acts as a native-speaker reviewer: it fixes overly literal phrasing, replaces vocabulary no real product would use, and returns already-good translations unchanged.
LARAGLOT_REVIEW=true
Cost notes, because they matter:
- The review only runs on strings that weren't cached — and the reviewed result is what gets cached. You pay the extra call once per string, ever.
- If the review call fails for any reason, the first draft is kept. A translation is never lost to the reviewer.
- LLM drivers only; Google and DeepL ignore the flag.
Placeholder Protection
Translation engines will happily turn :count into :nombre and break your app. Before any text leaves your server, LaraGlot replaces sensitive tokens with opaque markers, and swaps them back afterwards:
:name → __BRACE_0__
https://example.com → __URL_1__
<span translate="no">… → __HTML_2__
LaraGlot (glossary) → __TERM_3__
Input:
"Welcome back, :name! Visit https://example.com for details."
Sent to the API:
"Welcome back, __BRACE_0__! Visit __URL_1__ for details."
After restore:
"Bienvenue, :name ! Visitez https://example.com pour plus de détails."
This is a hard guarantee enforced in code, not a prompt instruction, and it applies to every driver and every translation path.
Model Setup
Add the trait to any model using spatie/laravel-translatable:
use Spatie\Translatable\HasTranslations; use Tonydev\LaraGlot\Traits\HasSmartTranslations; class Page extends Model { use HasTranslations; use HasSmartTranslations; public $translatable = ['title', 'body', 'seo_title', 'seo_description']; }
Register it in config/lara-glot.php:
'models' => [ \App\Models\Page::class, ],
Nested JSON content (page-builder sections, repeaters) is translated recursively. Keys listed in ignored_keys — identifiers, security values, media paths, locale/format config, geo and ordering fields, timestamps — are passed through untouched. The published config ships with a comprehensive list of keys that are never human-readable labels (id, uuid, slug, sku, token, avatar, latitude, created_at, …); add your own app-specific keys there. Deliberately absent are words like email or price that are often translatable labels in language files.
Language File Translation
Translates your lang/en/*.php files into every configured locale, preserving nested key structure:
# Everything, queued php artisan laraglot:files # One file, one locale, synchronously php artisan laraglot:files auth --locale=fr --sync # Overwrite existing translation files php artisan laraglot:files --force
Files listed in exclude_files (framework files like validation) are skipped.
Artisan Commands
laraglot:sync
Scans registered models and dispatches translation jobs for anything new or changed.
php artisan laraglot:sync # all registered models php artisan laraglot:sync "App\Models\Page" # one model php artisan laraglot:sync --locale=fr --locale=de # limit target locales php artisan laraglot:sync --force # ignore change detection
laraglot:files
Translates PHP language files. See the section above for options (file?, --locale=, --force, --sync).
laraglot:audit
Reports every language-file key and model attribute whose value is byte-identical to the source locale — the signature of a silent translation fallback — and can repair them in place:
php artisan laraglot:audit # audit all files + registered models php artisan laraglot:audit --locale=ar --locale=de php artisan laraglot:audit --files-only --file=footer php artisan laraglot:audit --models-only "--model=App\Models\Faq" php artisan laraglot:audit --repair # re-translate ONLY the identical values php artisan laraglot:audit --threshold=0.5 # flag at ≥50% identical (default 0.85)
- Exits non-zero when any file/model locale is flagged at or above the threshold, so it works as a CI gate or scheduled canary (
Schedule::command('laraglot:audit')). --repairre-translates only the values still equal to the source (missing keys included) with a forced cache refresh, so fixing fallbacks never re-bills the whole corpus and evicts poisoned cache entries.- Values that are supposed to be identical across locales belong in
lara-glot.audit.allowlist— they're never flagged or re-billed. The default list ships with common acronyms, units, and platform names ("OK", "SMS", "PDF", "iOS", "PayPal", …); extend it in the published config.ignored_keysandprotected_value_patternsare excluded automatically.
laraglot:retry
Whenever a value falls back to source text during a translation run, LaraGlot records it in the lara_glot_translation_retries table — a fallback is never silent. This command re-attempts those units:
php artisan laraglot:retry # process everything currently due
php artisan laraglot:retry --limit=100
- Bounded, with back-off: each failed attempt reschedules the unit at
base_delay_minutes × 2^attempts(default 30 min base); aftermax_attempts(default 5) the unit is parked as exhausted and the command exits non-zero so it surfaces for human review instead of re-billing forever. Tune vialara-glot.retry. - Never wastes an API call: units whose source text changed, whose key was removed, or that were already healed elsewhere (an audit
--repair, a--forcerun, a manual edit) are resolved without touching the driver. - Idempotent + resumable: run it on a schedule (
Schedule::command('laraglot:retry')->hourly()) and coverage converges; exhausted strings that are legitimately identical belong inlara-glot.audit.allowlist.
Queue Setup
Heavy work runs on Laravel queues (translations by default):
php artisan queue:work --queue=translations,default --timeout=620 --tries=4
For production, run the worker under Supervisor with --timeout above the model job timeout (600s).
Filament Integration
Register the plugin in your panel provider:
use Tonydev\LaraGlot\LaraGlotPlugin; public function panel(Panel $panel): Panel { return $panel->plugin(LaraGlotPlugin::make()); }
You get a management page showing translation coverage per model and language, with actions to queue missing or forced re-translations.
Custom Drivers
Register your own engine without forking the package. In a service provider's boot():
use Tonydev\LaraGlot\Services\TranslationService; TranslationService::extend('my-engine', fn () => new MyEngineDriver());
Then set LARAGLOT_DRIVER=my-engine. The driver needs to implement Tonydev\LaraGlot\Contracts\TranslationDriverInterface — two methods, translate() and translateBatch(). Extend AbstractLlmDriver instead if your engine is prompt-based and you want chunking, caching, deduplication, glossary support, and the review pass for free; then the only method to write is sendChunk().
Custom Prompts
The built-in LLM prompt asks for translations that read like a native speaker wrote them, matching the register of the source text. If you want full control:
'prompt' => 'Translate the JSON array from {source} to {target}. ... your rules ...',
The string replaces the built-in prompt entirely; {source} and {target} are substituted. Changing it invalidates the affected cache entries, same as glossary changes.
Failure Behaviour
Two rules, depending on where you are:
At render time (a page reading a translation), failure is graceful: a failed translation returns the original string, never an exception and never half-translated token soup. Placeholders are restored even on the fallback path — users never see __VAR_0__.
In jobs and commands, failure is loud — a batch that writes English into lang/ar/ while reporting success is corruption, not resilience:
- Chunks retry with exponential back-off; transient failures (timeouts, 429s, 5xx) additionally retry in halved sub-chunks before any string falls back.
- A batch driver error throws — the job fails visibly and retries, and nothing gets cached or written.
- A file whose output is 100% identical to the source is treated as a failed run: not written, job failed.
- Any individual string that does fall back is recorded in the retry table and re-attempted by
laraglot:retry— a fallback is acceptable, a silent fallback is not. - Corrupt cache entries read as misses and are evicted automatically; forced refreshes (
--force,--repair, retries) bypass every cache layer, including the driver's own. - Everything is logged under the
[LaraGlot:Driver]tag.
Upgrading to 2.1
2.1 fixes a data-corrupting bug and tightens failure behaviour. Read this if you ran 2.0 in production:
- Critical fix — multibyte corruption: under Laravel's default
processconcurrency driver, translations to Arabic, Chinese, Hindi, and other multibyte scripts were silently replaced with the English source (unserialize(): Error at offset …in the log was the only trace). Fixed for both file and model translation. Audit your existing locales —php artisan laraglot:auditreports the damage and--repairheals only the affected values. - Filament v4/v5 only: the admin page uses the Schemas API and a non-static
$view; Filament 3 panels are no longer supported. - Run
php artisan migrate: creates the optionallara_glot_translation_retriestable used bylaraglot:retry. - Behaviour change: batch translation failures in jobs/commands now throw instead of silently writing source text; a 100% source-identical file fails the job instead of being written. Escape hatch:
LARAGLOT_FAIL_ON_FULL_FALLBACK=false. ignored_keysdefaults grew substantially (identifiers, media paths, timestamps, …). If any of the new defaults collide with translatable label keys in your app, remove them in your published config.- New config blocks —
retry,audit(threshold + allowlist),fallback,protected_value_patterns, per-drivertimeout,drivers.google.max_length— all optional with sensible defaults; re-publish the config to pick them up.
Upgrading from 1.x
ignored_keysdefaults shrank to a generic list. If you relied on removed defaults (cta_url,is_active, and similar app-specific keys), add them to your published config.- Ollama caching changed from chunk-level to string-level. Old chunk cache entries are simply ignored; strings re-cache individually on the next run.
- New config blocks (
glossary,review,prompt,drivers.anthropic) are optional — re-publish the config or copy them in if you want the new features. - Model defaults moved to current generations (
gpt-5-mini,llama3.2). Pin your previous model via env if you need to stay put.
License
MIT. Created by TONY THE DEVELOPER
tonydev/lara-glot 适用场景与选型建议
tonydev/lara-glot 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 11 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 04 月 05 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「i18n」 「translation」 「laravel」 「localization」 「google-translate」 「deepl」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 tonydev/lara-glot 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 tonydev/lara-glot 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 tonydev/lara-glot 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Easy to use i18n translation PHP class for multi-language websites
A custom URL rule class for Yii 2 which allows to create translated URL rules
A Laravel Eloquent model trait for translatable resource
Bureaux A Partager Edit - Parse, validate, manipulate, and display dates in PHP w/ i18n support. Inspired by moment.js
Nette Framework adapter for I18n package
Yii2 integration for AirBnB Polyglot.js
统计信息
- 总下载量: 11
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 32
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-04-05