Courses

Multi-Language Laravel 11: All You Need to Know

Language Switcher with Sessions and DB

Summary of this lesson:
- Add language field to users table
- Create ChangeLanguageController for language switching
- Implement SetLocale middleware for user/guest language preferences
- Store language preference in database for logged-in users
- Use session for guest language selection

Another example of how we can add a language selector to our application is by using sessions and a DB table to store the user's preferred language.

This example will not use a URL to determine the language which will allow us to use the same URL for all languages.


Setup

Our setup process for Database stored language selection will touch these things:

  • Configuration: Adding a language list to our configuration - this will be used to display the language selector
  • DB Structure: Creating a new field on the users table called language
  • Controller: Create a controller that will handle the language change
  • Middleware: Add a middleware to handle language settings based on user's preferences
  • Views: Adding a language selector to our views
  • Routes: Adding a route that will allow us to change the language

Let's start with the configuration:

config/app.php

// ...
'locale' => 'en', // <-- Locate this line and add `available_locales` below it
 
'available_locales' => [
'en',
'es',
],
// ...

Now we need to add a new field to our users table:

php artisan make:migration add_language_to_users_table

Migration

Schema::table('users', function (Blueprint $table) {
$table->string('language')->default('en');
});

app/Models/User.php

protected $fillable = [
// ...
'language'
];

Let's make a Controller that will switch user's language and save it to remember it for the next time:

app/Http/Controllers/ChangeLanguageController.php

public function __invoke($locale)
{
// Check if the locale is available and valid
if (!in_array($locale, config('app.available_locales'))) {
return redirect()->back();
}
 
if (Auth::check()) {
// Update the user's language preference in the database
Auth::user()->update(['language' => $locale]);
} else {
// Set the language in the session for guests
session()->put('locale', $locale);
}
 
// Redirect back to the previous page
return redirect()->back();
}

Now we can create our Middleware:

php artisan make:middleware SetLocale

app/Http/Middleware/SetLocale.php

use Auth;
use Carbon\Carbon;
 
// ...
 
public function handle(Request $request, Closure $next): Response
{
// Logged-in users use their own language preference
if (Auth::check()) {
app()->setLocale(Auth::user()->language);
Carbon::setLocale(Auth::user()->language);
// Guests use the language set in the session
} else {
app()->setLocale(session('locale', 'en'));
Carbon::setLocale(session('locale', 'en'));
}
 
return $next($request);
}

Register our middleware in our bootstrap/app.php file:

bootstrap/app.php

return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->append(\App\Http\Middleware\SetLocale::class);
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();

Display the language selector in our views:

resources/views/layouts/navigation.blade.php

{{-- ... --}}
@foreach(config('app.available_locales') as $locale)
<x-nav-link
:href="route('change-locale', $locale)"
:active="app()->getLocale() == $locale">
{{ strtoupper($locale) }}
</x-nav-link>
@endforeach
<x-dropdown align="right" width="48">
{{-- ... --}}

And lastly, we need to modify our routes to use our Middleware and contain an endpoint to change the language:

routes/web.php

use App\Http\Controllers\ChangeLanguageController;
// ...
 
// Our language change endpoint
Route::get('lang/{locale}', ChangeLanguageController::class)->name('change-locale');
 
Route::middleware(SetLocale::class) // <-- Our middleware to set the language
->group(function () {
Route::get('/', function () {
return view('welcome');
});
 
 
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
 
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
 
// ...
});
 
require __DIR__ . '/auth.php';
});

That's it! Now we can change the language by clicking on the language selector in the navigation bar and it will remember the language for our users indefinitely while guests will have their language set for the current session.


Transforming Guest Language to a User Language Preference

One additional step that we can take to improve the experience and avoid resetting the language for our new users is to take what they set in the session and transform it into a language preference in the database:

app/Http/Controllers/Auth/RegisteredUserController.php

// ...
public function store(Request $request): RedirectResponse
{
// ...
 
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
// Here we set the language preference to the language set in the session
'language' => session('locale', config('app.locale')),
]);
 
// ...
}
// ...

Repository: https://github.com/LaravelDaily/laravel11-localization-course/tree/lesson/session-language-switching

Previous: Language Switcher from URL
avatar

I have followed the chapter several times, but I always have a problem

👍 2
avatar

What problem exactly? The repository code is available, so maybe you can compare your code?

avatar

There seems to be an issue with the 'ChangeLanguageController'. The message says: "ChangeLanguageController not invokable: The controller class ChangeLanguageController is not invokable. Did you forget to add the __invoke method or is the controller's method missing in your routes file?" Hm.

avatar

Hm, it seems that I forgot to add a use at the top. Please import the controller using:


use App\Http\Controllers\ChangeLanguageController;

// ... Route

and it will work!

avatar

It's OK for me thanks everyone

avatar
You can use Markdown
avatar

If you have installed Jetstream and Livewire recently and like to do the last step inte this tutorial, "Transforming Guest Language to a User Language Preference", the Controller is in another place. Open app/Action/Fortify/CreateNewUser.php add the line "'language' => session('locale', config('app.locale'))," to

return User::create([
  ...
	'language' => session('locale', config('app.locale')),
])
👍 2
avatar
You can use Markdown
avatar

And, If you have livewire installed, the middleware in this torurial doesn't affect the fortify nor the livewire routes. They need to be ignored in vendor and then all teh routes copied to your web.php. I follwed successfully Jeremy Belolo's updated (from Sep 24 2020) solution here: https://stackoverflow.com/questions/64016900/laravel-localization-and-routes-from-jetstream-fortify

avatar
You can use Markdown
avatar

Correct me if I'm wrong but it seems we would need both options (URL & Sessions) to make it bulletproof.

URL is necessary for SEO and when not logged in. Session is necessary so that a logged in user doesn't have to switch his language.

Or am I missing something?

avatar

You are correct, both are required for a seamless implementation

avatar
You can use Markdown
avatar

hellooo. i just start to learn multi language. but i have a problem

please see my route below:

Route::prefix('{locale}') ->where(['locale' => '[a-zA-Z]{2}']) ->middleware('setlocale') ->group(function () { Route::get('location/{slug}', [LocationController::class, 'show'])->name('locations.show'); });

and also this is my controller:

public function show(string $slug) { $location_detail = Location::where('status', '1')->where('slug', $slug)->first(); return view('front.location-detail')->with(compact('location_detail')); }

when i tried to access this url: http://localhost:8000/en/location everything is working fine. but when i access this url: http://localhost:8000/en/location/jungle the data is not showing. they said {{ $location_detail->title }} is null

can somebody please help me with this?

avatar

Hi, please dump the $location_detail inside of your controller and check if it was found in the first place.

You can also check fi $slug is set correctly

avatar

hi. the data in database is there. maybe the problem is here: $location_detail = Location::where('status', '1')->where('slug', $slug)->first()

do i need to setup locale to call the data from database?

avatar

the problem is when calling the data from database. it should be like this:

$location_detail = Location::where('status', '1')->where('slug->' . $locale, $slug)->first();

all fixed now

avatar
You can use Markdown
avatar
You can use Markdown