- App\Controllers\LocaleController: GET /locale?set=xx&return=/path - Sets 1-year cookie (HttpOnly=false for SSR, SameSite=Lax, Secure on HTTPS) - 302 redirect to explicit return URL > Referer > / - Pure buildResponse() helper for unit tests (no headers/exit) - current() helper: resolves locale from $_GET/$_COOKIE/Accept-Language - safeRedirect: rejects absolute URLs, protocol-relative (//evil.com), backslash tricks (\\evil.com), javascript:/data: schemes - 28 PHPUnit tests (LocaleControllerTest), all green - Total project tests now: 92
- App\Core\Locale: query-param > cookie > Accept-Language > 'de' fallback - BCP-47 region stripping (en-US -> en, uk-UA -> uk) - q-value sorting with stable order - og:locale mapping (de_DE, en_GB, uk_UA, ru_RU) - hreflang alternates helper - App\Core\I18n: t() with {placeholder} interpolation, lookup chain current-locale -> de -> key, in-memory cache - ADR-002: documents the architecture decision - 46 PHPUnit tests (LocaleTest, I18nTest), all green
- Add composer.json with PHPUnit 11 and PSR-4 autoloading - Add phpunit.xml configuration - Rename app/core/ → app/Core/ and app/controllers/ → app/Controllers/ (PSR-4) - Add 18 unit tests for App\Core\Router (31 assertions) - addRoute(): default action, custom action, overwrite - dispatch(): URL normalization, direct match, legacy redirects - dispatch(): 404 handling, controller/action not found exceptions - TestableRouter subclass to intercept side-effects - Update .gitignore (vendor/, .phpunit.cache/)