Files
decision/web/src/lib/i18n/index.svelte.ts
T

61 lines
1.9 KiB
TypeScript

// Interface language: fr, en, es (Yvv, 2026-10-21). French is the source.
import { en } from './en';
import { es } from './es';
import { fr, type Key, type Messages } from './fr';
import type { Lang } from '../types';
export const LANGS: Lang[] = ['fr', 'en', 'es'];
const CATALOGS: Record<Lang, Messages> = { fr, en, es };
const LOCALES: Record<Lang, string> = { fr: 'fr-FR', en: 'en-GB', es: 'es-ES' };
function initial(): Lang {
try {
const saved = localStorage.getItem('ld:lang');
if (saved === 'fr' || saved === 'en' || saved === 'es') return saved;
} catch {
/* no storage */
}
const nav = (typeof navigator !== 'undefined' ? navigator.language : 'fr').slice(0, 2);
return nav === 'en' || nav === 'es' ? nav : 'fr';
}
class I18n {
lang = $state<Lang>('fr');
init() {
this.set(initial());
}
set(lang: Lang) {
this.lang = lang;
document.documentElement.lang = lang;
try {
localStorage.setItem('ld:lang', lang);
} catch {
/* no storage */
}
}
t = (key: Key, params: Record<string, string | number> = {}): string => {
const raw = CATALOGS[this.lang][key] ?? fr[key] ?? key;
return raw.replace(/\{(\w+)\}/g, (_, k: string) => (k in params ? String(params[k]) : `{${k}}`));
};
/** `<key>.one` / `<key>.other` — French says « 0 session », English and Spanish « 0 sessions ». */
plural = (key: string, n: number, params: Record<string, string | number> = {}): string => {
const one = this.lang === 'fr' ? n <= 1 : n === 1;
return this.t(`${key}.${one ? 'one' : 'other'}` as Key, { n, ...params });
};
date = (iso: string | null | undefined): string => {
if (!iso) return '';
return new Date(iso).toLocaleDateString(LOCALES[this.lang], { day: 'numeric', month: 'long', year: 'numeric' });
};
number = (n: number): string => n.toLocaleString(LOCALES[this.lang]);
}
export const i18n = new I18n();
export const t = i18n.t;
export type { Key };