@jjlmoya/utils-tools 1.13.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/category/i18n/de.ts +3 -3
- package/src/category/i18n/en.ts +1 -1
- package/src/category/i18n/es.ts +1 -1
- package/src/category/i18n/fr.ts +16 -16
- package/src/category/i18n/id.ts +1 -1
- package/src/category/i18n/nl.ts +1 -1
- package/src/category/i18n/pl.ts +1 -1
- package/src/category/i18n/pt.ts +1 -1
- package/src/category/i18n/ru.ts +10 -10
- package/src/category/i18n/sv.ts +1 -1
- package/src/category/i18n/tr.ts +1 -1
- package/src/category/i18n/zh.ts +4 -4
- package/src/layouts/PreviewLayout.astro +7 -2
- package/src/tests/diacritics_density.test.ts +118 -0
- package/src/tests/inverted_punctuation.test.ts +84 -0
- package/src/tests/no_en_dash.test.ts +70 -0
- package/src/tests/script_density.test.ts +94 -0
- package/src/tool/date-diff-calculator/i18n/de.ts +4 -4
- package/src/tool/date-diff-calculator/i18n/pl.ts +1 -1
- package/src/tool/date-diff-calculator/i18n/ru.ts +4 -4
- package/src/tool/date-diff-calculator/i18n/zh.ts +3 -3
- package/src/tool/drive-direct-link/i18n/de.ts +4 -4
- package/src/tool/drive-direct-link/i18n/es.ts +1 -1
- package/src/tool/drive-direct-link/i18n/fr.ts +1 -1
- package/src/tool/drive-direct-link/i18n/pl.ts +4 -4
- package/src/tool/drive-direct-link/i18n/ru.ts +8 -8
- package/src/tool/drive-direct-link/i18n/zh.ts +4 -4
- package/src/tool/email-list-cleaner/i18n/de.ts +2 -2
- package/src/tool/email-list-cleaner/i18n/pl.ts +2 -2
- package/src/tool/email-list-cleaner/i18n/ru.ts +7 -7
- package/src/tool/email-list-cleaner/i18n/sv.ts +1 -1
- package/src/tool/email-list-cleaner/i18n/zh.ts +3 -3
- package/src/tool/env-badge-spain/i18n/ru.ts +10 -10
- package/src/tool/morse-beacon/bibliography.ts +3 -3
- package/src/tool/morse-beacon/i18n/de.ts +4 -4
- package/src/tool/morse-beacon/i18n/en.ts +2 -2
- package/src/tool/morse-beacon/i18n/es.ts +2 -2
- package/src/tool/morse-beacon/i18n/fr.ts +5 -5
- package/src/tool/morse-beacon/i18n/id.ts +2 -2
- package/src/tool/morse-beacon/i18n/it.ts +2 -2
- package/src/tool/morse-beacon/i18n/ja.ts +2 -2
- package/src/tool/morse-beacon/i18n/ko.ts +2 -2
- package/src/tool/morse-beacon/i18n/nl.ts +2 -2
- package/src/tool/morse-beacon/i18n/pl.ts +3 -3
- package/src/tool/morse-beacon/i18n/pt.ts +2 -2
- package/src/tool/morse-beacon/i18n/ru.ts +3 -3
- package/src/tool/morse-beacon/i18n/sv.ts +2 -2
- package/src/tool/morse-beacon/i18n/tr.ts +2 -2
- package/src/tool/morse-beacon/i18n/zh.ts +3 -3
- package/src/tool/password-generator/bibliography.ts +3 -3
- package/src/tool/password-generator/i18n/de.ts +5 -5
- package/src/tool/password-generator/i18n/fr.ts +1 -1
- package/src/tool/password-generator/i18n/pl.ts +3 -3
- package/src/tool/password-generator/i18n/ru.ts +4 -4
- package/src/tool/password-generator/i18n/zh.ts +3 -3
- package/src/tool/routes/bibliography.ts +5 -5
- package/src/tool/routes/i18n/de.ts +1 -1
- package/src/tool/routes/i18n/fr.ts +3 -3
- package/src/tool/routes/i18n/ru.ts +3 -3
- package/src/tool/routes/i18n/zh.ts +1 -1
- package/src/tool/rule-of-three/bibliography.ts +2 -2
- package/src/tool/rule-of-three/i18n/de.ts +2 -2
- package/src/tool/rule-of-three/i18n/fr.ts +7 -7
- package/src/tool/rule-of-three/i18n/nl.ts +2 -2
- package/src/tool/rule-of-three/i18n/pl.ts +2 -2
- package/src/tool/rule-of-three/i18n/ru.ts +3 -3
- package/src/tool/rule-of-three/i18n/tr.ts +1 -1
- package/src/tool/rule-of-three/i18n/zh.ts +2 -2
- package/src/tool/seo-content-optimizer/i18n/fr.ts +3 -3
- package/src/tool/seo-content-optimizer/i18n/ru.ts +1 -1
- package/src/tool/speed-reader/i18n/de.ts +3 -3
- package/src/tool/speed-reader/i18n/en.ts +2 -2
- package/src/tool/speed-reader/i18n/fr.ts +9 -9
- package/src/tool/speed-reader/i18n/id.ts +2 -2
- package/src/tool/speed-reader/i18n/it.ts +2 -2
- package/src/tool/speed-reader/i18n/nl.ts +2 -2
- package/src/tool/speed-reader/i18n/pl.ts +3 -3
- package/src/tool/speed-reader/i18n/pt.ts +2 -2
- package/src/tool/speed-reader/i18n/ru.ts +10 -10
- package/src/tool/speed-reader/i18n/sv.ts +2 -2
- package/src/tool/speed-reader/i18n/zh.ts +4 -4
- package/src/tool/text-pixel-calculator/i18n/de.ts +2 -2
- package/src/tool/text-pixel-calculator/i18n/fr.ts +1 -1
- package/src/tool/text-pixel-calculator/i18n/pl.ts +3 -3
- package/src/tool/text-pixel-calculator/i18n/ru.ts +4 -4
- package/src/tool/text-pixel-calculator/i18n/zh.ts +3 -3
- package/src/tool/whatsapp-link/bibliography.ts +2 -2
- package/src/tool/whatsapp-link/i18n/de.ts +4 -4
- package/src/tool/whatsapp-link/i18n/es.ts +2 -2
- package/src/tool/whatsapp-link/i18n/fr.ts +2 -2
- package/src/tool/whatsapp-link/i18n/pl.ts +4 -4
- package/src/tool/whatsapp-link/i18n/ru.ts +5 -5
- package/src/tool/whatsapp-link/i18n/zh.ts +4 -4
- package/src/tool/whatsapp-link/whatsapp-link-generator.css +41 -4
package/package.json
CHANGED
package/src/category/i18n/de.ts
CHANGED
|
@@ -34,7 +34,7 @@ export const content: CategoryLocaleContent = {
|
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
type: 'paragraph',
|
|
37
|
-
html: 'Zeit und Kraftstoff zu sparen ist durch Optimierung möglich. Der <strong>Routenoptimierer</strong> ermöglicht es Ihnen, mehrere Stopps zu organisieren, um die effizienteste Route zu berechnen
|
|
37
|
+
html: 'Zeit und Kraftstoff zu sparen ist durch Optimierung möglich. Der <strong>Routenoptimierer</strong> ermöglicht es Ihnen, mehrere Stopps zu organisieren, um die effizienteste Route zu berechnen - ideal für Lieferdienste, Vertreter oder Reisende. Andererseits ist unser Rechner für den <strong>Dreisatz</strong> der ultimative mathematische Lebensretter, um Skalierungen, Prozentsätze und Proportionen in Sekunden zu lösen.',
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
type: 'title',
|
|
@@ -84,7 +84,7 @@ export const content: CategoryLocaleContent = {
|
|
|
84
84
|
},
|
|
85
85
|
{
|
|
86
86
|
type: 'paragraph',
|
|
87
|
-
html: 'Ein starkes Passwort von heute bedeutet nicht, dass es auch morgen noch sicher ist. Wenn eine Datenbank bei einem anderen Dienst gehackt wird und Sie dasselbe Passwort verwenden, sind Sie gefährdet. Tools wie das Audit von Passwörtern gegen Datenbanken (HIBP
|
|
87
|
+
html: 'Ein starkes Passwort von heute bedeutet nicht, dass es auch morgen noch sicher ist. Wenn eine Datenbank bei einem anderen Dienst gehackt wird und Sie dasselbe Passwort verwenden, sind Sie gefährdet. Tools wie das Audit von Passwörtern gegen Datenbanken (HIBP - Have I Been Pwned) ermöglichen es Ihnen zu prüfen, ob Ihre Anmeldeinformationen im Darknet kursieren. Unsere Dienstprogramme beinhalten die Integration mit Sicherheits-APIs, damit Sie genau wissen, ob Ihr Passwort jemals kompromittiert wurde.',
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
90
|
type: 'title',
|
|
@@ -120,7 +120,7 @@ export const content: CategoryLocaleContent = {
|
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
type: 'paragraph',
|
|
123
|
-
html: 'Eine Liste von 500 E-Mail-Adressen
|
|
123
|
+
html: 'Eine Liste von 500 E-Mail-Adressen - wie viele davon sind echt? Unser <strong>Batch-Datenvalidator</strong> prüft Format, Domain und ob der Server E-Mails für diese Adresse akzeptiert. Bei Telefonnummern wird das internationale Format und die geografische Nummer validiert. In Geschäftsanwendungen verhindert die Datenvalidierung Katastrophen: Das Versenden von E-Mails an nicht existierende Adressen ist Verschwendung; das Anrufen gefälschter Nummern ist Frustration.',
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
type: 'title',
|
package/src/category/i18n/en.ts
CHANGED
|
@@ -120,7 +120,7 @@ export const content: CategoryLocaleContent = {
|
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
type: 'paragraph',
|
|
123
|
-
html: "A list of 500 email addresses
|
|
123
|
+
html: "A list of 500 email addresses - how many are real? Our <strong>bulk data validator</strong> verifies format, domain, and whether the server accepts mail for that address. For phone numbers, it validates international format and geographic number. In business applications, data validation prevents disasters: sending emails to non-existent addresses is waste; calling falsified numbers is frustration.",
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
type: 'title',
|
package/src/category/i18n/es.ts
CHANGED
|
@@ -120,7 +120,7 @@ export const content: CategoryLocaleContent = {
|
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
type: 'paragraph',
|
|
123
|
-
html: 'Una lista de 500 direcciones de email
|
|
123
|
+
html: 'Una lista de 500 direcciones de email - ¿cuántas son reales? Nuestro <strong>validador de datos en lote</strong> verifica formato, dominio, y si el servidor acepta correo para esa dirección. Para números de teléfono, valida formato internacional y número geográfico. En aplicaciones de negocio, validación de datos previene desastres: enviar correos a direcciones inexistentes es desperdicio; llamar a números falsificados es frustración.',
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
type: 'title',
|
package/src/category/i18n/fr.ts
CHANGED
|
@@ -7,12 +7,12 @@ export const content: CategoryLocaleContent = {
|
|
|
7
7
|
seo: [
|
|
8
8
|
{
|
|
9
9
|
type: 'title',
|
|
10
|
-
text: 'Utilitaires pratiques
|
|
10
|
+
text: 'Utilitaires pratiques: solutions numériques pour le quotidien',
|
|
11
11
|
level: 2,
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
type: 'paragraph',
|
|
15
|
-
html: "Dans un monde numérique saturé d'applications complexes, ce dont nous avons le plus besoin est parfois un <strong>outil simple, rapide et efficace</strong>. Dans cette section, nous avons rassemblé une \"ceinture d'utilitaires\" avec des <strong>outils gratuits en ligne</strong> qui résolvent des problèmes pratiques immédiats
|
|
15
|
+
html: "Dans un monde numérique saturé d'applications complexes, ce dont nous avons le plus besoin est parfois un <strong>outil simple, rapide et efficace</strong>. Dans cette section, nous avons rassemblé une \"ceinture d'utilitaires\" avec des <strong>outils gratuits en ligne</strong> qui résolvent des problèmes pratiques immédiats: de la sécurité de vos comptes à l'optimisation de vos déplacements physiques.",
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
type: 'paragraph',
|
|
@@ -20,7 +20,7 @@ export const content: CategoryLocaleContent = {
|
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
type: 'title',
|
|
23
|
-
text: 'Sécurité et protection
|
|
23
|
+
text: 'Sécurité et protection: génération de mots de passe robustes',
|
|
24
24
|
level: 2,
|
|
25
25
|
},
|
|
26
26
|
{
|
|
@@ -29,7 +29,7 @@ export const content: CategoryLocaleContent = {
|
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
type: 'title',
|
|
32
|
-
text: 'Logistique et mathématiques rapides
|
|
32
|
+
text: 'Logistique et mathématiques rapides: itinéraires et proportions',
|
|
33
33
|
level: 2,
|
|
34
34
|
},
|
|
35
35
|
{
|
|
@@ -38,7 +38,7 @@ export const content: CategoryLocaleContent = {
|
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
type: 'title',
|
|
41
|
-
text: 'Productivité cognitive
|
|
41
|
+
text: 'Productivité cognitive: lecture rapide RSVP',
|
|
42
42
|
level: 2,
|
|
43
43
|
},
|
|
44
44
|
{
|
|
@@ -47,7 +47,7 @@ export const content: CategoryLocaleContent = {
|
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
49
|
type: 'title',
|
|
50
|
-
text: 'Communication et liens
|
|
50
|
+
text: 'Communication et liens: WhatsApp et SEO',
|
|
51
51
|
level: 2,
|
|
52
52
|
},
|
|
53
53
|
{
|
|
@@ -56,7 +56,7 @@ export const content: CategoryLocaleContent = {
|
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
type: 'title',
|
|
59
|
-
text: "Utilitaires tactiques
|
|
59
|
+
text: "Utilitaires tactiques: balise Morse et urgences",
|
|
60
60
|
level: 2,
|
|
61
61
|
},
|
|
62
62
|
{
|
|
@@ -79,7 +79,7 @@ export const content: CategoryLocaleContent = {
|
|
|
79
79
|
},
|
|
80
80
|
{
|
|
81
81
|
type: 'title',
|
|
82
|
-
text: 'Évaluation de
|
|
82
|
+
text: 'Évaluation de sécurité: audit des mots de passe compromis',
|
|
83
83
|
level: 2,
|
|
84
84
|
},
|
|
85
85
|
{
|
|
@@ -88,7 +88,7 @@ export const content: CategoryLocaleContent = {
|
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
90
|
type: 'title',
|
|
91
|
-
text: 'Ergonomie numérique
|
|
91
|
+
text: 'Ergonomie numérique: réduire la friction, augmenter la productivité',
|
|
92
92
|
level: 2,
|
|
93
93
|
},
|
|
94
94
|
{
|
|
@@ -106,7 +106,7 @@ export const content: CategoryLocaleContent = {
|
|
|
106
106
|
},
|
|
107
107
|
{
|
|
108
108
|
type: 'title',
|
|
109
|
-
text: "Gestion des tâches et planification
|
|
109
|
+
text: "Gestion des tâches et planification: Pomodoro et isochrones",
|
|
110
110
|
level: 2,
|
|
111
111
|
},
|
|
112
112
|
{
|
|
@@ -120,16 +120,16 @@ export const content: CategoryLocaleContent = {
|
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
type: 'paragraph',
|
|
123
|
-
html: "Une liste de 500 adresses email
|
|
123
|
+
html: "Une liste de 500 adresses email - combien sont réelles ? Notre <strong>validateur de données en lot</strong> vérifie le format, le domaine et si le serveur accepte le courrier pour cette adresse. Pour les numéros de téléphone, il valide le format international et le numéro géographique.",
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
type: 'title',
|
|
127
|
-
text: "Conversion d'unités
|
|
127
|
+
text: "Conversion d'unités: masse, volume, température, énergie",
|
|
128
128
|
level: 2,
|
|
129
129
|
},
|
|
130
130
|
{
|
|
131
131
|
type: 'paragraph',
|
|
132
|
-
html: "Les conversions semblent simples jusqu'à ce que vous deviez convertir 100 Fahrenheit en Celsius sous pression. Notre <strong>convertisseur universel d'unités</strong> gère les conversions pour 500+ grandeurs
|
|
132
|
+
html: "Les conversions semblent simples jusqu'à ce que vous deviez convertir 100 Fahrenheit en Celsius sous pression. Notre <strong>convertisseur universel d'unités</strong> gère les conversions pour 500+ grandeurs: longueur (mètre vers pied vers mile), masse (kg vers livre vers once), volume (litre vers gallon), température (avec correction de formule exacte), énergie (joule vers calorie).",
|
|
133
133
|
},
|
|
134
134
|
{
|
|
135
135
|
type: 'title',
|
|
@@ -138,11 +138,11 @@ export const content: CategoryLocaleContent = {
|
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
type: 'paragraph',
|
|
141
|
-
html:
|
|
141
|
+
html: `10% de 100 c'est 10. Simple. Mais si vous augmentez 10% de 100 (résultant 110) puis augmentez encore 10%, le résultat est-il 121 ou 120 ? C'est 121 (variation composée). Notre <strong>calculateur de pourcentages avancé</strong> gère les variations imbriquées, les remises multiples et les calculs de " marge " versus " majoration ".`,
|
|
142
142
|
},
|
|
143
143
|
{
|
|
144
144
|
type: 'title',
|
|
145
|
-
text: 'Analyse de nombres
|
|
145
|
+
text: 'Analyse de nombres: factorisation, primalité, diviseurs',
|
|
146
146
|
level: 2,
|
|
147
147
|
},
|
|
148
148
|
{
|
|
@@ -156,7 +156,7 @@ export const content: CategoryLocaleContent = {
|
|
|
156
156
|
},
|
|
157
157
|
{
|
|
158
158
|
type: 'paragraph',
|
|
159
|
-
html:
|
|
159
|
+
html: `En 2026, le " simple " est redevenu premium. Nous en avons assez des applications qui essaient de tout faire et ne font rien bien. Ces outils sont le retour à l'essentiel: des utilitaires qui font une seule chose, mais avec une précision absolue et dans le respect de l'utilisateur.`,
|
|
160
160
|
},
|
|
161
161
|
{
|
|
162
162
|
type: 'stats',
|
package/src/category/i18n/id.ts
CHANGED
|
@@ -120,7 +120,7 @@ export const content: CategoryLocaleContent = {
|
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
type: 'paragraph',
|
|
123
|
-
html: 'Daftar berisi 500 alamat email
|
|
123
|
+
html: 'Daftar berisi 500 alamat email - berapa banyak yang asli? <strong>Valolidator data batch</strong> kami memverifikasi format, domain, dan apakah server menerima email untuk alamat tersebut. Untuk nomor telepon, validasi format internasional dan nomor geografis. Dalam aplikasi bisnis, validasi data mencegah bencana: mengirim email ke alamat yang tidak ada adalah pemborosan; menelepon nomor palsu adalah frustrasi.',
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
type: 'title',
|
package/src/category/i18n/nl.ts
CHANGED
|
@@ -120,7 +120,7 @@ export const content: CategoryLocaleContent = {
|
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
type: 'paragraph',
|
|
123
|
-
html: 'Een lijst met 500 e-mailadressen
|
|
123
|
+
html: 'Een lijst met 500 e-mailadressen - hoeveel daarvan zijn echt? Onze <strong>batch datavalidator</strong> verifieert formaat, domein en of de server e-mail accepteert voor dat adres. Voor telefoonnummers valideert het internationale formaten en geografische nummers. In zakelijke apps voorkomt datavalidatie rampen: e-mail sturen naar niet-bestaande adressen is verspilling; nepnummers bellen is frustrerend.',
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
type: 'title',
|
package/src/category/i18n/pl.ts
CHANGED
|
@@ -120,7 +120,7 @@ export const content: CategoryLocaleContent = {
|
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
type: 'paragraph',
|
|
123
|
-
html: 'Lista 500 adresów e-mail
|
|
123
|
+
html: 'Lista 500 adresów e-mail - ile z nich jest prawdziwych? Nasz <strong>wsadowy walidator danych</strong> weryfikuje format, domenę i to, czy serwer akceptuje pocztę dla tego adresu. Dla numerów telefonów waliduje format międzynarodowy i numer geograficzny. W aplikacjach biznesowych walidacja danych zapobiega katastrofom: wysyłanie e-maili na nieistniejące adresy to marnotrawstwo; dzwonienie pod fałszywe numery to frustracja.',
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
type: 'title',
|
package/src/category/i18n/pt.ts
CHANGED
|
@@ -120,7 +120,7 @@ export const content: CategoryLocaleContent = {
|
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
type: 'paragraph',
|
|
123
|
-
html: 'Uma lista de 500 endereços de email
|
|
123
|
+
html: 'Uma lista de 500 endereços de email - quantos são reais? O nosso <strong>validador de dados em lote</strong> verifica formato, domínio, e se o servidor aceita correio para esse endereço. Para números de telefone, valida formato internacional e número geográfico. Em aplicações de negócio, a validação de dados previne desastres: enviar correios para endereços inexistentes é desperdício; ligar para números falsificados é frustração.',
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
type: 'title',
|
package/src/category/i18n/ru.ts
CHANGED
|
@@ -12,7 +12,7 @@ export const content: CategoryLocaleContent = {
|
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
type: 'paragraph',
|
|
15
|
-
html: 'В цифровом мире, перенасыщенном сложными приложениями, иногда больше всего нам нужен <strong>простой, быстрый и эффективный инструмент</strong>. В этом разделе мы собрали
|
|
15
|
+
html: 'В цифровом мире, перенасыщенном сложными приложениями, иногда больше всего нам нужен <strong>простой, быстрый и эффективный инструмент</strong>. В этом разделе мы собрали "пояс с инструментами" из <strong>бесплатных онлайн-сервисов</strong>, которые решают насущные практические задачи: от безопасности ваших аккаунтов до оптимизации физических перемещений.',
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
type: 'paragraph',
|
|
@@ -25,7 +25,7 @@ export const content: CategoryLocaleContent = {
|
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
type: 'paragraph',
|
|
28
|
-
html: 'Первая линия обороны в интернете
|
|
28
|
+
html: 'Первая линия обороны в интернете - это сложный пароль. Наш <strong>генератор паролей</strong> использует криптографические алгоритмы случайности для создания ключей, которые невозможно предсказать или взломать методом перебора (brute force). Настройте длину и тип символов в соответствии со стандартами безопасности 2026 года.',
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
type: 'title',
|
|
@@ -34,7 +34,7 @@ export const content: CategoryLocaleContent = {
|
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
type: 'paragraph',
|
|
37
|
-
html: 'Экономия времени и топлива возможна благодаря оптимизации. <strong>Оптимизатор маршрутов</strong> позволяет организовать несколько остановок для расчета наиболее эффективного пути, что идеально подходит для курьеров, торговых представителей или путешественников. С другой стороны, наш калькулятор <strong>пропорций (правило трех)</strong>
|
|
37
|
+
html: 'Экономия времени и топлива возможна благодаря оптимизации. <strong>Оптимизатор маршрутов</strong> позволяет организовать несколько остановок для расчета наиболее эффективного пути, что идеально подходит для курьеров, торговых представителей или путешественников. С другой стороны, наш калькулятор <strong>пропорций (правило трех)</strong> - это незаменимый математический помощник для мгновенного решения задач с масштабами, процентами и соотношениями.',
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
type: 'title',
|
|
@@ -68,7 +68,7 @@ export const content: CategoryLocaleContent = {
|
|
|
68
68
|
items: [
|
|
69
69
|
'<strong>Многофункциональность:</strong> От математических расчетов до кибербезопасности и персональной логистики.',
|
|
70
70
|
'<strong>Скорость отклика:</strong> Инструменты оптимизированы для мгновенной загрузки на любом мобильном устройстве.',
|
|
71
|
-
'<strong>Zero Tracking:</strong> Мы не храним ваши пароли или маршруты; приватность
|
|
71
|
+
'<strong>Zero Tracking:</strong> Мы не храним ваши пароли или маршруты; приватность - наш технический приоритет.',
|
|
72
72
|
'<strong>Обновление 2026:</strong> Соответствие последним нормам безопасности и современным веб-стандартам.',
|
|
73
73
|
],
|
|
74
74
|
},
|
|
@@ -84,7 +84,7 @@ export const content: CategoryLocaleContent = {
|
|
|
84
84
|
},
|
|
85
85
|
{
|
|
86
86
|
type: 'paragraph',
|
|
87
|
-
html: 'Наличие надежного пароля сегодня не означает, что он будет безопасным завтра. Если база данных другого сервиса будет взломана, а вы используете тот же ключ, вы подвергаетесь риску. Инструменты аудита паролей по базам данных (HIBP
|
|
87
|
+
html: 'Наличие надежного пароля сегодня не означает, что он будет безопасным завтра. Если база данных другого сервиса будет взломана, а вы используете тот же ключ, вы подвергаетесь риску. Инструменты аудита паролей по базам данных (HIBP - Have I Been Pwned) позволяют проверить, циркулируют ли ваши учетные данные в даркнете. Наши утилиты включают интеграцию с API безопасности, чтобы вы точно знали, был ли ваш ключ когда-либо скомпрометирован.',
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
90
|
type: 'title',
|
|
@@ -93,7 +93,7 @@ export const content: CategoryLocaleContent = {
|
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
type: 'paragraph',
|
|
96
|
-
html: 'Цифровое трение невидимо, но обходится дорого. Каждый раз правильно форматировать номер телефона для WhatsApp, когда вы хотите с кем-то связаться
|
|
96
|
+
html: 'Цифровое трение невидимо, но обходится дорого. Каждый раз правильно форматировать номер телефона для WhatsApp, когда вы хотите с кем-то связаться - это трение. Вручную рассчитывать скидку - это трение. Наши инструменты устраняют эти повторяющиеся микрошаги. Оптимизатор маршрутов экономит вам 2 часа в неделю, если вы занимаетесь доставкой. Читалка RSVP экономит 3 часа чтения в неделю. В сумме за 52 недели это целые месяцы возвращенной жизни.',
|
|
97
97
|
},
|
|
98
98
|
{
|
|
99
99
|
type: 'title',
|
|
@@ -111,7 +111,7 @@ export const content: CategoryLocaleContent = {
|
|
|
111
111
|
},
|
|
112
112
|
{
|
|
113
113
|
type: 'paragraph',
|
|
114
|
-
html: 'Продуктивность
|
|
114
|
+
html: 'Продуктивность - это не только скорость, это разумное управление временем. Наша <strong>интерактивная техника Помодоро</strong> делит ваш рабочий день на сфокусированные блоки по 25 минут с рассчитанными перерывами для оптимального когнитивного восстановления. <strong>Изохронный калькулятор</strong> показывает, сколько времени займут ваши задачи на основе истории, позволяя планировать день реалистично, а не оптимистично.',
|
|
115
115
|
},
|
|
116
116
|
{
|
|
117
117
|
type: 'title',
|
|
@@ -120,7 +120,7 @@ export const content: CategoryLocaleContent = {
|
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
type: 'paragraph',
|
|
123
|
-
html: 'Список из 500 адресов электронной почты
|
|
123
|
+
html: 'Список из 500 адресов электронной почты - сколько из них настоящие? Наш <strong>пакетный валидатор данных</strong> проверяет формат, домен и принимает ли сервер почту для этого адреса. Для номеров телефонов он проверяет международный формат и географический номер. В бизнес-приложениях валидация данных предотвращает катастрофы: отправка писем на несуществующие адреса - это трата ресурсов; звонки на поддельные номера - это разочарование.',
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
type: 'title',
|
|
@@ -138,7 +138,7 @@ export const content: CategoryLocaleContent = {
|
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
type: 'paragraph',
|
|
141
|
-
html: '10% от 100
|
|
141
|
+
html: '10% от 100 - это 10. Просто. Но если вы увеличите 100 на 10% (получится 110), а затем увеличите еще на 10%, будет ли результат 121 или 120? Результат - 121 (сложная вариация). Наш <strong>продвинутый калькулятор процентов</strong> обрабатывает вложенные вариации, множественные скидки и расчеты "маржи" против "наценки". Розничный торговец, который покупает по 100 евро и добавляет наценку 50%, продает по 150 евро; если затем он применит скидку 20%, он продаст по 120 евро (а не по 130). Разница в 10 евро - это то, что многие предприятия теряют из-за неправильных расчетов.',
|
|
142
142
|
},
|
|
143
143
|
{
|
|
144
144
|
type: 'title',
|
|
@@ -156,7 +156,7 @@ export const content: CategoryLocaleContent = {
|
|
|
156
156
|
},
|
|
157
157
|
{
|
|
158
158
|
type: 'paragraph',
|
|
159
|
-
html: 'В 2026 году
|
|
159
|
+
html: 'В 2026 году "простота" снова стала премиальной. Мы устали от приложений, которые пытаются делать всё сразу и не делают ничего хорошо. Эти инструменты - возвращение к сути: утилиты, которые делают одну вещь, но делают её с абсолютной точностью и уважением к пользователю. Каждая минута, которую вы экономите с помощью хорошо продуманного инструмента - это минута, которую вы возвращаете себе в жизнь.',
|
|
160
160
|
},
|
|
161
161
|
{
|
|
162
162
|
type: 'stats',
|
package/src/category/i18n/sv.ts
CHANGED
|
@@ -120,7 +120,7 @@ export const content: CategoryLocaleContent = {
|
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
type: 'paragraph',
|
|
123
|
-
html: 'En lista med 500 e-postadresser
|
|
123
|
+
html: 'En lista med 500 e-postadresser - hur många är äkta? Vår <strong>batch-datavaliderare</strong> verifierar format, domän och om servern accepterar e-post för den adressen. För telefonnummer valideras internationella format och geografiska nummer. I affärsapplikationer förhindrar datavalidering katastrofer: att skicka e-post till obefintliga adresser är slöseri; att ringa falska nummer är frustration.',
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
type: 'title',
|
package/src/category/i18n/tr.ts
CHANGED
|
@@ -120,7 +120,7 @@ export const content: CategoryLocaleContent = {
|
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
type: 'paragraph',
|
|
123
|
-
html: '500 e-posta adresinden oluşan bir liste
|
|
123
|
+
html: '500 e-posta adresinden oluşan bir liste - kaç tanesi gerçek? <strong>Toplu veri doğrulayıcımız</strong> formatı, alanı ve sunucunun o adres için e-posta kabul edip etmediğini doğrular. Telefon numaraları için uluslararası formatı ve coğrafi numarayı doğrular. İş uygulamalarında veri doğrulama felaketleri önler: var olmayan adreslere e-posta göndermek israftır; sahte numaraları aramak hayal kırıklığıdır.',
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
type: 'title',
|
package/src/category/i18n/zh.ts
CHANGED
|
@@ -12,7 +12,7 @@ export const content: CategoryLocaleContent = {
|
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
type: 'paragraph',
|
|
15
|
-
html: '在充斥着复杂应用的数字世界中,有时我们最需要的是 <strong>简单、快速且有效</strong>
|
|
15
|
+
html: '在充斥着复杂应用的数字世界中,有时我们最需要的是 <strong>简单、快速且有效</strong> 的工具。在本节中,我们收集了一个"工具带",其中包含 <strong>免费在线工具</strong>,可解决即时实际问题:从账户安全到物理旅程的优化。',
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
type: 'paragraph',
|
|
@@ -120,7 +120,7 @@ export const content: CategoryLocaleContent = {
|
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
type: 'paragraph',
|
|
123
|
-
html: '500 个电子邮件地址的列表
|
|
123
|
+
html: '500 个电子邮件地址的列表 - 有多少是真的?我们的 <strong>批量数据验证器</strong> 验证格式、域名以及服务器是否接受该地址的邮件。对于电话号码,它验证国际格式和地理编号。在业务应用中,数据验证可防止灾难:向不存在的地址发送邮件是浪费;拨打虚假号码则是挫折。',
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
type: 'title',
|
|
@@ -138,7 +138,7 @@ export const content: CategoryLocaleContent = {
|
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
type: 'paragraph',
|
|
141
|
-
html: '100 的 10% 是 10。很简单。但如果您将 100 增加 10%(结果为 110),然后再增加 10%,结果是 121 还是 120?答案是 121(复合变动)。我们的 <strong>高级百分比计算器</strong>
|
|
141
|
+
html: '100 的 10% 是 10。很简单。但如果您将 100 增加 10%(结果为 110),然后再增加 10%,结果是 121 还是 120?答案是 121(复合变动)。我们的 <strong>高级百分比计算器</strong> 处理嵌套变动、多重折扣以及"利润"与"加价"的计算。以 100 欧元购买并增加 50% 加价的零售商售价为 150 欧元;如果随后应用 20% 的折扣,则售价为 120 欧元(而非 130 欧元)。这 10 欧元的差异是许多企业因计算错误而损失的金额。',
|
|
142
142
|
},
|
|
143
143
|
{
|
|
144
144
|
type: 'title',
|
|
@@ -156,7 +156,7 @@ export const content: CategoryLocaleContent = {
|
|
|
156
156
|
},
|
|
157
157
|
{
|
|
158
158
|
type: 'paragraph',
|
|
159
|
-
html: '在 2026
|
|
159
|
+
html: '在 2026 年,"简约"再次成为溢价的代名词。我们厌倦了那些试图做所有事情却什么都做不好的应用。这些工具是回归本质:只做一件事,但以绝对的精度完成并尊重用户。通过设计良好的工具节省的每一分钟,都是您从生活中找回的一分钟。',
|
|
160
160
|
},
|
|
161
161
|
{
|
|
162
162
|
type: 'stats',
|
|
@@ -10,7 +10,12 @@ interface Props {
|
|
|
10
10
|
hasSidebar?: boolean;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const {
|
|
13
|
+
const {
|
|
14
|
+
title,
|
|
15
|
+
currentLocale = "es",
|
|
16
|
+
localeUrls = {},
|
|
17
|
+
hasSidebar = false,
|
|
18
|
+
} = Astro.props;
|
|
14
19
|
---
|
|
15
20
|
|
|
16
21
|
<!doctype html>
|
|
@@ -78,6 +83,7 @@ const { title, currentLocale = "es", localeUrls = {}, hasSidebar = false } = Ast
|
|
|
78
83
|
transition:
|
|
79
84
|
background-color 0.3s ease,
|
|
80
85
|
color 0.3s ease;
|
|
86
|
+
font-family: Inter, sans-serif;
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
main {
|
|
@@ -114,4 +120,3 @@ const { title, currentLocale = "es", localeUrls = {}, hasSidebar = false } = Ast
|
|
|
114
120
|
}
|
|
115
121
|
}
|
|
116
122
|
</style>
|
|
117
|
-
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ALL_TOOLS } from '../tools';
|
|
3
|
+
|
|
4
|
+
type LocaleWithDiacritics = keyof typeof DIACRITIC_RULES;
|
|
5
|
+
|
|
6
|
+
const DIACRITIC_RULES = {
|
|
7
|
+
de: {
|
|
8
|
+
language: 'German',
|
|
9
|
+
expectedCharacters: 'ä ö ü ß',
|
|
10
|
+
characters: /[äöüÄÖÜß]/g,
|
|
11
|
+
minPerThousandLetters: 0.1,
|
|
12
|
+
},
|
|
13
|
+
es: {
|
|
14
|
+
language: 'Spanish',
|
|
15
|
+
expectedCharacters: 'á é í ó ú ü ñ',
|
|
16
|
+
characters: /[áéíóúüñÁÉÍÓÚÜÑ]/g,
|
|
17
|
+
minPerThousandLetters: 0.1,
|
|
18
|
+
},
|
|
19
|
+
fr: {
|
|
20
|
+
language: 'French',
|
|
21
|
+
expectedCharacters: 'à â æ ç é è ê ë î ï ô œ ù û ü ÿ',
|
|
22
|
+
characters: /[àâæçéèêëîïôœùûüÿÀÂÆÇÉÈÊËÎÏÔŒÙÛÜŸ]/g,
|
|
23
|
+
minPerThousandLetters: 0.1,
|
|
24
|
+
},
|
|
25
|
+
it: {
|
|
26
|
+
language: 'Italian',
|
|
27
|
+
expectedCharacters: 'à è é ì í î ò ó ù ú',
|
|
28
|
+
characters: /[àèéìíîòóùúÀÈÉÌÍÎÒÓÙÚ]/g,
|
|
29
|
+
minPerThousandLetters: 0.1,
|
|
30
|
+
},
|
|
31
|
+
pl: {
|
|
32
|
+
language: 'Polish',
|
|
33
|
+
expectedCharacters: 'ą ć ę ł ń ó ś ź ż',
|
|
34
|
+
characters: /[ąćęłńóśźżĄĆĘŁŃÓŚŹŻ]/g,
|
|
35
|
+
minPerThousandLetters: 0.1,
|
|
36
|
+
},
|
|
37
|
+
pt: {
|
|
38
|
+
language: 'Portuguese',
|
|
39
|
+
expectedCharacters: 'á â ã à ç é ê í ó ô õ ú ü',
|
|
40
|
+
characters: /[áâãàçéêíóôõúüÁÂÃÀÇÉÊÍÓÔÕÚÜ]/g,
|
|
41
|
+
minPerThousandLetters: 0.1,
|
|
42
|
+
},
|
|
43
|
+
sv: {
|
|
44
|
+
language: 'Swedish',
|
|
45
|
+
expectedCharacters: 'å ä ö',
|
|
46
|
+
characters: /[åäöÅÄÖ]/g,
|
|
47
|
+
minPerThousandLetters: 0.1,
|
|
48
|
+
},
|
|
49
|
+
tr: {
|
|
50
|
+
language: 'Turkish',
|
|
51
|
+
expectedCharacters: 'ç ğ ı İ ö ş ü',
|
|
52
|
+
characters: /[çğıöşüÇĞİÖŞÜ]/g,
|
|
53
|
+
minPerThousandLetters: 0.1,
|
|
54
|
+
},
|
|
55
|
+
} as const;
|
|
56
|
+
|
|
57
|
+
const LETTERS = /\p{L}/gu;
|
|
58
|
+
const TRANSLATABLE_KEYS = ['title', 'description', 'ui', 'seo', 'faq', 'howTo'] as const;
|
|
59
|
+
|
|
60
|
+
function collectStrings(value: unknown): string[] {
|
|
61
|
+
if (typeof value === 'string') return [value];
|
|
62
|
+
if (!value || typeof value !== 'object') return [];
|
|
63
|
+
if (Array.isArray(value)) return value.flatMap(collectStrings);
|
|
64
|
+
return Object.values(value).flatMap(collectStrings);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizeText(value: unknown): string {
|
|
68
|
+
return collectStrings(value).join(' ').normalize('NFC');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function translatableContent(content: Record<string, unknown>) {
|
|
72
|
+
return TRANSLATABLE_KEYS.map((key) => content[key]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function letterCount(text: string): number {
|
|
76
|
+
return text.match(LETTERS)?.length ?? 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function diacriticCount(text: string, locale: LocaleWithDiacritics): number {
|
|
80
|
+
return text.match(DIACRITIC_RULES[locale].characters)?.length ?? 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function diacriticsPerThousandLetters(text: string, locale: LocaleWithDiacritics): number {
|
|
84
|
+
const letters = letterCount(text);
|
|
85
|
+
if (letters === 0) return 0;
|
|
86
|
+
return diacriticCount(text, locale) / letters * 1000;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
describe('Diacritics density validation', () => {
|
|
90
|
+
ALL_TOOLS.forEach((tool) => {
|
|
91
|
+
describe(`Tool: ${tool.entry.id}`, () => {
|
|
92
|
+
Object.keys(DIACRITIC_RULES).forEach((locale) => {
|
|
93
|
+
it(`${locale} keeps the expected accent and special-letter set`, async () => {
|
|
94
|
+
const typedLocale = locale as LocaleWithDiacritics;
|
|
95
|
+
const loader = tool.entry.i18n[typedLocale];
|
|
96
|
+
if (!loader) return;
|
|
97
|
+
|
|
98
|
+
const content = await loader();
|
|
99
|
+
const text = normalizeText(translatableContent(content as Record<string, unknown>));
|
|
100
|
+
const rule = DIACRITIC_RULES[typedLocale];
|
|
101
|
+
const letters = letterCount(text);
|
|
102
|
+
const matches = diacriticCount(text, typedLocale);
|
|
103
|
+
const density = diacriticsPerThousandLetters(text, typedLocale);
|
|
104
|
+
|
|
105
|
+
expect(
|
|
106
|
+
density,
|
|
107
|
+
[
|
|
108
|
+
`Possible spelling or encoding issue detected in ${tool.entry.id}/${typedLocale} (${rule.language}).`,
|
|
109
|
+
`The text has ${matches} special characters (${density.toFixed(2)} per 1000 letters, ${letters} letters analyzed).`,
|
|
110
|
+
`This locale should include some of these characters: ${rule.expectedCharacters}.`,
|
|
111
|
+
'If the count is 0 or near 0, accents, tildes, or special letters were probably stripped by encoding or normalization.',
|
|
112
|
+
].join(' '),
|
|
113
|
+
).toBeGreaterThanOrEqual(rule.minPerThousandLetters);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ALL_TOOLS } from '../tools';
|
|
3
|
+
|
|
4
|
+
const INVERTED_PUNCTUATION_LOCALES = {
|
|
5
|
+
es: {
|
|
6
|
+
language: 'Spanish',
|
|
7
|
+
questionStart: '¿',
|
|
8
|
+
questionEnd: '?',
|
|
9
|
+
exclamationStart: '¡',
|
|
10
|
+
exclamationEnd: '!',
|
|
11
|
+
},
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
type InvertedPunctuationLocale = keyof typeof INVERTED_PUNCTUATION_LOCALES;
|
|
15
|
+
|
|
16
|
+
const TRANSLATABLE_KEYS = ['title', 'description', 'ui', 'seo', 'faq', 'howTo'] as const;
|
|
17
|
+
|
|
18
|
+
function collectStrings(value: unknown): string[] {
|
|
19
|
+
if (typeof value === 'string') return [value];
|
|
20
|
+
if (!value || typeof value !== 'object') return [];
|
|
21
|
+
if (Array.isArray(value)) return value.flatMap(collectStrings);
|
|
22
|
+
return Object.values(value).flatMap(collectStrings);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function translatableStrings(content: Record<string, unknown>): string[] {
|
|
26
|
+
return TRANSLATABLE_KEYS.flatMap((key) => collectStrings(content[key]));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function sentenceStart(text: string, endIndex: number): string {
|
|
30
|
+
const beforeMark = text.slice(0, endIndex).trimEnd();
|
|
31
|
+
const boundary = Math.max(
|
|
32
|
+
beforeMark.lastIndexOf('.'),
|
|
33
|
+
beforeMark.lastIndexOf(':'),
|
|
34
|
+
beforeMark.lastIndexOf(';'),
|
|
35
|
+
beforeMark.lastIndexOf('\n'),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return beforeMark.slice(boundary + 1).trimStart();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function findMissingInvertedMarks(
|
|
42
|
+
text: string,
|
|
43
|
+
startMark: string,
|
|
44
|
+
endMark: string,
|
|
45
|
+
): string[] {
|
|
46
|
+
return [...text.matchAll(new RegExp(`\\${endMark}`, 'g'))]
|
|
47
|
+
.map((match) => sentenceStart(text, match.index ?? 0))
|
|
48
|
+
.filter((segment) => segment.length > 0 && !segment.includes(startMark))
|
|
49
|
+
.map((segment) => `${segment}${endMark}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('Inverted punctuation validation', () => {
|
|
53
|
+
ALL_TOOLS.forEach((tool) => {
|
|
54
|
+
describe(`Tool: ${tool.entry.id}`, () => {
|
|
55
|
+
Object.keys(INVERTED_PUNCTUATION_LOCALES).forEach((locale) => {
|
|
56
|
+
it(`${locale} uses opening punctuation marks for questions and exclamations`, async () => {
|
|
57
|
+
const typedLocale = locale as InvertedPunctuationLocale;
|
|
58
|
+
const loader = tool.entry.i18n[typedLocale];
|
|
59
|
+
if (!loader) return;
|
|
60
|
+
|
|
61
|
+
const rule = INVERTED_PUNCTUATION_LOCALES[typedLocale];
|
|
62
|
+
const content = await loader();
|
|
63
|
+
const strings = translatableStrings(content as Record<string, unknown>);
|
|
64
|
+
const missingQuestions = strings.flatMap((text) =>
|
|
65
|
+
findMissingInvertedMarks(text, rule.questionStart, rule.questionEnd)
|
|
66
|
+
);
|
|
67
|
+
const missingExclamations = strings.flatMap((text) =>
|
|
68
|
+
findMissingInvertedMarks(text, rule.exclamationStart, rule.exclamationEnd)
|
|
69
|
+
);
|
|
70
|
+
const failures = [...missingQuestions, ...missingExclamations];
|
|
71
|
+
|
|
72
|
+
expect(
|
|
73
|
+
failures,
|
|
74
|
+
[
|
|
75
|
+
`Missing opening punctuation marks in ${tool.entry.id}/${typedLocale} (${rule.language}).`,
|
|
76
|
+
`Questions must use ${rule.questionStart}...${rule.questionEnd} and exclamations must use ${rule.exclamationStart}...${rule.exclamationEnd}.`,
|
|
77
|
+
`Examples: ${failures.slice(0, 5).join(' | ')}`,
|
|
78
|
+
].join(' '),
|
|
79
|
+
).toHaveLength(0);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|