Claude 08968a85a1
feat(ai-act-kompass): FR/IT/ES locales, EN fallback architecture, PWA install support
- Full UI dictionaries for French, Italian and Spanish (all views,
  wizard, legal gate, templates); browser-language auto-detection
  extended to all five locales
- Fallback architecture: LText now carries optional fr/it/es entries;
  regulatory legal texts (content pack, classify reasons, document
  strings) fall back to English until legal translations land —
  clearly stated in the language setting
- Locale-aware date formatting (DE dots, FR/IT/ES slashes, EN ISO)
- PWA: web app manifest, network-first service worker with offline
  cache fallback, generated icons (512/192/apple-touch) — installable
  on phone home screen and macOS dock
- 2 new tests incl. dictionary-completeness check for FR/IT/ES
  (58 total)

Claude-Session: https://claude.ai/code/session_017YjohVNx1ZGNM4MX7tHpfv
2026-07-04 10:05:28 +00:00

87 lines
2.9 KiB
TypeScript

import { useI18n } from '../i18n/context';
import type { Locale } from '../i18n/types';
import type { RiskClass, Role } from '../engine/types';
/** Kurze UI-Labels für Risikoklassen je Sprache. */
export const RISK_UI: Readonly<Record<Locale, Readonly<Record<RiskClass, string>>>> = {
de: {
'out-of-scope': 'Nicht anwendbar',
prohibited: 'Verboten',
high: 'Hochrisiko',
limited: 'Begrenztes Risiko',
minimal: 'Minimales Risiko',
},
en: {
'out-of-scope': 'Out of scope',
prohibited: 'Prohibited',
high: 'High-risk',
limited: 'Limited risk',
minimal: 'Minimal risk',
},
fr: {
'out-of-scope': 'Hors champ',
prohibited: 'Interdit',
high: 'Haut risque',
limited: 'Risque limité',
minimal: 'Risque minimal',
},
it: {
'out-of-scope': 'Fuori ambito',
prohibited: 'Vietato',
high: 'Alto rischio',
limited: 'Rischio limitato',
minimal: 'Rischio minimo',
},
es: {
'out-of-scope': 'Fuera de ámbito',
prohibited: 'Prohibido',
high: 'Alto riesgo',
limited: 'Riesgo limitado',
minimal: 'Riesgo mínimo',
},
};
/** Kurze UI-Labels für Rollen je Sprache. */
export const ROLE_UI: Readonly<Record<Locale, Readonly<Record<Role, string>>>> = {
de: { provider: 'Anbieter', deployer: 'Betreiber', importer: 'Einführer', distributor: 'Händler' },
en: { provider: 'Provider', deployer: 'Deployer', importer: 'Importer', distributor: 'Distributor' },
fr: { provider: 'Fournisseur', deployer: 'Déployeur', importer: 'Importateur', distributor: 'Distributeur' },
it: { provider: 'Fornitore', deployer: 'Deployer', importer: 'Importatore', distributor: 'Distributore' },
es: { provider: 'Proveedor', deployer: 'Responsable del despliegue', importer: 'Importador', distributor: 'Distribuidor' },
};
/** Farbiges Stempel-Badge für die Risikoklasse eines Systems. */
export function RiskBadge({ risk }: { readonly risk: RiskClass }): JSX.Element {
const { locale } = useI18n();
return <span className={`badge ${risk}`}>{RISK_UI[locale][risk]}</span>;
}
/**
* Löst einen Browser-Download für generierten Text aus.
*
* @param filename - Zieldateiname
* @param content - Dateiinhalt
* @param mime - MIME-Type (Default: Markdown)
*/
export function downloadText(filename: string, content: string, mime = 'text/markdown'): void {
const blob = new Blob([content], { type: `${mime};charset=utf-8` });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
/** Heutiges Datum im ISO-Format (YYYY-MM-DD). */
export function todayISO(): string {
return new Date().toISOString().slice(0, 10);
}
/** Formatiert ein ISO-Datum für die UI (DE: TT.MM.JJJJ, FR/IT/ES: TT/MM/JJJJ, EN: ISO). */
export function uiDate(iso: string, locale: Locale): string {
if (locale === 'en') return iso;
const sep = locale === 'de' ? '.' : '/';
return iso.split('-').reverse().join(sep);
}