Skip to main content

Black Friday 2025! Only until December 1st: coupon FRIDAY25 for 40% off Yearly/Lifetime membership!

Read more here

Language Switcher from URL

Premium
4 min read

Adding a language selector is crucial to any multilingual application. In this practical example, we'll add a language selector to our navigation bar:

In this first example, we will define the language from the URL segment, like /en/about or /es/register.


Setup

Here's our plan to set up a language selector:

  • Configuration: add a language list to the config - this will be used to display the language selector
  • Middleware: Add a Middleware to handle language change and URL redirection
  • Routes: Modify Routes to use the Middleware
  • Views: Add a language selector to our Views
  • Redirects: Modify our redirects for authentication

Let's start with the configuration:...

The Full Lesson is Only for Premium Members

Want to access all of our courses? (29 h 14 min)

You also get:

54 courses
Premium tutorials
Access to repositories
Private Discord
Get Premium for $129/year or $29/month

Already a member? Login here

Comments & Discussion

AA
Ali Al Qahtani ✓ Link copied!

In app/Http/Controllers/RegisteredUserController

after user register should redirect to right locale

Should change this:

return redirect(RouteServiceProvider::HOME);

To:

return redirect(app()->getLocale() .RouteServiceProvider::HOME);

M
Modestas ✓ Link copied!

Sorry for late reply, just checked and it indeed sometimes causes this issue. Updating the article!

BD
Bless Darah ✓ Link copied!

While the url works, I find it not working for routes like blog detail where I pass a dynamic slug. I keep having the error where it says route paramter for {slug} not provided. To solve this problem, you can create a helper function in app dir called helpers.php and add in this function:

function getLocaleUrlPath(string $locale)
{
    $currentPath = URL::full();
    return Str::replace('/'. strtolower(app()->getLocale()), '/' . $locale, $currentPath);
}

And in my navigation links with pure blade, I am able to use this:

<a href="{{URL(getLocaleUrlpath($locale))}}" class="login-button"> {{ strtoupper($locale) }} </a>

If there are any issues then let me know.

M
Modestas ✓ Link copied!

Hm, not sure I understand from where the issue comes. Could you add a bit more details? Would love to update the lesson if there's an issue!

BD
Bless Darah ✓ Link copied!

From the language switcher, if you're on the route for example https://localhost:8000/en/blog/first-post and you click on the language switcher, it will break the app because the only param that is supplied is the $locale whereas we also need to pass the slug of the post to reload that current route.

M
Modestas ✓ Link copied!

Hmm, could you try to use this:

route(\Illuminate\Support\Facades\Route::currentRouteName(), array_merge(Route::current()->parameters(),['locale' => $locale]))

in your resources/views/layouts/navigation.blade.php language selection and let me know if the problem persists?

BD
Bless Darah ✓ Link copied!

It works now like magic!!!

M
Modestas ✓ Link copied!

Awesome to hear! Updating the article, thank you!

BD
Bless Darah ✓ Link copied!

Thank you too. Now I got to learn about the Route::current()->parameters() function. Pretty neat!

DD
Daniel Drummond ✓ Link copied!

I'm having problems with this technique. The default language of my application is Brazilian Portuguese (pt_BR) and when I apply this technique it would only have to be pt and when I call the base URL the system calls it as pt_BR and gives an 404 error. How to resolve this issue?

M
Modestas ✓ Link copied!

Hi, can you add what error you have? Or where does the 404 come from?

Or better yet, add the configuration changes you made for pt_BR and I'll try to reproduce it and help you out

DD
Daniel Drummond ✓ Link copied!

It gives 404 because the system transforms the URL with /pt_BR since the correct configuration for Portuguese (Brazil) is this and not pt

DD
Daniel Drummond ✓ Link copied!

I change locate to pt_BR in app.php in config folder

DD
Daniel Drummond ✓ Link copied!

ops locale = 'pt_BR', sorry. Thanks

M
Modestas ✓ Link copied!

Okay, got it. Looked into the code and it is because of the Route::prefix() in your web.php file.

To fix your issue, you can use a different regex (by default we expect 2 characters only):

Route::prefix('{locale}')
    ->where(['locale' => '[a-zA-Z]{2}_[a-zA-Z]{2}'])

This should allow you to use pt_BR as a locale

GG
Gregor Gander ✓ Link copied!

Hello, I'm using "laravel/breeze": "^1.24" and I'm facing the problem that breeze no longer publishes auth controllers to the app directory

app/Http/Controllers/Auth/RegisteredUserController.php

rather it is now located in the vendor

vendor/laravel/breeze/stubs/default/app/Http/Controllers/Auth/RegisteredUserController.php

Is there a way I can have this back int the app directory? so I can modify the return redirect with the app()->getLocale:

return redirect(app()->getLocale() . RouteServiceProvider::HOME);

If I modify it in vendor it might be overwritten if I run composer update

M
Modestas ✓ Link copied!

Did you run php artisan breeze:install? Because that file that's located in the vendor is a stub (template) file and should not be used/modified.

ps. Never modify vendor files!

GG
Gregor Gander ✓ Link copied!

Thx for your reply.

Yes I did, seems to be a Livewire thing. Seems the authentication is implemented differently with the Breeze Livewire Stack. For now I was not able to make it completly multilingual.

I 've created two new projects for testing. During the process of php artisan breeze:install the installer asks Which Breeze stack would you like to install?

It works if you choose

  • Blade with Alpine

Mutiple Controllers including RegisteredUserController.php are in the app/Http/Controllers/Auth/-Directory

If you choose

  • Livewire (Volt Class API) with Alpine
  • Livewire (Volt Functional API) with Alpine

There is only VerifyEmailController.php in the app/Http/Controllers/Auth/-Directory

M
Modestas ✓ Link copied!

Oh... Now that makes sense!

We have not looked into the Livewire stack yet, as that stack uses Volt API. These files are in different locations (can't tell where, except for functional API is in... views...).

M
Modestas ✓ Link copied!

Maybe this video would help you:

https://www.youtube.com/watch?v=nzD0Uy7VMPE

GG
Gregor Gander ✓ Link copied!

With the livewire stack the auth stuff is implemented in resources/views/livewire/pages/auth/ instead.

In the file login.blade.php for example there is the redirect:

$this->redirect(
        session('url.intended', RouteServiceProvider::HOME),
        navigate: true
    );

So the locale needs to be prepended there.

$this->redirect(
        session('url.intended', app()->getLocale() . RouteServiceProvider::HOME),
        navigate: true
    );

Thank you for pointing me in the right direction!

M
Modestas ✓ Link copied!

Glad it helped! And thanks for the example.

SR
Steen Rabol ✓ Link copied!

Hi

I really like the simplicity in this solution, but I'm facing an issue.

I cannot figure if it is a livewire issue or an issue with the solution. I followed the above and it worked quit well until I accessede a page wher I have a Livewire table component - nothing fancy just a simple pagination compnent and it has some actions for each row. The action calls a normal controller function, but all the sudden my model binding stopped working.

The error I'm getting is like this:

App\Http\Controllers\UserDocTemplateController::setupTemplateFields(): Argument #1 ($userDocTemplate) must be of type App\Models\UserDocTemplate, string given, called in /Users/rabol/code/web/sign/vendor/laravel/framework/src/Illuminate/Routing/Controller.php on line 54

another error I have seen is is like this:

Missing required parameter for [Route: unsubscribe] [URI: {locale}/unsubscribe/{id?}/{type?}] [Missing parameter: locale].

this is the code:

$listUnsubscribeHeader = $listUnsubscribeHeader.'<'.route('unsubscribe', ['id' => '', 'type' => $type]).'>';

as mentioned I don't know if it's related to Livewire or something else

M
Modestas ✓ Link copied!

Take a look at url example for create button - all routes have to have a parent parameter passed. otherwise this will be the issue

SR
Steen Rabol ✓ Link copied!

aha so I always need to add the locale something like this.

return redirect()->route('my-route', ['locale' => app()->getLocale()]);

M
Modestas ✓ Link copied!

yep! it does need parent always

SR
Steen Rabol ✓ Link copied!

hmm

this causes error

route('users.documents.tracking',['locale' => app()->getLocale(),'userDoc' => $userDoc->id])

App\Http\Controllers\UserDocController::tracking(): Argument #1 ($userDoc) must be of type App\Models\UserDoc, string given, called in /Users/rabol/code/web/sign/vendor/laravel/framework/src/Illuminate/Routing/Controller.php on line 54

the language is passed not the model

looks like all routes that have a parameter like this route_name/{varName} have a issue

M
Modestas ✓ Link copied!

Not sure what could be there. And it's not so easy to give a potential solution wihout diving deep in the code and debugging, sorry

SR
Steen Rabol ✓ Link copied!

if you have a controller method like this:

public fuction show(Post $post)

then you could have a route like this:

Route:get(/{post}/show, [PostController::class, 'show'])->name('show')

With the solution from this lesson you will have a problem as all routes is prefixed with 'locale' so you rcontroller method actually needs to look like this:

public fuction show(string $locale, Post $post)

SR
Steen Rabol ✓ Link copied!

Try this:

git clone git@github.com:LaravelDaily/laravel-localization-course.git demo

cd demo

composer install

copy .env.example .env

modify .env

php artisan key:generate

create a db

php artisan make:model Post --all

modify the post migration

php artisan migrate

update the post factory

create a some postes via tinker

Follow the lesson

add this to your routes/web.php

Route::get('/posts/{post}/show', [PostController::class,'show'])->name('post.show');

then open your browser (I use laravel Valet)

http://demo.test/en/posts/1/show

then you can see the error

App\Http\Controllers\PostController::show(): Argument #1 ($post) must be of type App\Models\Post, string given, called in /Users/rabol/code/web/demo/vendor/laravel/framework/src/Illuminate/Routing/Controller.php on line 54

M
Modestas ✓ Link copied!

Yes, you are correct about this. You do have to always include this:

public function show(string $locale, Post $post)
    {
        dd($post);
    }
		```
		
		Otherwise it will indeed throw the error you are mentioning. This is because the first parameter in our URL is the locale and not the model. That's how Laravel model binding works
RK
Ruslan Kovalchuk ✓ Link copied!

Could you please tell me how to make the 'en' locale be used by default without adding it to the url, while the other locales are already added?

M
Modestas ✓ Link copied!

To do this efficiently, I would recommend you to use database/cookie based token for it.

The problem is - our language key is the FIRST parameter on the URL and removing it, can cause the page to think that /page/post needs to load page as our language.

Of course, you can play around and cover the URL defaults/parameter checks, but that's a bigger topic than I can fit in a comment, sorry

RK
Ruslan Kovalchuk ✓ Link copied!

Thank you