edulazaro/laratext
Laratext lets Laravel apps define translations with both a key and readable text, keeping language files clear and stable. It auto-translates missing keys via OpenAI or Google Translate, plus offers a @text Blade directive, text() helper, and scan/update commands.
php artisan laratext:scan --dry to size the work in minutes — it reports new keys, drifted source texts, and orphan keys without writing anything or calling the translator, so you can cost a market before committing to it.__() with JSON keys (fragile, unreadable), a SaaS TMS like Crowdin/Lokalise/Phrase (per-seat and per-word contracts, translator UI, but strings leave the repo), or this package (in-repo strings, CLI/CI-driven, usage-based API spend). The key+default-text pattern is the differentiator: keys stay stable while source text stays human-readable, which removes the classic "do we key on strings or on opaque IDs" compromise.default_translator / --translator=claude. You can benchmark OpenAI, Claude, and Google on the same corpus with --resync and pick per quality and cost, rather than negotiating a contract blind.--dry costs nothing, whereas dropping --write still spends tokens and discards results. That distinction belongs in a written runbook. Similarly, context (product description, formality, do-not-translate terms) and key names are sent to the model — so translation quality becomes a product-voice decision the PM actually owns, not a black box.laratext:lock the key. That is a staffing decision (who reviews ru, pl, ar) that should be budgeted with the locale, not discovered after launch.nav.home meaningfully improves output while common.name doesn't. That justifies codifying a key-naming convention in the style guide — a cheap process decision with measurable quality upside.--prune plus drift warnings turn orphan keys and stale translations into a health number you can put on a dashboard rather than an invisible liability.Consider adoption when:
text('key', 'Default') is the happy path.Look elsewhere — or defer — when:
text("activity.{$type}")). The scanner can't see them, so those keys are never created or translated and users see raw keys with no warning — a real correctness trap, not a nitpick.One pre-flight decision if you adopt: set source_locale explicitly before the first --write. If APP_LOCALE differs from the language your source strings are written in, every key looks drifted and a single run retranslates the entire project — a silent, expensive mistake.
For executives: Today, shipping a new language is a stop-the-line engineering task plus a vendor negotiation. This package collapses it into a config file and one command: we write copy in our source language inline in the code, a scan finds new and changed strings, and an AI translation provider renders them into the locales we choose. Launching a market becomes an API bill and a review pass instead of a sprint. It's MIT-licensed, lives in our own repository so it goes through normal code review, and the translation provider is a swappable class — we can switch vendors or fall back to manual without rework. The honest caveat is maturity: it's a small, young project with one maintainer, so I'd propose a scoped pilot on a single locale with a quality gate before we commit further.
For engineering: One composer require, publish config/texts.php, and replace __() call sites with text('key', 'Default text') / @text('key', 'Default text'). It preserves Laravel semantics you already rely on — :placeholder replacement and the | plural selector, including explicit ranges like [1,19] — and uses Laravel's own plural chooser, so per-language rules apply. Lifecycle is CLI-driven: --dry previews at zero cost and zero writes, --write applies, and --only-missing, --resync, --prune, --lang, --translator, and --diff cover the rest. Two constraints to design around: the scanner is static, so keys must be literals (refactor interpolated keys into a match); and the AI decides plural form counts, so review and laratext:lock the keys that matter. It's tested across every PHP 8.2–8.5 × Laravel 10–13 combination on each push and weekly against new releases.
Proposed decision ask: approve a bounded pilot — one locale, one translator, --dry to size the corpus, a named native-speaker reviewer for plural-heavy languages, and a go/no-go on sampled translation quality before any broader rollout.
How can I help you explore Laravel packages today?