Docker images / prepare-release (push) Successful in 9s
Docker images / backend-test (push) Successful in 2m19s
Docker images / frontend-image (push) Successful in 3m26s
Docker images / updater-image (push) Successful in 47s
Docker images / backend-image (push) Successful in 2m23s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 10s
- Introduced country-specific handling in traffic flow analytics, including new mappings for country nodes and edges. - Added mock data for countries and their respective service paths to improve visualization in the Network Map. - Updated the `countryName` function to utilize `Intl.DisplayNames` for better localization of country names. - Enhanced tests to validate country handling and ensure accurate representation in flow analytics. Co-authored-by: Cursor <[email protected]>
95 lines
2.9 KiB
TypeScript
95 lines
2.9 KiB
TypeScript
/**
|
|
* Flag — renders a country flag image from an ISO 3166-1 alpha-2 code.
|
|
* Images served from flagcdn.com (free, no auth required).
|
|
*/
|
|
|
|
const COUNTRY_NAMES: Record<string, string> = {
|
|
RU: "Россия",
|
|
DE: "Германия",
|
|
NL: "Нидерланды",
|
|
SG: "Сингапур",
|
|
US: "США",
|
|
GB: "Великобритания",
|
|
FR: "Франция",
|
|
FI: "Финляндия",
|
|
SE: "Швеция",
|
|
PL: "Польша",
|
|
UA: "Украина",
|
|
TR: "Турция",
|
|
JP: "Япония",
|
|
HK: "Гонконг",
|
|
}
|
|
|
|
let regionNames: Intl.DisplayNames | null | undefined
|
|
|
|
function regionDisplayName(iso: string): string | undefined {
|
|
try {
|
|
if (regionNames === undefined) {
|
|
regionNames = typeof Intl !== "undefined" && "DisplayNames" in Intl
|
|
? new Intl.DisplayNames(["ru"], { type: "region" })
|
|
: null
|
|
}
|
|
return regionNames?.of(iso) ?? undefined
|
|
} catch {
|
|
return undefined
|
|
}
|
|
}
|
|
|
|
/** Country name in Russian (fallback to ISO code) */
|
|
export function countryName(code: string): string {
|
|
const iso = code.toUpperCase()
|
|
if (!iso) return code
|
|
if (COUNTRY_NAMES[iso]) return COUNTRY_NAMES[iso]
|
|
const intl = regionDisplayName(iso)
|
|
if (intl && intl !== iso) return intl
|
|
return iso
|
|
}
|
|
|
|
interface FlagProps {
|
|
code: string
|
|
/** px size — width of the flag image (height auto-scales 3:2 ratio) */
|
|
size?: number
|
|
className?: string
|
|
}
|
|
|
|
// flagcdn.com supports only these widths
|
|
const CDN_SIZES = [20, 40, 80, 160, 320, 640, 1280, 2560]
|
|
|
|
function nearestCdnSize(px: number): number {
|
|
return CDN_SIZES.find(s => s >= px) ?? CDN_SIZES[CDN_SIZES.length - 1]
|
|
}
|
|
|
|
/** CDN URL for SVG `<image href>` (flagcdn widths only). */
|
|
export function flagCdnUrl(code: string, size = 40): string | null {
|
|
const lower = code.toLowerCase()
|
|
if (!/^[a-z]{2}$/.test(lower)) return null
|
|
return `https://flagcdn.com/w${nearestCdnSize(size)}/${lower}.png`
|
|
}
|
|
|
|
/**
|
|
* Renders a flag <img> for a given ISO 3166-1 alpha-2 country code.
|
|
* Source: https://flagcdn.com — free CDN, no API key needed.
|
|
*/
|
|
export function Flag({ code, size = 20, className }: FlagProps) {
|
|
if (!code) return null
|
|
const lower = code.toLowerCase()
|
|
if (!/^[a-z]{2}$/.test(lower)) return null
|
|
const name = countryName(code.toUpperCase())
|
|
const cdnSrc = nearestCdnSize(size)
|
|
const cdnSrc2x = nearestCdnSize(size * 2)
|
|
return (
|
|
// Внешний CDN (динамический URL) — next/image без remotePatterns не подходит
|
|
// eslint-disable-next-line @next/next/no-img-element -- flagcdn.com, размеры задаём явно
|
|
<img
|
|
src={`https://flagcdn.com/w${cdnSrc}/${lower}.png`}
|
|
srcSet={`https://flagcdn.com/w${cdnSrc2x}/${lower}.png 2x`}
|
|
width={size}
|
|
height={Math.round(size * 0.75)}
|
|
alt={name}
|
|
title={name}
|
|
className={className}
|
|
style={{ display: "inline-block", verticalAlign: "middle", borderRadius: 2 }}
|
|
/>
|
|
)
|
|
}
|