@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.
- package/LICENSE +21 -0
- package/README.md +218 -0
- package/dist/discloai.min.js +1 -0
- package/dist/index.d.mts +152 -0
- package/dist/index.d.ts +152 -0
- package/dist/index.js +795 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +764 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +58 -0
- package/src/__tests__/audit.test.ts +117 -0
- package/src/__tests__/init.test.ts +49 -0
- package/src/__tests__/wcag.test.ts +260 -0
- package/src/audit.ts +155 -0
- package/src/components/AIContentLabel.ts +108 -0
- package/src/components/BiometricNotice.ts +82 -0
- package/src/components/ChatbotDisclosure.ts +188 -0
- package/src/components/DeepfakeLabel.ts +123 -0
- package/src/config.ts +191 -0
- package/src/i18n/bg.json +9 -0
- package/src/i18n/cs.json +9 -0
- package/src/i18n/da.json +9 -0
- package/src/i18n/de.json +9 -0
- package/src/i18n/el.json +9 -0
- package/src/i18n/en.json +9 -0
- package/src/i18n/es.json +9 -0
- package/src/i18n/et.json +9 -0
- package/src/i18n/fi.json +9 -0
- package/src/i18n/fr.json +9 -0
- package/src/i18n/ga.json +9 -0
- package/src/i18n/hr.json +9 -0
- package/src/i18n/hu.json +9 -0
- package/src/i18n/index.ts +145 -0
- package/src/i18n/it.json +9 -0
- package/src/i18n/lt.json +9 -0
- package/src/i18n/lv.json +9 -0
- package/src/i18n/mt.json +9 -0
- package/src/i18n/nl.json +9 -0
- package/src/i18n/pl.json +9 -0
- package/src/i18n/pt.json +9 -0
- package/src/i18n/ro.json +9 -0
- package/src/i18n/sk.json +9 -0
- package/src/i18n/sl.json +9 -0
- package/src/i18n/sv.json +9 -0
- package/src/index.ts +19 -0
- package/src/init.ts +56 -0
- package/src/vendors.ts +29 -0
- package/src/version.ts +1 -0
- 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
|
+
}
|
package/src/i18n/bg.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/cs.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/da.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/de.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/el.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/en.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/es.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/et.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/fi.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/fr.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/ga.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/hr.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/hu.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/it.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/lt.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/lv.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/mt.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/nl.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/pl.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/pt.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/ro.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/sk.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/sl.json
ADDED
|
@@ -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
|
+
}
|
package/src/i18n/sv.json
ADDED
|
@@ -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";
|