DEV Community

Cover image for Create multilanguage apps in Laravel with Laratext
Eduardo Lázaro
Eduardo Lázaro

Posted on

Create multilanguage apps in Laravel with Laratext

You have a Laravel app in English and you need it in Spanish too. The built-in __() helper works, but it forces an awkward trade. Either your templates are full of __('some.dotted.key') you cannot read, or they hold the full sentence and every reworded string breaks its own translation. And either way you still write the Spanish by hand.

Laratext fixes both. You name each string with a key and a readable default in one call, and a scan command translates the missing ones into every language you support. Here it is, simplest first.

How to install

One Composer package, then publish the config file.

composer require edulazaro/laratext
php artisan vendor:publish --tag="texts"
Enter fullscreen mode Exit fullscreen mode

Step 1: name your strings

Replace your hardcoded strings, or your __() calls, with text() in PHP and @text in Blade. You pass a key and the English text as the default.

<h1>@text('home.hero_title', 'Find your next home')</h1>
<button>@text('common.save', 'Save changes')</button>
Enter fullscreen mode Exit fullscreen mode
$message = text('billing.trial_over', 'Your trial has ended.');
Enter fullscreen mode Exit fullscreen mode

The key is what the translation is stored under, so you reword the English later without breaking anything, and the English stays right there in the template so it reads cleanly. Dynamic values use Laravel's :name placeholders and survive translation intact.

@text('cart.summary', 'You have :count items.', ['count' => $cart->count()])
Enter fullscreen mode Exit fullscreen mode

Step 2: point it at a translator

Open the config/texts.php the publish step created. List the languages you support and pick the service that translates them. OpenAI, Google and Claude are built in.

'default_translator' => 'openai',

'languages' => [
    'en' => 'English',
    'es' => 'Spanish',
],
Enter fullscreen mode Exit fullscreen mode

The OpenAI translator reads its key from that config, which pulls OPENAI_API_KEY from your env by default.

OPENAI_API_KEY=sk-...
Enter fullscreen mode Exit fullscreen mode

The other two work the same way with their own keys. To translate through Claude instead, set default_translator to claude, which reads ANTHROPIC_API_KEY and runs on the Messages API with prompt caching, so repeated batches in a single scan reuse the cached instructions.

Step 3: scan and translate

Now run the scan. It reads every text() and @text call across your PHP and Blade files, finds the keys missing from Spanish, translates just those through the configured service, and writes them to lang/es.json.

php artisan laratext:scan --write
Enter fullscreen mode Exit fullscreen mode

Your English is the source, taken from app.locale, and every language in the config is a target. Before a real run, --dry lists what it would add and --diff shows the changes. You can also override the service for one run with --translator=claude. Later, when you reword an English string, --resync retranslates the keys whose source actually changed instead of only the brand-new ones.

php artisan laratext:scan --dry
php artisan laratext:scan --write --translator=claude
php artisan laratext:scan --write --resync
Enter fullscreen mode Exit fullscreen mode

One honest note: the OpenAI, Google and Claude translators all make real API calls, so a scan of a large app costs money on the provider side. Run --dry first, and translate one language at a time with --lang=es, if you want to keep it tight.

Writing your own translator

If none of the three built-in services fits, the translator is just an interface. Generate one.

php artisan make:translator DeepLTranslator
Enter fullscreen mode Exit fullscreen mode

You get a class extending the package's Translator base. Implement translate(), which takes one string and the target languages and returns the translations 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->deepl($text, $from, $language);
        }

        return $results;
    }
}
Enter fullscreen mode Exit fullscreen mode

Because it extends Translator, you also get batchTranslate, which chunks a big set of strings into safe batches before sending. If your API takes many strings at once, override translateMany to translate a batch in one request rather than one call per string. Then add the class to the translators list in config/texts.php and select it with --translator=deepl.

Wrapping up

That is a whole second language: name each string once with @text('key', 'English'), configure a translator, and run one scan to fill the rest. Swap the translator when you outgrow the defaults.

👉 Package on Packagist: https://packagist.org/packages/edulazaro/laratext
👉 Source on GitHub: https://github.com/edulazaro/laratext

Top comments (0)