2026-08-05 23:58:53 +03:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Modules\Core\Localization;
|
|
|
|
|
|
|
|
|
|
use Closure;
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Illuminate\Support\Collection;
|
|
|
|
|
use Illuminate\Support\Facades\App;
|
|
|
|
|
use Illuminate\Support\Facades\Cache;
|
2026-08-24 14:41:50 +03:00
|
|
|
use Illuminate\Support\Facades\URL;
|
|
|
|
|
use Illuminate\Support\Facades\View;
|
2026-08-05 23:58:53 +03:00
|
|
|
use Lunar\Models\Language;
|
|
|
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
|
|
|
|
|
|
class LocaleMiddleware
|
|
|
|
|
{
|
|
|
|
|
private const CACHE_KEY = 'core.localization.languages';
|
|
|
|
|
|
|
|
|
|
public function handle(Request $request, Closure $next): Response
|
|
|
|
|
{
|
|
|
|
|
$languages = $this->availableLanguages();
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2026-08-24 14:41:50 +03:00
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static function forgetLanguagesCache(): void
|
|
|
|
|
{
|
|
|
|
|
Cache::forget(self::CACHE_KEY);
|
|
|
|
|
}
|
2026-08-24 14:41:50 +03:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The store's default language code (e.g. 'el') - the fixed fallback other
|
|
|
|
|
* locale-aware code (Modules\Core\Catalog\ProductService) should use, as
|
|
|
|
|
* opposed to config('app.locale') which App::setLocale() mutates per
|
|
|
|
|
* request and so can't serve as a stable fallback.
|
|
|
|
|
*/
|
|
|
|
|
public static function defaultLocale(): ?string
|
|
|
|
|
{
|
|
|
|
|
return (new self)->availableLanguages()->firstWhere('default', true)?->code;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Shares the current/alternate locale (and the alternate's URL) with all
|
|
|
|
|
* views, so the header language switcher and layout hreflang tags don't
|
|
|
|
|
* have to recompute it.
|
|
|
|
|
*/
|
|
|
|
|
private function shareLocaleViewData(Request $request, Language $language, Collection $languages): void
|
|
|
|
|
{
|
|
|
|
|
$altLanguage = $languages->firstWhere('code', '!=', $language->code);
|
|
|
|
|
$route = $request->route();
|
|
|
|
|
$routeName = $route?->getName();
|
|
|
|
|
|
|
|
|
|
View::share('currentLocale', $language->code);
|
|
|
|
|
View::share('altLocale', $altLanguage?->code);
|
|
|
|
|
View::share(
|
|
|
|
|
'altLocaleUrl',
|
|
|
|
|
$altLanguage && $routeName
|
|
|
|
|
? route($routeName, array_merge($route->parameters(), ['locale' => $altLanguage->code]))
|
|
|
|
|
: ($altLanguage ? url('/'.$altLanguage->code) : null),
|
|
|
|
|
);
|
|
|
|
|
}
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function availableLanguages(): Collection
|
|
|
|
|
{
|
|
|
|
|
return Cache::rememberForever(
|
|
|
|
|
self::CACHE_KEY,
|
|
|
|
|
fn () => Language::query()->get(['id', 'code', 'name', 'default']),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|