The text provided describes a simple method for localizing a Laravel application, focusing on translating parts of the application rather than content stored in the database or localizing URLs. The guide details how to set up translations for strings appearing in the Laravel application template.
- Tested in: Laravel 8
- Stack: Jetstream / Livewire + Blade/Tailwind CSS, but can be used anywhere with front-end modifications
- Documentation: https://laravel.com/docs/8.x/localization
Logic
The logic is quite straightforward, with no need for database intervention.
Language Settings
config/app.php:
...
'locales' => [
'en' => 'English',
'cs' => 'Czech',
],
...
Middleware Language
app/Http/Middleware/Language.php:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\URL;
class Language
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
App::setLocale($request->language);
URL::defaults(['language' => $request->language]);
$request->route()->forgetParameter('language');
return $next($request);
}
}
Adding Middleware to the Kernel
app/Http/Kernel.php:
...
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\Language::class, // Localization
],
...
];
...
Routes in web.php
routes/web.php:
...
Route::group([
'prefix' => "{language}",
'where' => ['language' => '[a-zA-Z]{2}']
], function () {
...
});
...
LanguageController.php Controller
app/Http/Controllers/LanguageController.php:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\URL;
class LanguageController extends Controller
{
public function switchLang(Request $request, $lang)
{
// $urlParts[1] == current lang
$urlParts = explode('/', str_ireplace(array('http://', 'https://'), '', url()->previous()));
$newUrl = str_replace(
'/' . $urlParts[1] . '/',
'/' . $lang . '/',
url()->previous()
);
App::setLocale($lang);
URL::defaults(['language' => $lang]);
return redirect()->to($newUrl);
}
}
Adding the Controller Route to web.php
routes/web.php:
...
Route::get('lang/{lang}', [App\Http\Controllers\LanguageController::class, 'switchLang'])->name('langSwitcher');
...
RouteServiceProvider.php
app/Providers/RouteServiceProvider.php:
...
public const DASHBOARD = '/en/dashboard';
...
Creating a Language Switcher
Example for use in the stack: Laravel, Jetstream – Livewire + Blade, Tailwindcss
resources/views/navigation-menu.blade.php:
Desktop
...
<!-- Language Dropdown -->
<div class="ml-3 relative">
<x-jet-dropdown align="right" width="48">
<x-slot name="trigger">
<span class="inline-flex rounded-md">
<button type="button" class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition">
@lang(config('app.locales')[App::getLocale()])
<svg class="ml-2 -mr-0.5 h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</button>
</span>
</x-slot>
<x-slot name="content">
<!-- Language Switcher -->
<div class="block px-4 py-2 text-xs text-gray-400">
{{ __('Select language') }}
</div>
@foreach (config('app.locales') as $lang => $language)
@if ($lang !== App::getLocale())
<x-jet-dropdown-link href="{{ route('langSwitcher', $lang) }}">
@lang($language)
</x-jet-dropdown-link>
@endif
@endforeach
</x-slot>
</x-jet-dropdown>
</div>
...
Responsive Version
...
<div class="mt-3 space-y-1">
<div class="border-t border-gray-200"></div>
<div class="block px-4 py-2 text-xs text-gray-400">
{{ __('Select language') }}
</div>
@foreach (config('app.locales') as $lang => $language)
@if ($lang !== App::getLocale())
<x-jet-responsive-nav-link href="{{ route('langSwitcher', $lang) }}">
@lang($language)
</x-jet-responsive-nav-link>
@endif
@endforeach
<div class="border-t border-gray-200"></div>
</div>
...
Adding Translations
For example, resources/lang/cs.json:
{
"Dashboard": "Nástěnka",
"Select language": "Vyberte jazyk",
"Czech": "Čeština",
...
}
Result
Czech Version:
English Version:
Tip
If you use Jetstream with Livewire as in the example and want to localize the default routes, then a few more minor adjustments are necessary.
From the /vendor/jetstream/routes/ folder, copy the routes to your routes. The file can then look like this (routes/web.php):
<?php
use Illuminate\Support\Facades\Route;
use Laravel\Jetstream\Http\Controllers\CurrentTeamController;
use Laravel\Jetstream\Http\Controllers\Livewire\ApiTokenController;
use Laravel\Jetstream\Http\Controllers\Livewire\PrivacyPolicyController;
use Laravel\Jetstream\Http\Controllers\Livewire\TeamController;
use Laravel\Jetstream\Http\Controllers\Livewire\TermsOfServiceController;
use Laravel\Jetstream\Http\Controllers\Livewire\UserProfileController;
use Laravel\Jetstream\Http\Controllers\TeamInvitationController;
use Laravel\Jetstream\Jetstream;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('lang/{lang}', [App\Http\Controllers\LanguageController::class, 'switchLang'])->name('langSwitcher');
Route::get('/', function () {
return redirect()->route('login');
});
Route::group([
'prefix' => "{language}",
'where' => ['language' => '[a-zA-Z]{2}']
], function () {
Route::group(['middleware' => config('jetstream.middleware', ['web'])], function () {
if (Jetstream::hasTermsAndPrivacyPolicyFeature()) {
Route::get('/terms-of-service', [TermsOfServiceController::class, 'show'])->name('terms.show');
Route::get('/privacy-policy', [PrivacyPolicyController::class, 'show'])->name('policy.show');
}
Route::group(['middleware' => ['auth:sanctum', 'verified']], function () {
// User & Profile...
Route::get('/user/profile', [UserProfileController::class, 'show'])
->name('profile.show');
// API...
if (Jetstream::hasApiFeatures()) {
Route::get('/user/api-tokens', [ApiTokenController::class, 'index'])->name('api-tokens.index');
}
// Teams...
if (Jetstream::hasTeamFeatures()) {
Route::get('/teams/create', [TeamController::class, 'create'])->name('teams.create');
Route::get('/teams/{team}', [TeamController::class, 'show'])->name('teams.show');
Route::put('/current-team', [CurrentTeamController::class, 'update'])->name('current-team.update');
Route::get('/team-invitations/{invitation}', [TeamInvitationController::class, 'accept'])
->middleware(['signed'])
->name('team-invitations.accept');
}
});
});
Route::group(['middleware' => ['auth:sanctum', 'verified']], function () {
// Dashboard
Route::get('/dashboard', function () {
return view('dashboard');
})->name('dashboard');
});
});
For instance, if we modify the route for displaying the profile, like this (routes/web.php):
...
// Old value: /user/profile
Route::get('/profile', [UserProfileController::class, 'show'])->name('profile.show');
...
If we make the above change, the profile display will be available on our route /profile, and we can also use the default route /user/profile (loaded from the package – laravel/jetstream/routes/livewire.php). If we want to disable the default routes, then we must modify JetsreamServiceProvider.php (app/Providers/JetsreamServiceProvider.php).
...
public function register()
{
Jetstream::ignoreRoutes();
}
...