@magicfeedback/popup-sdk 1.6.0 → 1.7.1

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.
@@ -178,6 +178,274 @@ var init_font = __esm({
178
178
  }
179
179
  });
180
180
 
181
+ // src/i18n/labels.ts
182
+ function resolveLocale(lang) {
183
+ if (typeof lang !== "string") return "en";
184
+ const normalized = lang.trim().toLowerCase().replace(/_/g, "-");
185
+ if (!normalized) return "en";
186
+ if (normalized === "zh" || normalized.startsWith("zh-")) return "zh-CN";
187
+ const primary = normalized.split("-")[0];
188
+ switch (primary) {
189
+ case "en":
190
+ return "en";
191
+ case "es":
192
+ return "es";
193
+ case "da":
194
+ return "da";
195
+ case "no":
196
+ case "nb":
197
+ case "nn":
198
+ return "no";
199
+ case "sv":
200
+ return "sv";
201
+ case "fi":
202
+ return "fi";
203
+ case "de":
204
+ return "de";
205
+ case "fr":
206
+ return "fr";
207
+ case "pt":
208
+ return "pt";
209
+ case "ar":
210
+ return "ar";
211
+ case "bn":
212
+ return "bn";
213
+ default:
214
+ return "en";
215
+ }
216
+ }
217
+ function isRtlLanguage(lang) {
218
+ return RTL_LOCALES.includes(resolveLocale(lang));
219
+ }
220
+ function directionFor(lang) {
221
+ return isRtlLanguage(lang) ? "rtl" : "ltr";
222
+ }
223
+ function getLabels(lang) {
224
+ return LABELS[resolveLocale(lang)];
225
+ }
226
+ function resolveActionLabels(actions, lang) {
227
+ const labels = getLabels(lang);
228
+ const pick = (value, fallback) => {
229
+ const trimmed = typeof value === "string" ? value.trim() : "";
230
+ return trimmed ? value : fallback;
231
+ };
232
+ return {
233
+ accept: pick(actions?.accept?.label, labels.accept),
234
+ decline: pick(actions?.decline?.label, labels.decline),
235
+ start: pick(actions?.start?.label, labels.start),
236
+ complete: pick(actions?.complete?.label, labels.complete),
237
+ back: pick(actions?.back?.label, labels.back)
238
+ };
239
+ }
240
+ var EN, ES, DA, NO, SV, FI, DE, FR, PT, AR, BN, ZH_CN, LABELS, RTL_LOCALES, RESOLVE_LOCALE_JS;
241
+ var init_labels = __esm({
242
+ "src/i18n/labels.ts"() {
243
+ "use strict";
244
+ EN = {
245
+ accept: "Send",
246
+ decline: "Cancel",
247
+ start: "Start survey",
248
+ complete: "Complete survey",
249
+ back: "Back",
250
+ question: "Question",
251
+ of: "of",
252
+ followUp: "Follow-up",
253
+ closeAria: "Close popup",
254
+ loadingAria: "Loading survey",
255
+ errorRequired: "Please answer the required question to continue.",
256
+ errorSubmit: "An error occurred while submitting. Please try again or close the popup."
257
+ };
258
+ ES = {
259
+ accept: "Enviar",
260
+ decline: "Cancelar",
261
+ start: "Empezar encuesta",
262
+ complete: "Completar encuesta",
263
+ back: "Atr\xE1s",
264
+ question: "Pregunta",
265
+ of: "de",
266
+ followUp: "Seguimiento",
267
+ closeAria: "Cerrar ventana emergente",
268
+ loadingAria: "Cargando encuesta",
269
+ errorRequired: "Responde la pregunta obligatoria para continuar.",
270
+ errorSubmit: "Se produjo un error al enviar. Int\xE9ntalo de nuevo o cierra la ventana."
271
+ };
272
+ DA = {
273
+ accept: "Send",
274
+ decline: "Annuller",
275
+ start: "Start unders\xF8gelse",
276
+ complete: "Afslut unders\xF8gelse",
277
+ back: "Tilbage",
278
+ question: "Sp\xF8rgsm\xE5l",
279
+ of: "af",
280
+ followUp: "Opf\xF8lgning",
281
+ closeAria: "Luk pop op",
282
+ loadingAria: "Indl\xE6ser unders\xF8gelse",
283
+ errorRequired: "Besvar venligst det obligatoriske sp\xF8rgsm\xE5l for at forts\xE6tte.",
284
+ errorSubmit: "Der opstod en fejl under afsendelsen. Pr\xF8v igen, eller luk pop op-vinduet."
285
+ };
286
+ NO = {
287
+ accept: "Send",
288
+ decline: "Avbryt",
289
+ start: "Start unders\xF8kelse",
290
+ complete: "Fullf\xF8r unders\xF8kelse",
291
+ back: "Tilbake",
292
+ question: "Sp\xF8rsm\xE5l",
293
+ of: "av",
294
+ followUp: "Oppf\xF8lging",
295
+ closeAria: "Lukk popup",
296
+ loadingAria: "Laster unders\xF8kelse",
297
+ errorRequired: "Svar p\xE5 det obligatoriske sp\xF8rsm\xE5let for \xE5 fortsette.",
298
+ errorSubmit: "Det oppstod en feil under innsendingen. Pr\xF8v igjen, eller lukk popupen."
299
+ };
300
+ SV = {
301
+ accept: "Skicka",
302
+ decline: "Avbryt",
303
+ start: "Starta unders\xF6kning",
304
+ complete: "Slutf\xF6r unders\xF6kning",
305
+ back: "Tillbaka",
306
+ question: "Fr\xE5ga",
307
+ of: "av",
308
+ followUp: "Uppf\xF6ljning",
309
+ closeAria: "St\xE4ng popup",
310
+ loadingAria: "Laddar unders\xF6kning",
311
+ errorRequired: "Svara p\xE5 den obligatoriska fr\xE5gan f\xF6r att forts\xE4tta.",
312
+ errorSubmit: "Ett fel uppstod vid inskickningen. F\xF6rs\xF6k igen eller st\xE4ng popupen."
313
+ };
314
+ FI = {
315
+ accept: "L\xE4het\xE4",
316
+ decline: "Peruuta",
317
+ start: "Aloita kysely",
318
+ complete: "Viimeistele kysely",
319
+ back: "Takaisin",
320
+ question: "Kysymys",
321
+ of: "/",
322
+ followUp: "Jatkokysymys",
323
+ closeAria: "Sulje ponnahdusikkuna",
324
+ loadingAria: "Ladataan kysely\xE4",
325
+ errorRequired: "Vastaa pakolliseen kysymykseen jatkaaksesi.",
326
+ errorSubmit: "L\xE4hetyksess\xE4 tapahtui virhe. Yrit\xE4 uudelleen tai sulje ponnahdusikkuna."
327
+ };
328
+ DE = {
329
+ accept: "Senden",
330
+ decline: "Abbrechen",
331
+ start: "Umfrage starten",
332
+ complete: "Umfrage abschlie\xDFen",
333
+ back: "Zur\xFCck",
334
+ question: "Frage",
335
+ of: "von",
336
+ followUp: "Folgefrage",
337
+ closeAria: "Popup schlie\xDFen",
338
+ loadingAria: "Umfrage wird geladen",
339
+ errorRequired: "Bitte beantworte die Pflichtfrage, um fortzufahren.",
340
+ errorSubmit: "Beim Senden ist ein Fehler aufgetreten. Versuche es erneut oder schlie\xDFe das Popup."
341
+ };
342
+ FR = {
343
+ accept: "Envoyer",
344
+ decline: "Annuler",
345
+ start: "Commencer l'enqu\xEAte",
346
+ complete: "Terminer l'enqu\xEAte",
347
+ back: "Retour",
348
+ question: "Question",
349
+ of: "sur",
350
+ followUp: "Question compl\xE9mentaire",
351
+ closeAria: "Fermer la fen\xEAtre",
352
+ loadingAria: "Chargement de l'enqu\xEAte",
353
+ errorRequired: "Veuillez r\xE9pondre \xE0 la question obligatoire pour continuer.",
354
+ errorSubmit: "Une erreur s'est produite lors de l'envoi. R\xE9essayez ou fermez la fen\xEAtre."
355
+ };
356
+ PT = {
357
+ accept: "Enviar",
358
+ decline: "Cancelar",
359
+ start: "Iniciar question\xE1rio",
360
+ complete: "Concluir question\xE1rio",
361
+ back: "Voltar",
362
+ question: "Pergunta",
363
+ of: "de",
364
+ followUp: "Seguimento",
365
+ closeAria: "Fechar janela",
366
+ loadingAria: "A carregar question\xE1rio",
367
+ errorRequired: "Responda \xE0 pergunta obrigat\xF3ria para continuar.",
368
+ errorSubmit: "Ocorreu um erro ao enviar. Tente novamente ou feche a janela."
369
+ };
370
+ AR = {
371
+ accept: "\u0625\u0631\u0633\u0627\u0644",
372
+ decline: "\u0625\u0644\u063A\u0627\u0621",
373
+ start: "\u0627\u0628\u062F\u0623 \u0627\u0644\u0627\u0633\u062A\u0628\u064A\u0627\u0646",
374
+ complete: "\u0625\u0646\u0647\u0627\u0621 \u0627\u0644\u0627\u0633\u062A\u0628\u064A\u0627\u0646",
375
+ back: "\u0631\u062C\u0648\u0639",
376
+ question: "\u0633\u0624\u0627\u0644",
377
+ of: "\u0645\u0646",
378
+ followUp: "\u0633\u0624\u0627\u0644 \u0645\u062A\u0627\u0628\u0639\u0629",
379
+ closeAria: "\u0625\u063A\u0644\u0627\u0642 \u0627\u0644\u0646\u0627\u0641\u0630\u0629 \u0627\u0644\u0645\u0646\u0628\u062B\u0642\u0629",
380
+ loadingAria: "\u062C\u0627\u0631\u064D \u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0627\u0633\u062A\u0628\u064A\u0627\u0646",
381
+ errorRequired: "\u064A\u0631\u062C\u0649 \u0627\u0644\u0625\u062C\u0627\u0628\u0629 \u0639\u0644\u0649 \u0627\u0644\u0633\u0624\u0627\u0644 \u0627\u0644\u0645\u0637\u0644\u0648\u0628 \u0644\u0644\u0645\u062A\u0627\u0628\u0639\u0629.",
382
+ errorSubmit: "\u062D\u062F\u062B \u062E\u0637\u0623 \u0623\u062B\u0646\u0627\u0621 \u0627\u0644\u0625\u0631\u0633\u0627\u0644. \u064A\u0631\u062C\u0649 \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629 \u0645\u0631\u0629 \u0623\u062E\u0631\u0649 \u0623\u0648 \u0625\u063A\u0644\u0627\u0642 \u0627\u0644\u0646\u0627\u0641\u0630\u0629."
383
+ };
384
+ BN = {
385
+ accept: "\u09AA\u09BE\u09A0\u09BE\u09A8",
386
+ decline: "\u09AC\u09BE\u09A4\u09BF\u09B2",
387
+ start: "\u099C\u09B0\u09BF\u09AA \u09B6\u09C1\u09B0\u09C1 \u0995\u09B0\u09C1\u09A8",
388
+ complete: "\u099C\u09B0\u09BF\u09AA \u09B8\u09AE\u09CD\u09AA\u09C2\u09B0\u09CD\u09A3 \u0995\u09B0\u09C1\u09A8",
389
+ back: "\u09AA\u09BF\u099B\u09A8\u09C7",
390
+ question: "\u09AA\u09CD\u09B0\u09B6\u09CD\u09A8",
391
+ of: "/",
392
+ followUp: "\u09AB\u09B2\u09CB-\u0986\u09AA",
393
+ closeAria: "\u09AA\u09AA\u0986\u09AA \u09AC\u09A8\u09CD\u09A7 \u0995\u09B0\u09C1\u09A8",
394
+ loadingAria: "\u099C\u09B0\u09BF\u09AA \u09B2\u09CB\u09A1 \u09B9\u099A\u09CD\u099B\u09C7",
395
+ errorRequired: "\u099A\u09BE\u09B2\u09BF\u09AF\u09BC\u09C7 \u09AF\u09C7\u09A4\u09C7 \u0986\u09AC\u09B6\u09CD\u09AF\u0995 \u09AA\u09CD\u09B0\u09B6\u09CD\u09A8\u09C7\u09B0 \u0989\u09A4\u09CD\u09A4\u09B0 \u09A6\u09BF\u09A8\u0964",
396
+ errorSubmit: "\u099C\u09AE\u09BE \u09A6\u09C7\u0993\u09AF\u09BC\u09BE\u09B0 \u09B8\u09AE\u09AF\u09BC \u098F\u0995\u099F\u09BF \u09A4\u09CD\u09B0\u09C1\u099F\u09BF \u0998\u099F\u09C7\u099B\u09C7\u0964 \u0986\u09AC\u09BE\u09B0 \u099A\u09C7\u09B7\u09CD\u099F\u09BE \u0995\u09B0\u09C1\u09A8 \u09AC\u09BE \u09AA\u09AA\u0986\u09AA \u09AC\u09A8\u09CD\u09A7 \u0995\u09B0\u09C1\u09A8\u0964"
397
+ };
398
+ ZH_CN = {
399
+ accept: "\u53D1\u9001",
400
+ decline: "\u53D6\u6D88",
401
+ start: "\u5F00\u59CB\u95EE\u5377",
402
+ complete: "\u5B8C\u6210\u95EE\u5377",
403
+ back: "\u8FD4\u56DE",
404
+ question: "\u95EE\u9898",
405
+ of: "/",
406
+ followUp: "\u8FFD\u95EE",
407
+ closeAria: "\u5173\u95ED\u5F39\u7A97",
408
+ loadingAria: "\u6B63\u5728\u52A0\u8F7D\u95EE\u5377",
409
+ errorRequired: "\u8BF7\u56DE\u7B54\u5FC5\u7B54\u9898\u540E\u7EE7\u7EED\u3002",
410
+ errorSubmit: "\u63D0\u4EA4\u65F6\u51FA\u9519\u3002\u8BF7\u91CD\u8BD5\u6216\u5173\u95ED\u5F39\u7A97\u3002"
411
+ };
412
+ LABELS = {
413
+ en: EN,
414
+ es: ES,
415
+ da: DA,
416
+ no: NO,
417
+ sv: SV,
418
+ fi: FI,
419
+ de: DE,
420
+ fr: FR,
421
+ pt: PT,
422
+ ar: AR,
423
+ bn: BN,
424
+ "zh-CN": ZH_CN
425
+ };
426
+ RTL_LOCALES = ["ar"];
427
+ RESOLVE_LOCALE_JS = `function ddResolveLocale(lang){
428
+ if(typeof lang!=='string') return 'en';
429
+ var n=lang.trim().toLowerCase().replace(/_/g,'-');
430
+ if(!n) return 'en';
431
+ if(n==='zh'||n.indexOf('zh-')===0) return 'zh-CN';
432
+ var p=n.split('-')[0];
433
+ if(p==='en') return 'en';
434
+ if(p==='es') return 'es';
435
+ if(p==='da') return 'da';
436
+ if(p==='no'||p==='nb'||p==='nn') return 'no';
437
+ if(p==='sv') return 'sv';
438
+ if(p==='fi') return 'fi';
439
+ if(p==='de') return 'de';
440
+ if(p==='fr') return 'fr';
441
+ if(p==='pt') return 'pt';
442
+ if(p==='ar') return 'ar';
443
+ if(p==='bn') return 'bn';
444
+ return 'en';
445
+ }`;
446
+ }
447
+ });
448
+
181
449
  // src/assets/style.css
182
450
  var style_default;
183
451
  var init_style = __esm({
@@ -185,7 +453,7 @@ var init_style = __esm({
185
453
  style_default = `/**
186
454
  * NO EDITAR A MANO \u2014 generado por scripts/vendor-survey-css.mjs
187
455
  *
188
- * Copia de @magicfeedback/native@2.2.8 \u2192 dist/styles/magicfeedback-default.css
456
+ * Copia de @magicfeedback/native@2.2.22 \u2192 dist/styles/magicfeedback-default.css
189
457
  * (la hoja can\xF3nica del paquete, la que apunta su campo "style"), con estos cambios locales:
190
458
  *
191
459
  * 1. Sin el @import de Google Fonts (Nunito): no queremos una petici\xF3n externa desde la
@@ -405,7 +673,7 @@ var init_style = __esm({
405
673
  font-weight: var(--mf-font-weight-bold);
406
674
  line-height: 1.3;
407
675
  margin: var(--mf-space-lg) 0 var(--mf-space-sm);
408
- text-align: left;
676
+ text-align: start;
409
677
  }
410
678
 
411
679
  .magicfeedback-info-message h1, .magicfeedback-start-message h1 { font-size: var(--mf-font-size-xl); }
@@ -444,7 +712,7 @@ var init_style = __esm({
444
712
  .magicfeedback-start-message ul,
445
713
  .magicfeedback-start-message ol {
446
714
  margin: 0 0 var(--mf-space-md);
447
- padding-left: var(--mf-space-xl);
715
+ padding-inline-start: var(--mf-space-xl);
448
716
  color: var(--mf-text-secondary);
449
717
  line-height: var(--mf-line-height);
450
718
  }
@@ -458,7 +726,7 @@ var init_style = __esm({
458
726
  .magicfeedback-start-message blockquote {
459
727
  margin: 0 0 var(--mf-space-md);
460
728
  padding: var(--mf-space-sm) var(--mf-space-md);
461
- border-left: 3px solid var(--mf-primary-border);
729
+ border-inline-start: 3px solid var(--mf-primary-border);
462
730
  color: var(--mf-text-secondary);
463
731
  font-style: italic;
464
732
  }
@@ -547,7 +815,7 @@ var init_style = __esm({
547
815
  font-weight: var(--mf-font-weight-bold);
548
816
  color: var(--mf-text-primary);
549
817
  line-height: var(--mf-line-height);
550
- text-align: left;
818
+ text-align: start;
551
819
  letter-spacing: -0.01em;
552
820
  }
553
821
 
@@ -623,7 +891,7 @@ var init_style = __esm({
623
891
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748b' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
624
892
  background-repeat: no-repeat;
625
893
  background-position: right var(--mf-space-md) center;
626
- padding-right: var(--mf-space-xl);
894
+ padding-inline-end: var(--mf-space-xl);
627
895
  }
628
896
 
629
897
  /* Error State */
@@ -687,7 +955,7 @@ div.magicfeedback-radio {
687
955
  .magicfeedback-checkbox label,
688
956
  .magicfeedback-radio-container label {
689
957
  font-size: var(--mf-font-size-base);
690
- text-align: left;
958
+ text-align: start;
691
959
  color: var(--mf-text-primary);
692
960
  cursor: pointer;
693
961
  margin: 0;
@@ -785,7 +1053,7 @@ div.magicfeedback-radio {
785
1053
  }
786
1054
 
787
1055
  .magicfeedback-rating-placeholder-value:last-child {
788
- text-align: right;
1056
+ text-align: end;
789
1057
  }
790
1058
 
791
1059
  /* Rating with Images */
@@ -868,7 +1136,7 @@ div.magicfeedback-radio {
868
1136
  }
869
1137
 
870
1138
  .magicfeedback-rating-number-scale-label:last-child {
871
- text-align: right;
1139
+ text-align: end;
872
1140
  }
873
1141
 
874
1142
  /* Column, no per-option text: each row is its own separated chip, so
@@ -1186,7 +1454,7 @@ div.magicfeedback-radio {
1186
1454
  }
1187
1455
 
1188
1456
  .magicfeedback-multi-question-matrix-label {
1189
- text-align: left;
1457
+ text-align: start;
1190
1458
  font-size: var(--mf-font-size-base);
1191
1459
  color: var(--mf-text-primary);
1192
1460
  }
@@ -1221,7 +1489,7 @@ div.magicfeedback-radio {
1221
1489
  .magicfeedback-priority-list-item-label {
1222
1490
  flex: 1;
1223
1491
  font-size: var(--mf-font-size-base);
1224
- text-align: left;
1492
+ text-align: start;
1225
1493
  color: var(--mf-text-primary);
1226
1494
  margin: 0;
1227
1495
  }
@@ -1313,7 +1581,7 @@ div.magicfeedback-radio {
1313
1581
  .magicfeedback-consent-container label {
1314
1582
  flex: 1;
1315
1583
  font-size: var(--mf-font-size-sm);
1316
- text-align: left;
1584
+ text-align: start;
1317
1585
  color: var(--mf-text-secondary);
1318
1586
  line-height: var(--mf-line-height);
1319
1587
  cursor: pointer;
@@ -1465,7 +1733,7 @@ div.magicfeedback-radio {
1465
1733
  }
1466
1734
 
1467
1735
  .magicfeedback-modal-counter {
1468
- margin-right: auto;
1736
+ margin-inline-end: auto;
1469
1737
  display: flex;
1470
1738
  align-items: center;
1471
1739
  font-size: 14px;
@@ -1710,7 +1978,7 @@ div.magicfeedback-radio {
1710
1978
  .magicfeedback-modal-close {
1711
1979
  position: absolute;
1712
1980
  top: 8px;
1713
- right: 8px;
1981
+ inset-inline-end: 8px;
1714
1982
  background: transparent;
1715
1983
  border: none;
1716
1984
  font-size: 20px;
@@ -1931,6 +2199,55 @@ div.magicfeedback-radio {
1931
2199
  display: block;
1932
2200
  }
1933
2201
 
2202
+ /* ========================================
2203
+ RTL (Arabic)
2204
+ ========================================
2205
+ The SDK stamps dir="rtl" (and .magicfeedback-rtl) on the survey container
2206
+ for right-to-left languages. Everything else in this stylesheet uses logical
2207
+ properties (text-align: start, padding-inline-*, inset-inline-*), so it
2208
+ mirrors on its own; only the handful of rules that cannot be expressed
2209
+ logically live here.
2210
+
2211
+ Rules are scoped to [dir="rtl"] inside our own subtree so a host page that is
2212
+ itself RTL, or one that embeds an Arabic survey in an LTR page, both render
2213
+ correctly. */
2214
+
2215
+ /* background-position has no logical form: move the select caret to the
2216
+ left edge, opposite the padding that inset-inline-end already mirrored. */
2217
+ [dir="rtl"] select {
2218
+ background-position: left var(--mf-space-md) center;
2219
+ }
2220
+
2221
+ /* Emails, URLs, numbers, dates and phone numbers are latin/numeric content
2222
+ even in an Arabic survey: keep their own direction LTR so punctuation
2223
+ (@, ., +, /) lands where the user expects, but align them to the start of
2224
+ the RTL line so they still read as part of the form. */
2225
+ [dir="rtl"] input[type="email"],
2226
+ [dir="rtl"] input[type="url"],
2227
+ [dir="rtl"] input[type="number"],
2228
+ [dir="rtl"] input[type="tel"],
2229
+ [dir="rtl"] input[type="date"],
2230
+ [dir="rtl"] input[type="time"] {
2231
+ direction: ltr;
2232
+ text-align: right;
2233
+ }
2234
+
2235
+ /* Mirror of mf-slide-in: questions enter from the reading edge. */
2236
+ @keyframes mf-slide-in-rtl {
2237
+ from {
2238
+ opacity: 0;
2239
+ transform: translateX(16px);
2240
+ }
2241
+ to {
2242
+ opacity: 1;
2243
+ transform: translateX(0);
2244
+ }
2245
+ }
2246
+
2247
+ [dir="rtl"] .magicfeedback-div {
2248
+ animation-name: mf-slide-in-rtl;
2249
+ }
2250
+
1934
2251
  /* ========================================
1935
2252
  LOCAL \u2014 reglas del popup, no vienen del paquete
1936
2253
  ======================================== */
@@ -1989,12 +2306,14 @@ function insertPopupLogo(parent, anchor, style, id) {
1989
2306
  }
1990
2307
  switch (style.logoPosition) {
1991
2308
  case "left":
1992
- img.style.margin = "12px 16px 0 0";
1993
- img.style.marginLeft = "0";
2309
+ img.style.margin = "12px 0 0 0";
2310
+ img.style.setProperty("margin-inline-start", "0");
2311
+ img.style.setProperty("margin-inline-end", "16px");
1994
2312
  break;
1995
2313
  case "right":
1996
- img.style.margin = "12px 0 0 16px";
1997
- img.style.marginLeft = "auto";
2314
+ img.style.margin = "12px 0 0 0";
2315
+ img.style.setProperty("margin-inline-start", "auto");
2316
+ img.style.setProperty("margin-inline-end", "0");
1998
2317
  break;
1999
2318
  case "center":
2000
2319
  img.style.margin = "12px auto 0 auto";
@@ -2113,6 +2432,9 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
2113
2432
  let stylesInjected = false;
2114
2433
  let pageDepth = 0;
2115
2434
  let onStartPage = false;
2435
+ let labels = getLabels(options?.language);
2436
+ let actionLabels = resolveActionLabels(actions, options?.language);
2437
+ let direction = directionFor(options?.language);
2116
2438
  const isDark = style?.theme === "dark";
2117
2439
  const theme = {
2118
2440
  popupBg: isDark ? "#1e1e1e" : "#fff",
@@ -2139,6 +2461,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
2139
2461
  const popup = document.createElement("div");
2140
2462
  popup.id = "dd-popup";
2141
2463
  popup.className = "deepdots-popup";
2464
+ popup.setAttribute("dir", direction);
2142
2465
  popup.style.cssText = `
2143
2466
  position: relative;
2144
2467
  display: flex;
@@ -2168,7 +2491,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
2168
2491
  const titleEl = document.createElement("h2");
2169
2492
  titleEl.id = "dd-title";
2170
2493
  titleEl.className = "deepdots-popup-title";
2171
- titleEl.style.cssText = `margin:0; font-size:17px; font-weight:600; line-height:1.3; color:${theme.textPrimary}; text-transform:none; text-align:left; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;`;
2494
+ titleEl.style.cssText = `margin:0; font-size:17px; font-weight:600; line-height:1.3; color:${theme.textPrimary}; text-transform:none; text-align:start; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;`;
2172
2495
  titleEl.hidden = true;
2173
2496
  const setTitle = (value) => {
2174
2497
  if (!value) return;
@@ -2180,7 +2503,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
2180
2503
  const closeBtn = document.createElement("button");
2181
2504
  closeBtn.id = "dd-close";
2182
2505
  closeBtn.type = "button";
2183
- closeBtn.setAttribute("aria-label", "Close popup");
2506
+ closeBtn.setAttribute("aria-label", labels.closeAria);
2184
2507
  closeBtn.innerHTML = `
2185
2508
  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2186
2509
  <path d="M5 5L19 19M5 19L19 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="butt"/>
@@ -2239,7 +2562,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
2239
2562
  progressLabel.appendChild(progressTotal);
2240
2563
  const progressFollowUp = document.createElement("span");
2241
2564
  progressFollowUp.id = "dd-progress-followup";
2242
- progressFollowUp.textContent = "Follow-up";
2565
+ progressFollowUp.textContent = labels.followUp;
2243
2566
  progressFollowUp.style.cssText = "display:none; font-size:12px; font-weight:600; color:#fff; background:rgba(59,130,246,0.44); border-radius:999px; padding:2px 10px;";
2244
2567
  progressHead.appendChild(progressLabel);
2245
2568
  progressHead.appendChild(progressFollowUp);
@@ -2271,8 +2594,8 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
2271
2594
  progressCurrent.textContent = `${Math.round(pct)}%`;
2272
2595
  progressTotal.textContent = "";
2273
2596
  } else {
2274
- progressCurrent.textContent = `Question ${label}`;
2275
- progressTotal.textContent = ` of ${total}`;
2597
+ progressCurrent.textContent = `${labels.question} ${label}`;
2598
+ progressTotal.textContent = ` ${labels.of} ${total}`;
2276
2599
  }
2277
2600
  progressFollowUp.style.display = p.followup ? "inline-block" : "none";
2278
2601
  }
@@ -2309,7 +2632,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
2309
2632
  const spinnerEl = document.createElement("div");
2310
2633
  spinnerEl.className = "mf-spinner";
2311
2634
  spinnerEl.setAttribute("role", "status");
2312
- spinnerEl.setAttribute("aria-label", "Loading survey");
2635
+ spinnerEl.setAttribute("aria-label", labels.loadingAria);
2313
2636
  spinnerEl.innerHTML = '<div class="mf-spinner-circle"></div>';
2314
2637
  spinnerEl.style.cssText = "position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);";
2315
2638
  const formDivId = `magicfeedback-form-${surveyId}`;
@@ -2328,7 +2651,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
2328
2651
  const backButton = document.createElement("button");
2329
2652
  backButton.id = "dd-back";
2330
2653
  backButton.className = "dd-nav-btn";
2331
- backButton.textContent = actions?.back ? actions.back.label : "Back";
2654
+ backButton.textContent = actionLabels.back;
2332
2655
  backButton.style.cssText = `
2333
2656
  background: transparent;
2334
2657
  color: ${theme.textMuted};
@@ -2358,7 +2681,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
2358
2681
  const startButton = document.createElement("button");
2359
2682
  startButton.id = "dd-start";
2360
2683
  startButton.className = "dd-nav-btn";
2361
- startButton.textContent = actions?.start ? actions.start.label : "Start survey";
2684
+ startButton.textContent = actionLabels.start;
2362
2685
  startButton.style.cssText = `
2363
2686
  background: #1E293B;
2364
2687
  color: #fff;
@@ -2385,7 +2708,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
2385
2708
  const closeButton = document.createElement("button");
2386
2709
  closeButton.id = "dd-complete";
2387
2710
  closeButton.className = "dd-nav-btn";
2388
- closeButton.textContent = actions?.complete ? actions.complete.label : "Complete survey";
2711
+ closeButton.textContent = actionLabels.complete;
2389
2712
  closeButton.style.cssText = `
2390
2713
  background: #1E293B;
2391
2714
  color: #fff;
@@ -2416,7 +2739,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
2416
2739
  const submitButton = document.createElement("button");
2417
2740
  submitButton.id = "dd-submit";
2418
2741
  submitButton.className = "dd-nav-btn";
2419
- submitButton.textContent = actions?.accept ? actions.accept.label : "Send";
2742
+ submitButton.textContent = actionLabels.accept;
2420
2743
  submitButton.style.cssText = `
2421
2744
  background: #1E293B;
2422
2745
  color: #fff;
@@ -2440,6 +2763,19 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
2440
2763
  formInstance?.send?.();
2441
2764
  }
2442
2765
  };
2766
+ function applyLabels(lang) {
2767
+ labels = getLabels(lang);
2768
+ actionLabels = resolveActionLabels(actions, lang);
2769
+ backButton.textContent = actionLabels.back;
2770
+ startButton.textContent = actionLabels.start;
2771
+ closeButton.textContent = actionLabels.complete;
2772
+ submitButton.textContent = actionLabels.accept;
2773
+ closeBtn.setAttribute("aria-label", labels.closeAria);
2774
+ spinnerEl.setAttribute("aria-label", labels.loadingAria);
2775
+ progressFollowUp.textContent = labels.followUp;
2776
+ direction = directionFor(lang);
2777
+ popup.setAttribute("dir", direction);
2778
+ }
2443
2779
  backButton.style.display = "none";
2444
2780
  startButton.style.display = "none";
2445
2781
  submitButton.style.display = "none";
@@ -2560,6 +2896,8 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
2560
2896
  addSuccessScreen: false
2561
2897
  };
2562
2898
  generateOptions.onLoadedEvent = ({ formData }) => {
2899
+ const surveyLang = Array.isArray(formData?.lang) ? formData.lang.find((l) => typeof l === "string" && l.trim().length > 0) : void 0;
2900
+ if (surveyLang) applyLabels(surveyLang);
2563
2901
  try {
2564
2902
  } catch {
2565
2903
  }
@@ -2620,13 +2958,13 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
2620
2958
  if (errText) {
2621
2959
  setLoading(false);
2622
2960
  if (errText.toLowerCase().includes("no response")) {
2623
- errorHint.textContent = "Please answer the required question to continue.";
2961
+ errorHint.textContent = labels.errorRequired;
2624
2962
  errorHint.style.display = "block";
2625
2963
  emit("popup_clicked", surveyId, { action: "validation_error_required" });
2626
2964
  updateNavButtons();
2627
2965
  return;
2628
2966
  }
2629
- errorHint.textContent = "An error occurred while submitting. Please try again or close the popup.";
2967
+ errorHint.textContent = labels.errorSubmit;
2630
2968
  errorHint.style.display = "block";
2631
2969
  emit("popup_clicked", surveyId, { action: "submit_error", error: errText });
2632
2970
  return;
@@ -2669,6 +3007,7 @@ var init_renderPopup = __esm({
2669
3007
  init_font();
2670
3008
  init_logo();
2671
3009
  init_logger();
3010
+ init_labels();
2672
3011
  import_native = __toESM(require("@magicfeedback/native"));
2673
3012
  init_style();
2674
3013
  }
@@ -2880,6 +3219,7 @@ init_tracking_manager();
2880
3219
 
2881
3220
  // src/ui/surveyHtml.ts
2882
3221
  init_font();
3222
+ init_labels();
2883
3223
  init_style();
2884
3224
  var POSITION_MAP = {
2885
3225
  center: { justifyContent: "center", alignItems: "center", padding: "0", background: "rgba(0,0,0,0.5)" },
@@ -2893,9 +3233,13 @@ var POSITION_MAP = {
2893
3233
  function jsonForScript(value) {
2894
3234
  return JSON.stringify(value ?? null).replace(/</g, "\\u003C").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
2895
3235
  }
3236
+ function trimmedOrNull(value) {
3237
+ const trimmed = typeof value === "string" ? value.trim() : "";
3238
+ return trimmed ? value : null;
3239
+ }
2896
3240
  function buildSurveyHtml(opts) {
2897
3241
  const env = opts.env === "development" ? "dev" : "prod";
2898
- const version = opts.version ?? "2.2.8";
3242
+ const version = opts.version ?? "2.2.22";
2899
3243
  const cdn = `https://cdn.jsdelivr.net/npm/@magicfeedback/native@${version}/dist/magicfeedback-sdk.browser.js`;
2900
3244
  const sid = jsonForScript(opts.surveyId);
2901
3245
  const pid = jsonForScript(opts.productId);
@@ -2927,10 +3271,22 @@ function buildSurveyHtml(opts) {
2927
3271
  const titleJson = jsonForScript(opts.title ?? "");
2928
3272
  const customCss = (opts.surveyCss ?? "").replace(/<\/\s*style/gi, "<\\/style");
2929
3273
  const progressPref = opts.showProgressBar === void 0 ? "null" : String(opts.showProgressBar);
2930
- const backLabel = jsonForScript(opts.actions?.back?.label ?? "Back");
2931
- const startLabel = jsonForScript(opts.actions?.start?.label ?? "Start survey");
2932
- const completeLabel = jsonForScript(opts.actions?.complete?.label ?? "Complete survey");
2933
- const submitLabel = jsonForScript(opts.actions?.accept?.label ?? "Send");
3274
+ const initLabels = getLabels(opts.language);
3275
+ const initActionLabels = resolveActionLabels(opts.actions, opts.language);
3276
+ const backLabel = jsonForScript(initActionLabels.back);
3277
+ const startLabel = jsonForScript(initActionLabels.start);
3278
+ const completeLabel = jsonForScript(initActionLabels.complete);
3279
+ const submitLabel = jsonForScript(initActionLabels.accept);
3280
+ const labelsTableJson = jsonForScript(LABELS);
3281
+ const actionOverridesJson = jsonForScript({
3282
+ accept: trimmedOrNull(opts.actions?.accept?.label),
3283
+ start: trimmedOrNull(opts.actions?.start?.label),
3284
+ complete: trimmedOrNull(opts.actions?.complete?.label),
3285
+ back: trimmedOrNull(opts.actions?.back?.label)
3286
+ });
3287
+ const initLangJson = jsonForScript(opts.language ?? "");
3288
+ const initDirection = directionFor(opts.language);
3289
+ const rtlLocalesJson = jsonForScript(RTL_LOCALES);
2934
3290
  return `<!DOCTYPE html><html><head>
2935
3291
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
2936
3292
  <meta name="color-scheme" content="${colorScheme}"/>
@@ -2943,8 +3299,8 @@ body{display:${bodyDisplay};justify-content:${pos.justifyContent};align-items:${
2943
3299
  .deepdots-popup-header{display:flex;justify-content:space-between;align-items:center;gap:12px;width:100%;flex:0 0 auto}
2944
3300
  /* Neutraliza la regla \`.deepdots-popup h2\` del CSS del survey (uppercase, centrado y
2945
3301
  margin-bottom 40px), pensada para los enunciados y no para el t\xEDtulo de la cabecera. */
2946
- #dd-title{margin:0;font-family:var(--deepdots-font,'Montserrat',inherit);font-size:17px;font-weight:600;line-height:1.3;color:${textPrimary};text-transform:none;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
2947
- #dd-close{background:transparent;border:none;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:8px;cursor:pointer;color:${textPrimary};padding:4px;flex:0 0 auto;margin-left:auto}
3302
+ #dd-title{margin:0;font-family:var(--deepdots-font,'Montserrat',inherit);font-size:17px;font-weight:600;line-height:1.3;color:${textPrimary};text-transform:none;text-align:start;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
3303
+ #dd-close{background:transparent;border:none;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:8px;cursor:pointer;color:${textPrimary};padding:4px;flex:0 0 auto;margin-inline-start:auto}
2948
3304
  /* Progreso alineado con la cabecera: sin padding horizontal propio, lo marca la tarjeta.
2949
3305
  El vertical es el mismo arriba y abajo, y del mismo valor que el padding de la tarjeta. */
2950
3306
  .deepdots-progress{display:none;flex-direction:column;gap:8px;width:100%;flex:0 0 auto;padding:16px 0;box-sizing:border-box}
@@ -3001,10 +3357,10 @@ body{display:${bodyDisplay};justify-content:${pos.justifyContent};align-items:${
3001
3357
  ${customCss}
3002
3358
  </style>
3003
3359
  </head><body>
3004
- <div class="${popupClass}" id="dd-popup">
3360
+ <div class="${popupClass}" id="dd-popup" dir="${initDirection}">
3005
3361
  <div class="deepdots-popup-header">
3006
3362
  <h2 id="dd-title" class="deepdots-popup-title" hidden></h2>
3007
- <button type="button" id="dd-close" aria-label="Close popup">
3363
+ <button type="button" id="dd-close" aria-label="${initLabels.closeAria}">
3008
3364
  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
3009
3365
  <path d="M5 5L19 19M5 19L19 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="butt"/>
3010
3366
  </svg>
@@ -3013,23 +3369,23 @@ ${customCss}
3013
3369
  <div class="deepdots-progress" id="dd-progress">
3014
3370
  <div class="deepdots-progress-head">
3015
3371
  <span id="dd-progress-label"><span id="dd-progress-current"></span><span id="dd-progress-total"></span></span>
3016
- <span id="dd-progress-followup">Follow-up</span>
3372
+ <span id="dd-progress-followup">${initLabels.followUp}</span>
3017
3373
  </div>
3018
3374
  <div class="deepdots-progress-track"><div id="dd-progress-bar"></div></div>
3019
3375
  </div>
3020
3376
  <div class="deepdots-popup-container-content" id="dd-content">
3021
3377
  <div class="deepdots-popup-main" id="dd-main">
3022
3378
  <div id="dd-form-wrapper">
3023
- <div class="mf-spinner" id="dd-spinner" role="status" aria-label="Loading survey"><div class="mf-spinner-circle"></div></div>
3379
+ <div class="mf-spinner" id="dd-spinner" role="status" aria-label="${initLabels.loadingAria}"><div class="mf-spinner-circle"></div></div>
3024
3380
  <div id="mf"></div>
3025
3381
  </div>
3026
3382
  <div class="deepdots-error-hint" id="dd-error" role="alert" aria-live="polite"></div>
3027
3383
  </div>
3028
3384
  <div class="deepdots-popup-footer" id="dd-footer">
3029
- <button type="button" class="dd-nav-btn" id="dd-submit">Send</button>
3030
- <button type="button" class="dd-nav-btn" id="dd-back">Back</button>
3031
- <button type="button" class="dd-nav-btn" id="dd-complete">Complete survey</button>
3032
- <button type="button" class="dd-nav-btn" id="dd-start">Start survey</button>
3385
+ <button type="button" class="dd-nav-btn" id="dd-submit"></button>
3386
+ <button type="button" class="dd-nav-btn" id="dd-back"></button>
3387
+ <button type="button" class="dd-nav-btn" id="dd-complete"></button>
3388
+ <button type="button" class="dd-nav-btn" id="dd-start"></button>
3033
3389
  </div>
3034
3390
  </div>
3035
3391
  </div>
@@ -3058,6 +3414,32 @@ ${customCss}
3058
3414
  var progressTotal=document.getElementById('dd-progress-total');
3059
3415
  var progressFollowUp=document.getElementById('dd-progress-followup');
3060
3416
  var progressBar=document.getElementById('dd-progress-bar');
3417
+ var closeEl=document.getElementById('dd-close');
3418
+
3419
+ // --- i18n del chrome -------------------------------------------------------------------
3420
+ // La tabla completa viaja embebida porque el idioma del survey (\`formData.lang[0]\`) solo se
3421
+ // conoce ya dentro del WebView. \`ddResolveLocale\` es el espejo del \`resolveLocale\` de TS
3422
+ // (paridad cubierta por test).
3423
+ var DD_LABELS=${labelsTableJson};
3424
+ var DD_ACTION_OVERRIDES=${actionOverridesJson};
3425
+ ${RESOLVE_LOCALE_JS}
3426
+ var DD_LABELS_ACTIVE=DD_LABELS[ddResolveLocale(${initLangJson})];
3427
+ var DD_RTL=${rtlLocalesJson};
3428
+ function ddIsRtl(lang){ return DD_RTL.indexOf(ddResolveLocale(lang))!==-1; }
3429
+ function ddApplyLabels(lang){
3430
+ var next=DD_LABELS[ddResolveLocale(lang)];
3431
+ if(!next) return;
3432
+ DD_LABELS_ACTIVE=next;
3433
+ // El survey ya se ha volteado a s\xED mismo: el chrome tiene que acompa\xF1arlo.
3434
+ popup.setAttribute('dir', ddIsRtl(lang) ? 'rtl' : 'ltr');
3435
+ backBtn.textContent=DD_ACTION_OVERRIDES.back||next.back;
3436
+ startBtn.textContent=DD_ACTION_OVERRIDES.start||next.start;
3437
+ completeBtn.textContent=DD_ACTION_OVERRIDES.complete||next.complete;
3438
+ submitBtn.textContent=DD_ACTION_OVERRIDES.accept||next.accept;
3439
+ if(closeEl){ closeEl.setAttribute('aria-label',next.closeAria); }
3440
+ if(spinner){ spinner.setAttribute('aria-label',next.loadingAria); }
3441
+ progressFollowUp.textContent=next.followUp;
3442
+ }
3061
3443
 
3062
3444
  function setTitle(t){
3063
3445
  if(!t) return;
@@ -3098,8 +3480,8 @@ ${customCss}
3098
3480
  progressCurrent.textContent=Math.round(pct)+'%';
3099
3481
  progressTotal.textContent='';
3100
3482
  } else {
3101
- progressCurrent.textContent='Question '+label;
3102
- progressTotal.textContent=' of '+total;
3483
+ progressCurrent.textContent=DD_LABELS_ACTIVE.question+' '+label;
3484
+ progressTotal.textContent=' '+DD_LABELS_ACTIVE.of+' '+total;
3103
3485
  }
3104
3486
  progressFollowUp.style.display=p&&p.followup?'inline-block':'none';
3105
3487
  }
@@ -3109,7 +3491,7 @@ ${customCss}
3109
3491
  completeBtn.textContent=${completeLabel};
3110
3492
  submitBtn.textContent=${submitLabel};
3111
3493
 
3112
- document.getElementById('dd-close').onclick=function(){ emitJSON('popup_close'); };
3494
+ closeEl.onclick=function(){ emitJSON('popup_close'); };
3113
3495
  backBtn.onclick=function(){ if(window.DeepdotsForm && window.DeepdotsForm.back){ window.DeepdotsForm.back(); } };
3114
3496
  startBtn.onclick=function(){
3115
3497
  if(window.DeepdotsForm && window.DeepdotsForm.startForm){
@@ -3204,6 +3586,14 @@ ${customCss}
3204
3586
  // fallback es un literal gen\xE9rico que ignora style.successMessage.
3205
3587
  addSuccessScreen:false,
3206
3588
  onLoadedEvent:function(args){
3589
+ // Idioma del survey: manda sobre el del init para que el chrome no se quede en
3590
+ // ingl\xE9s delante de un survey traducido.
3591
+ var langs=args && args.formData ? args.formData.lang : null;
3592
+ if(langs && langs.length){
3593
+ for(var li=0;li<langs.length;li++){
3594
+ if(typeof langs[li]==='string' && langs[li].trim()){ ddApplyLabels(langs[li]); break; }
3595
+ }
3596
+ }
3207
3597
  var s=args && args.formData ? args.formData.style : null;
3208
3598
  if(s && !stylesInjected){
3209
3599
  stylesInjected=true;
@@ -3230,8 +3620,10 @@ ${customCss}
3230
3620
  if(s.logoSize==='small'){ logoImg.style.maxHeight='30px'; }
3231
3621
  else if(s.logoSize==='medium'){ logoImg.style.maxHeight='50px'; }
3232
3622
  else if(s.logoSize==='large'){ logoImg.style.maxHeight='70px'; }
3233
- if(s.logoPosition==='left'){ logoImg.style.margin='12px 16px 0 0'; logoImg.style.marginLeft='0'; }
3234
- else if(s.logoPosition==='right'){ logoImg.style.margin='12px 0 0 16px'; logoImg.style.marginLeft='auto'; }
3623
+ // M\xE1rgenes l\xF3gicos: con un survey RTL el popup va con dir="rtl" y el logo
3624
+ // tiene que acompa\xF1ar al chrome. En LTR el resultado es el mismo de antes.
3625
+ if(s.logoPosition==='left'){ logoImg.style.margin='12px 0 0 0'; logoImg.style.setProperty('margin-inline-start','0'); logoImg.style.setProperty('margin-inline-end','16px'); }
3626
+ else if(s.logoPosition==='right'){ logoImg.style.margin='12px 0 0 0'; logoImg.style.setProperty('margin-inline-start','auto'); logoImg.style.setProperty('margin-inline-end','0'); }
3235
3627
  else if(s.logoPosition==='center'){ logoImg.style.margin='12px auto 0 auto'; }
3236
3628
  // Encima de la barra de progreso, como hermano del header (espejo de ui/logo.ts en
3237
3629
  // web). Fuera de #dd-main, as\xED el logo no se va con el scroll del formulario.
@@ -3257,12 +3649,12 @@ ${customCss}
3257
3649
  setLoading(false);
3258
3650
  if(error.toLowerCase().indexOf('no response')!==-1){
3259
3651
  // La p\xE1gina no ha cambiado: el estado de navegaci\xF3n se queda como estaba.
3260
- errorHint.textContent='Please answer the required question to continue.';
3652
+ errorHint.textContent=DD_LABELS_ACTIVE.errorRequired;
3261
3653
  errorHint.style.display='block';
3262
3654
  updateNavButtons();
3263
3655
  return;
3264
3656
  }
3265
- errorHint.textContent='An error occurred while submitting. Please try again or close the popup.';
3657
+ errorHint.textContent=DD_LABELS_ACTIVE.errorSubmit;
3266
3658
  errorHint.style.display='block';
3267
3659
  return;
3268
3660
  }
@@ -3335,7 +3727,8 @@ var ReactNativePopupRenderer = class {
3335
3727
  chrome: renderChrome,
3336
3728
  title: options?.title,
3337
3729
  showProgressBar: options?.showProgressBar,
3338
- surveyCss: options?.surveyCss
3730
+ surveyCss: options?.surveyCss,
3731
+ language: options?.language
3339
3732
  });
3340
3733
  if (this.options.onShow) {
3341
3734
  this.options.onShow({ surveyId, productId, html });
@@ -4187,6 +4580,75 @@ var CrashReporter = class {
4187
4580
  }
4188
4581
  };
4189
4582
 
4583
+ // src/core/popup-state-store.ts
4584
+ var POPUP_STATE_STORAGE_KEY = "deepdots.popups.state";
4585
+ var MAX_TRACKED_POPUP_ENTRIES = 200;
4586
+ function emptyState() {
4587
+ return { lastShown: {}, progress: {} };
4588
+ }
4589
+ function isValidTimestamp(value) {
4590
+ return typeof value === "number" && Number.isFinite(value);
4591
+ }
4592
+ function capEntries(entries, timestampOf) {
4593
+ if (entries.length <= MAX_TRACKED_POPUP_ENTRIES) return entries;
4594
+ return [...entries].sort((a, b) => timestampOf(b[1]) - timestampOf(a[1])).slice(0, MAX_TRACKED_POPUP_ENTRIES);
4595
+ }
4596
+ function readPopupState(storage) {
4597
+ let raw = null;
4598
+ try {
4599
+ raw = storage.getItem(POPUP_STATE_STORAGE_KEY);
4600
+ } catch {
4601
+ return emptyState();
4602
+ }
4603
+ if (!raw) return emptyState();
4604
+ let parsed;
4605
+ try {
4606
+ parsed = JSON.parse(raw);
4607
+ } catch {
4608
+ return emptyState();
4609
+ }
4610
+ if (typeof parsed !== "object" || parsed === null) return emptyState();
4611
+ const source = parsed;
4612
+ const state = emptyState();
4613
+ if (typeof source.lastShown === "object" && source.lastShown !== null) {
4614
+ const valid = Object.entries(source.lastShown).filter((entry) => isValidTimestamp(entry[1]));
4615
+ capEntries(valid, (timestamp) => timestamp).forEach(([popupId, timestamp]) => {
4616
+ state.lastShown[popupId] = timestamp;
4617
+ });
4618
+ }
4619
+ if (typeof source.progress === "object" && source.progress !== null) {
4620
+ const valid = Object.entries(source.progress).filter((entry) => {
4621
+ const value = entry[1];
4622
+ if (typeof value !== "object" || value === null) return false;
4623
+ const candidate = value;
4624
+ return POPUP_TRIGGER_CONDITION_STATUSES.includes(candidate.status) && isValidTimestamp(candidate.timestamp);
4625
+ });
4626
+ capEntries(valid, (entry) => entry.timestamp).forEach(([surveyId, entry]) => {
4627
+ state.progress[surveyId] = { status: entry.status, timestamp: entry.timestamp };
4628
+ });
4629
+ }
4630
+ return state;
4631
+ }
4632
+ function writePopupState(storage, state) {
4633
+ const capped = emptyState();
4634
+ capEntries(Object.entries(state.lastShown), (timestamp) => timestamp).forEach(([popupId, timestamp]) => {
4635
+ capped.lastShown[popupId] = timestamp;
4636
+ });
4637
+ capEntries(Object.entries(state.progress), (entry) => entry.timestamp).forEach(([surveyId, entry]) => {
4638
+ capped.progress[surveyId] = entry;
4639
+ });
4640
+ try {
4641
+ storage.setItem(POPUP_STATE_STORAGE_KEY, JSON.stringify(capped));
4642
+ } catch {
4643
+ }
4644
+ }
4645
+ function clearPopupState(storage) {
4646
+ try {
4647
+ storage.removeItem(POPUP_STATE_STORAGE_KEY);
4648
+ } catch {
4649
+ }
4650
+ }
4651
+
4190
4652
  // src/core/deepdots-popups.ts
4191
4653
  init_logger();
4192
4654
 
@@ -4277,6 +4739,10 @@ var DeepdotsPopups = class {
4277
4739
  this.popupDefinitions = [];
4278
4740
  this.popupsLoaded = false;
4279
4741
  this.pendingAutoLaunch = false;
4742
+ // Estado que alimenta las reglas de redisplay. Copia de trabajo en memoria; la fuente
4743
+ // duradera es el storage (`popup-state-store`), rehidratada en `init()` — sin eso, una
4744
+ // recarga reiniciaba el cooldown y el popup volvía a salir. `answeredSurveys` se deriva
4745
+ // de las entradas COMPLETED de `surveyProgress` (los dos los escribe `markSurveyAnswered`).
4280
4746
  this.answeredSurveys = /* @__PURE__ */ new Set();
4281
4747
  this.surveyProgress = /* @__PURE__ */ new Map();
4282
4748
  this.lastShown = /* @__PURE__ */ new Map();
@@ -4352,6 +4818,7 @@ var DeepdotsPopups = class {
4352
4818
  };
4353
4819
  const storage = config.storage ?? createDefaultStorage();
4354
4820
  this.storage = storage;
4821
+ this.loadPopupState(storage);
4355
4822
  this.tracking = new TrackingManager({
4356
4823
  storage,
4357
4824
  clientUserId: this.config.userId,
@@ -4619,6 +5086,10 @@ var DeepdotsPopups = class {
4619
5086
  }) : null;
4620
5087
  this.analytics?.resetUserScope();
4621
5088
  this.messageGuard.reset();
5089
+ this.lastShown.clear();
5090
+ this.surveyProgress.clear();
5091
+ this.answeredSurveys.clear();
5092
+ clearPopupState(storage);
4622
5093
  this.log("tracking \xB7 user_change \xB7 user_id:", this.tracking.getUserId());
4623
5094
  this.openSession();
4624
5095
  }
@@ -4790,6 +5261,7 @@ var DeepdotsPopups = class {
4790
5261
  this.log("Showing popup (definition)", def);
4791
5262
  this.surveyToPopupId.set(def.surveyId, def.id);
4792
5263
  this.lastShown.set(def.id, Date.now());
5264
+ this.persistPopupState();
4793
5265
  this.renderPopup(def.surveyId, def.productId, def.actions, def.style, def.title);
4794
5266
  this.emitEvent("popup_shown", def.surveyId, { popupId: def.id });
4795
5267
  }
@@ -5148,7 +5620,7 @@ var DeepdotsPopups = class {
5148
5620
  miniService,
5149
5621
  analyticsFeedbackSessionId,
5150
5622
  this.renderChrome,
5151
- { title, showProgressBar: this.showProgressBar, surveyCss: this.surveyCss }
5623
+ { title, showProgressBar: this.showProgressBar, surveyCss: this.surveyCss, language: this.language }
5152
5624
  );
5153
5625
  return;
5154
5626
  }
@@ -5168,7 +5640,7 @@ var DeepdotsPopups = class {
5168
5640
  sessionId,
5169
5641
  miniService,
5170
5642
  analyticsFeedbackSessionId,
5171
- { title, showProgressBar: this.showProgressBar, surveyCss: this.surveyCss }
5643
+ { title, showProgressBar: this.showProgressBar, surveyCss: this.surveyCss, language: this.language }
5172
5644
  );
5173
5645
  });
5174
5646
  }
@@ -5269,6 +5741,29 @@ var DeepdotsPopups = class {
5269
5741
  return;
5270
5742
  }
5271
5743
  this.surveyProgress.set(surveyId, { status, timestamp: Date.now() });
5744
+ this.persistPopupState();
5745
+ }
5746
+ /**
5747
+ * Rehidrata el estado de redisplay desde el storage. `answeredSurveys` se reconstruye
5748
+ * desde las entradas COMPLETED: es exactamente lo que persiste `markSurveyAnswered`.
5749
+ */
5750
+ loadPopupState(storage) {
5751
+ const state = readPopupState(storage);
5752
+ Object.entries(state.lastShown).forEach(([popupId, timestamp]) => {
5753
+ this.lastShown.set(popupId, timestamp);
5754
+ });
5755
+ Object.entries(state.progress).forEach(([surveyId, entry]) => {
5756
+ this.surveyProgress.set(surveyId, { status: entry.status, timestamp: entry.timestamp });
5757
+ if (entry.status === "COMPLETED") this.answeredSurveys.add(surveyId);
5758
+ });
5759
+ }
5760
+ /** Vuelca el estado de redisplay al storage. Sin storage (o bloqueado) degrada a memoria. */
5761
+ persistPopupState() {
5762
+ if (!this.storage) return;
5763
+ writePopupState(this.storage, {
5764
+ lastShown: Object.fromEntries(this.lastShown),
5765
+ progress: Object.fromEntries(this.surveyProgress)
5766
+ });
5272
5767
  }
5273
5768
  isPopupDefinition(value) {
5274
5769
  if (typeof value !== "object" || value === null) return false;