@jjlmoya/utils-tools 1.12.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.
Files changed (240) hide show
  1. package/package.json +1 -1
  2. package/src/category/i18n/de.ts +3 -3
  3. package/src/category/i18n/en.ts +1 -1
  4. package/src/category/i18n/es.ts +1 -1
  5. package/src/category/i18n/fr.ts +16 -16
  6. package/src/category/i18n/id.ts +1 -1
  7. package/src/category/i18n/nl.ts +1 -1
  8. package/src/category/i18n/pl.ts +1 -1
  9. package/src/category/i18n/pt.ts +1 -1
  10. package/src/category/i18n/ru.ts +10 -10
  11. package/src/category/i18n/sv.ts +1 -1
  12. package/src/category/i18n/tr.ts +1 -1
  13. package/src/category/i18n/zh.ts +4 -4
  14. package/src/layouts/PreviewLayout.astro +7 -2
  15. package/src/pages/[locale]/[slug].astro +30 -13
  16. package/src/tests/diacritics_density.test.ts +118 -0
  17. package/src/tests/inverted_punctuation.test.ts +84 -0
  18. package/src/tests/locale_completeness.test.ts +3 -20
  19. package/src/tests/no_en_dash.test.ts +70 -0
  20. package/src/tests/script_density.test.ts +94 -0
  21. package/src/tests/shared-test-helpers.ts +56 -0
  22. package/src/tests/tool_exports.test.ts +34 -0
  23. package/src/tool/date-diff-calculator/bibliography.astro +2 -10
  24. package/src/tool/date-diff-calculator/bibliography.ts +7 -0
  25. package/src/tool/date-diff-calculator/i18n/de.ts +8 -11
  26. package/src/tool/date-diff-calculator/i18n/en.ts +4 -7
  27. package/src/tool/date-diff-calculator/i18n/es.ts +4 -7
  28. package/src/tool/date-diff-calculator/i18n/fr.ts +4 -7
  29. package/src/tool/date-diff-calculator/i18n/id.ts +4 -7
  30. package/src/tool/date-diff-calculator/i18n/it.ts +4 -7
  31. package/src/tool/date-diff-calculator/i18n/ja.ts +4 -7
  32. package/src/tool/date-diff-calculator/i18n/ko.ts +4 -7
  33. package/src/tool/date-diff-calculator/i18n/nl.ts +4 -7
  34. package/src/tool/date-diff-calculator/i18n/pl.ts +5 -8
  35. package/src/tool/date-diff-calculator/i18n/pt.ts +4 -7
  36. package/src/tool/date-diff-calculator/i18n/ru.ts +8 -11
  37. package/src/tool/date-diff-calculator/i18n/sv.ts +4 -7
  38. package/src/tool/date-diff-calculator/i18n/tr.ts +4 -7
  39. package/src/tool/date-diff-calculator/i18n/zh.ts +7 -10
  40. package/src/tool/date-diff-calculator/seo.astro +2 -1
  41. package/src/tool/drive-direct-link/bibliography.astro +2 -10
  42. package/src/tool/drive-direct-link/bibliography.ts +6 -0
  43. package/src/tool/drive-direct-link/i18n/de.ts +8 -10
  44. package/src/tool/drive-direct-link/i18n/en.ts +4 -6
  45. package/src/tool/drive-direct-link/i18n/es.ts +5 -7
  46. package/src/tool/drive-direct-link/i18n/fr.ts +5 -7
  47. package/src/tool/drive-direct-link/i18n/id.ts +4 -6
  48. package/src/tool/drive-direct-link/i18n/it.ts +4 -6
  49. package/src/tool/drive-direct-link/i18n/ja.ts +4 -6
  50. package/src/tool/drive-direct-link/i18n/ko.ts +4 -6
  51. package/src/tool/drive-direct-link/i18n/nl.ts +4 -6
  52. package/src/tool/drive-direct-link/i18n/pl.ts +8 -10
  53. package/src/tool/drive-direct-link/i18n/pt.ts +4 -6
  54. package/src/tool/drive-direct-link/i18n/ru.ts +12 -14
  55. package/src/tool/drive-direct-link/i18n/sv.ts +4 -6
  56. package/src/tool/drive-direct-link/i18n/tr.ts +4 -6
  57. package/src/tool/drive-direct-link/i18n/zh.ts +8 -10
  58. package/src/tool/drive-direct-link/seo.astro +2 -1
  59. package/src/tool/email-list-cleaner/bibliography.astro +2 -10
  60. package/src/tool/email-list-cleaner/bibliography.ts +7 -0
  61. package/src/tool/email-list-cleaner/i18n/de.ts +6 -9
  62. package/src/tool/email-list-cleaner/i18n/en.ts +4 -7
  63. package/src/tool/email-list-cleaner/i18n/es.ts +4 -7
  64. package/src/tool/email-list-cleaner/i18n/fr.ts +4 -7
  65. package/src/tool/email-list-cleaner/i18n/id.ts +4 -7
  66. package/src/tool/email-list-cleaner/i18n/it.ts +4 -7
  67. package/src/tool/email-list-cleaner/i18n/ja.ts +4 -7
  68. package/src/tool/email-list-cleaner/i18n/ko.ts +4 -7
  69. package/src/tool/email-list-cleaner/i18n/nl.ts +4 -7
  70. package/src/tool/email-list-cleaner/i18n/pl.ts +6 -9
  71. package/src/tool/email-list-cleaner/i18n/pt.ts +4 -7
  72. package/src/tool/email-list-cleaner/i18n/ru.ts +11 -14
  73. package/src/tool/email-list-cleaner/i18n/sv.ts +5 -8
  74. package/src/tool/email-list-cleaner/i18n/tr.ts +4 -7
  75. package/src/tool/email-list-cleaner/i18n/zh.ts +7 -10
  76. package/src/tool/email-list-cleaner/seo.astro +2 -1
  77. package/src/tool/env-badge-spain/bibliography.astro +2 -10
  78. package/src/tool/env-badge-spain/bibliography.ts +6 -0
  79. package/src/tool/env-badge-spain/i18n/de.ts +4 -6
  80. package/src/tool/env-badge-spain/i18n/en.ts +4 -6
  81. package/src/tool/env-badge-spain/i18n/es.ts +4 -6
  82. package/src/tool/env-badge-spain/i18n/fr.ts +4 -6
  83. package/src/tool/env-badge-spain/i18n/id.ts +4 -6
  84. package/src/tool/env-badge-spain/i18n/it.ts +4 -6
  85. package/src/tool/env-badge-spain/i18n/ja.ts +4 -6
  86. package/src/tool/env-badge-spain/i18n/ko.ts +4 -6
  87. package/src/tool/env-badge-spain/i18n/nl.ts +4 -6
  88. package/src/tool/env-badge-spain/i18n/pl.ts +4 -6
  89. package/src/tool/env-badge-spain/i18n/pt.ts +4 -6
  90. package/src/tool/env-badge-spain/i18n/ru.ts +14 -16
  91. package/src/tool/env-badge-spain/i18n/sv.ts +4 -6
  92. package/src/tool/env-badge-spain/i18n/tr.ts +4 -6
  93. package/src/tool/env-badge-spain/i18n/zh.ts +4 -6
  94. package/src/tool/env-badge-spain/seo.astro +2 -1
  95. package/src/tool/morse-beacon/bibliography.astro +2 -10
  96. package/src/tool/morse-beacon/bibliography.ts +7 -0
  97. package/src/tool/morse-beacon/i18n/de.ts +8 -11
  98. package/src/tool/morse-beacon/i18n/en.ts +6 -9
  99. package/src/tool/morse-beacon/i18n/es.ts +6 -9
  100. package/src/tool/morse-beacon/i18n/fr.ts +9 -12
  101. package/src/tool/morse-beacon/i18n/id.ts +6 -9
  102. package/src/tool/morse-beacon/i18n/it.ts +6 -9
  103. package/src/tool/morse-beacon/i18n/ja.ts +6 -9
  104. package/src/tool/morse-beacon/i18n/ko.ts +6 -9
  105. package/src/tool/morse-beacon/i18n/nl.ts +6 -9
  106. package/src/tool/morse-beacon/i18n/pl.ts +7 -10
  107. package/src/tool/morse-beacon/i18n/pt.ts +6 -9
  108. package/src/tool/morse-beacon/i18n/ru.ts +7 -10
  109. package/src/tool/morse-beacon/i18n/sv.ts +6 -9
  110. package/src/tool/morse-beacon/i18n/tr.ts +6 -9
  111. package/src/tool/morse-beacon/i18n/zh.ts +7 -10
  112. package/src/tool/morse-beacon/seo.astro +2 -1
  113. package/src/tool/password-generator/bibliography.astro +2 -10
  114. package/src/tool/password-generator/bibliography.ts +8 -0
  115. package/src/tool/password-generator/i18n/de.ts +9 -13
  116. package/src/tool/password-generator/i18n/en.ts +4 -8
  117. package/src/tool/password-generator/i18n/es.ts +4 -8
  118. package/src/tool/password-generator/i18n/fr.ts +5 -9
  119. package/src/tool/password-generator/i18n/id.ts +4 -8
  120. package/src/tool/password-generator/i18n/it.ts +4 -8
  121. package/src/tool/password-generator/i18n/ja.ts +4 -8
  122. package/src/tool/password-generator/i18n/ko.ts +4 -8
  123. package/src/tool/password-generator/i18n/nl.ts +4 -8
  124. package/src/tool/password-generator/i18n/pl.ts +7 -11
  125. package/src/tool/password-generator/i18n/pt.ts +4 -8
  126. package/src/tool/password-generator/i18n/ru.ts +8 -12
  127. package/src/tool/password-generator/i18n/sv.ts +4 -8
  128. package/src/tool/password-generator/i18n/tr.ts +4 -8
  129. package/src/tool/password-generator/i18n/zh.ts +7 -11
  130. package/src/tool/password-generator/seo.astro +2 -1
  131. package/src/tool/routes/bibliography.astro +2 -10
  132. package/src/tool/routes/bibliography.ts +9 -0
  133. package/src/tool/routes/i18n/de.ts +5 -10
  134. package/src/tool/routes/i18n/en.ts +4 -9
  135. package/src/tool/routes/i18n/es.ts +4 -9
  136. package/src/tool/routes/i18n/fr.ts +7 -12
  137. package/src/tool/routes/i18n/id.ts +4 -9
  138. package/src/tool/routes/i18n/it.ts +4 -9
  139. package/src/tool/routes/i18n/ja.ts +4 -9
  140. package/src/tool/routes/i18n/ko.ts +4 -9
  141. package/src/tool/routes/i18n/nl.ts +4 -9
  142. package/src/tool/routes/i18n/pl.ts +4 -9
  143. package/src/tool/routes/i18n/pt.ts +4 -9
  144. package/src/tool/routes/i18n/ru.ts +7 -12
  145. package/src/tool/routes/i18n/sv.ts +4 -9
  146. package/src/tool/routes/i18n/tr.ts +4 -9
  147. package/src/tool/routes/i18n/zh.ts +5 -10
  148. package/src/tool/routes/seo.astro +2 -1
  149. package/src/tool/rule-of-three/bibliography.astro +2 -10
  150. package/src/tool/rule-of-three/bibliography.ts +6 -0
  151. package/src/tool/rule-of-three/i18n/de.ts +6 -8
  152. package/src/tool/rule-of-three/i18n/en.ts +4 -6
  153. package/src/tool/rule-of-three/i18n/es.ts +4 -6
  154. package/src/tool/rule-of-three/i18n/fr.ts +11 -13
  155. package/src/tool/rule-of-three/i18n/id.ts +4 -6
  156. package/src/tool/rule-of-three/i18n/it.ts +4 -6
  157. package/src/tool/rule-of-three/i18n/ja.ts +4 -6
  158. package/src/tool/rule-of-three/i18n/ko.ts +4 -6
  159. package/src/tool/rule-of-three/i18n/nl.ts +6 -8
  160. package/src/tool/rule-of-three/i18n/pl.ts +6 -8
  161. package/src/tool/rule-of-three/i18n/pt.ts +4 -6
  162. package/src/tool/rule-of-three/i18n/ru.ts +7 -9
  163. package/src/tool/rule-of-three/i18n/sv.ts +4 -6
  164. package/src/tool/rule-of-three/i18n/tr.ts +5 -7
  165. package/src/tool/rule-of-three/i18n/zh.ts +6 -8
  166. package/src/tool/rule-of-three/seo.astro +2 -1
  167. package/src/tool/seo-content-optimizer/bibliography.astro +2 -10
  168. package/src/tool/seo-content-optimizer/bibliography.ts +6 -0
  169. package/src/tool/seo-content-optimizer/i18n/de.ts +4 -6
  170. package/src/tool/seo-content-optimizer/i18n/en.ts +4 -6
  171. package/src/tool/seo-content-optimizer/i18n/es.ts +4 -6
  172. package/src/tool/seo-content-optimizer/i18n/fr.ts +7 -9
  173. package/src/tool/seo-content-optimizer/i18n/id.ts +4 -6
  174. package/src/tool/seo-content-optimizer/i18n/it.ts +4 -6
  175. package/src/tool/seo-content-optimizer/i18n/ja.ts +4 -6
  176. package/src/tool/seo-content-optimizer/i18n/ko.ts +4 -6
  177. package/src/tool/seo-content-optimizer/i18n/nl.ts +4 -6
  178. package/src/tool/seo-content-optimizer/i18n/pl.ts +4 -6
  179. package/src/tool/seo-content-optimizer/i18n/pt.ts +4 -6
  180. package/src/tool/seo-content-optimizer/i18n/ru.ts +5 -7
  181. package/src/tool/seo-content-optimizer/i18n/sv.ts +4 -6
  182. package/src/tool/seo-content-optimizer/i18n/tr.ts +4 -6
  183. package/src/tool/seo-content-optimizer/i18n/zh.ts +4 -6
  184. package/src/tool/seo-content-optimizer/seo.astro +2 -1
  185. package/src/tool/speed-reader/bibliography.astro +2 -10
  186. package/src/tool/speed-reader/bibliography.ts +10 -0
  187. package/src/tool/speed-reader/i18n/de.ts +7 -13
  188. package/src/tool/speed-reader/i18n/en.ts +6 -12
  189. package/src/tool/speed-reader/i18n/es.ts +4 -10
  190. package/src/tool/speed-reader/i18n/fr.ts +13 -19
  191. package/src/tool/speed-reader/i18n/id.ts +6 -12
  192. package/src/tool/speed-reader/i18n/it.ts +6 -12
  193. package/src/tool/speed-reader/i18n/ja.ts +4 -10
  194. package/src/tool/speed-reader/i18n/ko.ts +4 -10
  195. package/src/tool/speed-reader/i18n/nl.ts +6 -12
  196. package/src/tool/speed-reader/i18n/pl.ts +7 -13
  197. package/src/tool/speed-reader/i18n/pt.ts +6 -12
  198. package/src/tool/speed-reader/i18n/ru.ts +14 -20
  199. package/src/tool/speed-reader/i18n/sv.ts +6 -12
  200. package/src/tool/speed-reader/i18n/tr.ts +4 -10
  201. package/src/tool/speed-reader/i18n/zh.ts +8 -14
  202. package/src/tool/speed-reader/seo.astro +2 -1
  203. package/src/tool/text-pixel-calculator/bibliography.astro +2 -10
  204. package/src/tool/text-pixel-calculator/bibliography.ts +6 -0
  205. package/src/tool/text-pixel-calculator/i18n/de.ts +6 -8
  206. package/src/tool/text-pixel-calculator/i18n/en.ts +4 -6
  207. package/src/tool/text-pixel-calculator/i18n/es.ts +4 -6
  208. package/src/tool/text-pixel-calculator/i18n/fr.ts +5 -7
  209. package/src/tool/text-pixel-calculator/i18n/id.ts +4 -6
  210. package/src/tool/text-pixel-calculator/i18n/it.ts +4 -6
  211. package/src/tool/text-pixel-calculator/i18n/ja.ts +4 -6
  212. package/src/tool/text-pixel-calculator/i18n/ko.ts +4 -6
  213. package/src/tool/text-pixel-calculator/i18n/nl.ts +4 -6
  214. package/src/tool/text-pixel-calculator/i18n/pl.ts +7 -9
  215. package/src/tool/text-pixel-calculator/i18n/pt.ts +4 -6
  216. package/src/tool/text-pixel-calculator/i18n/ru.ts +8 -10
  217. package/src/tool/text-pixel-calculator/i18n/sv.ts +4 -6
  218. package/src/tool/text-pixel-calculator/i18n/tr.ts +4 -6
  219. package/src/tool/text-pixel-calculator/i18n/zh.ts +7 -9
  220. package/src/tool/text-pixel-calculator/seo.astro +2 -1
  221. package/src/tool/whatsapp-link/bibliography.astro +2 -10
  222. package/src/tool/whatsapp-link/bibliography.ts +6 -0
  223. package/src/tool/whatsapp-link/i18n/de.ts +8 -10
  224. package/src/tool/whatsapp-link/i18n/en.ts +4 -6
  225. package/src/tool/whatsapp-link/i18n/es.ts +6 -8
  226. package/src/tool/whatsapp-link/i18n/fr.ts +6 -8
  227. package/src/tool/whatsapp-link/i18n/id.ts +4 -6
  228. package/src/tool/whatsapp-link/i18n/it.ts +4 -6
  229. package/src/tool/whatsapp-link/i18n/ja.ts +4 -6
  230. package/src/tool/whatsapp-link/i18n/ko.ts +4 -6
  231. package/src/tool/whatsapp-link/i18n/nl.ts +4 -6
  232. package/src/tool/whatsapp-link/i18n/pl.ts +8 -10
  233. package/src/tool/whatsapp-link/i18n/pt.ts +4 -6
  234. package/src/tool/whatsapp-link/i18n/ru.ts +9 -11
  235. package/src/tool/whatsapp-link/i18n/sv.ts +4 -6
  236. package/src/tool/whatsapp-link/i18n/tr.ts +4 -6
  237. package/src/tool/whatsapp-link/i18n/zh.ts +8 -10
  238. package/src/tool/whatsapp-link/seo.astro +2 -1
  239. package/src/tool/whatsapp-link/whatsapp-link-generator.css +41 -4
  240. package/src/types.ts +0 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-tools",
3
- "version": "1.12.0",
3
+ "version": "1.14.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -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 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.',
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 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.',
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 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.',
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',
@@ -120,7 +120,7 @@ export const content: CategoryLocaleContent = {
120
120
  },
121
121
  {
122
122
  type: 'paragraph',
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.",
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',
@@ -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 ¿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.',
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',
@@ -7,12 +7,12 @@ export const content: CategoryLocaleContent = {
7
7
  seo: [
8
8
  {
9
9
  type: 'title',
10
- text: 'Utilitaires pratiques : solutions numériques pour le quotidien',
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 : de la sécurité de vos comptes à l'optimisation de vos déplacements physiques.",
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 : génération de mots de passe robustes',
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 : itinéraires et proportions',
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 : lecture rapide RSVP',
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 : WhatsApp et SEO',
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 : balise Morse et urgences",
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 sécurité : audit des mots de passe compromis',
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 : réduire la friction, augmenter la productivité',
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 : Pomodoro et isochrones",
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 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.",
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 : masse, volume, température, énergie",
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 : 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).",
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: "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 ».",
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 : factorisation, primalité, diviseurs',
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: "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.",
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',
@@ -120,7 +120,7 @@ export const content: CategoryLocaleContent = {
120
120
  },
121
121
  {
122
122
  type: 'paragraph',
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.',
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',
@@ -120,7 +120,7 @@ export const content: CategoryLocaleContent = {
120
120
  },
121
121
  {
122
122
  type: 'paragraph',
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.',
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',
@@ -120,7 +120,7 @@ export const content: CategoryLocaleContent = {
120
120
  },
121
121
  {
122
122
  type: 'paragraph',
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.',
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',
@@ -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 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.',
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',
@@ -12,7 +12,7 @@ export const content: CategoryLocaleContent = {
12
12
  },
13
13
  {
14
14
  type: 'paragraph',
15
- html: 'В цифровом мире, перенасыщенном сложными приложениями, иногда больше всего нам нужен <strong>простой, быстрый и эффективный инструмент</strong>. В этом разделе мы собрали «пояс с инструментами» из <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: 'Первая линия обороны в интернете это сложный пароль. Наш <strong>генератор паролей</strong> использует криптографические алгоритмы случайности для создания ключей, которые невозможно предсказать или взломать методом перебора (brute force). Настройте длину и тип символов в соответствии со стандартами безопасности 2026 года.',
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 Have I Been Pwned) позволяют проверить, циркулируют ли ваши учетные данные в даркнете. Наши утилиты включают интеграцию с API безопасности, чтобы вы точно знали, был ли ваш ключ когда-либо скомпрометирован.',
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, когда вы хотите с кем-то связаться это трение. Вручную рассчитывать скидку это трение. Наши инструменты устраняют эти повторяющиеся микрошаги. Оптимизатор маршрутов экономит вам 2 часа в неделю, если вы занимаетесь доставкой. Читалка RSVP экономит 3 часа чтения в неделю. В сумме за 52 недели это целые месяцы возвращенной жизни.',
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: 'Продуктивность это не только скорость, это разумное управление временем. Наша <strong>интерактивная техника Помодоро</strong> делит ваш рабочий день на сфокусированные блоки по 25 минут с рассчитанными перерывами для оптимального когнитивного восстановления. <strong>Изохронный калькулятор</strong> показывает, сколько времени займут ваши задачи на основе истории, позволяя планировать день реалистично, а не оптимистично.',
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 адресов электронной почты сколько из них настоящие? Наш <strong>пакетный валидатор данных</strong> проверяет формат, домен и принимает ли сервер почту для этого адреса. Для номеров телефонов он проверяет международный формат и географический номер. В бизнес-приложениях валидация данных предотвращает катастрофы: отправка писем на несуществующие адреса это трата ресурсов; звонки на поддельные номера это разочарование.',
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 это 10. Просто. Но если вы увеличите 100 на 10% (получится 110), а затем увеличите еще на 10%, будет ли результат 121 или 120? Результат 121 (сложная вариация). Наш <strong>продвинутый калькулятор процентов</strong> обрабатывает вложенные вариации, множественные скидки и расчеты «маржи» против «наценки». Розничный торговец, который покупает по 100 евро и добавляет наценку 50%, продает по 150 евро; если затем он применит скидку 20%, он продаст по 120 евро (а не по 130). Разница в 10 евро это то, что многие предприятия теряют из-за неправильных расчетов.',
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',
@@ -120,7 +120,7 @@ export const content: CategoryLocaleContent = {
120
120
  },
121
121
  {
122
122
  type: 'paragraph',
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.',
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',
@@ -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 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.',
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',
@@ -12,7 +12,7 @@ export const content: CategoryLocaleContent = {
12
12
  },
13
13
  {
14
14
  type: 'paragraph',
15
- html: '在充斥着复杂应用的数字世界中,有时我们最需要的是 <strong>简单、快速且有效</strong> 的工具。在本节中,我们收集了一个“工具带”,其中包含 <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 个电子邮件地址的列表 —— 有多少是真的?我们的 <strong>批量数据验证器</strong> 验证格式、域名以及服务器是否接受该地址的邮件。对于电话号码,它验证国际格式和地理编号。在业务应用中,数据验证可防止灾难:向不存在的地址发送邮件是浪费;拨打虚假号码则是挫折。',
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> 处理嵌套变动、多重折扣以及“利润”与“加价”的计算。以 100 欧元购买并增加 50% 加价的零售商售价为 150 欧元;如果随后应用 20% 的折扣,则售价为 120 欧元(而非 130 欧元)。这 10 欧元的差异是许多企业因计算错误而损失的金额。',
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 { title, currentLocale = "es", localeUrls = {}, hasSidebar = false } = Astro.props;
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
-
@@ -34,18 +34,28 @@ export async function getStaticPaths() {
34
34
  ]),
35
35
  ) as Partial<Record<KnownLocale, string>>;
36
36
 
37
+ const firstLoader = entry.i18n.en ?? Object.values(entry.i18n)[0];
38
+ const englishSlug = firstLoader ? (await firstLoader()).slug : entry.id;
39
+
37
40
  for (const { locale, content } of localeContents) {
38
- const allToolsNav = await Promise.all(
39
- ALL_TOOLS.map(async ({ entry: navEntry }) => ({
40
- id: navEntry.id,
41
- title: (await navEntry.i18n[locale]!()).title,
42
- href: `/${locale}/${(await navEntry.i18n[locale]!()).slug}`,
43
- isActive: navEntry.id === entry.id,
44
- })),
45
- );
41
+ const allToolsNav = (
42
+ await Promise.all(
43
+ ALL_TOOLS.map(async ({ entry: navEntry }) => {
44
+ const loader = navEntry.i18n[locale] ?? navEntry.i18n.en;
45
+ if (!loader) return null;
46
+ const navContent = await loader();
47
+ return {
48
+ id: navEntry.id,
49
+ title: navContent.title,
50
+ href: `/${locale}/${navContent.slug}`,
51
+ isActive: navEntry.id === entry.id,
52
+ };
53
+ }),
54
+ )
55
+ ).filter(Boolean) as NavItem[];
46
56
  paths.push({
47
57
  params: { locale, slug: content.slug },
48
- props: { Component, locale, content, localeUrls, allToolsNav },
58
+ props: { Component, locale, content, localeUrls, allToolsNav, englishSlug },
49
59
  });
50
60
  }
51
61
  }
@@ -66,11 +76,17 @@ interface Props {
66
76
  content: ToolLocaleContent;
67
77
  localeUrls: Partial<Record<KnownLocale, string>>;
68
78
  allToolsNav: NavItem[];
79
+ englishSlug: string;
69
80
  }
70
81
 
71
- const { Component, locale, content, localeUrls, allToolsNav } = Astro.props;
82
+ const { Component, locale, content, localeUrls, allToolsNav, englishSlug } = Astro.props;
83
+
84
+ // eslint-disable-next-line custom/no-css-comments
85
+ const cssFiles = import.meta.glob("../../tool/**/*.css", { query: "?raw", import: "default" });
86
+ const cssKey = Object.keys(cssFiles).find((k) => k.endsWith(`/${englishSlug}.css`));
87
+ const toolCss = cssKey ? await cssFiles[cssKey]() as string : "";
72
88
 
73
- const seoContent: UtilitySEOContent = { locale, sections: content.seo };
89
+ const seoContent: UtilitySEOContent = { locale, sections: content.seo ?? [] };
74
90
 
75
91
  const words = content.title.split(" ");
76
92
  const titleHighlight = words[0] || "";
@@ -89,8 +105,9 @@ const titleBase = words.slice(1).join(" ") || "";
89
105
  tools={allToolsNav}
90
106
  />
91
107
  <Fragment slot="head">
108
+ {toolCss && <Fragment set:html={`<style is:inline>${toolCss}</style>`} />}
92
109
  {
93
- content.schemas.map((schema: unknown) => (
110
+ ( content.schemas ?? []).map((schema: unknown) => (
94
111
  <script
95
112
  is:inline
96
113
  type="application/ld+json"
@@ -116,7 +133,7 @@ const titleBase = words.slice(1).join(" ") || "";
116
133
  </section>
117
134
 
118
135
  <section class="section-faq">
119
- <FAQSection items={content.faq} />
136
+ <FAQSection items={content.faq} inLanguage={locale} />
120
137
  </section>
121
138
 
122
139
  <section class="section-bibliography">
@@ -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
+ });