@cloudflare/ai-search-snippet 0.0.36 → 0.0.38

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.
@@ -1,19 +1,113 @@
1
- var P = Object.defineProperty;
2
- var U = (n, i, e) => i in n ? P(n, i, { enumerable: !0, configurable: !0, writable: !0, value: e }) : n[i] = e;
3
- var a = (n, i, e) => U(n, typeof i != "symbol" ? i + "" : i, e);
4
- const g = [
5
- "Searching...",
6
- "Digging through results...",
7
- "Scanning the knowledge base...",
8
- "Finding the best matches...",
9
- "Sifting through the data...",
10
- "Almost there...",
11
- "Looking far and wide...",
12
- "Connecting the dots...",
13
- "Rummaging through pages...",
14
- "Hunting down answers..."
15
- ];
16
- function $(n, i) {
1
+ var G = Object.defineProperty;
2
+ var Y = (n, i, e) => i in n ? G(n, i, { enumerable: !0, configurable: !0, writable: !0, value: e }) : n[i] = e;
3
+ var a = (n, i, e) => Y(n, typeof i != "symbol" ? i + "" : i, e);
4
+ const L = {
5
+ // Shared
6
+ loadingAriaLabel: "Loading",
7
+ errorPrefix: "Error:",
8
+ missingApiUrlError: "The api-url attribute is required. Please provide a valid API URL.",
9
+ poweredBy: "Powered by",
10
+ poweredByLinkLabel: "Cloudflare AI Search",
11
+ // Search (shared between bar and modal)
12
+ placeholder: "Search...",
13
+ searchButtonLabel: "Search",
14
+ searchInputAriaLabel: "Search input",
15
+ searchResultsAriaLabel: "Search results",
16
+ emptyStateTitle: "Start Searching",
17
+ emptyStateDescription: "Enter a query to search for results",
18
+ modalEmptyStateDescription: "Start typing to search",
19
+ noResultsTitle: "No Results Found",
20
+ noResultsDescription: 'No results found for "{query}"',
21
+ modalNoResultsTitle: "No results found",
22
+ modalNoResultsDescription: 'No results for "{query}"',
23
+ resultsCount: "Found {n} result",
24
+ resultsCountPlural: "Found {n} results",
25
+ resultsCountOverflow: "Showing {n} of {total} results",
26
+ modalResultsCount: "{n} result",
27
+ modalResultsCountPlural: "{n} results",
28
+ modalResultsCountZero: "0 results",
29
+ modalResultsCountError: "Error",
30
+ seeMoreResults: "See more results",
31
+ // Modal-only
32
+ navigateHint: "Navigate",
33
+ selectHint: "Select",
34
+ closeHint: "Close",
35
+ // Chat (shared between bubble, page, and chat view)
36
+ chatTitle: "Chat",
37
+ chatPlaceholder: "Type a message...",
38
+ chatInputAriaLabel: "Chat message input",
39
+ sendButtonLabel: "Send",
40
+ sendButtonAriaLabel: "Send message",
41
+ chatEmptyTitle: "Start a Conversation",
42
+ chatEmptyDescription: "Send a message to begin chatting",
43
+ userAvatar: "U",
44
+ assistantAvatar: "AI",
45
+ unknownError: "Unknown error",
46
+ // Chat bubble
47
+ openChatAriaLabel: "Open chat",
48
+ clearHistoryAriaLabel: "Clear history",
49
+ minimizeAriaLabel: "Minimize",
50
+ closeAriaLabel: "Close",
51
+ // Chat page
52
+ historyTitle: "History",
53
+ newChatButton: "New Chat",
54
+ clearChatButton: "Clear Chat",
55
+ toggleSidebarTitle: "Toggle sidebar",
56
+ deleteChatTitle: "Delete chat",
57
+ noChatsYet: "No chats yet",
58
+ yesterday: "Yesterday",
59
+ // Relative timestamps (formatTimestamp)
60
+ justNow: "Just now",
61
+ minuteAgo: "{n} minute ago",
62
+ minutesAgo: "{n} minutes ago",
63
+ hourAgo: "{n} hour ago",
64
+ hoursAgo: "{n} hours ago",
65
+ // Cycling loading messages
66
+ loadingMessages: [
67
+ "Searching...",
68
+ "Digging through results...",
69
+ "Scanning the knowledge base...",
70
+ "Finding the best matches...",
71
+ "Sifting through the data...",
72
+ "Almost there...",
73
+ "Looking far and wide...",
74
+ "Connecting the dots...",
75
+ "Rummaging through pages...",
76
+ "Hunting down answers..."
77
+ ]
78
+ };
79
+ function d(n) {
80
+ if (!n || typeof n != "object")
81
+ return L;
82
+ const i = { ...L };
83
+ for (const e of Object.keys(n)) {
84
+ const t = n[e];
85
+ if (t != null) {
86
+ if (e === "loadingMessages") {
87
+ Array.isArray(t) && t.length > 0 && (i.loadingMessages = t.filter((s) => typeof s == "string"), i.loadingMessages.length === 0 && (i.loadingMessages = L.loadingMessages));
88
+ continue;
89
+ }
90
+ typeof t == "string" && (i[e] = t);
91
+ }
92
+ }
93
+ return i;
94
+ }
95
+ function b(n, i = {}) {
96
+ return n.replace(/\{(\w+)\}/g, (e, t) => Object.hasOwn(i, t) ? String(i[t]) : e);
97
+ }
98
+ function k(n, i) {
99
+ if (!n) return null;
100
+ try {
101
+ const e = JSON.parse(n);
102
+ if (e === null || typeof e != "object" || Array.isArray(e))
103
+ throw new Error("translations must be a JSON object");
104
+ return e;
105
+ } catch (e) {
106
+ return console.error(`${i}: invalid translations attribute`, e), null;
107
+ }
108
+ }
109
+ const T = 2500;
110
+ function P(n, i) {
17
111
  let e;
18
112
  function t(...s) {
19
113
  clearTimeout(e), e = setTimeout(() => {
@@ -22,49 +116,49 @@ function $(n, i) {
22
116
  }
23
117
  return t.cancel = () => clearTimeout(e), t;
24
118
  }
25
- function h(n) {
119
+ function l(n) {
26
120
  const i = document.createElement("div");
27
121
  return i.textContent = n, i.innerHTML;
28
122
  }
29
- function q(n) {
123
+ function U(n) {
30
124
  try {
31
125
  return decodeURI(n);
32
126
  } catch {
33
127
  return n;
34
128
  }
35
129
  }
36
- function E(n) {
130
+ function A(n) {
37
131
  return new DOMParser().parseFromString(n, "text/html").documentElement.textContent || "";
38
132
  }
39
- function O(n) {
40
- const i = new Date(n), t = (/* @__PURE__ */ new Date()).getTime() - i.getTime();
41
- if (t < 6e4)
42
- return "Just now";
43
- if (t < 36e5) {
44
- const s = Math.floor(t / 6e4);
45
- return `${s} ${s === 1 ? "minute" : "minutes"} ago`;
133
+ function J(n, i) {
134
+ const e = d(i), t = new Date(n), r = (/* @__PURE__ */ new Date()).getTime() - t.getTime();
135
+ if (r < 6e4)
136
+ return e.justNow;
137
+ if (r < 36e5) {
138
+ const o = Math.floor(r / 6e4), c = o === 1 ? e.minuteAgo : e.minutesAgo;
139
+ return b(c, { n: o });
46
140
  }
47
- if (t < 864e5) {
48
- const s = Math.floor(t / 36e5);
49
- return `${s} ${s === 1 ? "hour" : "hours"} ago`;
141
+ if (r < 864e5) {
142
+ const o = Math.floor(r / 36e5), c = o === 1 ? e.hourAgo : e.hoursAgo;
143
+ return b(c, { n: o });
50
144
  }
51
- return i.toLocaleString(void 0, {
145
+ return t.toLocaleString(void 0, {
52
146
  month: "short",
53
147
  day: "numeric",
54
148
  hour: "2-digit",
55
149
  minute: "2-digit"
56
150
  });
57
151
  }
58
- function B(n) {
152
+ function j(n) {
59
153
  return new Date(n).toLocaleDateString(void 0, {
60
154
  month: "short",
61
155
  day: "numeric"
62
156
  });
63
157
  }
64
- function M(n = "id") {
158
+ function I(n = "id") {
65
159
  return `${n}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
66
160
  }
67
- function d(n, i) {
161
+ function p(n, i) {
68
162
  return n !== null ? n : i;
69
163
  }
70
164
  function m(n, i) {
@@ -83,61 +177,62 @@ function v(n, i) {
83
177
  cancelable: !0
84
178
  });
85
179
  }
86
- function x(n) {
180
+ function S(n) {
87
181
  if (!n)
88
182
  throw new Error("API URL is required");
89
- return new K(n);
183
+ return new Q(n);
90
184
  }
91
- function w(n) {
185
+ function C(n) {
92
186
  return n !== null && typeof n == "object" && !Array.isArray(n);
93
187
  }
94
- function R(...n) {
188
+ function _(...n) {
95
189
  const i = {};
96
190
  for (const e of n)
97
191
  if (e)
98
192
  for (const [t, s] of Object.entries(e)) {
99
193
  const r = i[t];
100
- w(r) && w(s) ? i[t] = R(r, s) : i[t] = s;
194
+ C(r) && C(s) ? i[t] = _(r, s) : i[t] = s;
101
195
  }
102
196
  return i;
103
197
  }
104
- function D(n, i) {
105
- if (!w(i))
198
+ function W(n, i) {
199
+ if (!C(i))
106
200
  return n;
107
201
  const e = new URLSearchParams();
108
- for (const [l, p] of Object.entries(i))
109
- p != null && e.append(l, String(p));
202
+ for (const [h, u] of Object.entries(i))
203
+ u != null && e.append(h, String(u));
110
204
  const t = e.toString();
111
205
  if (!t)
112
206
  return n;
113
207
  const s = n.indexOf("#"), r = s === -1 ? n : n.slice(0, s), o = s === -1 ? "" : n.slice(s), c = r.includes("?") ? "&" : "?";
114
208
  return `${r}${c}${t}${o}`;
115
209
  }
116
- function j(n) {
117
- if (!w(n))
210
+ function Z(n) {
211
+ if (!C(n))
118
212
  return {};
119
213
  const i = {};
120
214
  for (const [e, t] of Object.entries(n))
121
215
  t != null && (i[e] = String(t));
122
216
  return i;
123
217
  }
124
- function V(n) {
125
- return w(n) ? n : void 0;
218
+ function X(n) {
219
+ return C(n) ? n : void 0;
126
220
  }
127
- class K {
221
+ class Q {
128
222
  constructor(i) {
129
223
  a(this, "activeRequests", /* @__PURE__ */ new Map());
130
224
  a(this, "baseUrl");
131
225
  this.baseUrl = i.replace(/\/$/, "");
132
226
  }
133
227
  request(i, e, t, s) {
134
- const r = e === "search" ? "snippet-search" : "snippet-chat-completions", o = D(`${this.baseUrl}/${e}`, s?.queryParams);
228
+ const r = e === "search" ? "snippet-search" : "snippet-chat-completions", o = W(`${this.baseUrl}/${e}`, s?.queryParams);
135
229
  return fetch(o, {
136
230
  method: "POST",
137
- body: JSON.stringify(R(V(s?.body), i)),
231
+ body: JSON.stringify(_(X(s?.body), i)),
138
232
  headers: {
139
- ...j(s?.headers),
233
+ ...Z(s?.headers),
140
234
  "Content-Type": "application/json",
235
+ Accept: i.stream ? "text/event-stream" : "application/json",
141
236
  "cf-ai-search-source": r
142
237
  },
143
238
  signal: t
@@ -172,17 +267,17 @@ class K {
172
267
  const c = await o.json();
173
268
  if (c.success && c.result)
174
269
  return c.result.chunks.map(
175
- (l) => ({
270
+ (h) => ({
176
271
  type: "result",
177
- id: l.id,
178
- title: E(l.item.metadata?.title),
179
- description: l.item.metadata?.description ? E(l.item.metadata?.description) : "",
180
- timestamp: l.item.timestamp ?? void 0,
181
- url: l.item.key,
182
- image: l.item.metadata?.image || void 0,
272
+ id: h.id,
273
+ title: A(h.item.metadata?.title),
274
+ description: h.item.metadata?.description ? A(h.item.metadata?.description) : "",
275
+ timestamp: h.item.timestamp ?? void 0,
276
+ url: h.item.key,
277
+ image: h.item.metadata?.image || void 0,
183
278
  metadata: {
184
- ...l.item.metadata,
185
- instance_id: l.instance_id
279
+ ...h.item.metadata,
280
+ instance_id: h.instance_id
186
281
  }
187
282
  })
188
283
  );
@@ -211,13 +306,13 @@ class K {
211
306
  if (!o.body)
212
307
  throw new Error("Response body is empty");
213
308
  let c = "";
214
- const l = o.body.getReader(), p = new TextDecoder();
309
+ const h = o.body.getReader(), u = new TextDecoder();
215
310
  for (; ; ) {
216
- const { done: u, value: S } = await l.read();
217
- if (u)
311
+ const { done: g, value: w } = await h.read();
312
+ if (g)
218
313
  break;
219
- const _ = p.decode(S, { stream: !0 });
220
- c += _;
314
+ const F = u.decode(w, { stream: !0 });
315
+ c += F;
221
316
  }
222
317
  yield {
223
318
  type: "result",
@@ -225,7 +320,7 @@ class K {
225
320
  title: "",
226
321
  description: c.replaceAll("data: ", "").trim().split(`
227
322
 
228
- `).map((u) => JSON.parse(u)).map((u) => u.response).join(""),
323
+ `).map((g) => JSON.parse(g)).map((g) => g.response).join(""),
229
324
  url: "",
230
325
  metadata: {}
231
326
  };
@@ -285,10 +380,10 @@ class K {
285
380
  return `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
286
381
  }
287
382
  }
288
- const G = `<svg width="32" height="10" viewBox="0 0 412 186" xmlns="http://www.w3.org/2000/svg" aria-label="Cloudflare" role="img">
383
+ const ee = `<svg width="32" height="10" viewBox="0 0 412 186" xmlns="http://www.w3.org/2000/svg" aria-label="Cloudflare" role="img">
289
384
  <path fill="#f38020" d="m280.8395,183.31456c11,-26 -4,-38 -19,-38l-148,-2c-4,0 -4,-6 1,-7l150,-2c17,-1 37,-15 43,-33c0,0 10,-21 9,-24a97,97 0 0 0 -187,-11c-38,-25 -78,9 -69,46c-48,3 -65,46 -60,72c0,1 1,2 3,2l274,0c1,0 3,-1 3,-3z"/>
290
385
  <path fill="#faae40" d="m330.8395,81.31456c-4,0 -6,-1 -7,1l-5,21c-5,16 3,30 20,31l32,2c4,0 4,6 -1,7l-33,1c-36,4 -46,39 -46,39c0,2 0,3 2,3l113,0l3,-2a81,81 0 0 0 -78,-103"/>
291
- </svg>`, F = "https://workers.cloudflare.com/product/ai-search", k = `Powered by <a href="${F}" target="_blank" rel="noopener noreferrer">Cloudflare AI Search ${G}</a>`, H = `
386
+ </svg>`, te = "https://workers.cloudflare.com/product/ai-search", E = `Powered by <a href="${te}" target="_blank" rel="noopener noreferrer">Cloudflare AI Search ${ee}</a>`, V = `
292
387
  /* Chat container */
293
388
  .chat-container {
294
389
  display: flex;
@@ -605,7 +700,7 @@ const G = `<svg width="32" height="10" viewBox="0 0 412 186" xmlns="http://www.w
605
700
  .chat-message-bubble a:hover {
606
701
  text-decoration: none;
607
702
  }
608
- `, C = `
703
+ `, M = `
609
704
  :host {
610
705
  /* Colors - Light Mode */
611
706
  --search-snippet-primary-color: #2563eb;
@@ -1040,17 +1135,17 @@ const G = `<svg width="32" height="10" viewBox="0 0 412 186" xmlns="http://www.w
1040
1135
  text-align: center;
1041
1136
  }
1042
1137
  `;
1043
- function Y(n) {
1138
+ function se(n) {
1044
1139
  let i = n;
1045
- i = J(i), i = i.replace(/```([\s\S]*?)```/g, (o, c) => `<pre><code>${c.trim()}</code></pre>`);
1140
+ i = ie(i), i = i.replace(/```([\s\S]*?)```/g, (o, c) => `<pre><code>${c.trim()}</code></pre>`);
1046
1141
  const e = i.split(`
1047
1142
  `), t = [];
1048
1143
  let s = !1, r = "";
1049
1144
  for (let o = 0; o < e.length; o++) {
1050
- const c = e[o], l = c.match(/^(#{1,6})\s+(.+)$/);
1051
- if (l) {
1052
- const u = l[1].length, S = l[2];
1053
- t.push(`<h${u}>${f(S)}</h${u}>`);
1145
+ const c = e[o], h = c.match(/^(#{1,6})\s+(.+)$/);
1146
+ if (h) {
1147
+ const g = h[1].length, w = h[2];
1148
+ t.push(`<h${g}>${x(w)}</h${g}>`);
1054
1149
  continue;
1055
1150
  }
1056
1151
  if (c.match(/^---+$/)) {
@@ -1058,37 +1153,37 @@ function Y(n) {
1058
1153
  continue;
1059
1154
  }
1060
1155
  if (c.match(/^>\s+/)) {
1061
- const u = c.replace(/^>\s+/, "");
1062
- t.push(`<blockquote>${f(u)}</blockquote>`);
1156
+ const g = c.replace(/^>\s+/, "");
1157
+ t.push(`<blockquote>${x(g)}</blockquote>`);
1063
1158
  continue;
1064
1159
  }
1065
- const p = c.match(/^[-*]\s+(.+)$/);
1066
- if (p) {
1067
- (!s || r !== "ul") && (s && t.push(`</${r}>`), t.push("<ul>"), s = !0, r = "ul"), t.push(`<li>${f(p[1])}</li>`);
1160
+ const u = c.match(/^[-*]\s+(.+)$/);
1161
+ if (u) {
1162
+ (!s || r !== "ul") && (s && t.push(`</${r}>`), t.push("<ul>"), s = !0, r = "ul"), t.push(`<li>${x(u[1])}</li>`);
1068
1163
  continue;
1069
1164
  }
1070
- const b = c.match(/^\d+\.\s+(.+)$/);
1071
- if (b) {
1072
- (!s || r !== "ol") && (s && t.push(`</${r}>`), t.push("<ol>"), s = !0, r = "ol"), t.push(`<li>${f(b[1])}</li>`);
1165
+ const f = c.match(/^\d+\.\s+(.+)$/);
1166
+ if (f) {
1167
+ (!s || r !== "ol") && (s && t.push(`</${r}>`), t.push("<ol>"), s = !0, r = "ol"), t.push(`<li>${x(f[1])}</li>`);
1073
1168
  continue;
1074
1169
  }
1075
1170
  if (s && (t.push(`</${r}>`), s = !1, r = ""), c.trim() === "") {
1076
1171
  t.push("<br />");
1077
1172
  continue;
1078
1173
  }
1079
- t.push(`<p>${f(c)}</p>`);
1174
+ t.push(`<p>${x(c)}</p>`);
1080
1175
  }
1081
1176
  return s && t.push(`</${r}>`), t.join(`
1082
1177
  `);
1083
1178
  }
1084
- function f(n) {
1179
+ function x(n) {
1085
1180
  let i = n;
1086
1181
  return i = i.replace(/`([^`]+)`/g, "<code>$1</code>"), i = i.replace(/\*\*\*(.+?)\*\*\*/g, "<strong><em>$1</em></strong>"), i = i.replace(/___(.+?)___/g, "<strong><em>$1</em></strong>"), i = i.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>"), i = i.replace(/__(.+?)__/g, "<strong>$1</strong>"), i = i.replace(/\*(.+?)\*/g, "<em>$1</em>"), i = i.replace(/_(.+?)_/g, "<em>$1</em>"), i = i.replace(
1087
1182
  /\[([^\]]+)\]\(([^)]+)\)/g,
1088
1183
  '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'
1089
1184
  ), i;
1090
1185
  }
1091
- function J(n) {
1186
+ function ie(n) {
1092
1187
  const i = {
1093
1188
  "&": "&amp;",
1094
1189
  "<": "&lt;",
@@ -1098,11 +1193,12 @@ function J(n) {
1098
1193
  };
1099
1194
  return n.replace(/[&<>"']/g, (e) => i[e] || e);
1100
1195
  }
1101
- class N {
1196
+ class K {
1102
1197
  constructor(i, e, t) {
1103
1198
  a(this, "container");
1104
1199
  a(this, "client");
1105
1200
  a(this, "props");
1201
+ a(this, "translations");
1106
1202
  a(this, "inputElement", null);
1107
1203
  a(this, "messagesContainer", null);
1108
1204
  a(this, "sendButton", null);
@@ -1115,42 +1211,49 @@ class N {
1115
1211
  a(this, "handleInputResize", null);
1116
1212
  a(this, "handleInputKeydown", null);
1117
1213
  a(this, "handleSendClick", null);
1118
- this.container = i, this.client = e, this.props = t, this.render(), this.attachEventListeners();
1214
+ this.container = i, this.client = e, this.props = t, this.translations = d(t.translations), this.render(), this.attachEventListeners();
1119
1215
  }
1120
1216
  /**
1121
1217
  * Render the chat interface
1122
1218
  */
1123
1219
  render() {
1220
+ const i = this.translations;
1124
1221
  this.container.innerHTML = `
1125
1222
  <div class="chat-container">
1126
1223
  <div class="chat-messages">
1127
- <div class="chat-empty">
1128
- <svg class="chat-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1129
- <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
1130
- </svg>
1131
- <div class="chat-empty-title">Start a Conversation</div>
1132
- <div class="chat-empty-description">
1133
- Send a message to begin chatting
1134
- </div>
1135
- </div>
1224
+ ${this.renderEmptyStateHTML()}
1136
1225
  </div>
1137
1226
  <div class="chat-input-area">
1138
1227
  <div class="chat-input-wrapper">
1139
1228
  <textarea
1140
1229
  class="chat-input"
1141
- placeholder="${h(this.props.placeholder || "Type a message...")}"
1142
- aria-label="Chat message input"
1230
+ placeholder="${l(this.props.placeholder || i.chatPlaceholder)}"
1231
+ aria-label="${l(i.chatInputAriaLabel)}"
1143
1232
  style="height: 40px;"
1144
1233
  rows="1"
1145
1234
  ></textarea>
1146
- <button class="button chat-send-button" aria-label="Send message">
1147
- <span>Send</span>
1235
+ <button class="button chat-send-button" aria-label="${l(i.sendButtonAriaLabel)}">
1236
+ <span>${l(i.sendButtonLabel)}</span>
1148
1237
  </button>
1149
1238
  </div>
1150
1239
  </div>
1151
1240
  </div>
1152
1241
  `, this.messagesContainer = this.container.querySelector(".chat-messages"), this.inputElement = this.container.querySelector(".chat-input"), this.sendButton = this.container.querySelector(".chat-send-button");
1153
1242
  }
1243
+ renderEmptyStateHTML() {
1244
+ const i = this.translations;
1245
+ return `
1246
+ <div class="chat-empty">
1247
+ <svg class="chat-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1248
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
1249
+ </svg>
1250
+ <div class="chat-empty-title">${l(i.chatEmptyTitle)}</div>
1251
+ <div class="chat-empty-description">
1252
+ ${l(i.chatEmptyDescription)}
1253
+ </div>
1254
+ </div>
1255
+ `;
1256
+ }
1154
1257
  /**
1155
1258
  * Attach event listeners
1156
1259
  */
@@ -1177,13 +1280,13 @@ class N {
1177
1280
  */
1178
1281
  async sendMessage(i) {
1179
1282
  const e = {
1180
- id: M("msg"),
1283
+ id: I("msg"),
1181
1284
  role: "user",
1182
1285
  content: i,
1183
1286
  timestamp: Date.now()
1184
1287
  };
1185
1288
  this.addMessage(e), this.renderMessages(!0), this.setStreamingState(!0);
1186
- const t = M("msg"), s = {
1289
+ const t = I("msg"), s = {
1187
1290
  id: t,
1188
1291
  role: "assistant",
1189
1292
  content: "",
@@ -1193,14 +1296,17 @@ class N {
1193
1296
  try {
1194
1297
  const r = this.client.chat(i);
1195
1298
  let o = "";
1196
- for await (const l of r)
1197
- if (l.type === "text" && l.message)
1198
- o += l.message, this.updateStreamingMessage(t, o);
1199
- else if (l.type === "error") {
1200
- this.showErrorInMessage(t, l.message || "Unknown error");
1299
+ for await (const h of r)
1300
+ if (h.type === "text" && h.message)
1301
+ o += h.message, this.updateStreamingMessage(t, o);
1302
+ else if (h.type === "error") {
1303
+ this.showErrorInMessage(
1304
+ t,
1305
+ h.message || this.translations.unknownError
1306
+ );
1201
1307
  break;
1202
1308
  }
1203
- const c = this.messages.findIndex((l) => l.id === t);
1309
+ const c = this.messages.findIndex((h) => h.id === t);
1204
1310
  c !== -1 && (this.messages[c].content = o), this.container.dispatchEvent(v("message", { message: s }));
1205
1311
  } catch (r) {
1206
1312
  this.showErrorInMessage(t, r.message), this.container.dispatchEvent(
@@ -1233,7 +1339,7 @@ class N {
1233
1339
  */
1234
1340
  showErrorInMessage(i, e) {
1235
1341
  const t = this.messages.findIndex((s) => s.id === i);
1236
- t !== -1 && (this.messages[t].content = `Error: ${e}`, this.renderMessages());
1342
+ t !== -1 && (this.messages[t].content = `${this.translations.errorPrefix} ${e}`, this.renderMessages());
1237
1343
  }
1238
1344
  /**
1239
1345
  * Render all messages
@@ -1241,17 +1347,7 @@ class N {
1241
1347
  renderMessages(i = !1) {
1242
1348
  if (!this.messagesContainer) return;
1243
1349
  if (this.messages.length === 0) {
1244
- this.messagesContainer.innerHTML = `
1245
- <div class="chat-empty">
1246
- <svg class="chat-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1247
- <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
1248
- </svg>
1249
- <div class="chat-empty-title">Start a Conversation</div>
1250
- <div class="chat-empty-description">
1251
- Send a message to begin chatting
1252
- </div>
1253
- </div>
1254
- `;
1350
+ this.messagesContainer.innerHTML = this.renderEmptyStateHTML();
1255
1351
  return;
1256
1352
  }
1257
1353
  const e = this.messages.map(
@@ -1263,17 +1359,17 @@ class N {
1263
1359
  * Render a single message
1264
1360
  */
1265
1361
  renderMessage(i, e = !1) {
1266
- const t = `chat-message-${i.role}`, s = i.role === "user" ? "U" : "AI";
1362
+ const t = this.translations, s = `chat-message-${i.role}`, r = i.role === "user" ? t.userAvatar : t.assistantAvatar, o = t.loadingMessages[this.loadingMessageIndex] ?? "";
1267
1363
  return `
1268
- <div class="chat-message ${t}">
1269
- <div class="chat-message-avatar">${s}</div>
1364
+ <div class="chat-message ${s}">
1365
+ <div class="chat-message-avatar">${l(r)}</div>
1270
1366
  <div class="chat-message-content">
1271
1367
  <div class="chat-message-bubble">
1272
- ${i.content ? `<div class="chat-message-text">${Y(i.content)}</div>` : ""}
1273
- ${e ? `<div class="chat-streaming"><span class="chat-streaming-dot"></span><span class="chat-streaming-dot"></span><span class="chat-streaming-dot"></span><span class="loading-text">${g[this.loadingMessageIndex]}</span></div>` : ""}
1368
+ ${i.content ? `<div class="chat-message-text">${se(i.content)}</div>` : ""}
1369
+ ${e ? `<div class="chat-streaming"><span class="chat-streaming-dot"></span><span class="chat-streaming-dot"></span><span class="chat-streaming-dot"></span><span class="loading-text">${l(o)}</span></div>` : ""}
1274
1370
  </div>
1275
1371
  <div class="chat-message-metadata">
1276
- <span class="chat-message-time">${O(i.timestamp)}</span>
1372
+ <span class="chat-message-time">${l(J(i.timestamp, this.translations))}</span>
1277
1373
  </div>
1278
1374
  </div>
1279
1375
  </div>
@@ -1291,12 +1387,15 @@ class N {
1291
1387
  * Set streaming state
1292
1388
  */
1293
1389
  setStreamingState(i) {
1294
- this.isStreaming = i, this.inputElement && (this.inputElement.disabled = i), this.sendButton && (this.sendButton.disabled = i, this.sendButton.innerHTML = i ? '<div class="loading"></div>' : "<span>Send</span>"), i ? this.startLoadingMessages() : this.clearLoadingMessages();
1390
+ this.isStreaming = i, this.inputElement && (this.inputElement.disabled = i), this.sendButton && (this.sendButton.disabled = i, this.sendButton.innerHTML = i ? '<div class="loading"></div>' : `<span>${l(this.translations.sendButtonLabel)}</span>`), i ? this.startLoadingMessages() : this.clearLoadingMessages();
1295
1391
  }
1296
1392
  startLoadingMessages() {
1297
- this.loadingMessageIndex = Math.floor(Math.random() * g.length), this.loadingMessageInterval = setInterval(() => {
1298
- this.loadingMessageIndex = (this.loadingMessageIndex + 1) % g.length, this.isStreaming && this.renderMessages(!0);
1299
- }, 2500);
1393
+ this.clearLoadingMessages();
1394
+ const i = this.translations.loadingMessages;
1395
+ this.loadingMessageIndex = Math.floor(Math.random() * i.length), this.loadingMessageInterval = setInterval(() => {
1396
+ const e = this.translations.loadingMessages;
1397
+ this.loadingMessageIndex = (this.loadingMessageIndex + 1) % e.length, this.isStreaming && this.renderMessages(!0);
1398
+ }, T);
1300
1399
  }
1301
1400
  clearLoadingMessages() {
1302
1401
  this.loadingMessageInterval && (clearInterval(this.loadingMessageInterval), this.loadingMessageInterval = null);
@@ -1319,6 +1418,19 @@ class N {
1319
1418
  setMessages(i) {
1320
1419
  this.messages = [...i], this.renderMessages();
1321
1420
  }
1421
+ /**
1422
+ * Update the props (e.g. placeholder / translations) and re-render.
1423
+ *
1424
+ * Safe to call at any time: existing messages and streaming state are
1425
+ * preserved; listeners on the old DOM are removed and re-attached to the
1426
+ * newly rendered elements.
1427
+ */
1428
+ setProps(i) {
1429
+ this.props = i, this.translations = d(i.translations), this.detachEventListeners(), this.render(), this.attachEventListeners(), this.renderMessages(this.isStreaming), this.isStreaming && this.setStreamingState(!0);
1430
+ }
1431
+ detachEventListeners() {
1432
+ this.inputElement && (this.handleInputResize && this.inputElement.removeEventListener("input", this.handleInputResize), this.handleInputKeydown && this.inputElement.removeEventListener("keydown", this.handleInputKeydown)), this.sendButton && this.handleSendClick && this.sendButton.removeEventListener("click", this.handleSendClick), this.handleInputResize = null, this.handleInputKeydown = null, this.handleSendClick = null;
1433
+ }
1322
1434
  /**
1323
1435
  * Destroy and cleanup
1324
1436
  */
@@ -1326,8 +1438,8 @@ class N {
1326
1438
  this.clearLoadingMessages(), this.isStreaming && this.client.cancelAllRequests(), this.inputElement && (this.handleInputResize && this.inputElement.removeEventListener("input", this.handleInputResize), this.handleInputKeydown && this.inputElement.removeEventListener("keydown", this.handleInputKeydown)), this.sendButton && this.handleSendClick && this.sendButton.removeEventListener("click", this.handleSendClick), this.handleInputResize = null, this.handleInputKeydown = null, this.handleSendClick = null;
1327
1439
  }
1328
1440
  }
1329
- const L = "chat-bubble-snippet";
1330
- class W extends HTMLElement {
1441
+ const z = "chat-bubble-snippet";
1442
+ class re extends HTMLElement {
1331
1443
  constructor() {
1332
1444
  super();
1333
1445
  a(this, "shadow");
@@ -1336,6 +1448,8 @@ class W extends HTMLElement {
1336
1448
  a(this, "container", null);
1337
1449
  a(this, "isExpanded", !1);
1338
1450
  a(this, "isMinimized", !1);
1451
+ a(this, "translationsOverride", null);
1452
+ a(this, "resolvedTranslations", d(null));
1339
1453
  // Event handler references for cleanup
1340
1454
  a(this, "handleBubbleClick", null);
1341
1455
  a(this, "handleCloseClick", null);
@@ -1344,23 +1458,59 @@ class W extends HTMLElement {
1344
1458
  this.shadow = this.attachShadow({ mode: "open" });
1345
1459
  }
1346
1460
  static get observedAttributes() {
1347
- return ["api-url", "placeholder", "theme", "hide-branding"];
1461
+ return ["api-url", "placeholder", "theme", "hide-branding", "translations"];
1348
1462
  }
1349
1463
  connectedCallback() {
1350
- this.render(), this.initializeClient(), this.dispatchEvent(v("ready", void 0));
1464
+ this.syncTranslationsFromAttribute(), this.render(), this.initializeClient(), this.dispatchEvent(v("ready", void 0));
1351
1465
  }
1352
1466
  disconnectedCallback() {
1353
1467
  this.cleanup();
1354
1468
  }
1355
1469
  attributeChangedCallback(e, t, s) {
1356
- t !== s && (e === "api-url" ? this.initializeClient() : e === "theme" && this.updateTheme(s));
1470
+ t !== s && (e === "api-url" ? this.initializeClient() : e === "theme" ? this.updateTheme(s) : e === "translations" && (this.syncTranslationsFromAttribute(), this.isConnected && this.rerenderAfterTranslationsChange()));
1471
+ }
1472
+ /**
1473
+ * Get the current translations object.
1474
+ */
1475
+ get translations() {
1476
+ return this.translationsOverride;
1477
+ }
1478
+ /**
1479
+ * Override any user-facing string. Omitted keys fall back to English defaults.
1480
+ */
1481
+ set translations(e) {
1482
+ this.translationsOverride = e ?? null, this.resolvedTranslations = d(this.translationsOverride), this.isConnected && this.rerenderAfterTranslationsChange();
1483
+ }
1484
+ syncTranslationsFromAttribute() {
1485
+ if (this.translationsOverride) {
1486
+ this.resolvedTranslations = d(this.translationsOverride);
1487
+ return;
1488
+ }
1489
+ const e = k(
1490
+ this.getAttribute("translations"),
1491
+ "ChatBubbleSnippet"
1492
+ );
1493
+ this.resolvedTranslations = d(e);
1494
+ }
1495
+ rerenderAfterTranslationsChange() {
1496
+ const e = this.isExpanded, t = this.isMinimized;
1497
+ this.removeEventListeners();
1498
+ const s = this.chatView ? this.shadow.querySelector(".chat-content") : null;
1499
+ s?.parentNode && s.parentNode.removeChild(s), this.render(), e && (this.shadow.querySelector(".bubble-button")?.classList.add("hidden"), this.shadow.querySelector(".chat-window")?.classList.add("expanded"), t && this.shadow.querySelector(".chat-window")?.classList.add("minimized"));
1500
+ const r = this.shadow.querySelector(".chat-window");
1501
+ if (this.chatView && s && r) {
1502
+ const o = r.querySelector(".chat-content");
1503
+ o ? r.replaceChild(s, o) : r.appendChild(s), this.chatView.setProps(this.getProps());
1504
+ } else e && this.initializeChatView();
1357
1505
  }
1358
1506
  getProps() {
1507
+ const e = this.resolvedTranslations;
1359
1508
  return {
1360
- apiUrl: d(this.getAttribute("api-url"), ""),
1361
- placeholder: d(this.getAttribute("placeholder"), "Type a message..."),
1362
- theme: d(this.getAttribute("theme"), "auto"),
1363
- hideBranding: m(this.getAttribute("hide-branding"), !1)
1509
+ apiUrl: p(this.getAttribute("api-url"), ""),
1510
+ placeholder: p(this.getAttribute("placeholder"), e.chatPlaceholder),
1511
+ theme: p(this.getAttribute("theme"), "auto"),
1512
+ hideBranding: m(this.getAttribute("hide-branding"), !1),
1513
+ translations: this.translationsOverride ?? void 0
1364
1514
  };
1365
1515
  }
1366
1516
  initializeClient() {
@@ -1370,15 +1520,15 @@ class W extends HTMLElement {
1370
1520
  return;
1371
1521
  }
1372
1522
  try {
1373
- this.client = x(e.apiUrl);
1523
+ this.client = S(e.apiUrl);
1374
1524
  } catch (t) {
1375
1525
  console.error("ChatBubbleSnippet:", t);
1376
1526
  }
1377
1527
  }
1378
1528
  render() {
1379
1529
  const e = document.createElement("style");
1380
- e.textContent = `${C}
1381
- ${H}
1530
+ e.textContent = `${M}
1531
+ ${V}
1382
1532
  ${this.getBubbleStyles()}`, this.container = document.createElement("div"), this.container.className = "chat-bubble-widget", this.container.innerHTML = this.getBaseHTML(), this.shadow.innerHTML = "", this.shadow.appendChild(e), this.shadow.appendChild(this.container), this.attachEventListeners();
1383
1533
  }
1384
1534
  getBubbleStyles() {
@@ -1523,8 +1673,9 @@ ${this.getBubbleStyles()}`, this.container = document.createElement("div"), this
1523
1673
  `;
1524
1674
  }
1525
1675
  getBaseHTML() {
1676
+ const e = this.getProps(), t = this.resolvedTranslations, s = e.hideBranding ? "" : `<div class="powered-by">${E}</div>`;
1526
1677
  return `
1527
- <button class="bubble-button" aria-label="Open chat">
1678
+ <button class="bubble-button" aria-label="${l(t.openChatAriaLabel)}">
1528
1679
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1529
1680
  <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
1530
1681
  </svg>
@@ -1535,21 +1686,21 @@ ${this.getBubbleStyles()}`, this.container = document.createElement("div"), this
1535
1686
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1536
1687
  <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
1537
1688
  </svg>
1538
- <span>Chat</span>
1689
+ <span>${l(t.chatTitle)}</span>
1539
1690
  </div>
1540
1691
  <div class="chat-header-actions">
1541
- <button class="icon-button clear-button" aria-label="Clear history">
1692
+ <button class="icon-button clear-button" aria-label="${l(t.clearHistoryAriaLabel)}">
1542
1693
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1543
1694
  <polyline points="3 6 5 6 21 6"></polyline>
1544
1695
  <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
1545
1696
  </svg>
1546
1697
  </button>
1547
- <button class="icon-button minimize-button" aria-label="Minimize">
1698
+ <button class="icon-button minimize-button" aria-label="${l(t.minimizeAriaLabel)}">
1548
1699
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1549
1700
  <line x1="5" y1="12" x2="19" y2="12"></line>
1550
1701
  </svg>
1551
1702
  </button>
1552
- <button class="icon-button close-button" aria-label="Close">
1703
+ <button class="icon-button close-button" aria-label="${l(t.closeAriaLabel)}">
1553
1704
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1554
1705
  <line x1="18" y1="6" x2="6" y2="18"></line>
1555
1706
  <line x1="6" y1="6" x2="18" y2="18"></line>
@@ -1558,7 +1709,7 @@ ${this.getBubbleStyles()}`, this.container = document.createElement("div"), this
1558
1709
  </div>
1559
1710
  </div>
1560
1711
  <div class="chat-content"></div>
1561
- ${this.getProps().hideBranding ? "" : `<div class="powered-by">${k}</div>`}
1712
+ ${s}
1562
1713
  </div>
1563
1714
  `;
1564
1715
  }
@@ -1590,15 +1741,16 @@ ${this.getBubbleStyles()}`, this.container = document.createElement("div"), this
1590
1741
  const e = this.shadow.querySelector(".chat-content");
1591
1742
  if (!e) return;
1592
1743
  if (!this.client) {
1744
+ const s = this.resolvedTranslations;
1593
1745
  e.innerHTML = `
1594
1746
  <div style="padding: 16px; color: var(--search-snippet-error-color, #ef4444); font-family: var(--search-snippet-font-family, sans-serif); font-size: var(--search-snippet-font-size-base, 14px);">
1595
- <strong>Error:</strong> The <code>api-url</code> attribute is required. Please provide a valid API URL.
1747
+ <strong>${l(s.errorPrefix)}</strong> ${l(s.missingApiUrlError)}
1596
1748
  </div>
1597
1749
  `;
1598
1750
  return;
1599
1751
  }
1600
1752
  const t = this.getProps();
1601
- this.chatView = new N(e, this.client, t);
1753
+ this.chatView = new K(e, this.client, t);
1602
1754
  }
1603
1755
  updateTheme(e) {
1604
1756
  (e === "light" || e === "dark" ? e : null) === null && this.hasAttribute("theme") && this.getAttribute("theme") !== "auto" && this.removeAttribute("theme");
@@ -1617,9 +1769,9 @@ ${this.getBubbleStyles()}`, this.container = document.createElement("div"), this
1617
1769
  return this.chatView?.getMessages() || [];
1618
1770
  }
1619
1771
  }
1620
- customElements.get(L) || customElements.define(L, W);
1621
- const I = "chat-page-snippet", z = "chat-page-sessions";
1622
- class X extends HTMLElement {
1772
+ customElements.get(z) || customElements.define(z, re);
1773
+ const $ = "chat-page-snippet", R = "chat-page-sessions";
1774
+ class ae extends HTMLElement {
1623
1775
  constructor() {
1624
1776
  super();
1625
1777
  a(this, "shadow");
@@ -1629,6 +1781,8 @@ class X extends HTMLElement {
1629
1781
  a(this, "sessions", []);
1630
1782
  a(this, "currentSessionId", null);
1631
1783
  a(this, "sidebarCollapsed", !1);
1784
+ a(this, "translationsOverride", null);
1785
+ a(this, "resolvedTranslations", d(null));
1632
1786
  // Event handler references for cleanup
1633
1787
  a(this, "handleClearClick", null);
1634
1788
  a(this, "handleNewChatClick", null);
@@ -1638,23 +1792,70 @@ class X extends HTMLElement {
1638
1792
  this.shadow = this.attachShadow({ mode: "open" }), this.loadSessions();
1639
1793
  }
1640
1794
  static get observedAttributes() {
1641
- return ["api-url", "placeholder", "theme", "hide-branding"];
1795
+ return ["api-url", "placeholder", "theme", "hide-branding", "translations"];
1642
1796
  }
1643
1797
  connectedCallback() {
1644
- this.render(), this.initializeClient(), this.setupView(), this.dispatchEvent(v("ready", void 0));
1798
+ this.syncTranslationsFromAttribute(), this.render(), this.initializeClient(), this.setupView(), this.dispatchEvent(v("ready", void 0));
1645
1799
  }
1646
1800
  disconnectedCallback() {
1647
1801
  this.saveCurrentSession(), this.cleanup();
1648
1802
  }
1649
1803
  attributeChangedCallback(e, t, s) {
1650
- t !== s && (e === "api-url" ? (this.initializeClient(), this.setupView()) : e === "theme" && this.updateTheme(s));
1804
+ t !== s && (e === "api-url" ? (this.initializeClient(), this.setupView()) : e === "theme" ? this.updateTheme(s) : e === "translations" && (this.syncTranslationsFromAttribute(), this.isConnected && this.rerenderAfterTranslationsChange()));
1805
+ }
1806
+ /**
1807
+ * Get the current translations object.
1808
+ */
1809
+ get translations() {
1810
+ return this.translationsOverride;
1811
+ }
1812
+ /**
1813
+ * Override any user-facing string. Omitted keys fall back to English defaults.
1814
+ */
1815
+ set translations(e) {
1816
+ this.translationsOverride = e ?? null, this.resolvedTranslations = d(this.translationsOverride), this.refreshDefaultSessionTitles(), this.isConnected && this.rerenderAfterTranslationsChange();
1817
+ }
1818
+ syncTranslationsFromAttribute() {
1819
+ if (this.translationsOverride) {
1820
+ this.resolvedTranslations = d(this.translationsOverride), this.refreshDefaultSessionTitles();
1821
+ return;
1822
+ }
1823
+ const e = k(this.getAttribute("translations"), "ChatPageSnippet");
1824
+ this.resolvedTranslations = d(e), this.refreshDefaultSessionTitles();
1825
+ }
1826
+ /**
1827
+ * Replace the stored title of any still-default-titled session with the
1828
+ * current `newChatButton` translation, so the sidebar reflects the active
1829
+ * language after a translations change.
1830
+ */
1831
+ refreshDefaultSessionTitles() {
1832
+ if (this.sessions.length === 0) return;
1833
+ const e = this.resolvedTranslations.newChatButton;
1834
+ let t = !1;
1835
+ for (const s of this.sessions)
1836
+ s.titleIsDefault && s.title !== e && (s.title = e, t = !0);
1837
+ t && this.saveSessions();
1838
+ }
1839
+ rerenderAfterTranslationsChange() {
1840
+ this.removeEventListeners();
1841
+ const e = this.chatView ? this.shadow.querySelector(".container") : null;
1842
+ e?.parentNode && e.parentNode.removeChild(e), this.render(), this.attachEventListeners(), this.renderChatList();
1843
+ const t = this.shadow.querySelector(".chat-page-content");
1844
+ if (this.chatView && e && t) {
1845
+ const s = t.querySelector(".container");
1846
+ s ? t.replaceChild(e, s) : t.appendChild(e), this.chatView.setProps(this.getProps()), this.handleMessageEvent = () => {
1847
+ this.saveCurrentSession(), this.updateSessionTitle(), this.renderChatList();
1848
+ }, e.addEventListener("message", this.handleMessageEvent);
1849
+ }
1651
1850
  }
1652
1851
  getProps() {
1852
+ const e = this.resolvedTranslations;
1653
1853
  return {
1654
- apiUrl: d(this.getAttribute("api-url"), ""),
1655
- placeholder: d(this.getAttribute("placeholder"), "Type a message..."),
1656
- theme: d(this.getAttribute("theme"), "auto"),
1657
- hideBranding: m(this.getAttribute("hide-branding"), !1)
1854
+ apiUrl: p(this.getAttribute("api-url"), ""),
1855
+ placeholder: p(this.getAttribute("placeholder"), e.chatPlaceholder),
1856
+ theme: p(this.getAttribute("theme"), "auto"),
1857
+ hideBranding: m(this.getAttribute("hide-branding"), !1),
1858
+ translations: this.translationsOverride ?? void 0
1658
1859
  };
1659
1860
  }
1660
1861
  initializeClient() {
@@ -1664,15 +1865,15 @@ class X extends HTMLElement {
1664
1865
  return;
1665
1866
  }
1666
1867
  try {
1667
- this.client = x(e.apiUrl);
1868
+ this.client = S(e.apiUrl);
1668
1869
  } catch (t) {
1669
1870
  console.error("ChatPageSnippet:", t);
1670
1871
  }
1671
1872
  }
1672
1873
  render() {
1673
1874
  const e = document.createElement("style");
1674
- e.textContent = `${C}
1675
- ${H}
1875
+ e.textContent = `${M}
1876
+ ${V}
1676
1877
  ${this.getPageStyles()}`, this.container = document.createElement("div"), this.container.className = "chat-page-container", this.container.innerHTML = this.getBaseHTML(), this.shadow.innerHTML = "", this.shadow.appendChild(e), this.shadow.appendChild(this.container), this.attachEventListeners();
1677
1878
  }
1678
1879
  getPageStyles() {
@@ -1952,24 +2153,25 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
1952
2153
  `;
1953
2154
  }
1954
2155
  getBaseHTML() {
2156
+ const e = this.getProps(), t = this.resolvedTranslations, s = e.hideBranding ? "" : `<div class="powered-by">${E}</div>`;
1955
2157
  return `
1956
2158
  <div class="chat-sidebar">
1957
2159
  <div class="sidebar-header">
1958
- <span class="sidebar-title">History</span>
2160
+ <span class="sidebar-title">${l(t.historyTitle)}</span>
1959
2161
  </div>
1960
2162
  <button class="new-chat-button">
1961
2163
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1962
2164
  <path d="M12 5v14M5 12h14"></path>
1963
2165
  </svg>
1964
- New Chat
2166
+ ${l(t.newChatButton)}
1965
2167
  </button>
1966
2168
  <div class="chat-list"></div>
1967
- ${this.getProps().hideBranding ? "" : `<div class="powered-by">${k}</div>`}
2169
+ ${s}
1968
2170
  </div>
1969
2171
  <div class="chat-main">
1970
2172
  <div class="chat-page-header">
1971
2173
  <div class="chat-page-header-left">
1972
- <button class="toggle-sidebar-button" title="Toggle sidebar">
2174
+ <button class="toggle-sidebar-button" title="${l(t.toggleSidebarTitle)}">
1973
2175
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1974
2176
  <path d="M3 12h18M3 6h18M3 18h18"></path>
1975
2177
  </svg>
@@ -1978,7 +2180,7 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
1978
2180
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1979
2181
  <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
1980
2182
  </svg>
1981
- <span>Chat</span>
2183
+ <span>${l(t.chatTitle)}</span>
1982
2184
  </div>
1983
2185
  </div>
1984
2186
  <div class="chat-page-header-actions">
@@ -1986,7 +2188,7 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
1986
2188
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1987
2189
  <path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
1988
2190
  </svg>
1989
- Clear Chat
2191
+ ${l(t.clearChatButton)}
1990
2192
  </button>
1991
2193
  </div>
1992
2194
  </div>
@@ -2007,16 +2209,19 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2007
2209
  setupView() {
2008
2210
  const e = this.shadow.querySelector(".container");
2009
2211
  if (!this.client) {
2010
- e && (e.innerHTML = `
2212
+ if (e) {
2213
+ const s = this.resolvedTranslations;
2214
+ e.innerHTML = `
2011
2215
  <div style="padding: 16px; color: var(--search-snippet-error-color, #ef4444); font-family: var(--search-snippet-font-family, sans-serif); font-size: var(--search-snippet-font-size-base, 14px);">
2012
- <strong>Error:</strong> The <code>api-url</code> attribute is required. Please provide a valid API URL.
2216
+ <strong>${l(s.errorPrefix)}</strong> ${l(s.missingApiUrlError)}
2013
2217
  </div>
2014
- `);
2218
+ `;
2219
+ }
2015
2220
  return;
2016
2221
  }
2017
2222
  if (!e) return;
2018
2223
  const t = this.getProps();
2019
- if (this.chatView = new N(e, this.client, t), this.sessions.length === 0)
2224
+ if (this.chatView = new K(e, this.client, t), this.sessions.length === 0)
2020
2225
  this.createNewChat();
2021
2226
  else {
2022
2227
  const s = this.sessions[0];
@@ -2031,7 +2236,7 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2031
2236
  }
2032
2237
  loadSessions() {
2033
2238
  try {
2034
- const e = localStorage.getItem(z);
2239
+ const e = localStorage.getItem(R);
2035
2240
  e && (this.sessions = JSON.parse(e), this.sessions.sort((t, s) => s.updatedAt - t.updatedAt));
2036
2241
  } catch (e) {
2037
2242
  console.error("Failed to load chat sessions:", e);
@@ -2039,7 +2244,7 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2039
2244
  }
2040
2245
  saveSessions() {
2041
2246
  try {
2042
- localStorage.setItem(z, JSON.stringify(this.sessions));
2247
+ localStorage.setItem(R, JSON.stringify(this.sessions));
2043
2248
  } catch (e) {
2044
2249
  console.error("Failed to save chat sessions:", e);
2045
2250
  }
@@ -2051,20 +2256,20 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2051
2256
  }
2052
2257
  updateSessionTitle() {
2053
2258
  if (!this.currentSessionId) return;
2054
- const e = this.sessions.find((t) => t.id === this.currentSessionId);
2055
- if (e && e.messages.length > 0 && e.title === "New Chat") {
2056
- const t = e.messages.find((s) => s.role === "user");
2057
- t && (e.title = t.content.slice(0, 50) + (t.content.length > 50 ? "..." : ""), this.saveSessions());
2058
- }
2259
+ const e = this.sessions.find((r) => r.id === this.currentSessionId);
2260
+ if (!e || e.messages.length === 0 || !(e.titleIsDefault ?? e.title === this.resolvedTranslations.newChatButton)) return;
2261
+ const s = e.messages.find((r) => r.role === "user");
2262
+ s && (e.title = s.content.slice(0, 50) + (s.content.length > 50 ? "..." : ""), e.titleIsDefault = !1, this.saveSessions());
2059
2263
  }
2060
2264
  createNewChat() {
2061
2265
  this.saveCurrentSession();
2062
2266
  const e = {
2063
2267
  id: this.generateSessionId(),
2064
- title: "New Chat",
2268
+ title: this.resolvedTranslations.newChatButton,
2065
2269
  messages: [],
2066
2270
  createdAt: Date.now(),
2067
- updatedAt: Date.now()
2271
+ updatedAt: Date.now(),
2272
+ titleIsDefault: !0
2068
2273
  };
2069
2274
  this.sessions.unshift(e), this.currentSessionId = e.id, this.saveSessions(), this.chatView?.clearMessages(), this.renderChatList();
2070
2275
  }
@@ -2081,7 +2286,7 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2081
2286
  clearCurrentChat() {
2082
2287
  if (!this.currentSessionId) return;
2083
2288
  const e = this.sessions.find((t) => t.id === this.currentSessionId);
2084
- e && (e.messages = [], e.title = "New Chat", e.updatedAt = Date.now(), this.saveSessions()), this.chatView?.clearMessages(), this.renderChatList();
2289
+ e && (e.messages = [], e.title = this.resolvedTranslations.newChatButton, e.titleIsDefault = !0, e.updatedAt = Date.now(), this.saveSessions()), this.chatView?.clearMessages(), this.renderChatList();
2085
2290
  }
2086
2291
  toggleSidebar() {
2087
2292
  this.sidebarCollapsed = !this.sidebarCollapsed, this.shadow.querySelector(".chat-sidebar")?.classList.toggle("collapsed", this.sidebarCollapsed);
@@ -2102,23 +2307,23 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2102
2307
  }
2103
2308
  renderChatList() {
2104
2309
  const e = this.shadow.querySelector(".chat-list");
2105
- if (e) {
2106
- if (this.sessions.length === 0) {
2107
- e.innerHTML = '<div class="chat-list-empty">No chats yet</div>';
2108
- return;
2109
- }
2110
- e.innerHTML = this.sessions.map((t) => this.renderChatListItem(t)).join("");
2310
+ if (!e) return;
2311
+ const t = this.resolvedTranslations;
2312
+ if (this.sessions.length === 0) {
2313
+ e.innerHTML = `<div class="chat-list-empty">${this.escapeHTML(t.noChatsYet)}</div>`;
2314
+ return;
2111
2315
  }
2316
+ e.innerHTML = this.sessions.map((s) => this.renderChatListItem(s)).join("");
2112
2317
  }
2113
2318
  renderChatListItem(e) {
2114
- const t = e.id === this.currentSessionId, s = this.formatDate(e.updatedAt);
2319
+ const t = e.id === this.currentSessionId, s = this.formatDate(e.updatedAt), r = this.resolvedTranslations.deleteChatTitle;
2115
2320
  return `
2116
2321
  <div class="chat-list-item ${t ? "active" : ""}" data-session-id="${e.id}">
2117
2322
  <div class="chat-list-item-content">
2118
2323
  <div class="chat-list-item-title">${this.escapeHTML(e.title)}</div>
2119
2324
  <div class="chat-list-item-date">${s}</div>
2120
2325
  </div>
2121
- <button class="chat-list-item-delete" data-session-id="${e.id}" title="Delete chat">
2326
+ <button class="chat-list-item-delete" data-session-id="${e.id}" title="${this.escapeHTML(r)}">
2122
2327
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2123
2328
  <path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
2124
2329
  </svg>
@@ -2128,7 +2333,7 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2128
2333
  }
2129
2334
  formatDate(e) {
2130
2335
  const t = new Date(e), r = (/* @__PURE__ */ new Date()).getTime() - t.getTime(), o = Math.floor(r / (1e3 * 60 * 60 * 24));
2131
- return o === 0 ? t.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" }) : o === 1 ? "Yesterday" : o < 7 ? t.toLocaleDateString(void 0, { weekday: "long" }) : t.toLocaleDateString(void 0, { month: "short", day: "numeric" });
2336
+ return o === 0 ? t.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" }) : o === 1 ? this.resolvedTranslations.yesterday : o < 7 ? t.toLocaleDateString(void 0, { weekday: "long" }) : t.toLocaleDateString(void 0, { month: "short", day: "numeric" });
2132
2337
  }
2133
2338
  escapeHTML(e) {
2134
2339
  const t = document.createElement("div");
@@ -2157,8 +2362,8 @@ ${this.getPageStyles()}`, this.container = document.createElement("div"), this.c
2157
2362
  return this.sessions.find((e) => e.id === this.currentSessionId) || null;
2158
2363
  }
2159
2364
  }
2160
- customElements.get(I) || customElements.define(I, X);
2161
- const Z = `
2365
+ customElements.get($) || customElements.define($, ae);
2366
+ const ne = `
2162
2367
  /* Search view states */
2163
2368
  .search-view {
2164
2369
  transition: var(--search-snippet-transition-slow);
@@ -2528,8 +2733,8 @@ a.search-result-item:focus-visible {
2528
2733
  border-radius: 2px;
2529
2734
  font-weight: var(--search-snippet-font-weight-medium);
2530
2735
  }
2531
- `, A = "search-bar-snippet", Q = 10, ee = 50;
2532
- class te extends HTMLElement {
2736
+ `, q = "search-bar-snippet", B = 10, H = 50;
2737
+ class oe extends HTMLElement {
2533
2738
  constructor() {
2534
2739
  super();
2535
2740
  a(this, "shadow");
@@ -2542,6 +2747,8 @@ class te extends HTMLElement {
2542
2747
  a(this, "currentSearchController", null);
2543
2748
  a(this, "loadingMessageInterval", null);
2544
2749
  a(this, "loadingMessageIndex", 0);
2750
+ a(this, "translationsOverride", null);
2751
+ a(this, "resolvedTranslations", d(null));
2545
2752
  // Event handler references for cleanup
2546
2753
  a(this, "handleInputChange", null);
2547
2754
  a(this, "handleInputKeydownEnter", null);
@@ -2554,6 +2761,7 @@ class te extends HTMLElement {
2554
2761
  "api-url",
2555
2762
  "placeholder",
2556
2763
  "max-results",
2764
+ "max-render-results",
2557
2765
  "debounce-ms",
2558
2766
  "theme",
2559
2767
  "hide-branding",
@@ -2561,30 +2769,71 @@ class te extends HTMLElement {
2561
2769
  "show-date",
2562
2770
  "hide-thumbnails",
2563
2771
  "see-more",
2564
- "request-options"
2772
+ "request-options",
2773
+ "translations"
2565
2774
  ];
2566
2775
  }
2567
2776
  connectedCallback() {
2568
- this.initializeClient(), this.render(), this.dispatchEvent(v("ready", void 0));
2777
+ this.syncTranslationsFromAttribute(), this.initializeClient(), this.render(), this.dispatchEvent(v("ready", void 0));
2569
2778
  }
2570
2779
  disconnectedCallback() {
2571
2780
  this.cleanup();
2572
2781
  }
2573
2782
  attributeChangedCallback(e, t, s) {
2574
- t !== s && (e === "api-url" ? this.initializeClient() : e === "theme" && this.updateTheme(s));
2783
+ t !== s && (e === "api-url" ? this.initializeClient() : e === "theme" ? this.updateTheme(s) : e === "translations" && (this.syncTranslationsFromAttribute(), this.isConnected && this.rerender()));
2784
+ }
2785
+ /**
2786
+ * Get the current translations object. Mirrors the property getter.
2787
+ */
2788
+ get translations() {
2789
+ return this.translationsOverride;
2790
+ }
2791
+ /**
2792
+ * Override any user-facing string. Omitted keys fall back to English defaults.
2793
+ */
2794
+ set translations(e) {
2795
+ this.translationsOverride = e ?? null, this.resolvedTranslations = d(this.translationsOverride), this.isConnected && this.rerender();
2796
+ }
2797
+ /**
2798
+ * Re-render preserving the current query and re-running the search so
2799
+ * results remain visible after a translations change at runtime.
2800
+ */
2801
+ rerender() {
2802
+ const e = this.inputElement?.value ?? "";
2803
+ this.render(), e && this.inputElement && (this.inputElement.value = e, e.trim().length > 0 && this.performSearch(e.trim()));
2804
+ }
2805
+ syncTranslationsFromAttribute() {
2806
+ if (this.translationsOverride) {
2807
+ this.resolvedTranslations = d(this.translationsOverride);
2808
+ return;
2809
+ }
2810
+ const e = k(
2811
+ this.getAttribute("translations"),
2812
+ "SearchBarSnippet"
2813
+ );
2814
+ this.resolvedTranslations = d(e);
2575
2815
  }
2576
2816
  getProps() {
2817
+ const e = this.resolvedTranslations;
2577
2818
  return {
2578
- apiUrl: d(this.getAttribute("api-url"), ""),
2579
- placeholder: d(this.getAttribute("placeholder"), "Search..."),
2580
- maxResults: y(this.getAttribute("max-results"), 10),
2819
+ apiUrl: p(this.getAttribute("api-url"), ""),
2820
+ placeholder: p(this.getAttribute("placeholder"), e.placeholder),
2821
+ maxResults: y(
2822
+ this.getAttribute("max-results"),
2823
+ H
2824
+ ),
2825
+ maxRenderResults: y(
2826
+ this.getAttribute("max-render-results"),
2827
+ B
2828
+ ),
2581
2829
  debounceMs: y(this.getAttribute("debounce-ms"), 300),
2582
- theme: d(this.getAttribute("theme"), "auto"),
2830
+ theme: p(this.getAttribute("theme"), "auto"),
2583
2831
  hideBranding: m(this.getAttribute("hide-branding"), !1),
2584
2832
  showUrl: m(this.getAttribute("show-url"), !1),
2585
2833
  showDate: m(this.getAttribute("show-date"), !1),
2586
2834
  hideThumbnails: m(this.getAttribute("hide-thumbnails"), !1),
2587
- seeMore: d(this.getAttribute("see-more"), "")
2835
+ seeMore: p(this.getAttribute("see-more"), ""),
2836
+ translations: this.translationsOverride ?? void 0
2588
2837
  };
2589
2838
  }
2590
2839
  getRequestOptions() {
@@ -2607,20 +2856,20 @@ class te extends HTMLElement {
2607
2856
  return;
2608
2857
  }
2609
2858
  try {
2610
- this.client = x(e.apiUrl);
2859
+ this.client = S(e.apiUrl);
2611
2860
  } catch (t) {
2612
2861
  console.error("SearchBarSnippet:", t);
2613
2862
  }
2614
2863
  }
2615
2864
  render() {
2616
- const e = this.getProps(), t = (r) => this.performSearch(r);
2617
- this.debouncedSearch = $(
2618
- t,
2865
+ const e = this.getProps(), t = this.resolvedTranslations, s = (o) => this.performSearch(o);
2866
+ this.debouncedSearch = P(
2867
+ s,
2619
2868
  e.debounceMs || 400
2620
2869
  );
2621
- const s = document.createElement("style");
2622
- s.textContent = `${C}
2623
- ${Z}`, this.container = document.createElement("div"), this.container.className = "container", this.container.innerHTML = `
2870
+ const r = document.createElement("style");
2871
+ r.textContent = `${M}
2872
+ ${ne}`, this.container = document.createElement("div"), this.container.className = "container", this.container.innerHTML = `
2624
2873
  <div class="search-view">
2625
2874
  <div class="search-input-wrapper">
2626
2875
  <svg xmlns="http://www.w3.org/2000/svg" class="search-icon" height="24px" viewBox="0 -960 960 960" width="24px" fill="currentColor"><path d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z"/></svg>
@@ -2628,12 +2877,12 @@ ${Z}`, this.container = document.createElement("div"), this.container.className
2628
2877
  type="text"
2629
2878
  name="search-input"
2630
2879
  class="search-input"
2631
- placeholder="${h(e.placeholder || "Search...")}"
2632
- aria-label="Search input"
2880
+ placeholder="${l(e.placeholder || t.placeholder)}"
2881
+ aria-label="${l(t.searchInputAriaLabel)}"
2633
2882
  autocomplete="off"
2634
2883
  />
2635
- <button class="button search-submit-button" aria-label="Search">
2636
- <span>Search</span>
2884
+ <button class="button search-submit-button" aria-label="${l(t.searchButtonLabel)}">
2885
+ <span>${l(t.searchButtonLabel)}</span>
2637
2886
  </button>
2638
2887
  </div>
2639
2888
  <div class="search-content">
@@ -2642,7 +2891,7 @@ ${Z}`, this.container = document.createElement("div"), this.container.className
2642
2891
  </div>
2643
2892
  </div>
2644
2893
  </div>
2645
- `, this.shadow.innerHTML = "", this.shadow.appendChild(s), this.shadow.appendChild(this.container), this.inputElement = this.container.querySelector(".search-input"), this.resultsContainer = this.container.querySelector(".search-results-wrapper"), this.searchButton = this.container.querySelector(".search-submit-button"), this.attachEventListeners(), this.client || this.showMissingApiUrlError();
2894
+ `, this.shadow.innerHTML = "", this.shadow.appendChild(r), this.shadow.appendChild(this.container), this.inputElement = this.container.querySelector(".search-input"), this.resultsContainer = this.container.querySelector(".search-results-wrapper"), this.searchButton = this.container.querySelector(".search-submit-button"), this.attachEventListeners(), this.client || this.showMissingApiUrlError();
2646
2895
  }
2647
2896
  attachEventListeners() {
2648
2897
  this.inputElement && (this.handleInputChange = (e) => {
@@ -2667,12 +2916,12 @@ ${Z}`, this.container = document.createElement("div"), this.container.className
2667
2916
  }
2668
2917
  this.currentSearchController && (this.currentSearchController.abort(), this.currentSearchController = null), this.currentSearchController = new AbortController(), this.showLoadingState();
2669
2918
  try {
2670
- const t = await this.client.search(e, {
2919
+ const t = this.getProps(), s = await this.client.search(e, {
2671
2920
  signal: this.currentSearchController.signal,
2672
- maxResults: ee,
2921
+ maxResults: t.maxResults || H,
2673
2922
  request: this.getRequestOptions()
2674
- }), s = this.getProps(), r = t.slice(0, s.maxResults || Q);
2675
- this.displayResults(r, e, t.length);
2923
+ }), r = s.slice(0, t.maxRenderResults || B);
2924
+ this.displayResults(r, e, s.length);
2676
2925
  } catch (t) {
2677
2926
  if (t.name === "AbortError")
2678
2927
  return;
@@ -2687,37 +2936,39 @@ ${Z}`, this.container = document.createElement("div"), this.container.className
2687
2936
  this.showNoResultsState(t);
2688
2937
  return;
2689
2938
  }
2690
- const r = this.getProps(), o = r.hideBranding ? "" : `<div class="powered-by-inline">${k}</div>`, c = s > e.length, l = c ? `Showing ${e.length} of ${s} results` : `Found ${s} result${s === 1 ? "" : "s"}`, p = r.seeMore && c ? `<div class="search-footer">
2691
- <a href="${h(r.seeMore + encodeURIComponent(t))}" class="search-see-more">
2692
- <span>See more results</span>
2939
+ const r = this.getProps(), o = this.resolvedTranslations, c = r.hideBranding ? "" : `<div class="powered-by-inline">${E}</div>`, h = s > e.length, u = h ? b(o.resultsCountOverflow, { n: e.length, total: s }) : b(s === 1 ? o.resultsCount : o.resultsCountPlural, {
2940
+ n: s
2941
+ }), f = r.seeMore && h ? `<div class="search-footer">
2942
+ <a href="${l(r.seeMore + encodeURIComponent(t))}" class="search-see-more">
2943
+ <span>${l(o.seeMoreResults)}</span>
2693
2944
  <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
2694
2945
  </a>
2695
- </div>` : "", b = `
2946
+ </div>` : "", g = `
2696
2947
  <div class="search-header">
2697
2948
  <div class="search-count">
2698
- ${l}
2949
+ ${l(u)}
2699
2950
  </div>
2700
- ${o}
2951
+ ${c}
2701
2952
  </div>
2702
2953
  <div class="search-results">
2703
- ${e.map((u) => this.renderResult(u)).join("")}
2954
+ ${e.map((w) => this.renderResult(w)).join("")}
2704
2955
  </div>
2705
- ${p}
2956
+ ${f}
2706
2957
  `;
2707
- this.resultsContainer.innerHTML = b, this.attachResultHandlers();
2958
+ this.resultsContainer.innerHTML = g, this.attachResultHandlers();
2708
2959
  }
2709
2960
  renderResult(e) {
2710
- const t = this.getProps(), s = t.hideThumbnails ? "" : this.renderResultImage(e.image, e.title), r = e.url ? h(e.url) : "#", o = e.url ? h(q(e.url)) : "", c = t.showDate && e.timestamp !== void 0 ? `<div class="search-result-date">${h(B(e.timestamp))}</div>` : "", l = t.showUrl && e.url || c ? `<div class="search-result-metadata">
2961
+ const t = this.getProps(), s = t.hideThumbnails ? "" : this.renderResultImage(e.image, e.title), r = e.url ? l(e.url) : "#", o = e.url ? l(U(e.url)) : "", c = t.showDate && e.timestamp !== void 0 ? `<div class="search-result-date">${l(j(e.timestamp))}</div>` : "", h = t.showUrl && e.url || c ? `<div class="search-result-metadata">
2711
2962
  ${t.showUrl && e.url ? `<span class="search-result-url">${o}</span>` : '<span class="search-result-url search-result-url-empty"></span>'}
2712
2963
  ${c}
2713
2964
  </div>` : "";
2714
2965
  return `
2715
- <a href="${r}" class="search-result-item" data-result-id="${h(e.url || "")}">
2966
+ <a href="${r}" class="search-result-item" data-result-id="${l(e.url || "")}">
2716
2967
  ${s}
2717
2968
  <div class="search-result-content">
2718
- <div class="search-result-title">${h(e.title || "")}</div>
2719
- <div class="search-result-snippet">${h(e.description || "")}</div>
2720
- ${l}
2969
+ <div class="search-result-title">${l(e.title || "")}</div>
2970
+ <div class="search-result-snippet">${l(e.description || "")}</div>
2971
+ ${h}
2721
2972
  </div>
2722
2973
  </a>
2723
2974
  `;
@@ -2730,8 +2981,8 @@ ${Z}`, this.container = document.createElement("div"), this.container.className
2730
2981
  <div class="search-result-image-placeholder" style="display: none;">${s}</div>
2731
2982
  <img
2732
2983
  class="search-result-image"
2733
- src="${h(e)}"
2734
- alt="${h(t)}"
2984
+ src="${l(e)}"
2985
+ alt="${l(t)}"
2735
2986
  loading="lazy"
2736
2987
  />
2737
2988
  </div>
@@ -2762,60 +3013,72 @@ ${Z}`, this.container = document.createElement("div"), this.container.className
2762
3013
  });
2763
3014
  }
2764
3015
  showLoadingState() {
2765
- this.resultsContainer && (this.clearLoadingInterval(), this.loadingMessageIndex = Math.floor(Math.random() * g.length), this.resultsContainer.innerHTML = `
3016
+ if (!this.resultsContainer) return;
3017
+ this.clearLoadingInterval();
3018
+ const e = this.resolvedTranslations.loadingMessages;
3019
+ this.loadingMessageIndex = Math.floor(Math.random() * e.length);
3020
+ const t = this.resolvedTranslations;
3021
+ this.resultsContainer.innerHTML = `
2766
3022
  <div class="search-loading">
2767
- <div class="loading" aria-label="Loading"></div>
2768
- <div class="loading-text loading-text-animate">${g[this.loadingMessageIndex]}</div>
3023
+ <div class="loading" aria-label="${l(t.loadingAriaLabel)}"></div>
3024
+ <div class="loading-text loading-text-animate">${l(e[this.loadingMessageIndex])}</div>
2769
3025
  </div>
2770
- `, this.startLoadingInterval());
3026
+ `, this.startLoadingInterval();
2771
3027
  }
2772
3028
  startLoadingInterval() {
2773
3029
  this.loadingMessageInterval = setInterval(() => {
2774
- this.loadingMessageIndex = (this.loadingMessageIndex + 1) % g.length;
2775
- const e = this.resultsContainer?.querySelector(".loading-text");
2776
- e && (e.classList.remove("loading-text-animate"), e.offsetWidth, e.textContent = g[this.loadingMessageIndex], e.classList.add("loading-text-animate"));
2777
- }, 2500);
3030
+ const e = this.resolvedTranslations.loadingMessages;
3031
+ this.loadingMessageIndex = (this.loadingMessageIndex + 1) % e.length;
3032
+ const t = this.resultsContainer?.querySelector(".loading-text");
3033
+ t && (t.classList.remove("loading-text-animate"), t.offsetWidth, t.textContent = e[this.loadingMessageIndex], t.classList.add("loading-text-animate"));
3034
+ }, T);
2778
3035
  }
2779
3036
  clearLoadingInterval() {
2780
3037
  this.loadingMessageInterval && (clearInterval(this.loadingMessageInterval), this.loadingMessageInterval = null);
2781
3038
  }
2782
3039
  showEmptyState() {
2783
- this.clearLoadingInterval(), this.resultsContainer && (this.resultsContainer.innerHTML = `
3040
+ if (this.clearLoadingInterval(), !this.resultsContainer) return;
3041
+ const e = this.resolvedTranslations;
3042
+ this.resultsContainer.innerHTML = `
2784
3043
  <div class="search-empty">
2785
3044
  <svg class="search-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2786
3045
  <circle cx="11" cy="11" r="8"></circle>
2787
3046
  <path d="m21 21-4.35-4.35"></path>
2788
3047
  </svg>
2789
- <div class="search-empty-title">Start Searching</div>
3048
+ <div class="search-empty-title">${l(e.emptyStateTitle)}</div>
2790
3049
  <div class="search-empty-description">
2791
- Enter a query to search for results
3050
+ ${l(e.emptyStateDescription)}
2792
3051
  </div>
2793
3052
  </div>
2794
- `);
3053
+ `;
2795
3054
  }
2796
3055
  showNoResultsState(e) {
2797
- this.clearLoadingInterval(), this.resultsContainer && (this.resultsContainer.innerHTML = `
3056
+ if (this.clearLoadingInterval(), !this.resultsContainer) return;
3057
+ const t = this.resolvedTranslations;
3058
+ this.resultsContainer.innerHTML = `
2798
3059
  <div class="search-empty">
2799
3060
  <svg class="search-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2800
3061
  <circle cx="11" cy="11" r="8"></circle>
2801
3062
  <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
2802
3063
  </svg>
2803
- <div class="search-empty-title">No Results Found</div>
3064
+ <div class="search-empty-title">${l(t.noResultsTitle)}</div>
2804
3065
  <div class="search-empty-description">
2805
- No results found for "${h(e)}"
3066
+ ${l(b(t.noResultsDescription, { query: e }))}
2806
3067
  </div>
2807
3068
  </div>
2808
- `);
3069
+ `;
2809
3070
  }
2810
3071
  showErrorState(e) {
2811
- this.clearLoadingInterval(), this.resultsContainer && (this.resultsContainer.innerHTML = `
3072
+ if (this.clearLoadingInterval(), !this.resultsContainer) return;
3073
+ const t = this.resolvedTranslations;
3074
+ this.resultsContainer.innerHTML = `
2812
3075
  <div class="error">
2813
- <strong>Error:</strong> ${h(e)}
3076
+ <strong>${l(t.errorPrefix)}</strong> ${l(e)}
2814
3077
  </div>
2815
- `);
3078
+ `;
2816
3079
  }
2817
3080
  showMissingApiUrlError() {
2818
- this.resultsContainer && this.showErrorState("The api-url attribute is required. Please provide a valid API URL.");
3081
+ this.resultsContainer && this.showErrorState(this.resolvedTranslations.missingApiUrlError);
2819
3082
  }
2820
3083
  updateTheme(e) {
2821
3084
  const t = e === "light" || e === "dark" || e === "auto" ? e : "auto";
@@ -2829,8 +3092,8 @@ ${Z}`, this.container = document.createElement("div"), this.container.className
2829
3092
  await this.performSearch(e);
2830
3093
  }
2831
3094
  }
2832
- customElements.get(A) || customElements.define(A, te);
2833
- const se = `
3095
+ customElements.get(q) || customElements.define(q, oe);
3096
+ const le = `
2834
3097
  /* Modal backdrop */
2835
3098
  .modal-backdrop {
2836
3099
  position: fixed;
@@ -3267,8 +3530,8 @@ a.modal-result-item:focus-visible {
3267
3530
  .modal-container.open {
3268
3531
  animation: modal-slide-in var(--search-snippet-transition) ease-out;
3269
3532
  }
3270
- `, T = "search-modal-snippet", ie = 10, re = 50;
3271
- class ae extends HTMLElement {
3533
+ `, O = "search-modal-snippet", D = 10, N = 50;
3534
+ class ce extends HTMLElement {
3272
3535
  constructor() {
3273
3536
  super();
3274
3537
  a(this, "shadow");
@@ -3285,6 +3548,8 @@ class ae extends HTMLElement {
3285
3548
  a(this, "currentSearchController", null);
3286
3549
  a(this, "loadingMessageInterval", null);
3287
3550
  a(this, "loadingMessageIndex", 0);
3551
+ a(this, "translationsOverride", null);
3552
+ a(this, "resolvedTranslations", d(null));
3288
3553
  // Event handler references for cleanup
3289
3554
  a(this, "handleGlobalKeydown", null);
3290
3555
  a(this, "handleInputChange", null);
@@ -3300,6 +3565,7 @@ class ae extends HTMLElement {
3300
3565
  "api-url",
3301
3566
  "placeholder",
3302
3567
  "max-results",
3568
+ "max-render-results",
3303
3569
  "theme",
3304
3570
  "shortcut",
3305
3571
  "use-meta-key",
@@ -3309,32 +3575,79 @@ class ae extends HTMLElement {
3309
3575
  "show-date",
3310
3576
  "hide-thumbnails",
3311
3577
  "see-more",
3312
- "request-options"
3578
+ "request-options",
3579
+ "translations"
3313
3580
  ];
3314
3581
  }
3315
3582
  connectedCallback() {
3316
- this.initializeClient(), this.render(), this.attachGlobalKeyboardShortcut(), this.dispatchEvent(v("ready", void 0));
3583
+ this.syncTranslationsFromAttribute(), this.initializeClient(), this.render(), this.attachGlobalKeyboardShortcut(), this.dispatchEvent(v("ready", void 0));
3317
3584
  }
3318
3585
  disconnectedCallback() {
3319
3586
  this.cleanup();
3320
3587
  }
3321
3588
  attributeChangedCallback(e, t, s) {
3322
- t !== s && (e === "api-url" ? this.initializeClient() : e === "theme" && this.updateTheme(s));
3589
+ t !== s && (e === "api-url" ? this.initializeClient() : e === "theme" ? this.updateTheme(s) : e === "translations" && (this.syncTranslationsFromAttribute(), this.isConnected && this.rerender()));
3590
+ }
3591
+ /**
3592
+ * Get the current translations object.
3593
+ */
3594
+ get translations() {
3595
+ return this.translationsOverride;
3596
+ }
3597
+ /**
3598
+ * Override any user-facing string. Omitted keys fall back to English defaults.
3599
+ */
3600
+ set translations(e) {
3601
+ this.translationsOverride = e ?? null, this.resolvedTranslations = d(this.translationsOverride), this.isConnected && this.rerender();
3602
+ }
3603
+ /**
3604
+ * Re-render while preserving open state and the current query. Results are
3605
+ * re-fetched so the list reflects the updated translation strings around
3606
+ * them (counts, footer hints, etc.). Selection resets to none — the same
3607
+ * behavior as the immediate post-search state.
3608
+ */
3609
+ rerender() {
3610
+ const e = this.isOpen, t = this.inputElement?.value ?? "";
3611
+ this.isOpen = !1, this.render(), e && (this.isOpen = !0, this.backdrop?.classList.add("open"), this.modal?.classList.add("open"), requestAnimationFrame(() => {
3612
+ requestAnimationFrame(() => {
3613
+ this.inputElement?.focus();
3614
+ });
3615
+ })), t && this.inputElement && (this.inputElement.value = t, t.trim().length > 0 && this.performSearch(t.trim()));
3616
+ }
3617
+ syncTranslationsFromAttribute() {
3618
+ if (this.translationsOverride) {
3619
+ this.resolvedTranslations = d(this.translationsOverride);
3620
+ return;
3621
+ }
3622
+ const e = k(
3623
+ this.getAttribute("translations"),
3624
+ "SearchModalSnippet"
3625
+ );
3626
+ this.resolvedTranslations = d(e);
3323
3627
  }
3324
3628
  getProps() {
3629
+ const e = this.resolvedTranslations;
3325
3630
  return {
3326
- apiUrl: d(this.getAttribute("api-url"), ""),
3327
- placeholder: d(this.getAttribute("placeholder"), "Search..."),
3328
- maxResults: y(this.getAttribute("max-results"), 10),
3631
+ apiUrl: p(this.getAttribute("api-url"), ""),
3632
+ placeholder: p(this.getAttribute("placeholder"), e.placeholder),
3633
+ maxResults: y(
3634
+ this.getAttribute("max-results"),
3635
+ N
3636
+ ),
3637
+ maxRenderResults: y(
3638
+ this.getAttribute("max-render-results"),
3639
+ D
3640
+ ),
3329
3641
  debounceMs: y(this.getAttribute("debounce-ms"), 300),
3330
- theme: d(this.getAttribute("theme"), "auto"),
3331
- shortcut: d(this.getAttribute("shortcut"), "k"),
3642
+ theme: p(this.getAttribute("theme"), "auto"),
3643
+ shortcut: p(this.getAttribute("shortcut"), "k"),
3332
3644
  useMetaKey: this.getAttribute("use-meta-key") !== "false",
3333
3645
  hideBranding: m(this.getAttribute("hide-branding"), !1),
3334
3646
  showUrl: m(this.getAttribute("show-url"), !1),
3335
3647
  showDate: m(this.getAttribute("show-date"), !1),
3336
3648
  hideThumbnails: m(this.getAttribute("hide-thumbnails"), !1),
3337
- seeMore: d(this.getAttribute("see-more"), "")
3649
+ seeMore: p(this.getAttribute("see-more"), ""),
3650
+ translations: this.translationsOverride ?? void 0
3338
3651
  };
3339
3652
  }
3340
3653
  getRequestOptions() {
@@ -3357,22 +3670,22 @@ class ae extends HTMLElement {
3357
3670
  return;
3358
3671
  }
3359
3672
  try {
3360
- this.client = x(e.apiUrl);
3673
+ this.client = S(e.apiUrl);
3361
3674
  } catch (t) {
3362
3675
  console.error("SearchModalSnippet:", t);
3363
3676
  }
3364
3677
  }
3365
3678
  render() {
3366
- const e = this.getProps(), t = (c) => this.performSearch(c);
3367
- this.debouncedSearch = $(
3368
- t,
3679
+ const e = this.getProps(), t = this.resolvedTranslations, s = (h) => this.performSearch(h);
3680
+ this.debouncedSearch = P(
3681
+ s,
3369
3682
  e.debounceMs || 300
3370
3683
  );
3371
- const s = document.createElement("style");
3372
- s.textContent = `${C}
3373
- ${se}`;
3374
- const r = e.hideBranding ? "" : `<div class="powered-by-inline">${k}</div>`, o = document.createElement("div");
3375
- o.innerHTML = `
3684
+ const r = document.createElement("style");
3685
+ r.textContent = `${M}
3686
+ ${le}`;
3687
+ const o = e.hideBranding ? "" : `<div class="powered-by-inline">${E}</div>`, c = document.createElement("div");
3688
+ c.innerHTML = `
3376
3689
  <div class="modal-backdrop" role="presentation"></div>
3377
3690
  <div class="modal-container" role="dialog" aria-modal="true" aria-labelledby="modal-title">
3378
3691
  <div class="modal-header">
@@ -3382,8 +3695,8 @@ ${se}`;
3382
3695
  <input
3383
3696
  type="text"
3384
3697
  class="modal-search-input"
3385
- placeholder="${h(e.placeholder || "Search...")}"
3386
- aria-label="Search"
3698
+ placeholder="${l(e.placeholder || t.placeholder)}"
3699
+ aria-label="${l(t.searchButtonLabel)}"
3387
3700
  aria-autocomplete="list"
3388
3701
  aria-controls="modal-results-list"
3389
3702
  aria-expanded="false"
@@ -3392,7 +3705,7 @@ ${se}`;
3392
3705
  />
3393
3706
  </div>
3394
3707
  <div class="modal-content">
3395
- <div class="modal-results" id="modal-results-list" role="listbox" aria-label="Search results">
3708
+ <div class="modal-results" id="modal-results-list" role="listbox" aria-label="${l(t.searchResultsAriaLabel)}">
3396
3709
  ${this.renderEmptyState()}
3397
3710
  </div>
3398
3711
  </div>
@@ -3401,21 +3714,21 @@ ${se}`;
3401
3714
  <div class="modal-footer-hint">
3402
3715
  <kbd class="modal-kbd">↑</kbd>
3403
3716
  <kbd class="modal-kbd">↓</kbd>
3404
- <span>Navigate</span>
3717
+ <span>${l(t.navigateHint)}</span>
3405
3718
  </div>
3406
3719
  <div class="modal-footer-hint">
3407
3720
  <kbd class="modal-kbd">↵</kbd>
3408
- <span>Select</span>
3721
+ <span>${l(t.selectHint)}</span>
3409
3722
  </div>
3410
3723
  <div class="modal-footer-hint">
3411
3724
  <kbd class="modal-kbd">Esc</kbd>
3412
- <span>Close</span>
3725
+ <span>${l(t.closeHint)}</span>
3413
3726
  </div>
3414
3727
  </div>
3415
- ${r}
3728
+ ${o}
3416
3729
  </div>
3417
3730
  </div>
3418
- `, this.shadow.innerHTML = "", this.shadow.appendChild(s), this.shadow.appendChild(o), this.backdrop = this.shadow.querySelector(".modal-backdrop"), this.modal = this.shadow.querySelector(".modal-container"), this.inputElement = this.shadow.querySelector(".modal-search-input"), this.resultsContainer = this.shadow.querySelector(".modal-results"), this.footerCount = this.shadow.querySelector(".modal-results-count"), this.attachEventListeners(), this.client || this.showMissingApiUrlError();
3731
+ `, this.shadow.innerHTML = "", this.shadow.appendChild(r), this.shadow.appendChild(c), this.backdrop = this.shadow.querySelector(".modal-backdrop"), this.modal = this.shadow.querySelector(".modal-container"), this.inputElement = this.shadow.querySelector(".modal-search-input"), this.resultsContainer = this.shadow.querySelector(".modal-results"), this.footerCount = this.shadow.querySelector(".modal-results-count"), this.attachEventListeners(), this.client || this.showMissingApiUrlError();
3419
3732
  }
3420
3733
  attachGlobalKeyboardShortcut() {
3421
3734
  const e = this.getProps(), t = e.shortcut?.toLowerCase() || "k";
@@ -3482,12 +3795,12 @@ ${se}`;
3482
3795
  }
3483
3796
  this.currentSearchController && (this.currentSearchController.abort(), this.currentSearchController = null), this.currentSearchController = new AbortController(), this.showLoadingState();
3484
3797
  try {
3485
- const t = await this.client.search(e, {
3798
+ const t = this.getProps(), s = await this.client.search(e, {
3486
3799
  signal: this.currentSearchController.signal,
3487
- maxResults: re,
3800
+ maxResults: t.maxResults || N,
3488
3801
  request: this.getRequestOptions()
3489
- }), s = this.getProps();
3490
- this.results = t.slice(0, s.maxResults || ie), this.activeIndex = this.results.length > 0 ? 0 : -1, this.displayResults(this.results, e, t.length);
3802
+ });
3803
+ this.results = s.slice(0, t.maxRenderResults || D), this.activeIndex = this.results.length > 0 ? 0 : -1, this.displayResults(this.results, e, s.length);
3491
3804
  } catch (t) {
3492
3805
  if (t.name === "AbortError")
3493
3806
  return;
@@ -3502,16 +3815,18 @@ ${se}`;
3502
3815
  this.showNoResultsState(t);
3503
3816
  return;
3504
3817
  }
3505
- const r = this.getProps(), o = e.map((b, u) => this.renderResult(b, u)).join(""), c = s > e.length, l = c ? `Showing ${e.length} of ${s} results` : `${s} result${s === 1 ? "" : "s"}`, p = r.seeMore && c ? `<a href="${h(r.seeMore + encodeURIComponent(t))}" class="modal-see-more">
3506
- <span>See more results</span>
3818
+ const r = this.getProps(), o = this.resolvedTranslations, c = e.map((g, w) => this.renderResult(g, w)).join(""), h = s > e.length, u = h ? b(o.resultsCountOverflow, { n: e.length, total: s }) : b(s === 1 ? o.modalResultsCount : o.modalResultsCountPlural, {
3819
+ n: s
3820
+ }), f = r.seeMore && h ? `<a href="${l(r.seeMore + encodeURIComponent(t))}" class="modal-see-more">
3821
+ <span>${l(o.seeMoreResults)}</span>
3507
3822
  <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
3508
3823
  </a>` : "";
3509
- this.resultsContainer.innerHTML = o + p, this.footerCount && (this.footerCount.textContent = l), this.inputElement && this.inputElement.setAttribute("aria-expanded", "true"), this.attachResultHandlers(), this.updateActiveResult();
3824
+ this.resultsContainer.innerHTML = c + f, this.footerCount && (this.footerCount.textContent = u), this.inputElement && this.inputElement.setAttribute("aria-expanded", "true"), this.attachResultHandlers(), this.updateActiveResult();
3510
3825
  }
3511
3826
  renderResult(e, t) {
3512
- const s = this.getProps(), r = s.hideThumbnails ? "" : this.renderResultImage(e.image, e.title), o = e.url ? h(e.url) : "#", c = e.url ? h(q(e.url)) : "", l = s.showDate && e.timestamp !== void 0 ? `<div class="modal-result-date">${h(B(e.timestamp))}</div>` : "", p = s.showUrl && e.url || l ? `<div class="modal-result-metadata">
3827
+ const s = this.getProps(), r = s.hideThumbnails ? "" : this.renderResultImage(e.image, e.title), o = e.url ? l(e.url) : "#", c = e.url ? l(U(e.url)) : "", h = s.showDate && e.timestamp !== void 0 ? `<div class="modal-result-date">${l(j(e.timestamp))}</div>` : "", u = s.showUrl && e.url || h ? `<div class="modal-result-metadata">
3513
3828
  ${s.showUrl && e.url ? `<span class="modal-result-url">${c}</span>` : '<span class="modal-result-url modal-result-url-empty"></span>'}
3514
- ${l}
3829
+ ${h}
3515
3830
  </div>` : "";
3516
3831
  return `
3517
3832
  <a
@@ -3522,13 +3837,13 @@ ${se}`;
3522
3837
  aria-selected="${t === this.activeIndex}"
3523
3838
  tabindex="-1"
3524
3839
  data-index="${t}"
3525
- data-url="${h(e.url || "")}"
3840
+ data-url="${l(e.url || "")}"
3526
3841
  >
3527
3842
  ${r}
3528
3843
  <div class="modal-result-content">
3529
- <div class="modal-result-title">${h(e.title || "")}</div>
3530
- ${e.description ? `<div class="modal-result-description">${h(e.description)}</div>` : ""}
3531
- ${p}
3844
+ <div class="modal-result-title">${l(e.title || "")}</div>
3845
+ ${e.description ? `<div class="modal-result-description">${l(e.description)}</div>` : ""}
3846
+ ${u}
3532
3847
  </div>
3533
3848
  </a>
3534
3849
  `;
@@ -3541,8 +3856,8 @@ ${se}`;
3541
3856
  <div class="modal-result-image-placeholder" style="display: none;">${s}</div>
3542
3857
  <img
3543
3858
  class="modal-result-image"
3544
- src="${h(e)}"
3545
- alt="${h(t)}"
3859
+ src="${l(e)}"
3860
+ alt="${l(t)}"
3546
3861
  loading="lazy"
3547
3862
  />
3548
3863
  </div>
@@ -3575,13 +3890,14 @@ ${se}`;
3575
3890
  });
3576
3891
  }
3577
3892
  renderEmptyState() {
3893
+ const e = this.resolvedTranslations;
3578
3894
  return `
3579
3895
  <div class="modal-empty">
3580
3896
  <svg class="modal-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3581
3897
  <circle cx="11" cy="11" r="8"></circle>
3582
3898
  <path d="m21 21-4.35-4.35"></path>
3583
3899
  </svg>
3584
- <div class="modal-empty-description">Start typing to search</div>
3900
+ <div class="modal-empty-description">${l(e.modalEmptyStateDescription)}</div>
3585
3901
  </div>
3586
3902
  `;
3587
3903
  }
@@ -3589,44 +3905,52 @@ ${se}`;
3589
3905
  this.clearLoadingInterval(), this.resultsContainer && (this.resultsContainer.innerHTML = this.renderEmptyState(), this.footerCount && (this.footerCount.textContent = ""), this.inputElement && this.inputElement.setAttribute("aria-expanded", "false"));
3590
3906
  }
3591
3907
  showLoadingState() {
3592
- this.resultsContainer && (this.clearLoadingInterval(), this.loadingMessageIndex = Math.floor(Math.random() * g.length), this.resultsContainer.innerHTML = `
3908
+ if (!this.resultsContainer) return;
3909
+ this.clearLoadingInterval();
3910
+ const e = this.resolvedTranslations.loadingMessages, t = this.resolvedTranslations;
3911
+ this.loadingMessageIndex = Math.floor(Math.random() * e.length), this.resultsContainer.innerHTML = `
3593
3912
  <div class="modal-loading">
3594
- <div class="loading" aria-label="Loading"></div>
3595
- <div class="loading-text loading-text-animate">${g[this.loadingMessageIndex]}</div>
3913
+ <div class="loading" aria-label="${l(t.loadingAriaLabel)}"></div>
3914
+ <div class="loading-text loading-text-animate">${l(e[this.loadingMessageIndex])}</div>
3596
3915
  </div>
3597
- `, this.footerCount && (this.footerCount.textContent = g[this.loadingMessageIndex]), this.startLoadingInterval());
3916
+ `, this.footerCount && (this.footerCount.textContent = e[this.loadingMessageIndex]), this.startLoadingInterval();
3598
3917
  }
3599
3918
  startLoadingInterval() {
3600
3919
  this.loadingMessageInterval = setInterval(() => {
3601
- this.loadingMessageIndex = (this.loadingMessageIndex + 1) % g.length;
3602
- const e = this.resultsContainer?.querySelector(".loading-text");
3603
- e && (e.classList.remove("loading-text-animate"), e.offsetWidth, e.textContent = g[this.loadingMessageIndex], e.classList.add("loading-text-animate")), this.footerCount && (this.footerCount.textContent = g[this.loadingMessageIndex]);
3604
- }, 2500);
3920
+ const e = this.resolvedTranslations.loadingMessages;
3921
+ this.loadingMessageIndex = (this.loadingMessageIndex + 1) % e.length;
3922
+ const t = this.resultsContainer?.querySelector(".loading-text");
3923
+ t && (t.classList.remove("loading-text-animate"), t.offsetWidth, t.textContent = e[this.loadingMessageIndex], t.classList.add("loading-text-animate")), this.footerCount && (this.footerCount.textContent = e[this.loadingMessageIndex]);
3924
+ }, T);
3605
3925
  }
3606
3926
  clearLoadingInterval() {
3607
3927
  this.loadingMessageInterval && (clearInterval(this.loadingMessageInterval), this.loadingMessageInterval = null);
3608
3928
  }
3609
3929
  showNoResultsState(e) {
3610
- this.clearLoadingInterval(), this.resultsContainer && (this.resultsContainer.innerHTML = `
3930
+ if (this.clearLoadingInterval(), !this.resultsContainer) return;
3931
+ const t = this.resolvedTranslations;
3932
+ this.resultsContainer.innerHTML = `
3611
3933
  <div class="modal-empty">
3612
3934
  <svg class="modal-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3613
3935
  <circle cx="11" cy="11" r="8"></circle>
3614
3936
  <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
3615
3937
  </svg>
3616
- <div class="modal-empty-title">No results found</div>
3617
- <div class="modal-empty-description">No results for "${h(e)}"</div>
3938
+ <div class="modal-empty-title">${l(t.modalNoResultsTitle)}</div>
3939
+ <div class="modal-empty-description">${l(b(t.modalNoResultsDescription, { query: e }))}</div>
3618
3940
  </div>
3619
- `, this.footerCount && (this.footerCount.textContent = "0 results"), this.inputElement && this.inputElement.setAttribute("aria-expanded", "false"));
3941
+ `, this.footerCount && (this.footerCount.textContent = t.modalResultsCountZero), this.inputElement && this.inputElement.setAttribute("aria-expanded", "false");
3620
3942
  }
3621
3943
  showErrorState(e) {
3622
- this.clearLoadingInterval(), this.resultsContainer && (this.resultsContainer.innerHTML = `
3944
+ if (this.clearLoadingInterval(), !this.resultsContainer) return;
3945
+ const t = this.resolvedTranslations;
3946
+ this.resultsContainer.innerHTML = `
3623
3947
  <div class="error">
3624
- <strong>Error:</strong> ${h(e)}
3948
+ <strong>${l(t.errorPrefix)}</strong> ${l(e)}
3625
3949
  </div>
3626
- `, this.footerCount && (this.footerCount.textContent = "Error"));
3950
+ `, this.footerCount && (this.footerCount.textContent = t.modalResultsCountError);
3627
3951
  }
3628
3952
  showMissingApiUrlError() {
3629
- this.resultsContainer && this.showErrorState("The api-url attribute is required. Please provide a valid API URL.");
3953
+ this.resultsContainer && this.showErrorState(this.resolvedTranslations.missingApiUrlError);
3630
3954
  }
3631
3955
  updateTheme(e) {
3632
3956
  const t = e === "light" || e === "dark" || e === "auto" ? e : "auto";
@@ -3692,13 +4016,15 @@ ${se}`;
3692
4016
  return this.isOpen;
3693
4017
  }
3694
4018
  }
3695
- customElements.get(T) || customElements.define(T, ae);
4019
+ customElements.get(O) || customElements.define(O, ce);
3696
4020
  export {
3697
- K as AISearchClient,
3698
- W as ChatBubbleSnippet,
3699
- X as ChatPageSnippet,
3700
- te as SearchBarSnippet,
3701
- ae as SearchModalSnippet,
3702
- te as default
4021
+ Q as AISearchClient,
4022
+ re as ChatBubbleSnippet,
4023
+ ae as ChatPageSnippet,
4024
+ L as DEFAULT_TRANSLATIONS,
4025
+ oe as SearchBarSnippet,
4026
+ ce as SearchModalSnippet,
4027
+ oe as default,
4028
+ d as mergeTranslations
3703
4029
  };
3704
4030
  //# sourceMappingURL=search-snippet.es.js.map