@discloai/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +218 -0
  3. package/dist/discloai.min.js +1 -0
  4. package/dist/index.d.mts +152 -0
  5. package/dist/index.d.ts +152 -0
  6. package/dist/index.js +795 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/index.mjs +764 -0
  9. package/dist/index.mjs.map +1 -0
  10. package/package.json +58 -0
  11. package/src/__tests__/audit.test.ts +117 -0
  12. package/src/__tests__/init.test.ts +49 -0
  13. package/src/__tests__/wcag.test.ts +260 -0
  14. package/src/audit.ts +155 -0
  15. package/src/components/AIContentLabel.ts +108 -0
  16. package/src/components/BiometricNotice.ts +82 -0
  17. package/src/components/ChatbotDisclosure.ts +188 -0
  18. package/src/components/DeepfakeLabel.ts +123 -0
  19. package/src/config.ts +191 -0
  20. package/src/i18n/bg.json +9 -0
  21. package/src/i18n/cs.json +9 -0
  22. package/src/i18n/da.json +9 -0
  23. package/src/i18n/de.json +9 -0
  24. package/src/i18n/el.json +9 -0
  25. package/src/i18n/en.json +9 -0
  26. package/src/i18n/es.json +9 -0
  27. package/src/i18n/et.json +9 -0
  28. package/src/i18n/fi.json +9 -0
  29. package/src/i18n/fr.json +9 -0
  30. package/src/i18n/ga.json +9 -0
  31. package/src/i18n/hr.json +9 -0
  32. package/src/i18n/hu.json +9 -0
  33. package/src/i18n/index.ts +145 -0
  34. package/src/i18n/it.json +9 -0
  35. package/src/i18n/lt.json +9 -0
  36. package/src/i18n/lv.json +9 -0
  37. package/src/i18n/mt.json +9 -0
  38. package/src/i18n/nl.json +9 -0
  39. package/src/i18n/pl.json +9 -0
  40. package/src/i18n/pt.json +9 -0
  41. package/src/i18n/ro.json +9 -0
  42. package/src/i18n/sk.json +9 -0
  43. package/src/i18n/sl.json +9 -0
  44. package/src/i18n/sv.json +9 -0
  45. package/src/index.ts +19 -0
  46. package/src/init.ts +56 -0
  47. package/src/vendors.ts +29 -0
  48. package/src/version.ts +1 -0
  49. package/src/wcag.ts +46 -0
package/src/config.ts ADDED
@@ -0,0 +1,191 @@
1
+ import type { ChatbotDisclosureConfig } from "./components/ChatbotDisclosure.js";
2
+ import type { AIContentLabelConfig } from "./components/AIContentLabel.js";
3
+ import type { DeepfakeLabelConfig } from "./components/DeepfakeLabel.js";
4
+ import type { BiometricNoticeConfig } from "./components/BiometricNotice.js";
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Public types
8
+ // ---------------------------------------------------------------------------
9
+
10
+ export interface DiscloAIInitOptions {
11
+ /** Public site identifier. Not a secret — safe to log and include in requests. */
12
+ siteId: string;
13
+ /** CSP nonce to apply to all injected <style> elements. */
14
+ cspNonce?: string;
15
+ /** BCP-47 locale override. Falls back to navigator.language then 'en'. */
16
+ locale?: string;
17
+ /** Override config fetch URL for local dev. Only https:// or http://localhost accepted. */
18
+ configEndpoint?: string;
19
+ chatbotDisclosure?: ChatbotDisclosureConfig;
20
+ aiContentLabel?: AIContentLabelConfig;
21
+ deepfakeLabel?: DeepfakeLabelConfig;
22
+ biometricNotice?: BiometricNoticeConfig;
23
+ }
24
+
25
+ export interface DiscloAIConfig {
26
+ siteId: string;
27
+ locale: string;
28
+ chatbotDisclosure?: ChatbotDisclosureConfig;
29
+ aiContentLabel?: AIContentLabelConfig;
30
+ deepfakeLabel?: DeepfakeLabelConfig;
31
+ biometricNotice?: BiometricNoticeConfig;
32
+ }
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Defaults
36
+ // ---------------------------------------------------------------------------
37
+
38
+ const DEFAULTS: Omit<DiscloAIConfig, "siteId"> = {
39
+ locale: "en",
40
+ };
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // CSS sanitizer
44
+ // ---------------------------------------------------------------------------
45
+
46
+ /** Forbidden patterns that could be used for XSS or data exfiltration via custom CSS. */
47
+ const FORBIDDEN_CSS_PATTERNS: RegExp[] = [
48
+ /url\s*\(/i,
49
+ /@import/i,
50
+ /expression\s*\(/i,
51
+ /javascript:/i,
52
+ ];
53
+
54
+ /**
55
+ * Sanitize a custom CSS string.
56
+ * Returns an empty string and emits a warning if any forbidden pattern is found.
57
+ * Forbidden: url(...), @import, expression(...), javascript:
58
+ *
59
+ * M-1 fix: CSS hex escape sequences (e.g. \75rl() === url()) are normalized
60
+ * before pattern matching to prevent bypass via Unicode escapes.
61
+ */
62
+ export function sanitizeCSS(css: string): string {
63
+ // Strip CSS escape sequences (\XX or \XXXXXX) before pattern matching
64
+ // A CSS escape is a backslash followed by 1–6 hex digits and an optional whitespace
65
+ const normalized = css.replace(/\\[0-9a-fA-F]{1,6}\s?/g, "ESCAPED");
66
+ for (const pattern of FORBIDDEN_CSS_PATTERNS) {
67
+ if (pattern.test(normalized)) {
68
+ console.warn("[DiscloAI] Custom CSS blocked: forbidden pattern detected");
69
+ return "";
70
+ }
71
+ }
72
+ return css;
73
+ }
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Remote config fetch
77
+ // ---------------------------------------------------------------------------
78
+
79
+ /**
80
+ * Fetch remote config from the DiscloAI API.
81
+ *
82
+ * SECURITY NOTES:
83
+ * - Public endpoint — no auth headers are sent.
84
+ * - siteId is a public tenant identifier, not a secret.
85
+ * - Remote response NEVER contains write tokens, session tokens, or server-side secrets.
86
+ * - Only known component-config shape keys are extracted (see extractSafeConfigShape).
87
+ *
88
+ * Server is expected to respond with: Cache-Control: max-age=60
89
+ *
90
+ * @param siteId - Public site identifier
91
+ * @returns Partial remote config, or null on timeout / network error
92
+ */
93
+ async function fetchRemoteConfig(
94
+ siteId: string,
95
+ configEndpoint?: string,
96
+ ): Promise<Partial<DiscloAIConfig> | null> {
97
+ const controller = new AbortController();
98
+ const timeoutId = setTimeout(() => controller.abort(), 2000);
99
+
100
+ // Allow http://localhost and http://127.0.0.1 for local dev; reject everything else non-HTTPS
101
+ const defaultUrl = `https://api.discloai.com/v1/config/${encodeURIComponent(siteId)}`;
102
+ const rawUrl = configEndpoint ?? defaultUrl;
103
+ const isLocalDev =
104
+ rawUrl.startsWith("http://localhost") ||
105
+ rawUrl.startsWith("http://127.0.0.1");
106
+ const url = rawUrl.startsWith("https://") || isLocalDev ? rawUrl : defaultUrl;
107
+
108
+ try {
109
+ const response = await fetch(url, { signal: controller.signal });
110
+
111
+ if (!response.ok) return null;
112
+
113
+ const raw = (await response.json()) as unknown;
114
+ return extractSafeConfigShape(raw);
115
+ } catch {
116
+ // 2-second timeout or network error — fall back silently to local/defaults
117
+ return null;
118
+ } finally {
119
+ clearTimeout(timeoutId);
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Extract only known, safe config keys from the remote response.
125
+ * Never forwards unknown keys or any token/secret field.
126
+ */
127
+ function extractSafeConfigShape(raw: unknown): Partial<DiscloAIConfig> | null {
128
+ if (typeof raw !== "object" || raw === null) return null;
129
+
130
+ const obj = raw as Record<string, unknown>;
131
+ const safe: Partial<DiscloAIConfig> = {};
132
+
133
+ if (typeof obj["locale"] === "string") {
134
+ safe.locale = obj["locale"];
135
+ }
136
+ if (
137
+ typeof obj["chatbotDisclosure"] === "object" &&
138
+ obj["chatbotDisclosure"] !== null
139
+ ) {
140
+ safe.chatbotDisclosure = obj[
141
+ "chatbotDisclosure"
142
+ ] as ChatbotDisclosureConfig;
143
+ }
144
+ if (
145
+ typeof obj["aiContentLabel"] === "object" &&
146
+ obj["aiContentLabel"] !== null
147
+ ) {
148
+ safe.aiContentLabel = obj["aiContentLabel"] as AIContentLabelConfig;
149
+ }
150
+ if (
151
+ typeof obj["deepfakeLabel"] === "object" &&
152
+ obj["deepfakeLabel"] !== null
153
+ ) {
154
+ safe.deepfakeLabel = obj["deepfakeLabel"] as DeepfakeLabelConfig;
155
+ }
156
+ if (
157
+ typeof obj["biometricNotice"] === "object" &&
158
+ obj["biometricNotice"] !== null
159
+ ) {
160
+ safe.biometricNotice = obj["biometricNotice"] as BiometricNoticeConfig;
161
+ }
162
+
163
+ return safe;
164
+ }
165
+
166
+ // ---------------------------------------------------------------------------
167
+ // Config resolution pipeline
168
+ // ---------------------------------------------------------------------------
169
+
170
+ /**
171
+ * Resolve the final runtime config by merging: Remote → Local → Defaults
172
+ *
173
+ * Resolution order (strict — never change):
174
+ * 1. Remote — GET https://api.discloai.com/v1/config/{siteId} (2s timeout, silent fallback)
175
+ * 2. Local — DiscloAI.init({...}) options (overrides remote; intended for dev/testing)
176
+ * 3. Defaults — hardcoded fallbacks in this file
177
+ */
178
+ export async function resolveConfig(
179
+ local: DiscloAIInitOptions,
180
+ ): Promise<DiscloAIConfig> {
181
+ const remote = await fetchRemoteConfig(local.siteId, local.configEndpoint);
182
+
183
+ return {
184
+ siteId: local.siteId,
185
+ locale: local.locale ?? remote?.locale ?? DEFAULTS.locale,
186
+ chatbotDisclosure: local.chatbotDisclosure ?? remote?.chatbotDisclosure,
187
+ aiContentLabel: local.aiContentLabel ?? remote?.aiContentLabel,
188
+ deepfakeLabel: local.deepfakeLabel ?? remote?.deepfakeLabel,
189
+ biometricNotice: local.biometricNotice ?? remote?.biometricNotice,
190
+ };
191
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Разговаряте с AI асистент.",
3
+ "chatbot.disclosure.prominent": "Този чат е захранван от изкуствен интелект.",
4
+ "content.label.default": "Съдържание, генерирано от AI",
5
+ "content.label.artistic": "Творческа работа с помощта на AI",
6
+ "deepfake.label.default": "AI-генерирано изображение",
7
+ "deepfake.label.artistic": "AI-генерирано художествено съдържание",
8
+ "biometric.notice.default": "Тази система обработва биометрични данни. Прилага се Член 50 §3 от Закона за AI на ЕС."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Mluvíte s AI asistentem.",
3
+ "chatbot.disclosure.prominent": "Tento chat je poháněn umělou inteligencí.",
4
+ "content.label.default": "Obsah generovaný AI",
5
+ "content.label.artistic": "Kreativní práce s pomocí AI",
6
+ "deepfake.label.default": "Obrázek generovaný AI",
7
+ "deepfake.label.artistic": "Umělecký obsah generovaný AI",
8
+ "biometric.notice.default": "Tento systém zpracovává biometrické údaje. Platí čl. 50 §3 zákona EU o AI."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Du taler med en AI-assistent.",
3
+ "chatbot.disclosure.prominent": "Denne chat drives af kunstig intelligens.",
4
+ "content.label.default": "AI-genereret indhold",
5
+ "content.label.artistic": "AI-assisteret kreativt arbejde",
6
+ "deepfake.label.default": "AI-genereret billede",
7
+ "deepfake.label.artistic": "AI-genereret kunstnerisk indhold",
8
+ "biometric.notice.default": "Dette system behandler biometriske data. EU AI Act artikel 50 §3 finder anvendelse."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Sie sprechen mit einem KI-Assistenten.",
3
+ "chatbot.disclosure.prominent": "Dieser Chat wird durch künstliche Intelligenz betrieben.",
4
+ "content.label.default": "KI-generierter Inhalt",
5
+ "content.label.artistic": "KI-unterstütztes kreatives Werk",
6
+ "deepfake.label.default": "KI-generiertes Bild",
7
+ "deepfake.label.artistic": "KI-generierter künstlerischer Inhalt",
8
+ "biometric.notice.default": "Dieses System verarbeitet biometrische Daten. EU-KI-Gesetz Artikel 50 §3 gilt."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Μιλάτε με έναν βοηθό AI.",
3
+ "chatbot.disclosure.prominent": "Αυτή η συνομιλία τροφοδοτείται από τεχνητή νοημοσύνη.",
4
+ "content.label.default": "Περιεχόμενο που δημιουργήθηκε από AI",
5
+ "content.label.artistic": "Δημιουργική εργασία με βοήθεια AI",
6
+ "deepfake.label.default": "Εικόνα που δημιουργήθηκε από AI",
7
+ "deepfake.label.artistic": "Καλλιτεχνικό περιεχόμενο που δημιουργήθηκε από AI",
8
+ "biometric.notice.default": "Αυτό το σύστημα επεξεργάζεται βιομετρικά δεδομένα. Ισχύει Άρθρο 50 §3 Νόμου ΕΕ για AI."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "You are talking to an AI assistant.",
3
+ "chatbot.disclosure.prominent": "This chat is powered by artificial intelligence.",
4
+ "content.label.default": "AI-generated content",
5
+ "content.label.artistic": "AI-assisted creative work",
6
+ "deepfake.label.default": "AI-generated image",
7
+ "deepfake.label.artistic": "AI-generated artistic content",
8
+ "biometric.notice.default": "This system processes biometric data. EU AI Act Article 50 §3 applies."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Estás hablando con un asistente de IA.",
3
+ "chatbot.disclosure.prominent": "Este chat está impulsado por inteligencia artificial.",
4
+ "content.label.default": "Contenido generado por IA",
5
+ "content.label.artistic": "Obra creativa asistida por IA",
6
+ "deepfake.label.default": "Imagen generada por IA",
7
+ "deepfake.label.artistic": "Contenido artístico generado por IA",
8
+ "biometric.notice.default": "Este sistema procesa datos biométricos. Se aplica el artículo 50 §3 de la Ley de IA de la UE."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Räägite AI-assistendiga.",
3
+ "chatbot.disclosure.prominent": "Seda vestlust käitab tehisintellekt.",
4
+ "content.label.default": "AI loodud sisu",
5
+ "content.label.artistic": "AI-abistatud loominguline töö",
6
+ "deepfake.label.default": "AI loodud pilt",
7
+ "deepfake.label.artistic": "AI loodud kunstiline sisu",
8
+ "biometric.notice.default": "See süsteem töötleb biomeetrilisi andmeid. Kohaldub EL AI seaduse artikkel 50 §3."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Puhut AI-assistentin kanssa.",
3
+ "chatbot.disclosure.prominent": "Tämä chat toimii tekoälyn avulla.",
4
+ "content.label.default": "Tekoälyn tuottama sisältö",
5
+ "content.label.artistic": "Tekoälyavusteinen luova työ",
6
+ "deepfake.label.default": "Tekoälyn luoma kuva",
7
+ "deepfake.label.artistic": "Tekoälyn luoma taiteellinen sisältö",
8
+ "biometric.notice.default": "Tämä järjestelmä käsittelee biometrisiä tietoja. EU:n tekoälylain 50 artiklan 3 kohta soveltuu."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Vous discutez avec un assistant IA.",
3
+ "chatbot.disclosure.prominent": "Cette conversation est alimentée par l'intelligence artificielle.",
4
+ "content.label.default": "Contenu généré par IA",
5
+ "content.label.artistic": "Œuvre créative assistée par IA",
6
+ "deepfake.label.default": "Image générée par IA",
7
+ "deepfake.label.artistic": "Contenu artistique généré par IA",
8
+ "biometric.notice.default": "Ce système traite des données biométriques. L'article 50 §3 de la loi européenne sur l'IA s'applique."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Tá tú ag caint le cúntóir AI.",
3
+ "chatbot.disclosure.prominent": "Tá an comhrá seo faoi chumhacht na hintleachta saorga.",
4
+ "content.label.default": "Ábhar arna ghiniúint ag AI",
5
+ "content.label.artistic": "Obair chruthaitheach le cúnamh AI",
6
+ "deepfake.label.default": "Íomhá arna ghiniúint ag AI",
7
+ "deepfake.label.artistic": "Ábhar ealaíne arna ghiniúint ag AI",
8
+ "biometric.notice.default": "Próiseálann an córas seo sonraí bithfhéiniúlachta. Tá Airteagal 50 §3 de Dhlí AI an AE infheidhme."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Razgovarate s AI asistentom.",
3
+ "chatbot.disclosure.prominent": "Ovaj chat pokreće umjetna inteligencija.",
4
+ "content.label.default": "Sadržaj generiran AI-jem",
5
+ "content.label.artistic": "Kreativni rad uz pomoć AI-ja",
6
+ "deepfake.label.default": "AI-generirana slika",
7
+ "deepfake.label.artistic": "AI-generirani umjetnički sadržaj",
8
+ "biometric.notice.default": "Ovaj sustav obrađuje biometrijske podatke. Primjenjuje se Članak 50 §3 Zakona EU o AI."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "AI-asszisztenssel beszél.",
3
+ "chatbot.disclosure.prominent": "Ez a csevegés mesterséges intelligencia által működik.",
4
+ "content.label.default": "AI által generált tartalom",
5
+ "content.label.artistic": "AI-val támogatott kreatív munka",
6
+ "deepfake.label.default": "AI által generált kép",
7
+ "deepfake.label.artistic": "AI által generált művészeti tartalom",
8
+ "biometric.notice.default": "Ez a rendszer biometrikus adatokat dolgoz fel. Az EU MI-törvény 50. cikk 3. §-a alkalmazandó."
9
+ }
@@ -0,0 +1,145 @@
1
+ import en from "./en.json";
2
+ import fr from "./fr.json";
3
+ import de from "./de.json";
4
+ import es from "./es.json";
5
+ import bg from "./bg.json";
6
+ import hr from "./hr.json";
7
+ import cs from "./cs.json";
8
+ import da from "./da.json";
9
+ import el from "./el.json";
10
+ import et from "./et.json";
11
+ import fi from "./fi.json";
12
+ import ga from "./ga.json";
13
+ import hu from "./hu.json";
14
+ import it from "./it.json";
15
+ import lv from "./lv.json";
16
+ import lt from "./lt.json";
17
+ import mt from "./mt.json";
18
+ import nl from "./nl.json";
19
+ import pl from "./pl.json";
20
+ import pt from "./pt.json";
21
+ import ro from "./ro.json";
22
+ import sk from "./sk.json";
23
+ import sl from "./sl.json";
24
+ import sv from "./sv.json";
25
+
26
+ export type Locale =
27
+ | "bg"
28
+ | "hr"
29
+ | "cs"
30
+ | "da"
31
+ | "de"
32
+ | "el"
33
+ | "en"
34
+ | "es"
35
+ | "et"
36
+ | "fi"
37
+ | "fr"
38
+ | "ga"
39
+ | "hu"
40
+ | "it"
41
+ | "lv"
42
+ | "lt"
43
+ | "mt"
44
+ | "nl"
45
+ | "pl"
46
+ | "pt"
47
+ | "ro"
48
+ | "sk"
49
+ | "sl"
50
+ | "sv";
51
+
52
+ export const SUPPORTED_LOCALES: Locale[] = [
53
+ "bg",
54
+ "hr",
55
+ "cs",
56
+ "da",
57
+ "de",
58
+ "el",
59
+ "en",
60
+ "es",
61
+ "et",
62
+ "fi",
63
+ "fr",
64
+ "ga",
65
+ "hu",
66
+ "it",
67
+ "lv",
68
+ "lt",
69
+ "mt",
70
+ "nl",
71
+ "pl",
72
+ "pt",
73
+ "ro",
74
+ "sk",
75
+ "sl",
76
+ "sv",
77
+ ];
78
+
79
+ type Messages = Record<string, string>;
80
+
81
+ export const messages: Record<Locale, Messages> = {
82
+ bg: bg as Messages,
83
+ hr: hr as Messages,
84
+ cs: cs as Messages,
85
+ da: da as Messages,
86
+ de: de as Messages,
87
+ el: el as Messages,
88
+ en: en as Messages,
89
+ es: es as Messages,
90
+ et: et as Messages,
91
+ fi: fi as Messages,
92
+ fr: fr as Messages,
93
+ ga: ga as Messages,
94
+ hu: hu as Messages,
95
+ it: it as Messages,
96
+ lv: lv as Messages,
97
+ lt: lt as Messages,
98
+ mt: mt as Messages,
99
+ nl: nl as Messages,
100
+ pl: pl as Messages,
101
+ pt: pt as Messages,
102
+ ro: ro as Messages,
103
+ sk: sk as Messages,
104
+ sl: sl as Messages,
105
+ sv: sv as Messages,
106
+ };
107
+
108
+ /**
109
+ * Resolve the best matching locale from navigator.language.
110
+ * Falls back to 'en' when running server-side or if no match is found.
111
+ */
112
+ export function resolveLocale(): Locale {
113
+ if (typeof navigator === "undefined") return "en";
114
+
115
+ const lang = navigator.language;
116
+
117
+ // Exact match (e.g. 'fr' or 'en')
118
+ if (SUPPORTED_LOCALES.includes(lang as Locale)) return lang as Locale;
119
+
120
+ // Prefix match (e.g. 'fr-FR' → 'fr', 'fr-CA' → 'fr')
121
+ const prefix = lang.split("-")[0];
122
+ if (prefix !== undefined && SUPPORTED_LOCALES.includes(prefix as Locale)) {
123
+ return prefix as Locale;
124
+ }
125
+
126
+ return "en";
127
+ }
128
+
129
+ /**
130
+ * Look up a translation key for the given (or auto-resolved) locale.
131
+ * Never throws, never returns undefined — falls back to en.json, then to the key itself.
132
+ */
133
+ export function t(key: string, locale?: Locale): string {
134
+ const resolved = locale ?? resolveLocale();
135
+ const dict = messages[resolved];
136
+ const value = dict[key];
137
+ if (value !== undefined) return value;
138
+
139
+ // Fall back to English
140
+ const fallback = messages["en"][key];
141
+ if (fallback !== undefined) return fallback;
142
+
143
+ // Last resort: return the key itself
144
+ return key;
145
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Stai parlando con un assistente AI.",
3
+ "chatbot.disclosure.prominent": "Questa chat è alimentata dall'intelligenza artificiale.",
4
+ "content.label.default": "Contenuto generato dall'AI",
5
+ "content.label.artistic": "Lavoro creativo assistito dall'AI",
6
+ "deepfake.label.default": "Immagine generata dall'AI",
7
+ "deepfake.label.artistic": "Contenuto artistico generato dall'AI",
8
+ "biometric.notice.default": "Questo sistema elabora dati biometrici. Si applica l'Articolo 50 §3 della Legge AI dell'UE."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Jūs kalbatės su AI asistentu.",
3
+ "chatbot.disclosure.prominent": "Šis pokalbis vyksta dirbtinio intelekto pagalba.",
4
+ "content.label.default": "AI sugeneruotas turinys",
5
+ "content.label.artistic": "AI padedamas kūrybinis darbas",
6
+ "deepfake.label.default": "AI sugeneruotas vaizdas",
7
+ "deepfake.label.artistic": "AI sugeneruotas meninis turinys",
8
+ "biometric.notice.default": "Ši sistema apdoroja biometrinius duomenis. Taikomas ES DI įstatymo 50 straipsnio 3 dalis."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Jūs runājat ar AI asistentu.",
3
+ "chatbot.disclosure.prominent": "Šo tērzēšanu darbina mākslīgais intelekts.",
4
+ "content.label.default": "AI ģenerēts saturs",
5
+ "content.label.artistic": "AI asistēts radošs darbs",
6
+ "deepfake.label.default": "AI ģenerēts attēls",
7
+ "deepfake.label.artistic": "AI ģenerēts māksliniecisks saturs",
8
+ "biometric.notice.default": "Šī sistēma apstrādā biometriskos datus. Piemērojams ES MI likuma 50. pants §3."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Qed titkellem ma' assistent AI.",
3
+ "chatbot.disclosure.prominent": "Dan il-chat huwa mħaddem mill-intelliġenza artifiċjali.",
4
+ "content.label.default": "Kontenut iġġenerat minn AI",
5
+ "content.label.artistic": "Xogħol kreattiv assistit minn AI",
6
+ "deepfake.label.default": "Immaġni iġġenerata minn AI",
7
+ "deepfake.label.artistic": "Kontenut artistiku iġġenerat minn AI",
8
+ "biometric.notice.default": "Dan is-sistema tipproċessa data bijometrika. Japplika l-Artikolu 50 §3 tal-Liġi AI tal-UE."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "U praat met een AI-assistent.",
3
+ "chatbot.disclosure.prominent": "Deze chat wordt aangedreven door kunstmatige intelligentie.",
4
+ "content.label.default": "AI-gegenereerde inhoud",
5
+ "content.label.artistic": "AI-ondersteund creatief werk",
6
+ "deepfake.label.default": "AI-gegenereerde afbeelding",
7
+ "deepfake.label.artistic": "AI-gegenereerde artistieke inhoud",
8
+ "biometric.notice.default": "Dit systeem verwerkt biometrische gegevens. EU AI-wet artikel 50 §3 is van toepassing."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Rozmawiasz z asystentem AI.",
3
+ "chatbot.disclosure.prominent": "Ten czat jest obsługiwany przez sztuczną inteligencję.",
4
+ "content.label.default": "Treść wygenerowana przez AI",
5
+ "content.label.artistic": "Praca twórcza wspierana przez AI",
6
+ "deepfake.label.default": "Obraz wygenerowany przez AI",
7
+ "deepfake.label.artistic": "Artystyczne treści wygenerowane przez AI",
8
+ "biometric.notice.default": "Ten system przetwarza dane biometryczne. Stosuje się art. 50 §3 Ustawy UE o AI."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Você está falando com um assistente de IA.",
3
+ "chatbot.disclosure.prominent": "Este chat é alimentado por inteligência artificial.",
4
+ "content.label.default": "Conteúdo gerado por IA",
5
+ "content.label.artistic": "Trabalho criativo assistido por IA",
6
+ "deepfake.label.default": "Imagem gerada por IA",
7
+ "deepfake.label.artistic": "Conteúdo artístico gerado por IA",
8
+ "biometric.notice.default": "Este sistema processa dados biométricos. Aplica-se o Artigo 50 §3 da Lei de IA da UE."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Vorbiți cu un asistent AI.",
3
+ "chatbot.disclosure.prominent": "Acest chat este alimentat de inteligența artificială.",
4
+ "content.label.default": "Conținut generat de AI",
5
+ "content.label.artistic": "Lucrare creativă asistată de AI",
6
+ "deepfake.label.default": "Imagine generată de AI",
7
+ "deepfake.label.artistic": "Conținut artistic generat de AI",
8
+ "biometric.notice.default": "Acest sistem procesează date biometrice. Se aplică Articolul 50 §3 din Legea AI a UE."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Hovoríte s AI asistentom.",
3
+ "chatbot.disclosure.prominent": "Tento chat je poháňaný umelou inteligenciou.",
4
+ "content.label.default": "Obsah generovaný AI",
5
+ "content.label.artistic": "Kreatívna práca s pomocou AI",
6
+ "deepfake.label.default": "Obrázok generovaný AI",
7
+ "deepfake.label.artistic": "Umelecký obsah generovaný AI",
8
+ "biometric.notice.default": "Tento systém spracúva biometrické údaje. Platí čl. 50 §3 zákona EÚ o AI."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Pogovarjate se z AI pomočnikom.",
3
+ "chatbot.disclosure.prominent": "Ta klepet poganja umetna inteligenca.",
4
+ "content.label.default": "Vsebina, ustvarjena z AI",
5
+ "content.label.artistic": "Ustvarjalno delo s pomočjo AI",
6
+ "deepfake.label.default": "Slika, ustvarjena z AI",
7
+ "deepfake.label.artistic": "Umetniška vsebina, ustvarjena z AI",
8
+ "biometric.notice.default": "Ta sistem obdeluje biometrične podatke. Velja Člen 50 §3 Zakona EU o AI."
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "chatbot.disclosure.default": "Du pratar med en AI-assistent.",
3
+ "chatbot.disclosure.prominent": "Denna chatt drivs av artificiell intelligens.",
4
+ "content.label.default": "AI-genererat innehåll",
5
+ "content.label.artistic": "AI-assisterat kreativt arbete",
6
+ "deepfake.label.default": "AI-genererad bild",
7
+ "deepfake.label.artistic": "AI-genererat konstnärligt innehåll",
8
+ "biometric.notice.default": "Detta system behandlar biometriska uppgifter. EU AI-lagens artikel 50 §3 är tillämplig."
9
+ }
package/src/index.ts ADDED
@@ -0,0 +1,19 @@
1
+ // Public API surface for @discloai/core
2
+ // For CDN / IIFE usage: window.DiscloAI.init({ siteId: '...' })
3
+ // For bundler usage: import { init } from '@discloai/core'
4
+
5
+ export { init } from "./init.js";
6
+
7
+ export type { DiscloAIInitOptions, DiscloAIConfig } from "./config.js";
8
+ export { sanitizeCSS } from "./config.js";
9
+
10
+ export type { ChatbotDisclosureConfig } from "./components/ChatbotDisclosure.js";
11
+ export type { AIContentLabelConfig } from "./components/AIContentLabel.js";
12
+ export type { DeepfakeLabelConfig } from "./components/DeepfakeLabel.js";
13
+ export type { BiometricNoticeConfig } from "./components/BiometricNotice.js";
14
+
15
+ export { VENDOR_PRESETS, resolveVendorSelector } from "./vendors.js";
16
+ export type { VendorPreset } from "./vendors.js";
17
+
18
+ export type { AuditEventParams } from "./audit.js";
19
+ export { sendAuditEvent } from "./audit.js";