Build a Multi-Language Dynamic Website with Laravel Localization

A multi-language website helps businesses reach a global audience and improve user engagement. Creating a multi-language website in Laravel allows us to offer content in multiple languages. Implementing Laravel localization makes it easy for us to manage translations. Using Laravel middleware for language detection, we can automatically switch languages. We can add a language switcher dropdown in Laravel and enhance the user experience by allowing visitors to choose their preferred language.

In this Laravel tutorial, we will build a dynamic multi-language website using Laravel localization. We will store languages in the database and we do not need to change any programming code in case the business needs support for a new language. We only need to create language translations for the new language.

Laravel multi-language support Laravel localization tutorial

Watch YouTube Video

How to setup Laravel Localization

Before developing this application, let us understand how Laravel multilingual site setup is done. Laravel offers easy ways to retrieve strings for various languages using language files. It uses translation files for each language under the 'lang' folder within the Laravel project. By default, Laravel does not provide the 'lang' directory. We need to use the below artisan command to publish it.


php artisan lang:publish

Once this is done, we will have a 'lang\en' folder in the project root. Laravel provides translation files for English(en) by default. If we add another language, we need to create a folder with the name as the language code under the 'lang' directory. For example, if we add French (fr), we should have a folder 'lang\fr' and all the translation files related to French should be placed under it.

The application's default language is set in config\app.php file. There you will see the Localization parameters as given below:


<?php
  'locale' => env('APP_LOCALE', 'en'),

  'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),

  'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),

You can change the default language for an HTTP request using App::setLocale($locale); where $locale is the language code.

How to Create Laravel Language Files

A language file is a .php file with key=>value pairs for translations. For example, we can have a messages.php file under 'lang\en' and 'lang\fr' for English and French respectively. The contents of the files can be as given below:

For English (lang\en\messages.php)


<?php
return [
'title' => 'Use Multiple Languages in Laravel',
'submit' => 'Submit',
'cancel' => 'Cancel',
'edit' => 'Edit',
];

For French (lang\fr\messages.php)


<?php
return [
'title' => 'Utiliser plusieurs langues dans Laravel',
'submit' => 'Soumettre',
'cancel' => 'Annuler',
'edit' => 'Modifier',
];

Only the right side (values) will change for different languages, left side (keys) will remain the same.

There is another way to create the translation files. these are JSON files. In that case, each language will have a JSON file under the 'lang' directory, like en.json, fr.json, etc. In this topic, we will use only the first approach which uses the php files for translations.

How to Retrieve Translations

To use the translations on a web page, we will use the keys from the language files; the values will be displayed by Laravel automatically. For example, if we want to display the title of a page, we will use the below line for the web page title in html.


<title>{{ __('messages.title') }}</title>

We can also use the trans() function to get the translated values.


<title>{{trans('messages.title')}}</title>

In case, a translation key does not exist, it will return the given key. I hope you have got an idea of how to set up Laravel Localization for different languages.

Develop a Multi-Language Website in Laravel

We will store the language names in a MySQL table using a form and display the content in a language selected by the user from a language switcher (dropdown). This will be dynamic because, for a new language, we do not need to change our code; only the languages files are to be created for the newly added language.

Let's start working on the project.

Create Laravel Project and Migration

We will use the below command to create the project, our project name is lara_multi_lang.


composer create-project --prefer-dist laravel/laravel lara_multi_lang

Create a database named 'lara_lang' in MySQL and update the .env file for database details as below:


DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=lara_lang
DB_USERNAME=root
DB_PASSWORD=

We will create a custom table named 'languages'. This table will store all language names. Along with the name and code, it will have a status column to show if the language is active or inactive.

We will create the model and migration for this table. Go to your project folder and run the below command from the VS code terminal to create the migration.


php artisan make:model Language -m

The above command will create the migration file under database/migrations folder as given below:

<yyyy_mm_dd_xxxxxx>_create_languages_table.php

Here yyyy_mm_dd_xxxxxx is the datetime when the migration files were created.

It will also create the Language model under the app/Models folder

Open the migration file and update the up() method as below:

Migration scripts for 'languages' table


public function up(): void
    {
        Schema::create('languages', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('code')->unique();
            $table->tinyInteger('status')->comment('0-inactive, 1-active');
            $table->timestamps();
        });
    }

Language code is unique, example, 'en' (English), 'fr'(French), 'es'(Spanish), 'ar'(Arabic), 'hi'(Hindi).

Now run Laravel migration to create the custom and the Laravel default tables. Run the below command from the project folder to create the tables:


php artisan migrate

Update the Models

Update the model to add $fillable columns.

Model Language

<?php
class Language extends Model
{
    protected $fillable = ['name', 'code', 'status'];
}

Setup Laravel Localization

We need to publish Laravel's language files by using the artisan command. Run the below command from the terminal to create the 'lang' directory and the default translation files.


php artisan lang:publish

This will create the 'lang\en' directory with the Laravel-supplied files for English as below:

Laravel translate website

The default language is set in the config/app.php file. This is set as 'en' (English). We will keep English only, so no need to change anything here.


<?php
 'locale' => env('APP_LOCALE', 'en'),

 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),

 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),

Now, we will create our language files for the website under the 'lang/en' directory. We will create two files, site.php for the website pages and form.php for the form validation. As of now, we will create for English only, later when we add more languages, we will create these files for each language.

We will write the title of the website in site.php for the time being:

lang/en/site.php


<?php
return [
'title' => 'Use Multiple Languages in Laravel',
];

Create Laravel language switcher with a top menu

We will create a Top Menu with a dropdown(switcher) to show the list of languages. Also, we will develop a form to add/update the language names.

To set the current language and to show the list of languages other than the selected language, we will use a middleware. All the routes will go through this middleware.

Create a middleware to set the selected language.

Create the middleware named LanguageMiddleware using the below command:


php artisan make:middleware LanguageMiddleware

This middleware will set the session for current language from the config\app.php using setLocale() method. It will also share the current language and a list of all other active languages with all the views using View::share. Let's see the code for the middleware:

app/Http/Middleware/LanguageMiddleware.php


<?php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\View;
use App\Models\Language;

class LanguageMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param    \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        // set sesion variable for the current language
        if(!Session::has('locale')){
            Session::put('locale',App::getLocale());
        }
        App::setLocale(session('locale'));

        // get current language from the database
        $curr_lang = Language::where('code',session('locale'))->where('status',1)->first();

        // get all other languages
        $other_lang = Language::where('code','!=' ,session('locale'))->where('status', 1)->get();

        View::share('cLang', $curr_lang);
        View::share('otherLangs', $other_lang);
        return $next($request);
    }
}

Here, current language is the language selecetd by the user and will be displayed on the top. Other languages will be displayed in the dropdown.

Now register the middleware in bootstrap/app.php to add the Middleware alias .


->withMiddleware(function (Middleware $middleware) {
        $middleware->alias(['lang'=> App\Http\Middleware\LanguageMiddleware::class,]);
    })

Create the Controllers

We will create two controllers - one for the Home page and the other, a resource controller, to add/update languages.


php artisan make:controller HomeController

php artisan make:controller LanguageController --resource

HomeController Methods

The method setLanguage() will set the selected language in the session and App locale. This function is called when a user selects a language from the dropdown.

app/Http/Controllers/HomeController.php


class HomeController extends Controller
{
    public function setLanguage($lang)
    {
        Session::put('locale',$lang);
        App::setLocale($lang);
        return redirect()->back(); 
    }
  public function index(){
    return view('index');
  }
}

LanguageController Methods

The LanguageController is used to add and update languages in the database. There will be standard methods that will be used here - create(), store(), edit() and update(). I am not going to explain these methods in detail. The only change we have to make here is whenever we want to show any message, we have to use language files. Our language file is lang/en/site.php and lang/en/form.php, so, we will update this as per the requirements. See the below methods:

app/Http/Controllers/LanguageController.php


<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Language;

class LanguageController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        $lang = Language::orderBy('id', 'asc')->get();
        return view('language.index', compact('lang'));
    }

    /**
     * Show the form for creating a new resource.
     */
    public function create()
    {
        return view('language.add_language');
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        $request->validate(
            [
                'name' => 'required',
                'code' => 'required|unique:languages',
                'status' => 'required',
            ],
            [
                'name.required' => trans('form.required',['attribute'=>trans('site.name')]),
                'code.required' =>
                trans('form.required', ['attribute' => trans('site.code')]),
                'status.required' =>
                trans('form.required', ['attribute' => trans('site.status')]),
                'code.unique' =>
                trans('form.unique', ['attribute' => trans('site.code')]),
            ],
        );

        try {
            Language::create($request->only('name', 'code', 'status'));
            return redirect()->route('languages.index')->withSuccess(trans('site.lang.add_success'));
        } catch (\Throwable $th) {
            // return back()->withError($th->getMessage())->withInput();
            return back()->withError(trans('site.lang.add_error'))->withInput();
        }

    }

    /**
     * Display the specified resource.
     */
    public function show(string $id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     */
    public function edit(string $id)
    {
        $edit = Language::findOrFail($id);
        return view('language.add_language', compact('edit'));
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, string $id)
    {
        $request->validate(
            [
                'name' => 'required',
                'code' => 'required|unique:languages,code,' . $id,
                'status' => 'required',
            ],
            [
                'name.required' => trans('form.required', ['attribute' => trans('site.name')]),
                'code.required' =>
                trans('form.required', ['attribute' => trans('site.code')]),
                'status.required' =>
                trans('form.required', ['attribute' => trans('site.status')]),
                'code.unique' =>
                trans('form.unique', ['attribute' => trans('site.code')]),
            ],
        );

        try {
            Language::where('id', $id)->update($request->only('name', 'code', 'status'));
            return redirect()->route('languages.index')->withSuccess(trans('site.lang.update_success'));
        } catch (\Throwable $th) {
            // return back()->withError($th->getMessage())->withInput();
            return back()->withError(trans('site.lang.update_error'))->withInput();
        }
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(string $id)
    {
        //
    }
  }

You can see we are not writing the actual messages, but we are using keys defined in the language files site.php and form.php. Note that for form validation we are using form.php with attribute. See below the contents of site.php and form.php.

lang/en/site.php


<?php
return [
    'home' => 'Home',
    'home_text' => 'This Tutorial is about implementing Multiple Languages in Laravel 11. You can maintain languages in a database table and also store and view data in different languages.',
    'title' => 'Laravel Multi-Language Website',
    'maintain_lang' => 'Maintain Languages',
    'submit' => 'Submit',
    'cancel' => 'Cancel',
    'not_found' => 'No Data Found',
    'status' => 'Status',
    'active' => 'Active',
    'inactive' => 'Inactive',
    'edit' => 'Edit',
    'action' => 'Action',
    'name' => 'Name',
    'code' => 'Code',

    'lang' => [
        'plc_name' => 'Enter Language Name',
        'plc_code' => 'Enter Language Code',
        'add' => 'Add Language',
        'update' => 'Update Language',
        'list' => 'List of Languages',
        'update_success' => 'Language Updated Successfully',
        'update_error' => 'Error: could not update language, please contact Admin',
        'add_success' => 'Language Added Successfully',
        'add_error' => 'Error: could not add language, please contact Admin',
    ],

];

lang/en/form.php


<?php
return [
    'required' => 'The :attribute is required.',
    'unique' => 'The :attribute has already been taken.',
];

These are all key=>value pairs. For another language you have to translate the values (right part), keys (left part) will remain the same.

Update the Routes

We will create one route for the home page, one for setting the language when the user selects a language from the dropdown and a resource route to add/update Languages. All these routes should go through the LanguageMiddleware.

routes/web.php


<?php
Route::middleware(['lang'])->group(function(){
    Route::get('/', [HomeController::class, 'index']);
    Route::resource('languages', LanguageController::class);
    Route::get('lang/{locale}',[HomeController::class, 'setLanguage'])->name('setlocale');
});

Views

Below are the views for the top menu and add/edit forms including the layouts. We have created two directories - 'layouts' and 'language' under 'views'. In all the views we will use language files.

resources/views/layouts/header.blade.php


<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="{{asset('css/style.css')}}">
    <title>{{__('site.title')}}</title> 
</head>

See in line 2, we are using app()->getLocale() for the language. '_' is normally used for regions. For example, if en_GB or en_US is defined in the App locale, we need to change '_' (underscore) to '-' (hyphen) using str_replace().

resources/views/layouts/footer.blade.php


<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>

resources/views/layouts/top_menu.blade.php


<nav class="navbar navbar-expand-lg bg-dark navbar-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="{{url('/')}}">Website Logo</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                <li class="nav-item">
                    <a class="nav-link active" aria-current="page" href="{{url('/')}}">{{__('site.home')}}</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="{{route('languages.index')}}">{{__('site.maintain_lang')}}</a>
                </li>
            </ul>
            <!-- dropdown for the languages -->
            <div class="pull-right">
                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                    <li class="nav-item dropdown">
                        <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button"
                            data-bs-toggle="dropdown">
                            {{ $cLang->name ?? ''}}
                        </a>
                        <ul class="dropdown-menu">
                            @foreach($otherLangs as $lang)
                            <li><a class="dropdown-item"
                                    href="{{ route('setlocale',['locale' => $lang->code]) }}">{{ $lang->name}}</a></li>
                            @endforeach
                        </ul>
                    </li>
                </ul>
            </div>

        </div>
    </div>
</nav>

resources/views/layouts/master.blade.php


@include('layouts.header')
<body>
    <div>
        @include('layouts.top_menu')
        <div class="container">
            @yield('main-content')
        </div>
        @include('layouts.footer')
    </div>
</body>
</html>

View to display a list of Languages from the database

laravel-language-switcher

The index view to show the above is given below:

resources/views/language/index.blade.php


@extends('layouts.master')
@section('main-content')

<div class="table-responsive">
    <h1>{{ __('site.lang.list') }}</h1>
    <div class="mb-3 mt-3 text-end"><a class="btn btn-primary"
            href="{{route('languages.create')}}">{{ __('site.lang.add') }}</a></div>
    @if(session('success'))
    <div class="alert alert-success">{{session('success')}}</div>
    @endif

    <table class="table table-bordered table-striped table-hover">
        <thead class="table-dark">
            <tr>
                <th>{{ __('site.name') }}</th>
                <th>{{ __('site.code') }}</th>
                <th>{{ __('site.status') }}</th>
                <th>{{ __('site.action') }}</th>
            </tr>
        </thead>
        <tbody>
            @forelse ($lang as $row)

            <tr class="">
                <td>{{$row->name}}</td>
                <td>{{$row->code}}</td>
                <td>{{$row->status ? trans('site.active') : trans('site.inactive')}}</td>
                <td>
                    <a href="{{route('languages.edit', $row->id)}}"
                        class="btn btn-primary {{session('locale') == $row->code ? 'disabled' : ''}}">{{ __('site.edit') }}</a>
                </td>
            </tr>
            @empty
            <tr>
                <td colspan=4>{{ __('site.not_found') }}</td>
            </tr>
            @endforelse

        </tbody>
    </table>
</div>
@endsection

Note that h1, h2 and the table header come from the language files. Also, note that the Edit button is disabled for the current language. This is done to prevent the current language from being updated as inactive.

Create a View to add/update Language

We will be using the same view to add and update the language. You can read the tutorial How to Use the Same Form for Create and Update in Laravel for more detail.

resources/views/language/add_language.blade.php


@extends('layouts.master');
@section('main-content')
@if (isset($edit->id))
<h1>{{__('site.lang.update')}}</h1>
@else
<h1>{{__('site.lang.add')}}</h1>
@endif
@if (isset($edit->id))
<form class="form1" action="{{route('languages.update', $edit->id)}}" method="post">
    @method('PUT')
    @else
    <form class="form1" action="{{route('languages.store')}}" method="post">
        @endif
        @if(session('error'))
        <div class="alert alert-danger">{{session('error')}}</div>
        @endif
        @csrf
        <div class="mb-3">
            <label for="" class="form-label">{{__('site.name')}}</label>
            <input type="text" class="form-control" name="name" id="name" placeholder="{{__('site.lang.plc_name')}}"
                value="{{old('name', isset($edit->id) ? $edit->name : '')}}">
            <div class="text-danger">@error('name') {{$message}}@enderror</div>
        </div>

        <div class="mb-3">
            <label for="" class="form-label">{{__('site.code')}}</label>
            <input type="text" class="form-control" name="code" id="code" placeholder="{{__('site.lang.plc_code')}}"
                value="{{old('code', isset($edit->id) ? $edit->code : '')}}">
            <div class="text-danger">@error('code') {{$message}}@enderror</div>
        </div>
        <div class="mb-3">
            <div><label for="" class="form-label">{{__('site.status')}}</label>
            </div>
            <div class="form-check form-check-inline">
                <input class="form-check-input" type="radio" name="status" id="inlineRadio1" value="1"
                    {{isset($edit->id) ? ($edit->status ? 'checked' : '') : ''}}>
                <label class="form-check-label" for="inlineRadio1">{{__('site.active')}}</label>
            </div>
            <div class="form-check form-check-inline">
                <input class="form-check-input" type="radio" name="status" id="inlineRadio2" value="0"
                    {{isset($edit->id) ? ($edit->status ? '' : 'checked') : ''}}>
                <label class="form-check-label" for="inlineRadio2">{{__('site.inactive')}}</label>
            </div>
            <div class="text-danger">@error('status') {{$message}}@enderror</div>
        </div>
        <input type="submit" class="btn btn-primary" value="{{__('site.submit')}}">
        <a href="{{route('languages.index')}}" class="btn btn-danger">{{__('site.cancel')}}</a>
    </form>
    @endsection

Since we use the same form for the Add and Update, separate routes are used for the form action.

Here is the stylesheet:

public/css/style.css


h1,h2,h3, p{
    text-align: center;
    margin:20px 0 20px 0;
}
.navbar-brand {
margin: 0 100px 0 50px;
}
.navbar-nav .nav-item{
margin-left: 50px;
}
.navbar-nav .nav-item .nav-link{
font-size: 16px;
font-weight: 500;
color:#fff;
}
.table-responsive{
    width: 60% !important;
    margin:auto;
    text-align: center;
}
.form1{
    margin:auto;
    width:60%;
}

Test the Application

Start the server using the below command:


php artisan serve

Run localhost:8000 in the browser, and add English (en) language using the Add Language form

After submitting the form, you will see the below screen. You can see English is displayed as the selected language.

How to add multiple languages in Laravel

Now, to add French, create a directory 'fr' under 'lang' and copy all the language files from 'en' directory and paste it into 'fr' directory. Get the translated values for French in the site.php and form.php. Use Google Translate to translate from English to French.

Once that is done, we will have the below site.php and form.php under 'lang/fr' directory.

lang/fr/site.php


<?php

return [
    'home' => 'Maison',
    'home_text' => 'Ce didacticiel concerne l\'implémentation de plusieurs langues dans Laravel 11. Vous pouvez gérer les langues dans une table de base de données et également stocker et afficher des données dans différentes langues.',
    'title' => 'Site Web Laravel multilingue',
    'maintain_lang' => 'Maintenir les langues',
    'submit' => 'Soumettre',
    'cancel' => 'Annuler',
    'not_found' => 'Aucune donnée disponible',
    'status' => 'Statut',
    'active' => 'Active',
    'inactive' => 'Inactive',
    'edit' => 'Modifier',
    'action' => 'Acción',
    'name' => 'Nom',
    'code' => 'Code',

    'lang' => [
        'plc_name' => 'Entrez le nom de la langue',
        'plc_code' => 'Entrez le code de langue',
        'add' => 'Ajouter une langue',
        'update' => 'Mettre à jour la langue',
        'list' => 'Liste des langues',
        'update_success' => 'Langue mise à jour avec succès',
        'update_error' => "Erreur : impossible de mettre à jour la langue, veuillez contacter l'administrateur",
        'add_success' => 'Langue ajoutée avec succès',
        'add_error' => 'Erreur : impossible d\'ajouter la langue, veuillez contacter l\'administrateur',
    ],

];

lang/fr/form.php


<?php
return [
    'required' => ':attribute le champ est obligatoire',
    'unique' => ':attribute a déjà été pris',
];

Now add the French language in the database using the add Language form. Use the code 'fr' for French. You will see French is added in the list.

Laravel multilingual site setup

If you now refresh the page and select French, you will see the below screen:

laravel localization tutorial

You can add more languages and create the corresponding language files.

I added Hindi and created the language files in lang\hi\site.php and lang\hi\form.php. See below:

Laravel language files example

You can add more languages, like Arabic (ar), Spanish(es), Bengali (bn) and create the corresponding site.php and form.php. The application will work without modifying any programming code.

Multi-language routing in Laravel source code Download Source code from GitHub.

Laravel localization best practices Conclusion

In this Laravel localization tutorial we have covered three areas:

  • We have discussed how to set up Laravel Localization for language translation.
  • Then we developed a form to add/update languages in the database.
  • We created language files for each language and added the language in the database.

This way we added Laravel multi-language support for the website. I hope this will help you understand how to add multiple languages in Laravel. Please post your comments, I will be happy to see that.

Post a Comment

Save my Name and Email id for future comments