v3 : interface Svelte — accueil, groupes, lecteur du texte en vigueur, panneau contextuel, éditeur à assistant, trustinWallet
- Accueil sobre, menu complet à gauche (Décisions, Docs de référence, Mandats, Protocoles), intro demandée, quatre sections, connexion Ğ1. - Lecteur : texte en vigueur en Literata, actions contextuelles par article (le vote, propositions, proposer), propositions en cours sous la « surface » avec leur niveau d'eau, aperçus « si les bien cotées passent » / « si tout passe », langue du texte fr/en/es (traductions éditoriales). - Panneau : vote pour/contre, seuil et formule (MathML), inertie et son changement (voté en inertie +), coquille sans vote, traduction, signalement de contradiction, historique des corrections. - trustinWallet (/acces) : groupes et membres, invitations à usage unique, administrateurs, retours d'usage. - Contrôles : vitest (markdown, catalogues, anti-lexique), Playwright C1–C10 + M1. - Sections du seed portées jusqu'à l'écran. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
// 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 and Spanish use « one » for 0 and 1. */
|
||||
plural = (key: string, n: number, params: Record<string, string | number> = {}): string => {
|
||||
const one = this.lang === 'en' ? 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 };
|
||||
Reference in New Issue
Block a user