feat(i18n): translation files DE/EN/UK/RU + layout integration (closes #74)
This commit is contained in:
71
app/Controllers/LocaleSwitcher.php
Normal file
71
app/Controllers/LocaleSwitcher.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Core\I18n;
|
||||
use App\Core\Locale;
|
||||
|
||||
/**
|
||||
* Renders the language switcher widget. Pure HTML generation — no
|
||||
* side effects, no header writing.
|
||||
*
|
||||
* Output is semantic HTML (a <ul> of links) with `aria-current` for the
|
||||
* active locale, `hreflang` for SEO, and `lang` for screen readers.
|
||||
* The basic list-of-locale-codes is the MVP. Sub-Issue D (responsive
|
||||
* SVG flag UI) refines the presentation.
|
||||
*/
|
||||
final class LocaleSwitcher
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $currentLocale,
|
||||
private readonly string $currentPath,
|
||||
) {
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$path = $this->sanitisePath($this->currentPath);
|
||||
$ariaLabel = htmlspecialchars(
|
||||
I18n::t('locale.switcher.aria', [], $this->currentLocale),
|
||||
ENT_QUOTES,
|
||||
'UTF-8',
|
||||
);
|
||||
|
||||
$html = '<ul class="locale-switcher" role="list" aria-label="' . $ariaLabel . '">';
|
||||
foreach (Locale::SUPPORTED as $code) {
|
||||
$isCurrent = $code === $this->currentLocale;
|
||||
$name = htmlspecialchars(I18n::t('locale.' . $code, [], $this->currentLocale), ENT_QUOTES, 'UTF-8');
|
||||
$codeAttr = htmlspecialchars($code, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
$html .= '<li' . ($isCurrent ? ' class="is-current" aria-current="true"' : '') . '>';
|
||||
if ($isCurrent) {
|
||||
$html .= '<span lang="' . $codeAttr . '">' . $codeAttr . '<span class="visually-hidden"> (' . $name . ')</span></span>';
|
||||
} else {
|
||||
$url = '/locale?set=' . rawurlencode($code) . '&return=' . rawurlencode($path);
|
||||
$html .= '<a href="' . $url . '" hreflang="' . $codeAttr . '" lang="' . $codeAttr . '" rel="alternate">'
|
||||
. $codeAttr
|
||||
. '<span class="visually-hidden"> (' . $name . ')</span>'
|
||||
. '</a>';
|
||||
}
|
||||
$html .= '</li>';
|
||||
}
|
||||
$html .= '</ul>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the path is safe to embed as a query string value and
|
||||
* a redirect target. Drops query/fragment, keeps only the path.
|
||||
*/
|
||||
private function sanitisePath(string $path): string
|
||||
{
|
||||
$path = parse_url($path, PHP_URL_PATH) ?: '/';
|
||||
if ($path === '' || $path[0] !== '/') {
|
||||
return '/';
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user