Skip to main content

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? (36 h 00 min)

You also get:

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

Already a member? Login here

Ali Al Qahtani avatar

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);

Modestas avatar

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

Bless Darah avatar

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.

Modestas avatar

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!

Bless Darah avatar

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.

Modestas avatar

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?

🥳 1
Bless Darah avatar

It works now like magic!!!

👍 1
Modestas avatar

Awesome to hear! Updating the article, thank you!

👍 1
Bless Darah avatar

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

🥳 1
Daniel Drummond avatar

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?

Modestas avatar

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

👍 2
Daniel Drummond avatar

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

Daniel Drummond avatar

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

Daniel Drummond avatar

ops locale = 'pt_BR', sorry. Thanks

Modestas avatar

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

👍 1
Gregor Gander avatar

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

Modestas avatar

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!

Gregor Gander avatar

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

Modestas avatar

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...).

Gregor Gander avatar

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!

Modestas avatar

Glad it helped! And thanks for the example.

Steen Rabol avatar

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

Modestas avatar

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

Steen Rabol avatar

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

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

Modestas avatar

yep! it does need parent always

Steen Rabol avatar

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

Modestas avatar

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

Steen Rabol avatar

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)

Steen Rabol avatar

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

Modestas avatar

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
Ruslan Kovalchuk avatar

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?

👍 2
Modestas avatar

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

Ruslan Kovalchuk avatar

Thank you

We'd Love Your Feedback

Tell us what you like or what we can improve

Feel free to share anything you like or dislike about this page or the platform in general.