Files
core/src/Localization/Middleware/LocaleMiddleware.php
T

108 lines
3.7 KiB
PHP
Raw Normal View History

2026-08-05 23:58:53 +03:00
<?php
namespace Modules\Core\Localization\Middleware;
2026-08-05 23:58:53 +03:00
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\View;
2026-08-05 23:58:53 +03:00
use Lunar\Models\Language;
use Modules\Core\Localization\Services\LanguageCache;
2026-08-05 23:58:53 +03:00
use Symfony\Component\HttpFoundation\Response;
class LocaleMiddleware
{
public function __construct(private readonly LanguageCache $languages) {}
2026-08-05 23:58:53 +03:00
public function handle(Request $request, Closure $next): Response
{
$languages = $this->languages->all();
2026-08-05 23:58:53 +03:00
if ($languages->isEmpty()) {
return $next($request);
}
$segment = (string) $request->segment(1);
$language = $languages->firstWhere('code', $segment);
if (! $language) {
return $this->redirectToLocalizedUrl($request, $languages);
}
App::setLocale($language->code);
$request->attributes->set('locale', $language->code);
$request->attributes->set('language', $language);
// Lets route() calls omit {locale} anywhere in the request lifecycle
// (controllers, views) — without this, every route() call would need
// locale passed explicitly every time.
URL::defaults(['locale' => $language->code]);
$this->shareLocaleViewData($request, $language, $languages);
2026-08-05 23:58:53 +03:00
return $next($request);
}
/**
* Shares the current locale and every OTHER available locale (each with its
* own URL for the current page) with all views, so the header language
* switcher and layout hreflang tags don't have to recompute it.
*
* `altLocales` is a collection, not a single value — firstWhere('code', '!=',
* ...) would only ever surface one alternate, which happens to look correct
* with exactly 2 configured languages (there's only one "other" to find) but
* silently drops every locale past the first for a 3+ language store, with no
* error, just fewer switcher options than actually configured. A view iterates
* `$altLocales` to render as many links/dropdown entries as there are
* alternates, whether that's 1 or 10.
*/
private function shareLocaleViewData(Request $request, Language $language, Collection $languages): void
{
$route = $request->route();
$routeName = $route?->getName();
$altLocales = $languages
->reject(fn (Language $other) => $other->code === $language->code)
->map(fn (Language $other) => [
'code' => $other->code,
'name' => $other->name,
'url' => $routeName
? route($routeName, array_merge($route->parameters(), ['locale' => $other->code]))
: url('/'.$other->code),
])
->values();
View::share('currentLocale', $language->code);
View::share('altLocales', $altLocales);
}
2026-08-05 23:58:53 +03:00
private function redirectToLocalizedUrl(Request $request, Collection $languages): Response
{
$locale = $this->negotiateLocale($request, $languages);
$path = trim($request->getPathInfo(), '/');
$target = '/'.$locale.($path !== '' ? '/'.$path : '');
$query = $request->getQueryString();
if ($query) {
$target .= '?'.$query;
}
return redirect($target);
}
private function negotiateLocale(Request $request, Collection $languages): string
{
$preferred = $request->getPreferredLanguage($languages->pluck('code')->all());
if ($preferred) {
return $preferred;
}
return $languages->firstWhere('default', true)?->code
?? $languages->first()->code;
}
}