db5a997e50
Architecture décidée et documentée dans ADR.md : SvelteKit natif en fichiers plats plutôt que Strapi (55 outils, données statiques, souveraineté et budget performance prioritaires — doctrine §1.2, §4.4). - frontend/ : SvelteKit 2 + Svelte 5 (runes), adapter-static, 58 pages prérendues, CSS natif en @layer (reset→tokens→base→layouts→components), tokens oklch(), container queries, typo fluide clamp(), 2 polices WOFF2 auto-hébergées (Atkinson Hyperlegible, Fraunces), zéro CDN tiers - i18n FR/gcf typé (clé manquante = erreur de build), bascule Kréyòl persistée en localStorage, micro-textes créoles (« Ou pa manké ayen ») - Composants : ToolCard, DifficultyBadge (forme+texte+couleur, jamais la couleur seule), CategoryNav (≤5 entrées, divulgation progressive), SearchBar (raccourci /), LanguageSwitcher, EndMarker - content/ : 55 fiches migrées depuis src/ (script frontend/scripts/ migrate-tools.mjs, idempotent), licences SPDX reprises de directory.json - docs/ : DESIGN_SYSTEM.md et CONTENT_GUIDE.md Budgets vérifiés : JS 41 Ko gzip, CSS 2,9 Ko gzip, svelte-check 0 erreur. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
55 lines
1.7 KiB
TypeScript
55 lines
1.7 KiB
TypeScript
import fr from './fr.json'
|
|
import gcf from './gcf.json'
|
|
import { browser } from '$app/environment'
|
|
import { defaultLocale, type Locale } from './locales'
|
|
|
|
export { locales, localeNames, defaultLocale, type Locale } from './locales'
|
|
|
|
type Dict = typeof fr
|
|
// gcf est vérifié par TypeScript contre la forme du français : une clé
|
|
// manquante est une erreur de build, pas un fallback silencieux (ADR-001).
|
|
const dicts: Record<Locale, Dict> = { fr, gcf }
|
|
|
|
const STORAGE_KEY = 'oki-lang'
|
|
|
|
function initialLocale(): Locale {
|
|
if (!browser) return defaultLocale
|
|
const saved = localStorage.getItem(STORAGE_KEY)
|
|
return saved === 'gcf' || saved === 'fr' ? saved : defaultLocale
|
|
}
|
|
|
|
// Rune d'état partagée (Svelte 5) : la langue de l'interface.
|
|
// Le HTML prérendu est en fr ; le créole est une amélioration progressive
|
|
// (persistance localStorage — mission §4.3, doctrine §4.2).
|
|
const state = $state({ locale: initialLocale() })
|
|
|
|
export function getLocale(): Locale {
|
|
return state.locale
|
|
}
|
|
|
|
export function setLocale(locale: Locale) {
|
|
state.locale = locale
|
|
if (browser) {
|
|
localStorage.setItem(STORAGE_KEY, locale)
|
|
document.documentElement.lang = locale
|
|
}
|
|
}
|
|
|
|
function lookup(dict: Dict, path: string): unknown {
|
|
return path.split('.').reduce<unknown>((node, key) => {
|
|
if (node && typeof node === 'object' && key in node) {
|
|
return (node as Record<string, unknown>)[key]
|
|
}
|
|
return undefined
|
|
}, dict)
|
|
}
|
|
|
|
/** Chaîne d'interface pour `path` dans la langue courante (réactif). */
|
|
export function t(path: string): string {
|
|
const hit = lookup(dicts[state.locale], path) ?? lookup(dicts[defaultLocale], path)
|
|
if (typeof hit !== 'string') {
|
|
throw new Error(`Clé i18n manquante « ${path} » (locale : ${state.locale})`)
|
|
}
|
|
return hit
|
|
}
|