1
0
Files
breadcrumb-the-shire/core/Http/LocaleResolver.php
fs 0e86925464 refactor: rename lib/ to core/ for clearer core-module separation
Rename the top-level lib/ directory to core/ so the project root
immediately communicates which code is core platform and which lives
in modules/. PHP namespaces (MintyPHP\*) are unchanged — only the
Composer PSR-4 path mapping moves from lib/ to core/.

Module-internal lib/ directories (modules/*/lib/) are untouched.

Updated across the full stack:
- composer.json PSR-4 mapping
- bootstrap entry points (web/index.php, bin/*, tests/bootstrap.php)
- tooling configs (phpstan.neon, phpunit.xml, php-cs-fixer)
- 26 architecture contract tests
- enforcement-policy, guard-catalog, quality-gates
- all documentation (CLAUDE.md, README, 25 docs/, .agents/skills/)
- bin/qa-extended.sh search paths
- .claude/settings.local.json permission paths

Workflow: .agents/runs/CORE-LIB-RENAME-001/ (Analyst → Planner →
Executor → Code Review (4 findings fixed) → Security Review →
Acceptance Test → Finalizer)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 23:20:42 +02:00

127 lines
3.7 KiB
PHP

<?php
namespace MintyPHP\Http;
use MintyPHP\I18n;
/**
* 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(\MintyPHP\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 = Request::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 looksLikeLocale(string $candidate): bool
{
return preg_match('/^[a-z]{2}(?:-[a-z]{2})?$/i', $candidate) === 1;
}
}