Step-by-step
-
1
Define supported locales in config
Create or update
config/app.phpto list the locales you support. Adding a customsupported_localeskey keeps your route and middleware logic DRY — they read from config instead of hardcoding language codes.php// config/app.php 'locale' => 'en', 'fallback_locale' => 'en', 'supported_locales' => ['en', 'ar'], -
2
Wrap routes in a locale prefix
In
routes/web.php, wrap all your public routes in a route group that uses{locale}as a URL prefix. This gives you clean, SEO-friendly URLs like/en/aboutand/ar/aboutwithout any query-string hacks.php// routes/web.php Route::prefix('{locale}') ->middleware(['setLocale']) ->group(function () { Route::get('/', [HomeController::class, 'index'])->name('home'); Route::get('/about', [AboutController::class, 'index'])->name('about'); // ... all other public routes }); -
3
Create the SetLocale middleware
This middleware reads the
{locale}URL segment, validates it against your supported locales, and callsApp::setLocale(). It also stores the locale in the session so the language switcher can read the current selection.bashphp artisan make:middleware SetLocale -
4
Write the middleware logic
Open
app/Http/Middleware/SetLocale.phpand add the locale-setting logic. If the locale in the URL is not in your supported list, fall back to the default rather than throwing a 404.php<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\App; class SetLocale { public function handle(Request $request, Closure $next) { $locale = $request->route('locale'); $supported = config('app.supported_locales', ['en']); if (in_array($locale, $supported)) { App::setLocale($locale); session(['locale' => $locale]); } else { App::setLocale(config('app.locale')); } return $next($request); } } -
5
Create translation files
Laravel loads translation strings from
resources/lang/{locale}/. Create one PHP file per logical group — for examplecommon.phpfor shared UI strings. Use the__('common.save')helper anywhere in your code or Blade templates.php// resources/lang/en/common.php return [ 'save' => 'Save', 'cancel' => 'Cancel', 'welcome' => 'Welcome, :name', 'page_title' => 'My App', ]; // resources/lang/ar/common.php return [ 'save' => 'حفظ', 'cancel' => 'إلغاء', 'welcome' => 'مرحبًا، :name', 'page_title' => 'تطبيقي', ]; -
6
Install spatie/laravel-translatable for database content
Static UI strings live in translation files, but content stored in the database — blog titles, product names, page content — needs a different approach.
spatie/laravel-translatablestores each translatable column as a JSON object keyed by locale, and gives you a clean API to get and set values by locale.bashcomposer require spatie/laravel-translatable -
7
Make an Eloquent model translatable
Add the
HasTranslationstrait to any model with multilingual columns, and list those columns in the$translatablearray. The migration stores these asjsoncolumns. When you call$post->title, Spatie returns the value for the currently active locale automatically.php<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Spatie\Translatable\HasTranslations; class Post extends Model { use HasTranslations; public array $translatable = ['title', 'slug', 'content']; } // Migration Schema::create('posts', function (Blueprint $table) { $table->id(); $table->json('title'); $table->json('slug'); $table->json('content'); $table->timestamps(); }); // Writing translations $post->setTranslation('title', 'en', 'Hello World'); $post->setTranslation('title', 'ar', 'مرحبا بالعالم'); $post->save(); // Querying by a specific locale Post::where('slug->en', 'hello-world')->first(); -
8
Add a language switcher component
Build a Blade component that generates locale-switching links by swapping the current URL's locale segment. The
route()helper and the current route name make this clean.html{{-- resources/views/components/language-switcher.blade.php --}} @foreach(config('app.supported_locales') as $lang) @php $params = array_merge(request()->route()->parameters(), ['locale' => $lang]); $url = route(request()->route()->getName(), $params); @endphp <a href="{{ $url }}" class="{{ App::getLocale() === $lang ? 'active' : '' }}"> {{ strtoupper($lang) }} </a> @endforeach -
9
Handle RTL layout for Arabic
Arabic and other RTL languages need the HTML
dirattribute flipped tortl. Set this in your main Blade layout based on the active locale. You can then use CSS attribute selectors like[dir="rtl"]to apply mirrored styles without duplicating your entire stylesheet.html{{-- resources/views/layouts/app.blade.php --}} @php $rtlLocales = ['ar']; $isRtl = in_array(App::getLocale(), $rtlLocales); @endphp <html lang="{{ App::getLocale() }}" dir="{{ $isRtl ? 'rtl' : 'ltr' }}">
Tips & gotchas
- Always generate locale-aware URLs using the `route()` helper with the `locale` parameter — never hardcode `/en/` or `/ar/` in your Blade templates.
- For the slug column specifically, query it with the JSON arrow syntax: `where("slug->{$locale}", $value)` — Spatie does not override `where()`, so you need the raw JSON path.
- Use `App::getLocale()` in your controllers to conditionally load locale-specific data; never rely on `session('locale')` alone because the session may not be set on the first request.
- Keep your translation files organised by feature, not by page — `auth.php`, `common.php`, `blog.php` — so they stay manageable as the app grows.
Wrapping up
Your Laravel app now supports multiple languages end-to-end: URL-based locale routing, UI strings via translation files, and database content via Spatie's translatable columns, with proper RTL handling for Arabic. From here, consider adding a locale redirect at / based on the user's Accept-Language header, or persisting the locale preference in the user's profile.