From b9d096fa5908bedc102a874faa87c6eedf424206 Mon Sep 17 00:00:00 2001 From: Vasyka Date: Mon, 13 Jul 2026 20:17:12 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20language=20switcher=20works=20=E2=80=94?= =?UTF-8?q?=20GET=20route,=20simple=20links,=20forced=20RO=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous POST-based switcher failed silently: CSRF token check returned 419 when clicking, and the session never persisted. Root causes: - POST requires @csrf token, but the test path fetched pages that had no matching form so the token in the DOM didn't match the session - Some tenant subdomains had SESSION_DOMAIN scoped differently, so the cookie set by POST didn't come back on the follow-up GET - Prod .env had APP_LOCALE=en which took precedence over the config edit; when session had no locale yet, defaulted to English Fixes: 1. Route accepts BOTH GET and POST via Route::match(['get', 'post']). Setting your own language is not a security concern — GET is fine. 2. Route explicitly calls $request->session()->save() before redirect, forcing the session store to write before the redirect fires. Also honors ?redirect= query so the user lands back on their original page rather than referer-guessing. 3. lang-switcher partial rewrites to plain tags (no @csrf, no forms). Each link points at /locale/{code}?redirect={current-url} so the switch happens in a single hop with predictable target. 4. SetLocale middleware hard-codes 'ro' as the ultimate fallback, ignoring config/env. The Romanian portal is the default client-facing surface; if a client has no session locale set and no user account, they see Romanian (safer than English which has no portal translations). Suite: 306 passed (853 assertions). Unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/Http/Middleware/SetLocale.php | 4 +- .../views/partials/lang-switcher.blade.php | 46 ++++++++----------- routes/web.php | 13 ++++-- 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/app/Http/Middleware/SetLocale.php b/app/Http/Middleware/SetLocale.php index d886ab0..d07fe12 100644 --- a/app/Http/Middleware/SetLocale.php +++ b/app/Http/Middleware/SetLocale.php @@ -44,6 +44,8 @@ class SetLocale return $tenantLang; } - return config('app.locale', 'ro'); + // Hard-code fallback la 'ro' — indiferent de APP_LOCALE din env, + // portal-ul client-facing e livrat cu RO ca implicit safe (RU disponibil via switcher). + return 'ro'; } } diff --git a/resources/views/partials/lang-switcher.blade.php b/resources/views/partials/lang-switcher.blade.php index 8b7880c..9143fdc 100644 --- a/resources/views/partials/lang-switcher.blade.php +++ b/resources/views/partials/lang-switcher.blade.php @@ -1,45 +1,37 @@ @php $locales = [ - 'ro' => ['label' => 'RO', 'full' => 'Română', 'flag' => '🇷🇴'], - 'ru' => ['label' => 'RU', 'full' => 'Русский', 'flag' => '🇷🇺'], - 'en' => ['label' => 'EN', 'full' => 'English', 'flag' => '🇬🇧'], + 'ro' => ['label' => 'RO', 'full' => 'Română'], + 'ru' => ['label' => 'RU', 'full' => 'Русский'], + 'en' => ['label' => 'EN', 'full' => 'English'], ]; $current = app()->getLocale(); - // Default: dark-glass style suits colored header (shop, tracking). Override via $style param. $style = $style ?? 'chip'; + $back = urlencode(request()->fullUrl()); @endphp @if ($style === 'chip')
@foreach ($locales as $code => $meta) -
- @csrf - - -
+
+ {{ $meta['label'] }} + @endforeach
@else - {{-- light style for pale backgrounds (invitations, auth pages) --}}
@foreach ($locales as $code => $meta) -
- @csrf - -
+ + {{ $meta['label'] }} + @endforeach
@endif diff --git a/routes/web.php b/routes/web.php index 215ea4c..1707ff4 100644 --- a/routes/web.php +++ b/routes/web.php @@ -129,16 +129,23 @@ Route::post('/t/{token}/approve/{kind}/{lineToken}', [\App\Http\Controllers\Trac ->where('lineToken', '[A-Za-z0-9]{16,32}') ->name('tracking.approve'); -// Locale switch — POST /locale/{lang} sets session and persists to user. -Route::post('/locale/{lang}', function (Request $request, string $lang) { +// Locale switch — GET/POST /locale/{lang} sets session and persists to user. +// GET intentionally supported so simple links (no CSRF token) work; anyone +// setting their own language is not a security concern. +Route::match(['get', 'post'], '/locale/{lang}', function (Request $request, string $lang) { if (! in_array($lang, ['ro', 'ru', 'en'], true)) { abort(404); } $request->session()->put('locale', $lang); + $request->session()->save(); // force write to session store before redirecting if ($u = $request->user()) { $u->forceFill(['locale' => $lang])->saveQuietly(); } - return back(); + // Prefer explicit redirect from query, otherwise fall back to referer / homepage + $to = $request->query('redirect') + ?: $request->header('referer') + ?: '/'; + return redirect($to); })->name('locale.switch'); // PWA — manifest pentru panou central (service.mir.md).