Files
annu-kute-ced/src/lib/i18n/index.ts
T
sucupira d6efb6736f Réécriture SvelteKit statique (Svelte 5 runes + TS, adapter-static)
- Données PeerTube (GADE), Castopod (KUTE) et Mastodon (BOKANTE) bakées au
  build via +page.server.ts (throttle + retry 429 + déduplication)
- Charte OKI complète : tokens, thème sombre par défaut (clair opt-in,
  anti-FOUC), Archivo/Inter self-hébergées, flag-bar, sprite SVG (zéro emoji
  en interface, zéro Font Awesome/CDN)
- i18n FR/EN par routage [[locale]], bundles JSON, hreflang, %lang% serveur
- Pages : accueil, /video/[uuid] (embed, téléchargements, partage,
  commentaires, JSON-LD VideoObject), /categories/[id], /recherche (index
  JSON baké, recherche client), /direct (live + annonce multi-fuseaux),
  /dons, /mentions-legales, /offline + 404 kréyòl
- Motion gwoka : KineticText (scrub view()), ScrollProgressBar, FlagChip,
  reveal syncopé, View Transitions, gate prefers-reduced-motion unique
- PWA : manifest + Workbox generateSW (fallback /offline/), registerSW
  statique
- Sécurité : CSP par page en <meta> (SHA-256 des inline via
  scripts/postbuild-csp.mjs) + headers globaux (_headers Cloudflare et
  .htaccess o2switch), redirections des anciennes URLs PHP
- Doc : docs/DEPLOIEMENT-SVELTEKIT.md
2026-07-23 00:49:02 -04:00

76 lines
2.6 KiB
TypeScript

import { resolve } from '$app/paths';
import type { Locale } from '$lib/types';
import fr from './fr.json';
import en from './en.json';
export type Bundle = typeof fr;
const bundles: Record<Locale, Bundle> = { fr, en: en as Bundle };
export const locales: Locale[] = ['fr', 'en'];
export const defaultLocale: Locale = 'fr';
export function getBundle(locale: Locale): Bundle {
return bundles[locale] ?? bundles[defaultLocale];
}
export function localeFromPath(pathname: string): Locale {
return pathname.split('/')[1] === 'en' ? 'en' : 'fr';
}
export function stripLocale(pathname: string): string {
if (pathname === '/en') return '/';
if (pathname.startsWith('/en/')) return pathname.slice(3);
return pathname;
}
export function hrefFor(locale: Locale, path: string): string {
const clean = path.startsWith('/') ? path : `/${path}`;
const full = locale === 'en' ? `/en${clean === '/' ? '/' : clean}` : clean;
const [pathname, rest = ''] = full.split(/(?=[?#])/);
const withSlash = pathname.endsWith('/') ? pathname : `${pathname}/`;
// Chemin interne calculé (locale préfixée) : cast requis par le typage des routes
return resolve(`${withSlash}${rest}` as '/');
}
export function alternatePath(pathname: string, target: Locale): string {
const bare = stripLocale(pathname);
return hrefFor(target, bare);
}
export function t(template: string, vars: Record<string, string | number> = {}): string {
return template.replace(/\{(\w+)\}/g, (_, key) => String(vars[key] ?? `{${key}}`));
}
export function formatDate(dateString: string, locale: Locale): string {
const date = new Date(dateString);
if (Number.isNaN(date.getTime())) return dateString;
return new Intl.DateTimeFormat(locale === 'en' ? 'en-GB' : 'fr-FR', {
day: 'numeric',
month: 'long',
year: 'numeric'
}).format(date);
}
export function formatDuration(seconds: number): string {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = Math.floor(seconds % 60);
if (h > 0) return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
return `${m}:${String(s).padStart(2, '0')}`;
}
export function formatViewCount(views: number): string {
if (views >= 1_000_000) return `${(views / 1_000_000).toFixed(1)}M`;
if (views >= 1_000) return `${(views / 1_000).toFixed(1)}K`;
return String(views);
}
export function formatFileSize(bytes: number): string {
if (bytes <= 0) return '';
const units = ['o', 'Ko', 'Mo', 'Go'];
const exponent = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
const value = bytes / 1024 ** exponent;
return `${value.toFixed(value >= 10 || exponent === 0 ? 0 : 1)} ${units[exponent]}`;
}