@schukai/monster 4.150.0 → 4.150.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.
- package/package.json +1 -1
- package/source/components/accessibility/locale-picker.mjs +22 -14
- package/source/components/accessibility/locale-select.mjs +2 -0
- package/source/components/content/stylesheet/camera-capture.mjs +1 -1
- package/source/components/content/stylesheet/copy.mjs +1 -1
- package/source/components/content/viewer/message.mjs +22 -0
- package/source/components/content/viewer/stylesheet/message.mjs +1 -1
- package/source/components/content/viewer.mjs +6 -6
- package/source/components/datatable/datatable.mjs +139 -7
- package/source/components/datatable/filter/date-range.mjs +168 -3
- package/source/components/datatable/filter/input.mjs +47 -3
- package/source/components/datatable/filter/range.mjs +91 -3
- package/source/components/datatable/filter-button.mjs +47 -3
- package/source/components/datatable/filter.mjs +11 -0
- package/source/components/datatable/save-button.mjs +47 -3
- package/source/components/datatable/stylesheet/filter-controls-defaults.mjs +1 -1
- package/source/components/files/file-manager.mjs +96 -13
- package/source/components/form/buy-box.mjs +87 -87
- package/source/components/form/cart-control.mjs +24 -24
- package/source/components/form/credential-button.mjs +13 -0
- package/source/components/form/field-set.mjs +80 -3
- package/source/components/form/login.mjs +143 -96
- package/source/components/form/register-wizard.mjs +298 -298
- package/source/components/form/select.mjs +103 -79
- package/source/components/form/stylesheet/button-bar.mjs +1 -1
- package/source/components/form/stylesheet/confirm-button.mjs +1 -1
- package/source/components/form/stylesheet/context-error.mjs +1 -1
- package/source/components/form/stylesheet/context-help.mjs +1 -1
- package/source/components/form/stylesheet/control-bar.mjs +1 -1
- package/source/components/form/stylesheet/digits.mjs +1 -1
- package/source/components/form/stylesheet/field-set.mjs +1 -1
- package/source/components/form/stylesheet/login.mjs +1 -1
- package/source/components/form/stylesheet/popper-button.mjs +1 -1
- package/source/components/form/stylesheet/select.mjs +1 -1
- package/source/components/form/variant-select.mjs +17 -17
- package/source/components/form/wizard.mjs +20 -20
- package/source/components/layout/details.mjs +47 -3
- package/source/components/layout/stylesheet/popper.mjs +1 -1
- package/source/components/layout/tabs.mjs +65 -10
- package/source/components/layout/vertical-tabs.mjs +65 -10
- package/source/components/style/floating-ui.css +13 -0
- package/source/components/style/floating-ui.pcss +4 -0
- package/source/components/stylesheet/floating-ui.mjs +1 -1
- package/source/components/time/month-calendar.mjs +75 -2
- package/source/i18n/internal.mjs +60 -18
- package/source/i18n/map/languages.mjs +2 -1
- package/source/i18n/time-ago.mjs +129 -0
- package/test/cases/components/datatable/datasource-rest-lifecycle.mjs +9 -1
- package/test/cases/components/form/locale-defaults.mjs +58 -0
- package/test/cases/components/form/popper-button.mjs +12 -0
- package/test/cases/components/form/register-wizard.mjs +15 -0
- package/test/cases/i18n/built-in-locales.mjs +151 -0
- package/test/cases/i18n/time-ago.mjs +52 -32
package/source/i18n/internal.mjs
CHANGED
|
@@ -41,72 +41,114 @@ function getInternalTranslations() {
|
|
|
41
41
|
case "de":
|
|
42
42
|
messages = {
|
|
43
43
|
"the-response-does-not-contain-an-accepted-status": `Der Server hat die Anfrage nicht akzeptiert (Status \${status}).`,
|
|
44
|
-
"the-response-does-not-contain-a-valid-json": `Die Antwort des Servers ist kein gültiges JSON (
|
|
44
|
+
"the-response-does-not-contain-a-valid-json": `Die Antwort des Servers ist kein gültiges JSON (tatsächlich=\${actual}).`,
|
|
45
45
|
};
|
|
46
46
|
break;
|
|
47
47
|
case "es":
|
|
48
48
|
messages = {
|
|
49
|
-
"the-response-does-not-contain-an-accepted-status": `El servidor no ha aceptado la solicitud (
|
|
50
|
-
"the-response-does-not-contain-a-valid-json": `La respuesta del servidor no es un JSON válido (
|
|
49
|
+
"the-response-does-not-contain-an-accepted-status": `El servidor no ha aceptado la solicitud (estado \${status}).`,
|
|
50
|
+
"the-response-does-not-contain-a-valid-json": `La respuesta del servidor no es un JSON válido (real=\${actual}).`,
|
|
51
51
|
};
|
|
52
52
|
break;
|
|
53
53
|
case "zh":
|
|
54
54
|
messages = {
|
|
55
55
|
"the-response-does-not-contain-an-accepted-status": `服务器未接受请求(状态 \${status})。`,
|
|
56
|
-
"the-response-does-not-contain-a-valid-json": `服务器响应不是有效的JSON(实际=\${actual})。`,
|
|
56
|
+
"the-response-does-not-contain-a-valid-json": `服务器响应不是有效的 JSON(实际=\${actual})。`,
|
|
57
|
+
};
|
|
58
|
+
break;
|
|
59
|
+
case "hi":
|
|
60
|
+
messages = {
|
|
61
|
+
"the-response-does-not-contain-an-accepted-status": `सर्वर ने अनुरोध स्वीकार नहीं किया (स्थिति \${status})।`,
|
|
62
|
+
"the-response-does-not-contain-a-valid-json": `सर्वर की प्रतिक्रिया मान्य JSON नहीं है (वास्तविक=\${actual})।`,
|
|
63
|
+
};
|
|
64
|
+
break;
|
|
65
|
+
case "bn":
|
|
66
|
+
messages = {
|
|
67
|
+
"the-response-does-not-contain-an-accepted-status": `সার্ভার অনুরোধটি গ্রহণ করেনি (স্থিতি \${status})।`,
|
|
68
|
+
"the-response-does-not-contain-a-valid-json": `সার্ভারের প্রতিক্রিয়াটি বৈধ JSON নয় (প্রকৃত=\${actual})।`,
|
|
69
|
+
};
|
|
70
|
+
break;
|
|
71
|
+
case "pt":
|
|
72
|
+
messages = {
|
|
73
|
+
"the-response-does-not-contain-an-accepted-status": `O servidor não aceitou a solicitação (status \${status}).`,
|
|
74
|
+
"the-response-does-not-contain-a-valid-json": `A resposta do servidor não é um JSON válido (real=\${actual}).`,
|
|
75
|
+
};
|
|
76
|
+
break;
|
|
77
|
+
case "ru":
|
|
78
|
+
messages = {
|
|
79
|
+
"the-response-does-not-contain-an-accepted-status": `Сервер не принял запрос (статус \${status}).`,
|
|
80
|
+
"the-response-does-not-contain-a-valid-json": `Ответ сервера не является допустимым JSON (фактическое значение=\${actual}).`,
|
|
81
|
+
};
|
|
82
|
+
break;
|
|
83
|
+
case "ja":
|
|
84
|
+
messages = {
|
|
85
|
+
"the-response-does-not-contain-an-accepted-status": `サーバーがリクエストを受け付けませんでした(ステータス \${status})。`,
|
|
86
|
+
"the-response-does-not-contain-a-valid-json": `サーバーの応答は有効な JSON ではありません(実際=\${actual})。`,
|
|
87
|
+
};
|
|
88
|
+
break;
|
|
89
|
+
case "pa":
|
|
90
|
+
messages = {
|
|
91
|
+
"the-response-does-not-contain-an-accepted-status": `ਸਰਵਰ ਨੇ ਬੇਨਤੀ ਸਵੀਕਾਰ ਨਹੀਂ ਕੀਤੀ (ਸਥਿਤੀ \${status})।`,
|
|
92
|
+
"the-response-does-not-contain-a-valid-json": `ਸਰਵਰ ਦਾ ਜਵਾਬ ਵੈਧ JSON ਨਹੀਂ ਹੈ (ਅਸਲ=\${actual})।`,
|
|
93
|
+
};
|
|
94
|
+
break;
|
|
95
|
+
case "mr":
|
|
96
|
+
messages = {
|
|
97
|
+
"the-response-does-not-contain-an-accepted-status": `सर्व्हरने विनंती स्वीकारली नाही (स्थिती \${status}).`,
|
|
98
|
+
"the-response-does-not-contain-a-valid-json": `सर्व्हरचा प्रतिसाद वैध JSON नाही (प्रत्यक्ष=\${actual}).`,
|
|
57
99
|
};
|
|
58
100
|
break;
|
|
59
101
|
case "fr":
|
|
60
102
|
messages = {
|
|
61
|
-
"the-response-does-not-contain-an-accepted-status": `Le serveur n'a pas accepté la demande (
|
|
62
|
-
"the-response-does-not-contain-a-valid-json": `La réponse du serveur n'est pas un JSON valide (
|
|
103
|
+
"the-response-does-not-contain-an-accepted-status": `Le serveur n'a pas accepté la demande (statut \${status}).`,
|
|
104
|
+
"the-response-does-not-contain-a-valid-json": `La réponse du serveur n'est pas un JSON valide (réel=\${actual}).`,
|
|
63
105
|
};
|
|
64
106
|
break;
|
|
65
107
|
case "it":
|
|
66
108
|
messages = {
|
|
67
|
-
"the-response-does-not-contain-an-accepted-status": `Il server non ha accettato la richiesta (
|
|
68
|
-
"the-response-does-not-contain-a-valid-json": `La risposta del server non è un JSON valido (
|
|
109
|
+
"the-response-does-not-contain-an-accepted-status": `Il server non ha accettato la richiesta (stato \${status}).`,
|
|
110
|
+
"the-response-does-not-contain-a-valid-json": `La risposta del server non è un JSON valido (effettivo=\${actual}).`,
|
|
69
111
|
};
|
|
70
112
|
break;
|
|
71
113
|
case "nl":
|
|
72
114
|
messages = {
|
|
73
|
-
"the-response-does-not-contain-an-accepted-status": `De server heeft het verzoek niet geaccepteerd (
|
|
74
|
-
"the-response-does-not-contain-a-valid-json": `De serverrespons is geen geldige JSON (
|
|
115
|
+
"the-response-does-not-contain-an-accepted-status": `De server heeft het verzoek niet geaccepteerd (status \${status}).`,
|
|
116
|
+
"the-response-does-not-contain-a-valid-json": `De serverrespons is geen geldige JSON (werkelijk=\${actual}).`,
|
|
75
117
|
};
|
|
76
118
|
break;
|
|
77
119
|
case "sv":
|
|
78
120
|
messages = {
|
|
79
|
-
"the-response-does-not-contain-an-accepted-status": `Servern accepterade inte begäran (
|
|
121
|
+
"the-response-does-not-contain-an-accepted-status": `Servern accepterade inte begäran (status \${status}).`,
|
|
80
122
|
"the-response-does-not-contain-a-valid-json": `Serverns svar är inte en giltig JSON (faktisk=\${actual}).`,
|
|
81
123
|
};
|
|
82
124
|
break;
|
|
83
125
|
case "pl":
|
|
84
126
|
messages = {
|
|
85
|
-
"the-response-does-not-contain-an-accepted-status": `Serwer nie zaakceptował żądania (
|
|
86
|
-
"the-response-does-not-contain-a-valid-json": `Odpowiedź serwera nie jest prawidłowym JSON-em (
|
|
127
|
+
"the-response-does-not-contain-an-accepted-status": `Serwer nie zaakceptował żądania (status \${status}).`,
|
|
128
|
+
"the-response-does-not-contain-a-valid-json": `Odpowiedź serwera nie jest prawidłowym JSON-em (rzeczywiste=\${actual}).`,
|
|
87
129
|
};
|
|
88
130
|
break;
|
|
89
131
|
case "da":
|
|
90
132
|
messages = {
|
|
91
|
-
"the-response-does-not-contain-an-accepted-status": `Serveren accepterede ikke forespørgslen (
|
|
92
|
-
"the-response-does-not-contain-a-valid-json": `Serverens svar er ikke
|
|
133
|
+
"the-response-does-not-contain-an-accepted-status": `Serveren accepterede ikke forespørgslen (status \${status}).`,
|
|
134
|
+
"the-response-does-not-contain-a-valid-json": `Serverens svar er ikke gyldig JSON (faktisk=\${actual}).`,
|
|
93
135
|
};
|
|
94
136
|
break;
|
|
95
137
|
case "fi":
|
|
96
138
|
messages = {
|
|
97
|
-
"the-response-does-not-contain-an-accepted-status": `Palvelin ei hyväksynyt pyyntöä (
|
|
139
|
+
"the-response-does-not-contain-an-accepted-status": `Palvelin ei hyväksynyt pyyntöä (tila \${status}).`,
|
|
98
140
|
"the-response-does-not-contain-a-valid-json": `Palvelimen vastaus ei ole kelvollinen JSON (todellinen=\${actual}).`,
|
|
99
141
|
};
|
|
100
142
|
break;
|
|
101
143
|
case "no":
|
|
102
144
|
messages = {
|
|
103
|
-
"the-response-does-not-contain-an-accepted-status": `Serveren aksepterte ikke forespørselen (
|
|
145
|
+
"the-response-does-not-contain-an-accepted-status": `Serveren aksepterte ikke forespørselen (status \${status}).`,
|
|
104
146
|
"the-response-does-not-contain-a-valid-json": `Serverens respons er ikke en gyldig JSON (faktisk=\${actual}).`,
|
|
105
147
|
};
|
|
106
148
|
break;
|
|
107
149
|
case "cs":
|
|
108
150
|
messages = {
|
|
109
|
-
"the-response-does-not-contain-an-accepted-status": `Server nepřijal požadavek (
|
|
151
|
+
"the-response-does-not-contain-an-accepted-status": `Server nepřijal požadavek (stav \${status}).`,
|
|
110
152
|
"the-response-does-not-contain-a-valid-json": `Odpověď serveru není platný JSON (skutečný=\${actual}).`,
|
|
111
153
|
};
|
|
112
154
|
break;
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* Languages currently supported by Monster.
|
|
17
17
|
* @type {string[]}
|
|
18
18
|
*/
|
|
19
19
|
export const currentSupportedLanguages = [
|
|
@@ -74,6 +74,7 @@ export const languages = {
|
|
|
74
74
|
hi: "हिन्दी",
|
|
75
75
|
bn: "বাংলা",
|
|
76
76
|
pa: "ਪੰਜਾਬੀ",
|
|
77
|
+
mr: "मराठी",
|
|
77
78
|
id: "Bahasa Indonesia",
|
|
78
79
|
vi: "Tiếng Việt",
|
|
79
80
|
tr: "Türkçe",
|
package/source/i18n/time-ago.mjs
CHANGED
|
@@ -439,6 +439,135 @@ const translations = {
|
|
|
439
439
|
},
|
|
440
440
|
},
|
|
441
441
|
|
|
442
|
+
bn: {
|
|
443
|
+
template: {
|
|
444
|
+
on: (value) => `${value}-এ`,
|
|
445
|
+
past: (value) => `${value} আগে`,
|
|
446
|
+
future: (value) => `${value} পরে`,
|
|
447
|
+
justNow: () => `এইমাত্র`,
|
|
448
|
+
},
|
|
449
|
+
units: {
|
|
450
|
+
day: {
|
|
451
|
+
zero: "দিন",
|
|
452
|
+
one: "দিন",
|
|
453
|
+
two: "দিন",
|
|
454
|
+
few: "দিন",
|
|
455
|
+
many: "দিন",
|
|
456
|
+
other: "দিন",
|
|
457
|
+
},
|
|
458
|
+
hour: {
|
|
459
|
+
zero: "ঘণ্টা",
|
|
460
|
+
one: "ঘণ্টা",
|
|
461
|
+
two: "ঘণ্টা",
|
|
462
|
+
few: "ঘণ্টা",
|
|
463
|
+
many: "ঘণ্টা",
|
|
464
|
+
other: "ঘণ্টা",
|
|
465
|
+
},
|
|
466
|
+
minute: {
|
|
467
|
+
zero: "মিনিট",
|
|
468
|
+
one: "মিনিট",
|
|
469
|
+
two: "মিনিট",
|
|
470
|
+
few: "মিনিট",
|
|
471
|
+
many: "মিনিট",
|
|
472
|
+
other: "মিনিট",
|
|
473
|
+
},
|
|
474
|
+
second: {
|
|
475
|
+
zero: "সেকেন্ড",
|
|
476
|
+
one: "সেকেন্ড",
|
|
477
|
+
two: "সেকেন্ড",
|
|
478
|
+
few: "সেকেন্ড",
|
|
479
|
+
many: "সেকেন্ড",
|
|
480
|
+
other: "সেকেন্ড",
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
|
|
485
|
+
pa: {
|
|
486
|
+
template: {
|
|
487
|
+
on: (value) => `${value} ਨੂੰ`,
|
|
488
|
+
past: (value) => `${value} ਪਹਿਲਾਂ`,
|
|
489
|
+
future: (value) => `${value} ਬਾਅਦ`,
|
|
490
|
+
justNow: () => `ਹੁਣੇ`,
|
|
491
|
+
},
|
|
492
|
+
units: {
|
|
493
|
+
day: {
|
|
494
|
+
zero: "ਦਿਨ",
|
|
495
|
+
one: "ਦਿਨ",
|
|
496
|
+
two: "ਦਿਨ",
|
|
497
|
+
few: "ਦਿਨ",
|
|
498
|
+
many: "ਦਿਨ",
|
|
499
|
+
other: "ਦਿਨ",
|
|
500
|
+
},
|
|
501
|
+
hour: {
|
|
502
|
+
zero: "ਘੰਟੇ",
|
|
503
|
+
one: "ਘੰਟਾ",
|
|
504
|
+
two: "ਘੰਟੇ",
|
|
505
|
+
few: "ਘੰਟੇ",
|
|
506
|
+
many: "ਘੰਟੇ",
|
|
507
|
+
other: "ਘੰਟੇ",
|
|
508
|
+
},
|
|
509
|
+
minute: {
|
|
510
|
+
zero: "ਮਿੰਟ",
|
|
511
|
+
one: "ਮਿੰਟ",
|
|
512
|
+
two: "ਮਿੰਟ",
|
|
513
|
+
few: "ਮਿੰਟ",
|
|
514
|
+
many: "ਮਿੰਟ",
|
|
515
|
+
other: "ਮਿੰਟ",
|
|
516
|
+
},
|
|
517
|
+
second: {
|
|
518
|
+
zero: "ਸਕਿੰਟ",
|
|
519
|
+
one: "ਸਕਿੰਟ",
|
|
520
|
+
two: "ਸਕਿੰਟ",
|
|
521
|
+
few: "ਸਕਿੰਟ",
|
|
522
|
+
many: "ਸਕਿੰਟ",
|
|
523
|
+
other: "ਸਕਿੰਟ",
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
},
|
|
527
|
+
|
|
528
|
+
mr: {
|
|
529
|
+
template: {
|
|
530
|
+
on: (value) => `${value} रोजी`,
|
|
531
|
+
past: (value) => `${value} पूर्वी`,
|
|
532
|
+
future: (value) => `${value} नंतर`,
|
|
533
|
+
justNow: () => `आत्ताच`,
|
|
534
|
+
},
|
|
535
|
+
units: {
|
|
536
|
+
day: {
|
|
537
|
+
zero: "दिवस",
|
|
538
|
+
one: "दिवस",
|
|
539
|
+
two: "दिवस",
|
|
540
|
+
few: "दिवस",
|
|
541
|
+
many: "दिवस",
|
|
542
|
+
other: "दिवस",
|
|
543
|
+
},
|
|
544
|
+
hour: {
|
|
545
|
+
zero: "तास",
|
|
546
|
+
one: "तास",
|
|
547
|
+
two: "तास",
|
|
548
|
+
few: "तास",
|
|
549
|
+
many: "तास",
|
|
550
|
+
other: "तास",
|
|
551
|
+
},
|
|
552
|
+
minute: {
|
|
553
|
+
zero: "मिनिटे",
|
|
554
|
+
one: "मिनिट",
|
|
555
|
+
two: "मिनिटे",
|
|
556
|
+
few: "मिनिटे",
|
|
557
|
+
many: "मिनिटे",
|
|
558
|
+
other: "मिनिटे",
|
|
559
|
+
},
|
|
560
|
+
second: {
|
|
561
|
+
zero: "सेकंद",
|
|
562
|
+
one: "सेकंद",
|
|
563
|
+
two: "सेकंद",
|
|
564
|
+
few: "सेकंद",
|
|
565
|
+
many: "सेकंद",
|
|
566
|
+
other: "सेकंद",
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
|
|
442
571
|
pt: {
|
|
443
572
|
template: {
|
|
444
573
|
on: (value) => `em ${value}`,
|
|
@@ -335,7 +335,7 @@ describe("REST datasource lifecycle", function () {
|
|
|
335
335
|
expectNoPendingChanges(second);
|
|
336
336
|
});
|
|
337
337
|
|
|
338
|
-
it("preserves
|
|
338
|
+
it("preserves the exact read baseline when the save write finishes later", async function () {
|
|
339
339
|
const datasource = createDatasource("read-first-datasource");
|
|
340
340
|
const loaded = { data: [{ id: "area-1", name: "Inbound" }] };
|
|
341
341
|
const written = { data: [{ id: "area-1", name: "Written change" }] };
|
|
@@ -373,6 +373,14 @@ describe("REST datasource lifecycle", function () {
|
|
|
373
373
|
writeResult.resolve({ ok: true });
|
|
374
374
|
await waitForUpdates();
|
|
375
375
|
expectPendingChanges(button);
|
|
376
|
+
|
|
377
|
+
datasource.data = refreshed;
|
|
378
|
+
await waitForUpdates();
|
|
379
|
+
expectNoPendingChanges(button);
|
|
380
|
+
|
|
381
|
+
datasource.data = written;
|
|
382
|
+
await waitForUpdates();
|
|
383
|
+
expectPendingChanges(button);
|
|
376
384
|
});
|
|
377
385
|
|
|
378
386
|
it("establishes the read baseline when the save write finishes first", async function () {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
import { getGlobal } from "../../../../source/types/global.mjs";
|
|
3
|
+
import { initJSDOM } from "../../../util/jsdom.mjs";
|
|
4
|
+
import { ResizeObserverMock } from "../../../util/resize-observer.mjs";
|
|
5
|
+
|
|
6
|
+
describe("localized form defaults", () => {
|
|
7
|
+
before(async () => {
|
|
8
|
+
await initJSDOM();
|
|
9
|
+
await import("element-internals-polyfill");
|
|
10
|
+
|
|
11
|
+
const global = getGlobal();
|
|
12
|
+
global.ResizeObserver ??= ResizeObserverMock;
|
|
13
|
+
|
|
14
|
+
await Promise.all([
|
|
15
|
+
import("../../../../source/components/form/buy-box.mjs"),
|
|
16
|
+
import("../../../../source/components/form/cart-control.mjs"),
|
|
17
|
+
import("../../../../source/components/form/login.mjs"),
|
|
18
|
+
import("../../../../source/components/form/select.mjs"),
|
|
19
|
+
import("../../../../source/components/form/variant-select.mjs"),
|
|
20
|
+
import("../../../../source/components/form/wizard.mjs"),
|
|
21
|
+
]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
document.documentElement.lang = "en";
|
|
26
|
+
document.getElementById("mocks").replaceChildren();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("uses native text for localized component options", () => {
|
|
30
|
+
for (const [tag, locale, option, expected] of [
|
|
31
|
+
[
|
|
32
|
+
"monster-buy-box",
|
|
33
|
+
"de",
|
|
34
|
+
"labels.selectVariant",
|
|
35
|
+
"Bitte eine Variante wählen.",
|
|
36
|
+
],
|
|
37
|
+
["monster-buy-box", "ru", "labels.addToCart", "В корзину"],
|
|
38
|
+
["monster-cart-control", "cs", "labels.items", "Položky"],
|
|
39
|
+
["monster-login", "hi", "labels.username", "उपयोगकर्ता नाम या ई-मेल"],
|
|
40
|
+
["monster-login", "bn", "labels.password", "পাসওয়ার্ড"],
|
|
41
|
+
["monster-login", "pa", "labels.login", "ਲਾਗ ਇਨ ਕਰੋ"],
|
|
42
|
+
["monster-login", "mr", "labels.mailAddress", "ई-मेल पत्ता"],
|
|
43
|
+
["monster-login", "fi", "labels.login", "Kirjaudu sisään"],
|
|
44
|
+
["monster-variant-select", "pt", "messages.valid", "A seleção é válida."],
|
|
45
|
+
[
|
|
46
|
+
"monster-select",
|
|
47
|
+
"fr",
|
|
48
|
+
"labels.no-options-available",
|
|
49
|
+
"Aucune option disponible.",
|
|
50
|
+
],
|
|
51
|
+
["monster-wizard", "sv", "labels.submit", "Slutför"],
|
|
52
|
+
]) {
|
|
53
|
+
document.documentElement.lang = locale;
|
|
54
|
+
const element = document.createElement(tag);
|
|
55
|
+
assert.equal(element.getOption(option), expected, `${tag}:${locale}`);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -48,6 +48,18 @@ describe("PopperButton", function () {
|
|
|
48
48
|
expect(cssText).to.contain("textarea{resize:vertical;min-height:6rem");
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
+
it("should preserve focus indicator space inside clipped popper content", function () {
|
|
52
|
+
const cssText = PopperButton.getCSSStyleSheet()
|
|
53
|
+
.flatMap((styleSheet) => Array.from(styleSheet.cssRules))
|
|
54
|
+
.map((rule) => rule.cssText)
|
|
55
|
+
.join("\n")
|
|
56
|
+
.replaceAll(/\s+/g, "");
|
|
57
|
+
|
|
58
|
+
expect(cssText).to.contain(
|
|
59
|
+
"[part=content]:not([data-monster-overflow-mode=visible]){padding:var(--monster-popper-focus-bleed,max(0px,calc(var(--monster-focus-ring-width,2px)+var(--monster-focus-ring-offset,2px))))",
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
51
63
|
it("should apply content overflow mode to the rendered content wrapper", function (done) {
|
|
52
64
|
let mocks = document.getElementById("mocks");
|
|
53
65
|
const button = document.createElement("monster-popper-button");
|
|
@@ -18,6 +18,21 @@ describe("RegisterWizard", () => {
|
|
|
18
18
|
|
|
19
19
|
afterEach(() => {
|
|
20
20
|
document.getElementById("mocks").replaceChildren();
|
|
21
|
+
document.documentElement.lang = "en";
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("uses native localized defaults", () => {
|
|
25
|
+
for (const [locale, option, expected] of [
|
|
26
|
+
["de", "labels.street", "Straße"],
|
|
27
|
+
["hi", "labels.successTitle", "पंजीकरण पूरा हुआ"],
|
|
28
|
+
["ru", "labels.stepAccount", "Учётная запись"],
|
|
29
|
+
["ja", "labels.successTitle", "登録完了"],
|
|
30
|
+
["fi", "labels.email", "Sähköposti"],
|
|
31
|
+
]) {
|
|
32
|
+
document.documentElement.lang = locale;
|
|
33
|
+
const element = document.createElement("monster-register-wizard");
|
|
34
|
+
assert.equal(element.getOption(option), expected, `${locale}:${option}`);
|
|
35
|
+
}
|
|
21
36
|
});
|
|
22
37
|
|
|
23
38
|
it("keeps explicit labels inside nested field sets", async () => {
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { expect } from "chai";
|
|
5
|
+
import * as espree from "espree";
|
|
6
|
+
import { globSync } from "glob";
|
|
7
|
+
import { currentSupportedLanguages } from "../../../source/i18n/map/languages.mjs";
|
|
8
|
+
|
|
9
|
+
const repositoryRoot = resolve(
|
|
10
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
11
|
+
"../../..",
|
|
12
|
+
);
|
|
13
|
+
const sourceFiles = globSync("source/**/*.mjs", {
|
|
14
|
+
cwd: repositoryRoot,
|
|
15
|
+
absolute: true,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function walk(node, callback) {
|
|
19
|
+
if (!node || typeof node !== "object") {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (typeof node.type === "string") {
|
|
24
|
+
callback(node);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
for (const [key, value] of Object.entries(node)) {
|
|
28
|
+
if (["comments", "loc", "range", "tokens"].includes(key)) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
for (const child of value) {
|
|
33
|
+
walk(child, callback);
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
walk(value, callback);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getPropertyKey(property) {
|
|
42
|
+
if (property.computed || property.type !== "Property") {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return property.key.name ?? property.key.value ?? null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function inspectLocaleTables() {
|
|
49
|
+
const localeSwitches = [];
|
|
50
|
+
const localeObjects = [];
|
|
51
|
+
|
|
52
|
+
for (const filename of sourceFiles) {
|
|
53
|
+
const source = readFileSync(filename, "utf8");
|
|
54
|
+
const ast = espree.parse(source, {
|
|
55
|
+
ecmaVersion: "latest",
|
|
56
|
+
loc: true,
|
|
57
|
+
range: true,
|
|
58
|
+
sourceType: "module",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
walk(ast, (node) => {
|
|
62
|
+
if (node.type === "SwitchStatement") {
|
|
63
|
+
const discriminant = source.slice(
|
|
64
|
+
node.discriminant.start,
|
|
65
|
+
node.discriminant.end,
|
|
66
|
+
);
|
|
67
|
+
if (!/\.language\b/.test(discriminant)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
localeSwitches.push({
|
|
72
|
+
filename,
|
|
73
|
+
line: node.loc.start.line,
|
|
74
|
+
codes: node.cases
|
|
75
|
+
.map((entry) => entry.test?.value)
|
|
76
|
+
.filter((entry) => typeof entry === "string"),
|
|
77
|
+
hasDefault: node.cases.some((entry) => entry.test === null),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (node.type === "ObjectExpression") {
|
|
82
|
+
const keys = node.properties
|
|
83
|
+
.map((property) => getPropertyKey(property))
|
|
84
|
+
.filter((key) => typeof key === "string");
|
|
85
|
+
const supportedKeys = keys.filter((key) =>
|
|
86
|
+
currentSupportedLanguages.includes(key),
|
|
87
|
+
);
|
|
88
|
+
if (supportedKeys.length >= 5) {
|
|
89
|
+
localeObjects.push({
|
|
90
|
+
filename,
|
|
91
|
+
line: node.loc.start.line,
|
|
92
|
+
keys,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { localeSwitches, localeObjects };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
describe("built-in locale coverage", () => {
|
|
103
|
+
const { localeSwitches, localeObjects } = inspectLocaleTables();
|
|
104
|
+
|
|
105
|
+
it("keeps the supported locale list unique", () => {
|
|
106
|
+
expect(new Set(currentSupportedLanguages).size).to.equal(
|
|
107
|
+
currentSupportedLanguages.length,
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("covers every supported locale in language switches", () => {
|
|
112
|
+
expect(localeSwitches.length).to.be.greaterThan(30);
|
|
113
|
+
|
|
114
|
+
for (const table of localeSwitches) {
|
|
115
|
+
const location = `${table.filename}:${table.line}`;
|
|
116
|
+
for (const language of currentSupportedLanguages) {
|
|
117
|
+
if (language === "en" && table.hasDefault) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
expect(table.codes, `${location} is missing ${language}`).to.include(
|
|
121
|
+
language,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
expect(
|
|
125
|
+
table.codes,
|
|
126
|
+
`${location} uses an obsolete Spanish code`,
|
|
127
|
+
).not.to.include("sp");
|
|
128
|
+
expect(
|
|
129
|
+
table.codes,
|
|
130
|
+
`${location} uses an obsolete Danish code`,
|
|
131
|
+
).not.to.include("dk");
|
|
132
|
+
expect(
|
|
133
|
+
table.codes,
|
|
134
|
+
`${location} uses an obsolete Swedish code`,
|
|
135
|
+
).not.to.include("sw");
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("covers every supported locale in language maps", () => {
|
|
140
|
+
expect(localeObjects.length).to.be.greaterThan(3);
|
|
141
|
+
|
|
142
|
+
for (const table of localeObjects) {
|
|
143
|
+
const location = `${table.filename}:${table.line}`;
|
|
144
|
+
for (const language of currentSupportedLanguages) {
|
|
145
|
+
expect(table.keys, `${location} is missing ${language}`).to.include(
|
|
146
|
+
language,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -1,33 +1,53 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { currentSupportedLanguages } from "../../../source/i18n/map/languages.mjs";
|
|
3
|
+
import { formatTimeAgo } from "../../../source/i18n/time-ago.mjs";
|
|
4
|
+
|
|
5
|
+
describe("formatTimeAgo", () => {
|
|
6
|
+
it('returns "just now" for times less than a second ago', () => {
|
|
7
|
+
const now = new Date();
|
|
8
|
+
expect(formatTimeAgo(now, "en")).to.equal("just now");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('returns "in 1 second" for one second in the future', () => {
|
|
12
|
+
const oneSecondFuture = new Date(Date.now() + 1000);
|
|
13
|
+
expect(formatTimeAgo(oneSecondFuture, "en")).to.equal("in 1 second");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('returns "1 second ago" for one second in the past', () => {
|
|
17
|
+
const oneSecondPast = new Date(Date.now() - 1000);
|
|
18
|
+
expect(formatTimeAgo(oneSecondPast, "en")).to.equal("1 second ago");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('returns "in 1 minute, 30 seconds" for 90 seconds in the future', () => {
|
|
22
|
+
const ninetySecondsFuture = new Date(Date.now() + 90000);
|
|
23
|
+
expect(formatTimeAgo(ninetySecondsFuture, "en")).to.equal(
|
|
24
|
+
"in 1 minute and 30 seconds",
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('returns "1 minute, 30 seconds ago" for 90 seconds in the past', () => {
|
|
29
|
+
const ninetySecondsPast = new Date(Date.now() - 90000);
|
|
30
|
+
expect(formatTimeAgo(ninetySecondsPast, "en")).to.equal(
|
|
31
|
+
"1 minute and 30 seconds ago",
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("formats relative times for every supported South Asian locale", () => {
|
|
36
|
+
const oneHourFuture = new Date(Date.now() + 3600000);
|
|
37
|
+
|
|
38
|
+
expect(formatTimeAgo(oneHourFuture, "bn")).to.equal("1 ঘণ্টা পরে");
|
|
39
|
+
expect(formatTimeAgo(oneHourFuture, "pa")).to.equal("1 ਘੰਟਾ ਬਾਅਦ");
|
|
40
|
+
expect(formatTimeAgo(oneHourFuture, "mr")).to.equal("1 तास नंतर");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("formats every supported locale without falling back to a missing table", () => {
|
|
44
|
+
const oneHourFuture = new Date(Date.now() + 3600000);
|
|
45
|
+
|
|
46
|
+
for (const locale of currentSupportedLanguages) {
|
|
47
|
+
expect(formatTimeAgo(oneHourFuture, locale), locale).to.be.a("string").and
|
|
48
|
+
.not.be.empty;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Weitere Tests können hinzugefügt werden
|
|
33
53
|
});
|