Laratext

Laratext names a string with a stable key and a readable default in the same call, then fills in the other languages for you from a scan of your source.
Installation
composer require edulazaro/laratext
php artisan vendor:publish --tag="texts"
That publishes config/texts.php, where you list the languages you support and the service that translates them.
Naming a string
The core is one helper. You give it a key and the readable text as a default, and it returns the translation for the current locale, falling back to your default when there is not one yet.
text('auth.failed', 'These credentials do not match our records.');
In Blade the same thing is a directive.
@text('auth.failed', 'These credentials do not match our records.')
The key is stable, so you can reword the English freely. The sentence sits in the template, so you read it without opening a language file.
If you pass only a key with no default, the string still resolves: the scan turns a key like contact_us into a readable "Contact Us".
Placeholders
Placeholders use Laravel's :name syntax and take a replacement array. They survive translation, so :name still means :name in Spanish.
text('welcome.user', 'Welcome, :name!', ['name' => $user->name]);
Configuring languages and the translator
In config/texts.php you declare which languages you support and which service translates them.
'default_translator' => 'openai',
'translators' => [
'openai' => EduLazaro\Laratext\Translators\OpenAITranslator::class,
'google' => EduLazaro\Laratext\Translators\GoogleTranslator::class,
'claude' => EduLazaro\Laratext\Translators\ClaudeTranslator::class,
],
'languages' => [
'en' => 'English',
'es' => 'Spanish',
'fr' => 'French',
],
OpenAI, Google and Claude ship with the package. The Claude translator runs through the Messages API with prompt caching, so repeated batches within a single scan reuse the cached instructions.
The scan command
The scan walks your PHP and Blade files, collects every text() and @text key with its default, works out which are missing from each language, translates only those, and writes them into lang/es.json, lang/fr.json and so on.
php artisan laratext:scan --write
The base language is your app.locale; the targets are the languages in the config.
| Option | What it does |
|---|---|
--dry |
Preview without writing |
--diff |
Show what would change |
--lang=es |
Restrict to one language |
--translator=google |
Override the configured service |
--resync |
Retranslate keys whose source text actually changed, not only the missing ones |
Writing your own translator
The translation service is an interface, so you can point it at anything: a different API, a glossary-aware endpoint, a human review queue.
php artisan make:translator DeepLTranslator
That scaffolds a class extending the package's Translator base and implementing TranslatorInterface. You implement one method, which translates a single string into every target language and returns them keyed by language code.
namespace App\Translators;
use EduLazaro\Laratext\Contracts\TranslatorInterface;
use EduLazaro\Laratext\Translator;
class DeepLTranslator extends Translator implements TranslatorInterface
{
public function translate(string $text, string $from, array $to): array
{
$results = [];
foreach ($to as $language) {
$results[$language] = $this->callDeepL($text, $from, $language);
}
return $results;
}
}
Extending the base Translator hands you batchTranslate, which splits a large set of strings into safe batches capped by character count and item count.
If your API accepts many strings per call, override translateMany to translate a whole batch in one request instead of one call per string. That is the difference between one round trip and a hundred.
Register the class in the translators map in config/texts.php and select it with --translator, or set it as the default.
built and maintained by Edu Lazaro · MIT license