Add Live Translations to Laravel in 5 Minutes
You've got a Laravel app. You need translations. And you'd rather not build a deployment pipeline around every copy change. Here's how to get live, instant-update translations in 5 minutes: one composer package, four lines in your .env, one artisan command. You keep using __() exactly as you do today. The only requirements are Laravel 10+ and PHP 8.2 or higher.
The Problem in a Real Laravel App
Laravel's localization system is solid, but the workflow around it isn't. Your translations live in PHP files inside the repo:
<?php
// lang/es/messages.php
return [
'welcome' => 'Bienvenido a nuestra aplicación',
'orders_empty' => 'Todavía no tienes pedidos.',
'invoice_ready' => 'Tu factura está lista para descargar.',
];
Which means every change, no matter how small, goes through the full engineering pipeline:
git checkout -b fix/spanish-typo
# edit lang/es/messages.php by hand
git commit -am "Fix typo in Spanish welcome message"
git push origin fix/spanish-typo
# open a PR, wait for review, merge, deploy
That's a code review and a deployment to fix one word. Worse, the person who actually spotted the typo (support, marketing, a native speaker on the team) can't fix it themselves. They file a ticket, a developer picks it up two days later, and the broken string sits in production the whole time.
If your app ships to three or four locales, multiply that friction by every language. This is the exact gap between plain lang files and a managed workflow, and it's worth understanding before you commit either way. We wrote up the full comparison in LangSyncer vs plain Laravel lang files.
Here's the five-minute alternative.
The 5-Minute Setup
Minute 1: Create Your Account
Go to LangSyncer, click Start for Free, and register with your email. You land on the Free tier: 3 projects, 100 AI translations per month, and 2 users. No credit card needed.
Minute 2: Create a Project
- Navigate to Projects in the sidebar and click New Project
- Complete the project details and save
- Open the project's actions modal and go to the API Key & CDN tab
- Copy your API Key and the CDN URL shown there
You'll need both values in a moment. Don't guess the CDN URL; it's specific to your project.
Minute 3: Install the Package
composer require headwires/translator-client
php artisan vendor:publish --tag=translator-client-config
The first command pulls in the Laravel client, the second publishes its config file so you can tweak defaults later if you want to.
Minute 4: Configure Your Environment
Add this to your .env, pasting the two values you copied in minute 2:
CLI_TRANSLATOR_API_KEY=your-api-key-here
CLI_TRANSLATOR_CDN_URL=https://cdn.langsyncer.com
CLI_TRANSLATOR_CLIENT_MODE=live
CLI_TRANSLATOR_CLIENT_WEBHOOK_ENABLED=true
The CLI_TRANSLATOR_CLIENT_MODE setting deserves a sentence. There are three modes: static stores translations as files and updates when you run php artisan translator:sync (or via webhook); live serves translations from cache and picks up published changes in seconds through webhooks; auto detects the best mode for your environment, choosing live for Vapor and other serverless setups. For the instant-update experience this post is about, use live with webhooks enabled.
There are more optional knobs (cache TTL, sync strategy, a custom webhook route) documented in the getting started guide if you need them. The four lines above are enough to go live.
Minute 5: Warm Up and Test
Pre-cache your translations so the first request doesn't pay a network round trip:
php artisan translator:warmup
This downloads everything from the CDN and caches it locally. Now test the loop end to end:
- In the LangSyncer dashboard, open your project's Translations
- Create a translation:
messages.hello= "Hello World" - Click Publish
- Refresh your Laravel app
{{ __('messages.hello') }}
{{-- Output: Hello World --}}
Change the value in LangSyncer, publish, refresh. Instant update. No commit, no PR, no deploy. If something looks off, php artisan translator:status shows your sync state and statistics.
Your First Real Workflow
With the plumbing in place, here's what a typical first session looks like. Add the keys your app actually uses (auth.login, nav.dashboard, errors.not_found) and fill in your base language values. Creating keys is free and unlimited on every tier, so don't ration them. Then click Translate Missing and let AI fill in the other languages; a project with 100 keys and a handful of languages finishes in minutes. Spot-check the output, publish, and your app picks up everything at once.
That's the whole cycle: create, translate, review, publish. Most teams repeat it every time they ship a feature.
When This Isn't the Right Fit
Honest trade-offs, because live translations aren't free lunch:
If your app has one locale and the copy changes twice a year, plain lang files in the repo are simpler. You get code review on every string and zero external dependencies. A managed platform earns its keep when non-developers edit copy or you maintain multiple languages.
Live mode also introduces a runtime dependency on your cache and webhook endpoint. If you'd rather keep translations as plain files on disk with an explicit sync step, run static mode instead: same dashboard workflow, but updates land when you run translator:sync, which fits teams that want translation changes to flow through their normal release cadence.
And note the tier boundaries: the Free tier caps you at 3 projects and 2 users. The Icebreaker tier (10 projects, 3,000 AI translations per month, 10 users) comes with a 7-day free trial and no upfront charge, so you can test the full workflow before paying.
Gotchas
- Copy the CDN URL from your project's API Key & CDN tab. It's shown right next to your API key; a wrong base URL is the most common cause of empty translations.
- The client mode defaults to
static. If you skipCLI_TRANSLATOR_CLIENT_MODE=live, publishing in the dashboard won't update your app until you runtranslator:sync. - Webhooks power the "instant" part. Keep
CLI_TRANSLATOR_CLIENT_WEBHOOK_ENABLED=trueand make sure the default/api/translator/webhookroute is reachable from the outside.
Related Posts
- Live vs Static Mode: Which One Should You Use?
- Migrate Your Existing Laravel Translations in Minutes
- Translate Your Entire App in Seconds with AI