Do not speak Portuguese? Translate this site with Google or Bing Translator
Working with Multi-languages in Laravel Without Packages

Posted on: August 15, 2024 02:20 PM

Posted by: Renato

Categories: Laravel

Views: 120

Working with Multi-languages in Laravel Without Packages

6 min read

Laravel has advantageous features for localization. Let’s leverage this feature to build our app. We will not use any packages and make our own handler for translations, routes, models, and everything.

In this article, I will cover:

  1. Set up your supported languages.
  2. Create multi-language routes.
  3. Automatically translate the page into the selected language.
  4. Design and implement a translatable database.

Let’s get started.

1. Set up your supported languages

I have a fresh Laravel installed on my machine, and the first thing we will do is set up our supported languages. Create a new file in the config folder called languages.php.

<?php 

return [
'en',
'pt',
'fr',
'jp'
];

Done. Now we will use this config file to work across the app.

2. Create multi-language routes

According to the Laravel’s documentation: “By default, the Laravel application skeleton does not include the lang directory. If you would like to customize Laravel’s language files or create your own, you should scaffold the lang directory via the lang:publish Artisan command.

php artisan lang:publish

Now, let’s create routes that support all languages. We will loop through the supported languages to create unique routes for every language.

But before that, let’s create some folders for our supported languages in the lang directory. I will name the file paths.php since it will store all of our route paths.

The folder will look something like this:

├── lang/       
│ ├── en/
│ │ ├── paths.php
│ ├── fr/
│ │ ├── paths.php
│ ├── jp/
│ │ ├── paths.php
│ ├── pt/
│ │ ├── paths.php

The content inside the paths.php is:

lang/en/paths.php

<?php
return [
'about' => 'about',
];

lang/fr/paths.php

<?php
return [
'about' => 'a-propos',
];

lang/jp/paths.php

<?php
return [
'about' => 'about',
];

lang/pt/paths.php

<?php
return [
'about' => 'sobre',
];

Now, we’ll define our routes in web/routes.php:

foreach (config('languages') as $locale) {
if (config('app')['locale'] == $locale) {
Route::get(__('paths.about', [], $locale), AboutController::class)->name("about.{$locale}");
} else {
Route::get("{$locale}/".__('paths.about', [], $locale), AboutController::class)->name("about.{$locale}");
}
}

In the code above, you will register some new routes for all available languages:

If you want to access the path in the English version use /about but if you want to access the path in other languages use /pt/sobre for example.

3. Automatically translate the page into the selected language

Now, you probably notice that, when you access the page, the translation won’t be applied. That’s because we haven’t set our locale according to the path.

Let’s create a middleware to translate the page into the selected locale.

php artisan make:middleware SetLocale

Paste the following code for the handle method in the app/Http/Middleware/SetLocale.php

public function handle(Request $request, Closure $next): Response
{
$lang = $request->segment(1);

if(strlen($lang) === 2 && in_array($lang, config('languages'))){
app()->setLocale($lang);
}

return $next($request);
}

The code above will set the language according to segment 1 in the request, if segment 1 is equal to our supported languages we will set it as the current language.

Now register the middleware in the app/Http/Kernel.php

protected $routeMiddleware = [
...
'set-locale' => \App\Http\Middleware\SetLocale::class,
];

Update our routes to use the middleware:

Route::group([
'middleware' => 'set-locale'
], function() {
foreach (config('languages') as $locale) {
if (config('app')['locale'] == $locale) {
Route::get(__('paths.about', [], $locale), AboutController::class)->name("about.{$locale}");
} else {
Route::get("{$locale}/".__('paths.about', [], $locale), AboutController::class)->name("about.{$locale}");
}
}
});

After that, we are ready to print the page according to the selected language. For example, I have a very simple controller that prints “Hello World” using Laravel Localization.

class AboutController extends Controller
{
public function __invoke(Request $request)
{
return __("general.hello_world");
}
}

Before we see the output, we need to create new translation files called “general” for all languages. If you don’t create for all languages, the fallback language will be using the English version.

├── lang/       
│ ├── en/
│ │ ├── general.php
│ │ ├── paths.php
│ ├── fr/
│ │ ├── general.php
│ │ ├── paths.php
│ ├── jp/
│ │ ├── general.php
│ │ ├── paths.php
│ ├── pt/
│ │ ├── general.php
│ │ ├── paths.php

lang/en/general.php

<?php
return [
'hello_world' => 'Hello World',
];

lang/fr/paths.php

<?php
return [
'hello_world' => 'Bonjour le monde',
];

You can continue to apply this to the rest of the language.

Let’s see how it looks when the page is rendered.

English Version
French Version

Now you know how to leverage this localization feature for the real project.

4. Design and implement a translatable database

Let's say you have normal tables for categories and posts with the following structure:

categories
- id
- name
- slug
posts
- id
- category_id
- title
- slug
- content

I know that you can simply add a locale or language column in both tables, but that will cause inconsistency for the category_id. For example:

categories

id: 1
name: "News EN"
slug: "news-en"
locale: "en"

id: 2
name: "Nouvelles"
slug: "nouvelles"
locale: "fr"
posts

id: 1
category_id: 1
title: "Post English"
slug: "post-english"
locale: "en"
content: "Content post in English"

id: 2
category_id: 2
title: "Post France"
slug: "post-france"
locale: "fr"
content: "Content post in France"

As you can see in the plain text above, both categories cover the same meaning, which is “News” but they have different IDs. If you change the category_id for “Post France” to 1, it will cause a problem because the France post is referenced to the English category.

Let’s solve that problem by creating a third table for categories called category_translations and changing some columns in the categories table.

categories
- id
category_translations
- id
- category_id
- name
- slug

Implement new table structure:

categories

id: 1
category_translations

id: 1
category_id: 1
name: "News EN"
slug: "news-en"
locale: "en"

id: 2
category_id: 1
name: "Nouvelles"
slug: "nouvelles"
locale: "fr"
posts

id: 1
category_id: 1
title: "Post English"
slug: "post-english"
locale: "en"
content: "Content post in English"

id: 2
category_id: 1
title: "Post France"
slug: "post-france"
locale: "fr"
content: "Content post in France"

Now both posts are referenced to the same ID and we can display the category in English or French.

Let’s Code it in Laravel

I will focus on how we take advantage of the Laravel Relationship for categories and category_translations.

First, we define the migration files:

php artisan make:migration create_categories_table
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
php artisan make:migration create_category_translations_table
Schema::create('category_translations', function (Blueprint $table) {
$table->id();
$table->foreignId('category_id');
$table->string('name');
$table->string('slug');
$table->string('locale');
$table->timestamps();
});

If you don’t know how to make a migration, check out this documentation.

Now, let’s create our models:

php artisan make:model Category
php artisan make:model CategoryTranslation

We will give our Category model access to the CategoryTranslation by creating a relationship:

<?php

namespace App\Models;

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

class Category extends Model
{
use HasFactory;

protected $fillable = [
'id',
];

public function translations()
{
return $this->hasMany(CategoryTranslation::class);
}

public function en()
{
return $this->hasOne(CategoryTranslation::class)->where('locale', 'en');
}

public function fr()
{
return $this->hasOne(CategoryTranslation::class)->where('locale', 'fr');
}

public function pt()
{
return $this->hasOne(CategoryTranslation::class)->where('locale', 'pt');
}

public function jp()
{
return $this->hasOne(CategoryTranslation::class)->where('locale', 'jp');
}
}

In the code above, when you select a single category, you can access the English version of the category using $category->en as well as other versions like $category->fr, $category->jp, etc

And you can also be able to access the whole version of the category translations using $category->translations.

Conclusion

Well, I think that’s all from me. I believe that now you get the idea of how we can leverage the Laravel Localization feature for your real project.

- https://laravel.com/docs/11.x/localization

- https://medium.com/@styles77/working-with-multi-languages-in-laravel-without-packages-b4dc8bfa9ab8


2

Share

Donate to Site


About Author

Renato

Developer

Add a Comment
Comments 0 Comments

No comments yet! Be the first to comment

Blog Search


Categories

OUTROS (16) Variados (109) PHP (133) Laravel (171) Black Hat (3) front-end (29) linux (114) postgresql (39) Docker (28) rest (5) soap (1) webservice (6) October (1) CMS (2) node (7) backend (13) ubuntu (56) devops (25) nodejs (5) npm (3) nvm (1) git (8) firefox (1) react (7) reactnative (5) collections (1) javascript (7) reactjs (8) yarn (0) adb (1) Solid (2) blade (3) models (1) controllers (0) log (1) html (2) hardware (3) aws (14) Transcribe (2) transcription (1) google (4) ibm (1) nuance (1) PHP Swoole (5) mysql (31) macox (4) flutter (1) symfony (1) cor (1) colors (2) homeOffice (2) jobs (3) imagick (2) ec2 (1) sw (1) websocket (2) markdown (1) ckeditor (1) tecnologia (14) faceapp (1) eloquent (14) query (4) sql (40) ddd (3) nginx (9) apache (4) certbot (1) lets-encrypt (3) debian (12) liquid (1) magento (2) ruby (1) LETSENCRYPT (1) Fibonacci (1) wine (1) transaction (1) pendrive (1) boot (1) usb (1) prf (1) policia (2) federal (1) lucena (1) mongodb (4) paypal (1) payment (1) zend (1) vim (4) ciencia (6) js (1) nosql (1) java (1) JasperReports (1) phpjasper (1) covid19 (1) saude (1) athena (1) cinnamon (1) phpunit (2) binaural (1) mysqli (3) database (42) windows (6) vala (1) json (2) oracle (1) mariadb (4) dev (12) webdev (24) s3 (4) storage (1) kitematic (1) gnome (2) web (2) intel (3) piada (1) cron (2) dba (18) lumen (1) ffmpeg (2) android (2) aplicativo (1) fedora (2) shell (4) bash (3) script (3) lider (1) htm (1) csv (1) dropbox (1) db (3) combustivel (2) haru (1) presenter (1) gasolina (1) MeioAmbiente (1) Grunt (1) biologia (1) programming (22) performance (3) brain (1) smartphones (1) telefonia (1) privacidade (1) opensource (3) microg (1) iode (1) ssh (3) zsh (2) terminal (3) dracula (1) spaceship (1) mac (2) idiomas (1) laptop (2) developer (37) api (5) data (1) matematica (1) seguranca (2) 100DaysOfCode (9) hotfix (1) documentation (1) laravelphp (10) RabbitMQ (3) Elasticsearch (1) redis (2) Raspberry (4) Padrao de design (4) JQuery (1) angularjs (4) Dicas (43) Kubernetes (3) vscode (2) backup (1) angular (3) servers (2) pipelines (1) AppSec (1) DevSecOps (4) rust (1) RustLang (1) Mozilla (1) algoritimo (1) sqlite (1) Passport (2) jwt (5) security (2) translate (1) kube (2) iot (1) politica (2) bolsonaro (1) flow (1) podcast (1) Brasil (1) containers (3) traefik (1) networking (1) host (1) POO (2) microservices (2) bug (1) cqrs (1) arquitetura (3) Architecture (4) sail (3) militar (1) artigo (1) economia (1) forcas armadas (1) ffaa (1) autenticacao (2) autorizacao (2) authentication (4) authorization (3) NoCookies (1) wsl (4) memcached (1) macos (2) unix (2) kali-linux (1) linux-tools (5) apple (1) noticias (2) composer (1) rancher (1) k8s (1) escopos (1) orm (1) jenkins (4) github (5) gitlab (3) queue (1) Passwordless (1) sonarqube (1) phpswoole (1) laraveloctane (1) Swoole (1) Swoole (1) octane (1) Structurizr (1) Diagramas (1) c4 (1) c4-models (1) compactar (1) compression (1) messaging (1) restfull (1) eventdrive (1) services (1) http (1) Monolith (1) microservice (1) historia (1) educacao (1) cavalotroia (1) OOD (0) odd (1) chatgpt (1) openai (3) vicuna (1) llama (1) gpt (1) transformers (1) pytorch (1) tensorflow (1) akitando (1) ia (1) nvidia (1) agi (1) guard (1) multiple_authen (2) rpi (1) auth (1) auth (1) livros (2) ElonMusk (2) Oh My Zsh (1) Manjaro (1) BigLinux (2) ArchLinux (1) Migration (1) Error (1) Monitor (1) Filament (1) LaravelFilament (1) replication (1) phpfpm (1) cache (1) vpn (1) l2tp (1) zorin-os (1) optimization (1) scheduling (1) monitoring (2) linkedin (1) community (1) inteligencia-artificial (2) wsl2 (1) maps (1) API_KEY_GOOGLE_MAPS (1) repmgr (1) altadisponibilidade (1) banco (1) modelagemdedados (1) inteligenciadedados (4) governancadedados (1) bancodedados (2) Observability (1) picpay (1) ecommerce (1) Curisidades (1) Samurai (1) KubeCon (1) GitOps (1) Axios (1) Fetch (1) Deepin (1) vue (4) nuxt (1) PKCE (1) Oauth2 (2) webhook (1) TypeScript (1) tailwind (1) gource (2)

New Articles



Get Latest Updates by Email