05010771a3
A tiny typed i18n layer (no library): en.ts is the single source of UI copy and the clarity rewrite lives there — shorter sentences, plain words, the dense Methodology/Scoreboard copy untangled. de.ts mirrors its shape exactly, enforced by the compiler, with placeholder parity covered by tests. Every page now reads from the dictionary via useT(); dates flip locale through useFormat() (en-GB/de-DE); raw server values (status, outcome, stage) map through total records. The header gains an EN/DE toggle (persisted, defaults from the browser language); <html lang> stays in sync. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
51 lines
2.0 KiB
TypeScript
51 lines
2.0 KiB
TypeScript
// Date/score formatting in the viewer's local timezone, in the app locale.
|
|
import type { Locale } from '@/stores/localeStore';
|
|
|
|
const TAG: Record<Locale, string> = { en: 'en-GB', de: 'de-DE' };
|
|
const cache = new Map<string, Intl.DateTimeFormat>();
|
|
function dtf(locale: Locale, kind: 'time' | 'day'): Intl.DateTimeFormat {
|
|
const key = `${locale}:${kind}`;
|
|
let f = cache.get(key);
|
|
if (!f) {
|
|
f = new Intl.DateTimeFormat(
|
|
TAG[locale],
|
|
kind === 'time' ? { hour: '2-digit', minute: '2-digit' } : { weekday: 'short', day: 'numeric', month: 'short' },
|
|
);
|
|
cache.set(key, f);
|
|
}
|
|
return f;
|
|
}
|
|
const dayKey = new Intl.DateTimeFormat('en-CA', { year: 'numeric', month: '2-digit', day: '2-digit' });
|
|
|
|
export function kickoffTime(iso: string, locale: Locale = 'en'): string {
|
|
return dtf(locale, 'time').format(new Date(iso));
|
|
}
|
|
|
|
export function kickoffDay(iso: string, locale: Locale = 'en'): string {
|
|
return dtf(locale, 'day').format(new Date(iso));
|
|
}
|
|
|
|
/** Stable local-day key (YYYY-MM-DD) for grouping fixtures by date — locale-independent. */
|
|
export function dayKeyOf(iso: string): string {
|
|
return dayKey.format(new Date(iso));
|
|
}
|
|
|
|
export function isToday(iso: string): boolean {
|
|
return dayKeyOf(iso) === dayKey.format(new Date());
|
|
}
|
|
|
|
/** "in 2m", "today 19:00", "Thu 11 Jun · 19:00" — a compact when-label.
|
|
* `rel` carries the localized templates (common.inMinutes / common.todayAt). */
|
|
export function relativeKickoff(
|
|
iso: string,
|
|
locale: Locale = 'en',
|
|
rel: { inMinutes: string; todayAt: string } = { inMinutes: 'in {n}m', todayAt: 'today {time}' },
|
|
): string {
|
|
const diffMin = Math.round((new Date(iso).getTime() - Date.now()) / 60000);
|
|
if (diffMin < 0) return kickoffTime(iso, locale);
|
|
if (diffMin < 60) return rel.inMinutes.replace('{n}', String(diffMin));
|
|
if (isToday(iso)) return rel.todayAt.replace('{time}', kickoffTime(iso, locale));
|
|
if (diffMin < 60 * 24 * 7) return `${kickoffDay(iso, locale)} · ${kickoffTime(iso, locale)}`;
|
|
return kickoffDay(iso, locale);
|
|
}
|