Skip to content

Commit 0ba13ab

Browse files
committed
frontend configuration + middleware to set app locale
1 parent 9b2e1e0 commit 0ba13ab

40 files changed

+982
-159
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Settings;
4+
5+
use App\Http\Controllers\Controller;
6+
use Illuminate\Http\RedirectResponse;
7+
use Illuminate\Http\Request;
8+
use Illuminate\Validation\Rule;
9+
10+
class LanguageController extends Controller
11+
{
12+
/**
13+
* Update the user's language preference.
14+
*/
15+
public function update(Request $request): RedirectResponse
16+
{
17+
$validated = $request->validate([
18+
'locale' => ['required', 'string', Rule::in(['en', 'fr'])],
19+
]);
20+
21+
$request->user()->update([
22+
'locale' => $validated['locale'],
23+
]);
24+
25+
return back()->with('status', 'Language updated successfully');
26+
}
27+
}

app/Http/Middleware/HandleInertiaRequests.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Illuminate\Foundation\Inspiring;
66
use Illuminate\Http\Request;
7+
use Illuminate\Support\Facades\App;
78
use Inertia\Middleware;
89
use Tighten\Ziggy\Ziggy;
910

@@ -51,6 +52,11 @@ public function share(Request $request): array
5152
'location' => $request->url(),
5253
],
5354
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
55+
'locale' => [
56+
'current' => App::getLocale(),
57+
'available' => config('app.available_locales'),
58+
'user_preference' => $request->user()?->locale,
59+
],
5460
];
5561
}
5662
}

app/Http/Middleware/SetUserLocale.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
namespace App\Http\Middleware;
4+
5+
use Closure;
6+
use Illuminate\Http\Request;
7+
use Illuminate\Support\Facades\App;
8+
use Symfony\Component\HttpFoundation\Response;
9+
10+
class SetUserLocale
11+
{
12+
/**
13+
* Handle an incoming request.
14+
*
15+
* @param \Closure(Request): (Response) $next
16+
*/
17+
public function handle(Request $request, Closure $next): Response
18+
{
19+
// Priority 1: User's saved locale preference
20+
if ($userLocale = $request->user()?->locale) {
21+
App::setLocale($userLocale);
22+
return $next($request);
23+
}
24+
25+
// Priority 2: Session locale (for guest users who changed language)
26+
if ($sessionLocale = $request->session()->get('locale')) {
27+
if (in_array($sessionLocale, $this->getSupportedLocales())) {
28+
App::setLocale($sessionLocale);
29+
return $next($request);
30+
}
31+
}
32+
33+
// Priority 3: Browser language preferences
34+
$this->setLocaleFromBrowser($request);
35+
36+
return $next($request);
37+
}
38+
39+
/**
40+
* Set locale based on browser language preferences
41+
*/
42+
private function setLocaleFromBrowser(Request $request): void
43+
{
44+
$browserLocales = $request->getLanguages();
45+
$supportedLocales = $this->getSupportedLocales();
46+
47+
foreach ($browserLocales as $browserLocale) {
48+
// Try exact match first (e.g., en-US)
49+
$normalizedLocale = str_replace('_', '-', strtolower($browserLocale));
50+
if (in_array($normalizedLocale, $supportedLocales)) {
51+
App::setLocale($normalizedLocale);
52+
return;
53+
}
54+
55+
// Try language code only (e.g., en from en-US)
56+
$languageCode = strtolower(substr($browserLocale, 0, 2));
57+
if (in_array($languageCode, $supportedLocales)) {
58+
App::setLocale($languageCode);
59+
return;
60+
}
61+
62+
// Try to find a supported locale that starts with the language code
63+
foreach ($supportedLocales as $supportedLocale) {
64+
if (str_starts_with($supportedLocale, $languageCode . '-')) {
65+
App::setLocale($supportedLocale);
66+
return;
67+
}
68+
}
69+
}
70+
71+
// Fallback to default locale if no match found
72+
App::setLocale(config('app.locale'));
73+
}
74+
75+
/**
76+
* Get supported locales from config
77+
*/
78+
private function getSupportedLocales(): array
79+
{
80+
return config('app.available_locales');
81+
}
82+
}

app/Models/User.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class User extends Authenticatable
2020
protected $fillable = [
2121
'name',
2222
'email',
23+
'locale',
2324
'password',
2425
];
2526

bootstrap/app.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use App\Http\Middleware\HandleAppearance;
44
use App\Http\Middleware\HandleInertiaRequests;
5+
use App\Http\Middleware\SetUserLocale;
56
use Illuminate\Foundation\Application;
67
use Illuminate\Foundation\Configuration\Exceptions;
78
use Illuminate\Foundation\Configuration\Middleware;
@@ -18,6 +19,7 @@
1819

1920
$middleware->web(append: [
2021
HandleAppearance::class,
22+
SetUserLocale::class,
2123
HandleInertiaRequests::class,
2224
AddLinkHeadersForPreloadedAssets::class,
2325
]);

config/app.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,20 @@
123123
'store' => env('APP_MAINTENANCE_STORE', 'database'),
124124
],
125125

126+
/*
127+
|--------------------------------------------------------------------------
128+
| Available Locales
129+
|--------------------------------------------------------------------------
130+
|
131+
| This array defines the list of supported locales for your application.
132+
| These will be used to validate user preferences, detect browser languages,
133+
| and limit localization to only approved languages.
134+
|
135+
*/
136+
137+
'available_locales' => [
138+
'en',
139+
'fr'
140+
],
141+
126142
];

database/migrations/0001_01_01_000000_create_users_table.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public function up(): void
1515
$table->id();
1616
$table->string('name');
1717
$table->string('email')->unique();
18+
$table->string('locale', 5)->default('en');
1819
$table->timestamp('email_verified_at')->nullable();
1920
$table->string('password');
2021
$table->rememberToken();

package-lock.json

Lines changed: 89 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"typescript": "^5.2.2",
4040
"vite": "^7.0.4",
4141
"vue": "^3.5.13",
42+
"vue-i18n": "^12.0.0-alpha.3",
4243
"ziggy-js": "^2.4.2"
4344
},
4445
"optionalDependencies": {

resources/js/app.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,25 @@ import type { DefineComponent } from 'vue';
66
import { createApp, h } from 'vue';
77
import { ZiggyVue } from 'ziggy-js';
88
import { initializeTheme } from './composables/useAppearance';
9+
import i18n from './i18n';
910

1011
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
1112

1213
createInertiaApp({
1314
title: (title) => (title ? `${title} - ${appName}` : appName),
1415
resolve: (name) => resolvePageComponent(`./pages/${name}.vue`, import.meta.glob<DefineComponent>('./pages/**/*.vue')),
1516
setup({ el, App, props, plugin }) {
16-
createApp({ render: () => h(App, props) })
17-
.use(plugin)
18-
.use(ZiggyVue)
19-
.mount(el);
17+
const app = createApp({ render: () => h(App, props) });
18+
19+
// Handle the new locale structure where locale is an object with 'current' property
20+
const localeData = (props.initialPage.props as any)?.locale;
21+
const currentLocale = typeof localeData === 'object' && localeData?.current
22+
? localeData.current
23+
: (typeof localeData === 'string' ? localeData : 'en');
24+
25+
i18n.global.locale.value = currentLocale;
26+
27+
app.use(plugin).use(ZiggyVue).use(i18n).mount(el);
2028
},
2129
progress: {
2230
color: '#4B5563',

0 commit comments

Comments
 (0)