fix(flags): replace hand-coded inline SVGs with official flag-icons assets

The previous inline flag SVGs were visually broken — most notably the
'en' Union Jack, which was reduced to a single X plus a cross and did
not resemble the real flag at all. The 'de' and 'ru' stripes also had
slight off-by-pixel rounding errors.

Switched to lipis/flag-icons (CC-BY 4.0) shipped as static files under
public/img/flags/. These are the canonical, professionally-designed
flag icons with correct proportions and all the details of the real
flags. Loaded via plain <img> tags (no JS, no external CDN at
runtime, no FOUC, no extra request after the page is cached).

Locale code mapping: en -> gb (per ADR-002, en = en-GB). Unknown
locales fall back to a 1x1 transparent gif so the layout stays
intact.
This commit is contained in:
Hermes
2026-06-04 19:43:23 +00:00
parent 70691ff242
commit 391985cd42
7 changed files with 82 additions and 62 deletions

View File

@@ -47,7 +47,7 @@ final class LocaleSwitcher
'UTF-8',
);
$currentCode = htmlspecialchars($this->currentLocale, ENT_QUOTES, 'UTF-8');
$currentFlag = self::flagSvg($this->currentLocale);
$currentFlag = self::flagImg($this->currentLocale);
$html = '<details class="locale-switcher">';
$html .= '<summary class="locale-switcher__trigger"'
@@ -68,7 +68,7 @@ final class LocaleSwitcher
'UTF-8',
);
$codeAttr = htmlspecialchars($code, ENT_QUOTES, 'UTF-8');
$flag = self::flagSvg($code);
$flag = self::flagImg($code);
$html .= '<li>';
if ($isCurrent) {
@@ -99,46 +99,40 @@ final class LocaleSwitcher
}
/**
* Inline 24×16 SVG for the four supported locales.
* Country flag for the given locale. Renders a 24×18 <img>
* pointing at the official flag-icons SVG asset shipped under
* public/img/flags/. 4:3 aspect (de/gb/ua/ru), crisp at any DPI,
* no external CDN dependency.
*
* - DE: black/red/gold horizontal stripes (Germany)
* - EN: simplified Union Jack (en-GB per ADR-002)
* - UK: blue/yellow horizontal stripes (Ukraine)
* - RU: white/blue/red horizontal stripes (Russia)
*
* Decorative: marked `aria-hidden="true"` + `focusable="false"`.
* The full accessible name is conveyed by the visible label and
* the <a>'s hreflang/lang.
* Decorative: `alt=""` (the visible locale-switcher label and
* the <a>'s `hreflang`/`lang` carry the accessible name).
*/
public static function flagSvg(string $locale): string
public static function flagImg(string $locale): string
{
$svg = match ($locale) {
'de' => '<svg class="flag" viewBox="0 0 24 16" aria-hidden="true" focusable="false">'
. '<rect width="24" height="5.33" fill="#000"/>'
. '<rect y="5.33" width="24" height="5.34" fill="#DD0000"/>'
. '<rect y="10.67" width="24" height="5.33" fill="#FFCC00"/>'
. '</svg>',
'en' => '<svg class="flag" viewBox="0 0 24 16" aria-hidden="true" focusable="false">'
. '<rect width="24" height="16" fill="#012169"/>'
. '<path d="M0,0 L24,16 M24,0 L0,16" stroke="#fff" stroke-width="2.4"/>'
. '<path d="M0,0 L24,16 M24,0 L0,16" stroke="#C8102E" stroke-width="1.2"/>'
. '<path d="M12,0 V16 M0,8 H24" stroke="#fff" stroke-width="3.2"/>'
. '<path d="M12,0 V16 M0,8 H24" stroke="#C8102E" stroke-width="1.6"/>'
. '</svg>',
'uk' => '<svg class="flag" viewBox="0 0 24 16" aria-hidden="true" focusable="false">'
. '<rect width="24" height="8" fill="#005BBB"/>'
. '<rect y="8" width="24" height="8" fill="#FFD500"/>'
. '</svg>',
'ru' => '<svg class="flag" viewBox="0 0 24 16" aria-hidden="true" focusable="false">'
. '<rect width="24" height="5.33" fill="#fff"/>'
. '<rect y="5.33" width="24" height="5.34" fill="#0039A6"/>'
. '<rect y="10.67" width="24" height="5.33" fill="#D52B1E"/>'
. '</svg>',
default => '<svg class="flag" viewBox="0 0 24 16" aria-hidden="true" focusable="false">'
. '<rect width="24" height="16" fill="#888"/>'
. '</svg>',
$src = self::flagSource($locale);
return '<img class="flag" src="' . $src . '" alt="" width="24" height="18" loading="lazy" decoding="async">';
}
/**
* Map our locale codes to flag-icons file names. Locale "en"
* is en-GB per ADR-002, so the asset is "gb.svg". Anything we
* do not know falls back to a transparent 1×1 gif so the layout
* stays intact and the alt text (from the surrounding <a>) is
* the only signal.
*/
private static function flagSource(string $locale): string
{
$file = match ($locale) {
'de' => 'de',
'en' => 'gb',
'uk' => 'ua',
'ru' => 'ru',
default => null,
};
return $svg;
if ($file === null) {
return 'data:image/gif;base64,R0lGODlhAQABAAAAACwAAAAAAQABAAACAkQBADs=';
}
return '/img/flags/' . $file . '.svg';
}
/**