143 lines
4.2 KiB
PHP
143 lines
4.2 KiB
PHP
<?php
|
|
|
|
namespace MintyPHP\Http;
|
|
|
|
use MintyPHP\I18n;
|
|
use MintyPHP\Router;
|
|
|
|
/**
|
|
* Handles locale detection and URL manipulation for multi-language support.
|
|
*/
|
|
class LocaleResolver
|
|
{
|
|
private array $availableLocales;
|
|
private string $defaultLocale;
|
|
private string $basePath;
|
|
|
|
public function __construct(?array $availableLocales = null, ?string $defaultLocale = null)
|
|
{
|
|
$this->defaultLocale = $defaultLocale ?? I18n::$defaultLocale;
|
|
$this->availableLocales = $availableLocales ?? (defined('APP_LOCALES') ? APP_LOCALES : [$this->defaultLocale]);
|
|
$this->basePath = trim(parse_url(Router::getBaseUrl(), PHP_URL_PATH) ?: '/', '/');
|
|
}
|
|
|
|
/**
|
|
* Parse a request URI and extract locale information.
|
|
*
|
|
* @return array{
|
|
* locale: string,
|
|
* pathWithoutLocale: string,
|
|
* query: string,
|
|
* hadLocaleInUrl: bool,
|
|
* hadInvalidLocale: bool
|
|
* }
|
|
*/
|
|
public function parseUri(string $uri): array
|
|
{
|
|
$path = parse_url($uri, PHP_URL_PATH) ?: '';
|
|
$query = parse_url($uri, PHP_URL_QUERY) ?: '';
|
|
|
|
$relativePath = $this->stripBasePath($path);
|
|
$segments = $relativePath === '' ? [] : explode('/', $relativePath);
|
|
|
|
$localeCandidate = $segments[0] ?? '';
|
|
$hadLocaleInUrl = false;
|
|
$hadInvalidLocale = false;
|
|
$locale = '';
|
|
|
|
if ($localeCandidate !== '' && in_array($localeCandidate, $this->availableLocales, true)) {
|
|
$locale = array_shift($segments);
|
|
$hadLocaleInUrl = true;
|
|
} elseif ($localeCandidate !== '' && $this->looksLikeLocale($localeCandidate)) {
|
|
array_shift($segments);
|
|
$hadInvalidLocale = true;
|
|
}
|
|
|
|
return [
|
|
'locale' => $locale,
|
|
'pathWithoutLocale' => implode('/', $segments),
|
|
'query' => $query,
|
|
'hadLocaleInUrl' => $hadLocaleInUrl,
|
|
'hadInvalidLocale' => $hadInvalidLocale,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Resolve the effective locale from multiple sources (priority order):
|
|
* 1. URL segment (explicit)
|
|
* 2. User session preference
|
|
* 3. Cookie preference
|
|
* 4. Default locale
|
|
*/
|
|
public function resolveLocale(string $urlLocale, ?string $userLocale = null, ?string $cookieLocale = null): string
|
|
{
|
|
if ($urlLocale !== '' && $this->isValidLocale($urlLocale)) {
|
|
return $urlLocale;
|
|
}
|
|
|
|
if ($userLocale !== null && $userLocale !== '' && $this->isValidLocale($userLocale)) {
|
|
return $userLocale;
|
|
}
|
|
|
|
if ($cookieLocale !== null && $cookieLocale !== '' && $this->isValidLocale($cookieLocale)) {
|
|
return $cookieLocale;
|
|
}
|
|
|
|
return $this->defaultLocale;
|
|
}
|
|
|
|
/**
|
|
* Build a new REQUEST_URI without the locale segment.
|
|
*/
|
|
public function buildUriWithoutLocale(string $pathWithoutLocale, string $query): string
|
|
{
|
|
$newPath = $this->basePath !== '' ? '/' . $this->basePath : '';
|
|
|
|
if ($pathWithoutLocale !== '') {
|
|
$newPath .= '/' . $pathWithoutLocale;
|
|
} elseif ($newPath === '') {
|
|
$newPath = '/';
|
|
}
|
|
|
|
return $newPath . ($query !== '' ? '?' . $query : '');
|
|
}
|
|
|
|
/**
|
|
* Check if a locale string is valid (exists in available locales).
|
|
*/
|
|
public function isValidLocale(string $locale): bool
|
|
{
|
|
return in_array($locale, $this->availableLocales, true);
|
|
}
|
|
|
|
public function getAvailableLocales(): array
|
|
{
|
|
return $this->availableLocales;
|
|
}
|
|
|
|
public function getDefaultLocale(): string
|
|
{
|
|
return $this->defaultLocale;
|
|
}
|
|
|
|
private function stripBasePath(string $path): string
|
|
{
|
|
$relativePath = ltrim($path, '/');
|
|
|
|
if ($this->basePath !== '' && strpos($relativePath, $this->basePath . '/') === 0) {
|
|
return substr($relativePath, strlen($this->basePath) + 1);
|
|
}
|
|
|
|
if ($this->basePath !== '' && $relativePath === $this->basePath) {
|
|
return '';
|
|
}
|
|
|
|
return $relativePath;
|
|
}
|
|
|
|
private function looksLikeLocale(string $candidate): bool
|
|
{
|
|
return preg_match('/^[a-z]{2}(?:-[a-z]{2})?$/i', $candidate) === 1;
|
|
}
|
|
}
|