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:
Yvv
2026-10-21 02:58:34 +02:00
co-authored by Claude Opus 5.5
parent 67ddee28b2
commit 7a022dd35e
66 changed files with 7771 additions and 15 deletions
+60
View File
@@ -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 };