Laravel 9 Localization With Multi-languages Database Tutorial

How to create a multi-languages database. And change your web application language according to the URL. In other words, let’s localize our database and create a middleware to change the web app language.

Introduction: Understanding How Localization work

I have created two videos for this tutorial on YouTube:

Laravel localization with middleware

Laravel 9: Multi-Language CRUD With Eloquent API Resources – Database Localization

Laravel by default supports multi-language websites we call this feature Laravel’s Localization. It retrieves a string in various languages according to your application language. And we can configure our app language in config/app.php the configuration file

'locale' => 'en',
'fallback_locale' => 'en',

These language strings can be stored in files within the lang/ directory in a folder for each language we use.

/lang
    /en
        auth.php
        pagination.php
        validation.php
    /ar
        auth.php
        pagination.php
        validation.php

We can save a group of strings in a specific file. Laravel comes with English language files and if we want to add another language, We must create the language directory with the same files and array index. And make sure the language folder name is the same as the one you use in the configuration file.

If we open the en/auth.php file

<?php

return [


    'failed' => 'These credentials do not match our records.',
    'password' => 'The provided password is incorrect.',
    'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
    'Unauthorised' =>'Unauthorised',

];

To add a translation so we create ar/auth.php file as the following

<?php

return [

    'failed'   => 'بيانات الاعتماد هذه غير متطابقة مع البيانات المسجلة لدينا.',
    'throttle' => 'لقد تعديت الحد المسموح لعمليات الدخول المتكررة. يرجى المحاولة مرة أخرى بعد :seconds ثانية.',
    'password' => 'كلمة المرور غير صحيحة.',
    'Unauthorised' =>'غير مسموح',
  
];

If you notice there is :seconds that we don’t translate because this is a parameter in the translation string we can replace it with any value we want. this placeholder must be prefixed with :

Example: let’s say we have a translation string for welcoming the user in lang/en/message.php.

'hello' => 'Hello!',
'welcome back' => 'Eww, Welcome back, :name',

To use it we use this statement __('translationFileName.Arrayindex') and if we have a parameter we use __('translationFileName.Arrayindex', ['parameter'=>'value'])

echo __('messages.hello');
echo __('messages.welcome back', ['name' => 'Ahmed']);

Set the app language.

we will create a middleware to change the app language according to the local parameter in the URL. it will hold the language ID.

php artisan make:middleware SetAppLang

After creating the middleware we need to register it in the Http\Kernel.php file.

 protected $routeMiddleware = [
        //
        'setapplang' => \App\Http\Middleware\SetAppLang::class,
    ];

Let’s add the following code in Http\Middleware\SetAppLang.php for changing the app language.

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
class SetAppLang
{
  
    public function handle(Request $request, Closure $next)
    {
           // url = domain/en/post
        if (! in_array($request->segment(2),     config('app.available_locales'))) {
            abort(400);
        }
     
        App::setLocale($request->segment(2));
        return $next($request);
    }
}
  • Our URL will be example.com/{locale}/anything-else locale = lang_id such as en, ar, es
  • We get the locale via $request->segment(2) then we check if it’s in our available_locales the array that will create later.
  • App::setLocale($request->segment(2)); for setting the app language.

In config\app.php let’s create available_locales array

  'locale' => 'en',
    'available_locales' => [
        'en',
        'ar'
    ],

Finally, creating the routes, we will add the middleware to a group of routes with prefix locale so we can create our URL format. example.com/{locale}/anything else

In routes\api.php file

Route::middleware('setapplang')->prefix('{locale}')->group(function(){
    Route::post('register',[RegisterController::class, 'register']);
//other routes here

});

Multi-Langauge Database structure

Our database structure will contain for each model such as category, two tables, the first one is a reference table for the ref_id and shared data between the different language versions of the post such as images and price. The second table will contain the post data for each language and it will have the language id column and the reference id as well.

We are going to create a categories table to store the category translations if Arabic or English and then we create category_refs for storing the shared data between the translations and reference id.

Here are the migrations for each table.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->bigIncrements('id'); 
            $table->string('lang_id')->nullable();
            $table->string('name')->nullable();
            $table->string('slug')->nullable();
            $table->string('alt_image')->nullable();

            $table->longText('description')->nullable();
            $table->unsignedInteger('parent_id')->nullable() ;
            $table->unsignedBigInteger('ref_id')->nullable() ;

            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('categories');
    }
};
  • lang_id for locale of the URL and the language for the category en, ar, es
  • ref_id for the category_refs id

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('category_refs', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('image')->nullable();
            $table->string('icon')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('category_refs');
    }
};

The relationship between the two tables will be one to many. And here are the model classes for getting the shared data we created ref()

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Category extends Model
{
 
    protected $guarded = ['id'];

    public function ref(){
      return  $this->belongsTo(CategoryRef::class,'ref_id');
    }


}

And to get all the available translations for reference, we created children() function

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class CategoryRef extends Model
{
    use HasFactory;
    
    protected $guarded = ['id'];

    public function children(){
        return  $this->hasMany(Category::class,'ref_id');
    }
}
public function store(CategoryStoreRequest $request)
    {
        $data = $request->validated();
        $dataRef = [];
     
         
        if($request->hasFile('image')){
            $dataX = $this->saveImageAndThumbnail($request->file('image'),true);
       
        }
 
        if(empty($request->ref_id)){
            $categoryRef = CategoryRef::create($dataRef);
            $data['ref_id']=   $categoryRef->id;
        }


        $success['data'] = Category::create($data)
   
        $success['success'] = true;
        return response()->json( $success,200);


    }

Here is the store function of the controller, if we create a category for the first time we create a category reference but if we create a translation of an existing category we send the ref_id with the request.

2 thoughts on “Laravel 9 Localization With Multi-languages Database Tutorial

Comments are closed.