Laravel 13 + Filament 5 admin panel and REST API for a non-profit developer collective, backed by the Pireon OAuth identity server. The public site is a separate, server-rendered application consuming /api/public/v1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
94 lines
2.9 KiB
PHP
94 lines
2.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
use Carbon\Carbon;
|
|
use Carbon\CarbonImmutable;
|
|
use Closure;
|
|
use Illuminate\Http\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
/**
|
|
* Resolves the locale for the request and applies it to the framework, to
|
|
* Carbon, and to translatable model attributes.
|
|
*
|
|
* Precedence, most specific first:
|
|
*
|
|
* 1. ?lang=xx — an explicit, single-request override (API friendly)
|
|
* 2. session — what the signed-in visitor picked in the switcher
|
|
* 3. users.locale — their stored preference
|
|
* 4. Accept-Language — what their client asked for
|
|
* 5. locales.default
|
|
*/
|
|
class SetLocale
|
|
{
|
|
public function handle(Request $request, Closure $next): Response
|
|
{
|
|
$locale = $this->resolve($request);
|
|
|
|
app()->setLocale($locale);
|
|
|
|
// Dates rendered in the panel and serialised by the API follow suit.
|
|
CarbonImmutable::setLocale($locale);
|
|
Carbon::setLocale($locale);
|
|
|
|
// Translatable attributes read this locale, and fall back to
|
|
// locales.fallback when a record has no translation for it.
|
|
app()->setFallbackLocale((string) config('locales.fallback'));
|
|
|
|
$response = $next($request);
|
|
|
|
// Lets caches and clients see which language they actually received.
|
|
$response->headers->set('Content-Language', $locale);
|
|
|
|
return $response;
|
|
}
|
|
|
|
protected function resolve(Request $request): string
|
|
{
|
|
$supported = array_keys(config('locales.supported'));
|
|
|
|
$candidates = [
|
|
$request->query(config('locales.query_parameter')),
|
|
// The API stack is stateless, so there is no session to consult.
|
|
// hasSession() is the safe check — session() throws when unset.
|
|
$request->hasSession()
|
|
? $request->session()->get(config('locales.session_key'))
|
|
: null,
|
|
$request->user()?->locale,
|
|
$this->fromAcceptLanguage($request, $supported),
|
|
config('locales.default'),
|
|
];
|
|
|
|
foreach ($candidates as $candidate) {
|
|
if (is_string($candidate) && in_array($candidate, $supported, true)) {
|
|
return $candidate;
|
|
}
|
|
}
|
|
|
|
return (string) config('locales.default');
|
|
}
|
|
|
|
/**
|
|
* Pick the best supported locale from the Accept-Language header, honouring
|
|
* its quality values. "ca-ES" matches the "ca" we support.
|
|
*
|
|
* @param array<int, string> $supported
|
|
*/
|
|
protected function fromAcceptLanguage(Request $request, array $supported): ?string
|
|
{
|
|
foreach ($request->getLanguages() as $language) {
|
|
$base = strtolower(str_replace('_', '-', $language));
|
|
$base = explode('-', $base)[0];
|
|
|
|
if (in_array($base, $supported, true)) {
|
|
return $base;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|