@jjlmoya/utils-converters 1.16.0 → 1.18.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 +4 -2
- package/src/category/i18n/de.ts +1 -1
- package/src/category/i18n/fr.ts +9 -9
- package/src/category/i18n/ru.ts +1 -1
- package/src/category/i18n/sv.ts +1 -1
- package/src/data.ts +0 -21
- package/src/layouts/PreviewLayout.astro +2 -1
- package/src/pages/[locale]/[slug].astro +28 -12
- 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/pagespeed_best_practices.test.ts +198 -0
- package/src/tests/script_density.test.ts +94 -0
- package/src/tests/shared-test-helpers.ts +56 -0
- package/src/tests/tool_exports.test.ts +34 -0
- package/src/tool/avifAJpg/bibliography.ts +12 -0
- package/src/tool/avifAJpg/entry.ts +2 -0
- package/src/tool/avifAJpg/i18n/de.ts +2 -12
- package/src/tool/avifAJpg/i18n/en.ts +2 -12
- package/src/tool/avifAJpg/i18n/es.ts +2 -12
- package/src/tool/avifAJpg/i18n/fr.ts +11 -21
- package/src/tool/avifAJpg/i18n/id.ts +2 -12
- package/src/tool/avifAJpg/i18n/it.ts +2 -12
- package/src/tool/avifAJpg/i18n/ja.ts +1 -11
- package/src/tool/avifAJpg/i18n/ko.ts +1 -11
- package/src/tool/avifAJpg/i18n/nl.ts +2 -12
- package/src/tool/avifAJpg/i18n/pl.ts +2 -12
- package/src/tool/avifAJpg/i18n/pt.ts +2 -12
- package/src/tool/avifAJpg/i18n/ru.ts +7 -17
- package/src/tool/avifAJpg/i18n/sv.ts +2 -12
- package/src/tool/avifAJpg/i18n/tr.ts +1 -11
- package/src/tool/avifAJpg/i18n/zh.ts +4 -14
- package/src/tool/avifAJpg/seo.astro +2 -1
- package/src/tool/avifAPng/bibliography.ts +12 -0
- package/src/tool/avifAPng/entry.ts +2 -0
- package/src/tool/avifAPng/i18n/de.ts +4 -14
- package/src/tool/avifAPng/i18n/en.ts +4 -14
- package/src/tool/avifAPng/i18n/es.ts +4 -14
- package/src/tool/avifAPng/i18n/fr.ts +10 -20
- package/src/tool/avifAPng/i18n/id.ts +4 -14
- package/src/tool/avifAPng/i18n/it.ts +1 -11
- package/src/tool/avifAPng/i18n/ja.ts +1 -11
- package/src/tool/avifAPng/i18n/ko.ts +1 -11
- package/src/tool/avifAPng/i18n/nl.ts +4 -14
- package/src/tool/avifAPng/i18n/pl.ts +4 -14
- package/src/tool/avifAPng/i18n/pt.ts +4 -14
- package/src/tool/avifAPng/i18n/ru.ts +5 -15
- package/src/tool/avifAPng/i18n/sv.ts +4 -14
- package/src/tool/avifAPng/i18n/tr.ts +1 -11
- package/src/tool/avifAPng/i18n/zh.ts +3 -13
- package/src/tool/avifAPng/seo.astro +2 -1
- package/src/tool/avifAWebp/bibliography.ts +12 -0
- package/src/tool/avifAWebp/entry.ts +2 -0
- package/src/tool/avifAWebp/i18n/de.ts +6 -16
- package/src/tool/avifAWebp/i18n/en.ts +6 -16
- package/src/tool/avifAWebp/i18n/es.ts +6 -16
- package/src/tool/avifAWebp/i18n/fr.ts +13 -23
- package/src/tool/avifAWebp/i18n/id.ts +6 -16
- package/src/tool/avifAWebp/i18n/it.ts +1 -11
- package/src/tool/avifAWebp/i18n/ja.ts +1 -11
- package/src/tool/avifAWebp/i18n/ko.ts +2 -12
- package/src/tool/avifAWebp/i18n/nl.ts +6 -16
- package/src/tool/avifAWebp/i18n/pl.ts +6 -16
- package/src/tool/avifAWebp/i18n/pt.ts +6 -16
- package/src/tool/avifAWebp/i18n/ru.ts +7 -17
- package/src/tool/avifAWebp/i18n/sv.ts +6 -16
- package/src/tool/avifAWebp/i18n/tr.ts +2 -12
- package/src/tool/avifAWebp/i18n/zh.ts +6 -16
- package/src/tool/avifAWebp/seo.astro +2 -1
- package/src/tool/bmpAJpg/bibliography.ts +12 -0
- package/src/tool/bmpAJpg/entry.ts +2 -0
- package/src/tool/bmpAJpg/i18n/de.ts +1 -11
- package/src/tool/bmpAJpg/i18n/en.ts +1 -11
- package/src/tool/bmpAJpg/i18n/es.ts +1 -11
- package/src/tool/bmpAJpg/i18n/fr.ts +9 -19
- package/src/tool/bmpAJpg/i18n/id.ts +1 -11
- package/src/tool/bmpAJpg/i18n/it.ts +1 -11
- package/src/tool/bmpAJpg/i18n/ja.ts +1 -11
- package/src/tool/bmpAJpg/i18n/ko.ts +1 -11
- package/src/tool/bmpAJpg/i18n/nl.ts +1 -11
- package/src/tool/bmpAJpg/i18n/pl.ts +1 -11
- package/src/tool/bmpAJpg/i18n/pt.ts +1 -11
- package/src/tool/bmpAJpg/i18n/ru.ts +5 -15
- package/src/tool/bmpAJpg/i18n/sv.ts +1 -11
- package/src/tool/bmpAJpg/i18n/tr.ts +1 -11
- package/src/tool/bmpAJpg/i18n/zh.ts +1 -11
- package/src/tool/bmpAJpg/seo.astro +2 -1
- package/src/tool/bmpAPng/bibliography.ts +12 -0
- package/src/tool/bmpAPng/entry.ts +2 -0
- package/src/tool/bmpAPng/i18n/de.ts +2 -12
- package/src/tool/bmpAPng/i18n/en.ts +3 -13
- package/src/tool/bmpAPng/i18n/es.ts +2 -12
- package/src/tool/bmpAPng/i18n/fr.ts +12 -22
- package/src/tool/bmpAPng/i18n/id.ts +2 -12
- package/src/tool/bmpAPng/i18n/it.ts +2 -12
- package/src/tool/bmpAPng/i18n/ja.ts +1 -11
- package/src/tool/bmpAPng/i18n/ko.ts +1 -11
- package/src/tool/bmpAPng/i18n/nl.ts +2 -12
- package/src/tool/bmpAPng/i18n/pl.ts +2 -12
- package/src/tool/bmpAPng/i18n/pt.ts +2 -12
- package/src/tool/bmpAPng/i18n/ru.ts +9 -19
- package/src/tool/bmpAPng/i18n/sv.ts +2 -12
- package/src/tool/bmpAPng/i18n/tr.ts +1 -11
- package/src/tool/bmpAPng/i18n/zh.ts +1 -11
- package/src/tool/bmpAPng/seo.astro +2 -1
- package/src/tool/bmpAWebp/bibliography.ts +12 -0
- package/src/tool/bmpAWebp/entry.ts +2 -0
- package/src/tool/bmpAWebp/i18n/de.ts +8 -18
- package/src/tool/bmpAWebp/i18n/en.ts +8 -18
- package/src/tool/bmpAWebp/i18n/es.ts +8 -18
- package/src/tool/bmpAWebp/i18n/fr.ts +13 -23
- package/src/tool/bmpAWebp/i18n/id.ts +8 -18
- package/src/tool/bmpAWebp/i18n/it.ts +8 -18
- package/src/tool/bmpAWebp/i18n/ja.ts +7 -17
- package/src/tool/bmpAWebp/i18n/ko.ts +7 -17
- package/src/tool/bmpAWebp/i18n/nl.ts +8 -18
- package/src/tool/bmpAWebp/i18n/pl.ts +8 -18
- package/src/tool/bmpAWebp/i18n/pt.ts +8 -18
- package/src/tool/bmpAWebp/i18n/ru.ts +10 -20
- package/src/tool/bmpAWebp/i18n/sv.ts +8 -18
- package/src/tool/bmpAWebp/i18n/tr.ts +8 -18
- package/src/tool/bmpAWebp/i18n/zh.ts +8 -18
- package/src/tool/bmpAWebp/seo.astro +2 -1
- package/src/tool/gifAJpg/bibliography.ts +12 -0
- package/src/tool/gifAJpg/entry.ts +2 -0
- package/src/tool/gifAJpg/i18n/de.ts +1 -11
- package/src/tool/gifAJpg/i18n/en.ts +1 -11
- package/src/tool/gifAJpg/i18n/es.ts +1 -11
- package/src/tool/gifAJpg/i18n/fr.ts +9 -19
- package/src/tool/gifAJpg/i18n/id.ts +1 -11
- package/src/tool/gifAJpg/i18n/it.ts +1 -11
- package/src/tool/gifAJpg/i18n/ja.ts +1 -11
- package/src/tool/gifAJpg/i18n/ko.ts +1 -11
- package/src/tool/gifAJpg/i18n/nl.ts +1 -11
- package/src/tool/gifAJpg/i18n/pl.ts +1 -11
- package/src/tool/gifAJpg/i18n/pt.ts +1 -11
- package/src/tool/gifAJpg/i18n/ru.ts +2 -12
- package/src/tool/gifAJpg/i18n/sv.ts +1 -11
- package/src/tool/gifAJpg/i18n/tr.ts +1 -11
- package/src/tool/gifAJpg/i18n/zh.ts +1 -11
- package/src/tool/gifAJpg/seo.astro +2 -1
- package/src/tool/gifAPng/bibliography.ts +12 -0
- package/src/tool/gifAPng/entry.ts +2 -0
- package/src/tool/gifAPng/i18n/de.ts +2 -12
- package/src/tool/gifAPng/i18n/en.ts +3 -13
- package/src/tool/gifAPng/i18n/es.ts +2 -12
- package/src/tool/gifAPng/i18n/fr.ts +11 -21
- package/src/tool/gifAPng/i18n/id.ts +2 -12
- package/src/tool/gifAPng/i18n/it.ts +2 -12
- package/src/tool/gifAPng/i18n/ja.ts +1 -11
- package/src/tool/gifAPng/i18n/ko.ts +1 -11
- package/src/tool/gifAPng/i18n/nl.ts +2 -12
- package/src/tool/gifAPng/i18n/pl.ts +3 -13
- package/src/tool/gifAPng/i18n/pt.ts +2 -12
- package/src/tool/gifAPng/i18n/ru.ts +3 -13
- package/src/tool/gifAPng/i18n/sv.ts +3 -13
- package/src/tool/gifAPng/i18n/tr.ts +1 -11
- package/src/tool/gifAPng/i18n/zh.ts +2 -12
- package/src/tool/gifAPng/seo.astro +2 -1
- package/src/tool/gifAWebp/bibliography.ts +12 -0
- package/src/tool/gifAWebp/entry.ts +2 -0
- package/src/tool/gifAWebp/i18n/de.ts +3 -13
- package/src/tool/gifAWebp/i18n/en.ts +3 -13
- package/src/tool/gifAWebp/i18n/es.ts +1 -11
- package/src/tool/gifAWebp/i18n/fr.ts +11 -21
- package/src/tool/gifAWebp/i18n/id.ts +3 -13
- package/src/tool/gifAWebp/i18n/it.ts +1 -11
- package/src/tool/gifAWebp/i18n/ja.ts +1 -11
- package/src/tool/gifAWebp/i18n/ko.ts +1 -11
- package/src/tool/gifAWebp/i18n/nl.ts +3 -13
- package/src/tool/gifAWebp/i18n/pl.ts +3 -13
- package/src/tool/gifAWebp/i18n/pt.ts +3 -13
- package/src/tool/gifAWebp/i18n/ru.ts +7 -17
- package/src/tool/gifAWebp/i18n/sv.ts +3 -13
- package/src/tool/gifAWebp/i18n/tr.ts +1 -11
- package/src/tool/gifAWebp/i18n/zh.ts +4 -14
- package/src/tool/gifAWebp/seo.astro +2 -1
- package/src/tool/imagenBase64/bibliography.ts +12 -0
- package/src/tool/imagenBase64/entry.ts +2 -0
- package/src/tool/imagenBase64/i18n/de.ts +7 -17
- package/src/tool/imagenBase64/i18n/en.ts +7 -17
- package/src/tool/imagenBase64/i18n/es.ts +7 -17
- package/src/tool/imagenBase64/i18n/fr.ts +17 -27
- package/src/tool/imagenBase64/i18n/id.ts +7 -17
- package/src/tool/imagenBase64/i18n/it.ts +6 -16
- package/src/tool/imagenBase64/i18n/ja.ts +1 -11
- package/src/tool/imagenBase64/i18n/ko.ts +1 -11
- package/src/tool/imagenBase64/i18n/nl.ts +7 -17
- package/src/tool/imagenBase64/i18n/pl.ts +7 -17
- package/src/tool/imagenBase64/i18n/pt.ts +7 -17
- package/src/tool/imagenBase64/i18n/ru.ts +9 -19
- package/src/tool/imagenBase64/i18n/sv.ts +7 -17
- package/src/tool/imagenBase64/i18n/tr.ts +2 -12
- package/src/tool/imagenBase64/i18n/zh.ts +7 -17
- package/src/tool/imagenBase64/seo.astro +2 -1
- package/src/tool/jpgAIco/bibliography.ts +12 -0
- package/src/tool/jpgAIco/entry.ts +2 -0
- package/src/tool/jpgAIco/i18n/de.ts +9 -19
- package/src/tool/jpgAIco/i18n/en.ts +9 -19
- package/src/tool/jpgAIco/i18n/es.ts +7 -17
- package/src/tool/jpgAIco/i18n/fr.ts +13 -23
- package/src/tool/jpgAIco/i18n/id.ts +9 -19
- package/src/tool/jpgAIco/i18n/it.ts +3 -13
- package/src/tool/jpgAIco/i18n/ja.ts +2 -12
- package/src/tool/jpgAIco/i18n/ko.ts +2 -12
- package/src/tool/jpgAIco/i18n/nl.ts +9 -19
- package/src/tool/jpgAIco/i18n/pl.ts +9 -19
- package/src/tool/jpgAIco/i18n/pt.ts +9 -19
- package/src/tool/jpgAIco/i18n/ru.ts +11 -21
- package/src/tool/jpgAIco/i18n/sv.ts +9 -19
- package/src/tool/jpgAIco/i18n/tr.ts +3 -13
- package/src/tool/jpgAIco/i18n/zh.ts +9 -19
- package/src/tool/jpgAIco/seo.astro +2 -1
- package/src/tool/jpgAPng/bibliography.ts +12 -0
- package/src/tool/jpgAPng/entry.ts +2 -0
- package/src/tool/jpgAPng/i18n/de.ts +7 -17
- package/src/tool/jpgAPng/i18n/en.ts +9 -19
- package/src/tool/jpgAPng/i18n/es.ts +7 -17
- package/src/tool/jpgAPng/i18n/fr.ts +13 -23
- package/src/tool/jpgAPng/i18n/id.ts +7 -17
- package/src/tool/jpgAPng/i18n/it.ts +5 -15
- package/src/tool/jpgAPng/i18n/ja.ts +3 -13
- package/src/tool/jpgAPng/i18n/ko.ts +3 -13
- package/src/tool/jpgAPng/i18n/nl.ts +7 -17
- package/src/tool/jpgAPng/i18n/pl.ts +7 -17
- package/src/tool/jpgAPng/i18n/pt.ts +7 -17
- package/src/tool/jpgAPng/i18n/ru.ts +10 -20
- package/src/tool/jpgAPng/i18n/sv.ts +7 -17
- package/src/tool/jpgAPng/i18n/tr.ts +6 -16
- package/src/tool/jpgAPng/i18n/zh.ts +8 -18
- package/src/tool/jpgAPng/seo.astro +2 -1
- package/src/tool/jpgAWebp/bibliography.ts +12 -0
- package/src/tool/jpgAWebp/entry.ts +2 -0
- package/src/tool/jpgAWebp/i18n/de.ts +10 -20
- package/src/tool/jpgAWebp/i18n/en.ts +10 -20
- package/src/tool/jpgAWebp/i18n/es.ts +4 -14
- package/src/tool/jpgAWebp/i18n/fr.ts +16 -26
- package/src/tool/jpgAWebp/i18n/id.ts +10 -20
- package/src/tool/jpgAWebp/i18n/it.ts +6 -16
- package/src/tool/jpgAWebp/i18n/ja.ts +2 -12
- package/src/tool/jpgAWebp/i18n/ko.ts +3 -13
- package/src/tool/jpgAWebp/i18n/nl.ts +10 -20
- package/src/tool/jpgAWebp/i18n/pl.ts +10 -20
- package/src/tool/jpgAWebp/i18n/pt.ts +10 -20
- package/src/tool/jpgAWebp/i18n/ru.ts +10 -20
- package/src/tool/jpgAWebp/i18n/sv.ts +10 -20
- package/src/tool/jpgAWebp/i18n/tr.ts +5 -15
- package/src/tool/jpgAWebp/i18n/zh.ts +9 -19
- package/src/tool/jpgAWebp/seo.astro +2 -1
- package/src/tool/pngAIco/bibliography.ts +12 -0
- package/src/tool/pngAIco/entry.ts +2 -0
- package/src/tool/pngAIco/i18n/de.ts +8 -18
- package/src/tool/pngAIco/i18n/en.ts +8 -18
- package/src/tool/pngAIco/i18n/es.ts +8 -18
- package/src/tool/pngAIco/i18n/fr.ts +14 -24
- package/src/tool/pngAIco/i18n/id.ts +8 -18
- package/src/tool/pngAIco/i18n/it.ts +4 -14
- package/src/tool/pngAIco/i18n/ja.ts +2 -12
- package/src/tool/pngAIco/i18n/ko.ts +2 -12
- package/src/tool/pngAIco/i18n/nl.ts +8 -18
- package/src/tool/pngAIco/i18n/pl.ts +8 -18
- package/src/tool/pngAIco/i18n/pt.ts +8 -18
- package/src/tool/pngAIco/i18n/ru.ts +11 -21
- package/src/tool/pngAIco/i18n/sv.ts +8 -18
- package/src/tool/pngAIco/i18n/tr.ts +7 -17
- package/src/tool/pngAIco/i18n/zh.ts +8 -18
- package/src/tool/pngAIco/seo.astro +2 -1
- package/src/tool/pngAJpg/bibliography.ts +12 -0
- package/src/tool/pngAJpg/entry.ts +2 -0
- package/src/tool/pngAJpg/i18n/de.ts +1 -11
- package/src/tool/pngAJpg/i18n/en.ts +1 -11
- package/src/tool/pngAJpg/i18n/es.ts +1 -11
- package/src/tool/pngAJpg/i18n/fr.ts +3 -13
- package/src/tool/pngAJpg/i18n/id.ts +1 -11
- package/src/tool/pngAJpg/i18n/it.ts +1 -11
- package/src/tool/pngAJpg/i18n/ja.ts +1 -11
- package/src/tool/pngAJpg/i18n/ko.ts +1 -11
- package/src/tool/pngAJpg/i18n/nl.ts +1 -11
- package/src/tool/pngAJpg/i18n/pl.ts +7 -17
- package/src/tool/pngAJpg/i18n/pt.ts +1 -11
- package/src/tool/pngAJpg/i18n/ru.ts +9 -19
- package/src/tool/pngAJpg/i18n/sv.ts +1 -11
- package/src/tool/pngAJpg/i18n/tr.ts +1 -11
- package/src/tool/pngAJpg/i18n/zh.ts +6 -16
- package/src/tool/pngAJpg/seo.astro +2 -1
- package/src/tool/pngAWebp/bibliography.ts +16 -0
- package/src/tool/pngAWebp/entry.ts +2 -0
- package/src/tool/pngAWebp/i18n/de.ts +7 -21
- package/src/tool/pngAWebp/i18n/en.ts +7 -21
- package/src/tool/pngAWebp/i18n/es.ts +6 -20
- package/src/tool/pngAWebp/i18n/fr.ts +13 -27
- package/src/tool/pngAWebp/i18n/id.ts +7 -21
- package/src/tool/pngAWebp/i18n/it.ts +3 -17
- package/src/tool/pngAWebp/i18n/ja.ts +2 -16
- package/src/tool/pngAWebp/i18n/ko.ts +2 -16
- package/src/tool/pngAWebp/i18n/nl.ts +7 -21
- package/src/tool/pngAWebp/i18n/pl.ts +7 -21
- package/src/tool/pngAWebp/i18n/pt.ts +6 -20
- package/src/tool/pngAWebp/i18n/ru.ts +8 -22
- package/src/tool/pngAWebp/i18n/sv.ts +7 -21
- package/src/tool/pngAWebp/i18n/tr.ts +6 -20
- package/src/tool/pngAWebp/i18n/zh.ts +6 -20
- package/src/tool/pngAWebp/seo.astro +2 -1
- package/src/tool/svgAJpg/bibliography.ts +12 -0
- package/src/tool/svgAJpg/entry.ts +2 -0
- package/src/tool/svgAJpg/i18n/de.ts +2 -12
- package/src/tool/svgAJpg/i18n/en.ts +1 -11
- package/src/tool/svgAJpg/i18n/es.ts +1 -11
- package/src/tool/svgAJpg/i18n/fr.ts +6 -16
- package/src/tool/svgAJpg/i18n/id.ts +1 -11
- package/src/tool/svgAJpg/i18n/it.ts +1 -11
- package/src/tool/svgAJpg/i18n/ja.ts +1 -11
- package/src/tool/svgAJpg/i18n/ko.ts +1 -11
- package/src/tool/svgAJpg/i18n/nl.ts +1 -11
- package/src/tool/svgAJpg/i18n/pl.ts +1 -11
- package/src/tool/svgAJpg/i18n/pt.ts +1 -11
- package/src/tool/svgAJpg/i18n/ru.ts +3 -13
- package/src/tool/svgAJpg/i18n/sv.ts +1 -11
- package/src/tool/svgAJpg/i18n/tr.ts +1 -11
- package/src/tool/svgAJpg/i18n/zh.ts +1 -11
- package/src/tool/svgAJpg/seo.astro +2 -1
- package/src/tool/svgAPng/bibliography.ts +12 -0
- package/src/tool/svgAPng/entry.ts +2 -0
- package/src/tool/svgAPng/i18n/de.ts +3 -13
- package/src/tool/svgAPng/i18n/en.ts +3 -13
- package/src/tool/svgAPng/i18n/es.ts +1 -11
- package/src/tool/svgAPng/i18n/fr.ts +9 -19
- package/src/tool/svgAPng/i18n/id.ts +3 -13
- package/src/tool/svgAPng/i18n/it.ts +3 -13
- package/src/tool/svgAPng/i18n/ja.ts +1 -11
- package/src/tool/svgAPng/i18n/ko.ts +1 -11
- package/src/tool/svgAPng/i18n/nl.ts +3 -13
- package/src/tool/svgAPng/i18n/pl.ts +3 -13
- package/src/tool/svgAPng/i18n/pt.ts +3 -13
- package/src/tool/svgAPng/i18n/ru.ts +5 -15
- package/src/tool/svgAPng/i18n/sv.ts +3 -13
- package/src/tool/svgAPng/i18n/tr.ts +3 -13
- package/src/tool/svgAPng/i18n/zh.ts +3 -13
- package/src/tool/svgAPng/seo.astro +2 -1
- package/src/tool/webpAIco/bibliography.ts +12 -0
- package/src/tool/webpAIco/entry.ts +2 -0
- package/src/tool/webpAIco/i18n/de.ts +1 -11
- package/src/tool/webpAIco/i18n/en.ts +1 -11
- package/src/tool/webpAIco/i18n/es.ts +1 -11
- package/src/tool/webpAIco/i18n/fr.ts +5 -15
- package/src/tool/webpAIco/i18n/id.ts +1 -11
- package/src/tool/webpAIco/i18n/it.ts +1 -11
- package/src/tool/webpAIco/i18n/ja.ts +1 -11
- package/src/tool/webpAIco/i18n/ko.ts +1 -11
- package/src/tool/webpAIco/i18n/nl.ts +1 -11
- package/src/tool/webpAIco/i18n/pl.ts +1 -11
- package/src/tool/webpAIco/i18n/pt.ts +1 -11
- package/src/tool/webpAIco/i18n/ru.ts +3 -13
- package/src/tool/webpAIco/i18n/sv.ts +1 -11
- package/src/tool/webpAIco/i18n/tr.ts +1 -11
- package/src/tool/webpAIco/i18n/zh.ts +1 -12
- package/src/tool/webpAIco/seo.astro +2 -1
- package/src/tool/webpAJpg/bibliography.ts +16 -0
- package/src/tool/webpAJpg/entry.ts +2 -0
- package/src/tool/webpAJpg/i18n/de.ts +3 -17
- package/src/tool/webpAJpg/i18n/en.ts +3 -17
- package/src/tool/webpAJpg/i18n/es.ts +1 -15
- package/src/tool/webpAJpg/i18n/fr.ts +10 -24
- package/src/tool/webpAJpg/i18n/id.ts +3 -17
- package/src/tool/webpAJpg/i18n/it.ts +2 -16
- package/src/tool/webpAJpg/i18n/ja.ts +1 -15
- package/src/tool/webpAJpg/i18n/ko.ts +1 -15
- package/src/tool/webpAJpg/i18n/nl.ts +2 -16
- package/src/tool/webpAJpg/i18n/pl.ts +3 -17
- package/src/tool/webpAJpg/i18n/pt.ts +3 -17
- package/src/tool/webpAJpg/i18n/ru.ts +7 -21
- package/src/tool/webpAJpg/i18n/sv.ts +3 -17
- package/src/tool/webpAJpg/i18n/tr.ts +2 -16
- package/src/tool/webpAJpg/i18n/zh.ts +2 -16
- package/src/tool/webpAJpg/seo.astro +2 -1
- package/src/tool/webpAPng/bibliography.ts +16 -0
- package/src/tool/webpAPng/entry.ts +2 -0
- package/src/tool/webpAPng/i18n/de.ts +3 -17
- package/src/tool/webpAPng/i18n/en.ts +3 -17
- package/src/tool/webpAPng/i18n/es.ts +1 -15
- package/src/tool/webpAPng/i18n/fr.ts +9 -23
- package/src/tool/webpAPng/i18n/id.ts +3 -17
- package/src/tool/webpAPng/i18n/it.ts +3 -17
- package/src/tool/webpAPng/i18n/ja.ts +1 -15
- package/src/tool/webpAPng/i18n/ko.ts +1 -15
- package/src/tool/webpAPng/i18n/nl.ts +3 -17
- package/src/tool/webpAPng/i18n/pl.ts +3 -17
- package/src/tool/webpAPng/i18n/pt.ts +3 -17
- package/src/tool/webpAPng/i18n/ru.ts +4 -18
- package/src/tool/webpAPng/i18n/sv.ts +3 -17
- package/src/tool/webpAPng/i18n/tr.ts +2 -17
- package/src/tool/webpAPng/i18n/zh.ts +3 -17
- package/src/tool/webpAPng/seo.astro +2 -1
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jjlmoya/utils-converters",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.ts",
|
|
9
9
|
"./data": "./src/data.ts",
|
|
10
|
-
"./entries": "./src/entries.ts"
|
|
10
|
+
"./entries": "./src/entries.ts",
|
|
11
|
+
"./runtime/*": "./src/tool/*/index.ts",
|
|
12
|
+
"./category-seo": "./src/category/seo.astro"
|
|
11
13
|
},
|
|
12
14
|
"files": [
|
|
13
15
|
"src",
|
package/src/category/i18n/de.ts
CHANGED
|
@@ -22,7 +22,7 @@ export const content: CategoryLocaleContent = {
|
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
type: 'paragraph',
|
|
25
|
-
html: 'Die Wahl des richtigen Bildformats hat direkten Einfluss auf die Web-Performance, die Gerätekompatibilität und die wahrgenommene visuelle Qualität. Unsere <strong>Online-Bild-Konverter</strong> decken die von Designern, Webentwicklern und Fotografen am häufigsten benötigten Konvertierungswege ab
|
|
25
|
+
html: 'Die Wahl des richtigen Bildformats hat direkten Einfluss auf die Web-Performance, die Gerätekompatibilität und die wahrgenommene visuelle Qualität. Unsere <strong>Online-Bild-Konverter</strong> decken die von Designern, Webentwicklern und Fotografen am häufigsten benötigten Konvertierungswege ab - ganz ohne externe Server, die die Vertraulichkeit Ihrer Dateien gefährden könnten.',
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
type: 'title',
|
package/src/category/i18n/fr.ts
CHANGED
|
@@ -9,15 +9,15 @@ export const content: CategoryLocaleContent = {
|
|
|
9
9
|
type: 'summary',
|
|
10
10
|
title: 'Pourquoi utiliser nos convertisseurs',
|
|
11
11
|
items: [
|
|
12
|
-
'Conversion 100% locale
|
|
13
|
-
'Support de 21 combinaisons de formats
|
|
12
|
+
'Conversion 100% locale: vos images ne quittent jamais votre appareil.',
|
|
13
|
+
'Support de 21 combinaisons de formats: PNG, JPG, WebP, SVG, AVIF, GIF, BMP et ICO.',
|
|
14
14
|
'Traitement par lots avec téléchargement ZIP.',
|
|
15
15
|
'Sans inscription, sans limites, sans publicité intrusive.',
|
|
16
16
|
],
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
type: 'title',
|
|
20
|
-
text: 'Conversion d\'Images
|
|
20
|
+
text: 'Conversion d\'Images: Guide Complet des Formats',
|
|
21
21
|
level: 2,
|
|
22
22
|
},
|
|
23
23
|
{
|
|
@@ -26,12 +26,12 @@ export const content: CategoryLocaleContent = {
|
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
type: 'title',
|
|
29
|
-
text: 'PNG, JPG et WebP
|
|
29
|
+
text: 'PNG, JPG et WebP: Les Trois Piliers du Web Moderne',
|
|
30
30
|
level: 2,
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
type: 'paragraph',
|
|
34
|
-
html: 'Le <strong>format PNG</strong> se distingue par sa compression sans perte et son support de la transparence (canal alpha), étant le standard pour les logos, icônes et graphiques aux bords nets. Le <strong>format JPG</strong> offre une compatibilité universelle maximale et des tailles réduites pour les photos, mais sacrifie la qualité à chaque sauvegarde. Le <strong>format WebP</strong>, développé par Google, combine le meilleur des deux
|
|
34
|
+
html: 'Le <strong>format PNG</strong> se distingue par sa compression sans perte et son support de la transparence (canal alpha), étant le standard pour les logos, icônes et graphiques aux bords nets. Le <strong>format JPG</strong> offre une compatibilité universelle maximale et des tailles réduites pour les photos, mais sacrifie la qualité à chaque sauvegarde. Le <strong>format WebP</strong>, développé par Google, combine le meilleur des deux: une compression 30-40% supérieure à PNG/JPG avec support de la transparence et des animations.',
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
type: 'table',
|
|
@@ -47,7 +47,7 @@ export const content: CategoryLocaleContent = {
|
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
49
|
type: 'title',
|
|
50
|
-
text: 'Formats Nouvelle Génération
|
|
50
|
+
text: 'Formats Nouvelle Génération: AVIF et WebP',
|
|
51
51
|
level: 2,
|
|
52
52
|
},
|
|
53
53
|
{
|
|
@@ -61,7 +61,7 @@ export const content: CategoryLocaleContent = {
|
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
63
|
type: 'title',
|
|
64
|
-
text: 'SVG, BMP et ICO
|
|
64
|
+
text: 'SVG, BMP et ICO: Cas d\'Usage Spéciaux',
|
|
65
65
|
level: 2,
|
|
66
66
|
},
|
|
67
67
|
{
|
|
@@ -79,12 +79,12 @@ export const content: CategoryLocaleContent = {
|
|
|
79
79
|
},
|
|
80
80
|
{
|
|
81
81
|
type: 'title',
|
|
82
|
-
text: 'Confidentialité Totale
|
|
82
|
+
text: 'Confidentialité Totale: Conversion sans Serveurs',
|
|
83
83
|
level: 2,
|
|
84
84
|
},
|
|
85
85
|
{
|
|
86
86
|
type: 'paragraph',
|
|
87
|
-
html: 'Contrairement à d\'autres outils en ligne qui téléchargent vos images sur des serveurs distants, nos convertisseurs utilisent exclusivement les APIs natives du navigateur
|
|
87
|
+
html: 'Contrairement à d\'autres outils en ligne qui téléchargent vos images sur des serveurs distants, nos convertisseurs utilisent exclusivement les APIs natives du navigateur: <strong>Canvas 2D API</strong> pour la rastérisation, <strong>FileReader API</strong> pour lire les fichiers localement et <strong>Blob/URL.createObjectURL</strong> pour les téléchargements. Vous pouvez ainsi convertir des images confidentielles, des logos d\'entreprise ou des documents privés en toute sécurité.',
|
|
88
88
|
},
|
|
89
89
|
],
|
|
90
90
|
};
|
package/src/category/i18n/ru.ts
CHANGED
|
@@ -66,7 +66,7 @@ export const content: CategoryLocaleContent = {
|
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
68
|
type: 'paragraph',
|
|
69
|
-
html: '<strong>Файлы SVG</strong>
|
|
69
|
+
html: '<strong>Файлы SVG</strong> - это математические векторы, которые масштабируются без потерь до любого разрешения, что идеально подходит для логотипов и графического дизайна. Однако многие платформы и приложения для редактирования не поддерживают их напрямую, поэтому для распространения их необходимо растрировать в PNG или JPG. <strong>Битмап-файлы (BMP)</strong>, хотя и устарели для использования в Интернете, по-прежнему часто встречаются в среде Windows и устаревшем программном обеспечении для редактирования. Наконец, <strong>формат ICO</strong> является стандартом для веб-фавиконов и настройки значков Windows, требуя определенной структуры двоичного заголовка, которую наш конвертер генерирует нативно.',
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
72
|
type: 'stats',
|
package/src/category/i18n/sv.ts
CHANGED
|
@@ -31,7 +31,7 @@ export const content: CategoryLocaleContent = {
|
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
type: 'paragraph',
|
|
34
|
-
html: '<strong>PNG-formatet</strong> utmärker sig för sin förlustfria komprimering och stöd för transparens (alfakanal), och är standarden för logotyper, ikoner och grafik med skarpa kanter. <strong>JPG-formatet</strong> erbjuder maximal universell kompatibilitet och reducerade storlekar för foton, men offrar kvalitet vid varje sparande. <strong>WebP-formatet</strong>, utvecklat av Google, kombinerar det bästa av båda: 30
|
|
34
|
+
html: '<strong>PNG-formatet</strong> utmärker sig för sin förlustfria komprimering och stöd för transparens (alfakanal), och är standarden för logotyper, ikoner och grafik med skarpa kanter. <strong>JPG-formatet</strong> erbjuder maximal universell kompatibilitet och reducerade storlekar för foton, men offrar kvalitet vid varje sparande. <strong>WebP-formatet</strong>, utvecklat av Google, kombinerar det bästa av båda: 30-40 % bättre komprimering än PNG/JPG med stöd för transparens och animering, och är nu det rekommenderade formatet av Core Web Vitals för att förbättra LCP.',
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
type: 'table',
|
package/src/data.ts
CHANGED
|
@@ -1,24 +1,3 @@
|
|
|
1
|
-
export { pngAJpg } from './tool/pngAJpg';
|
|
2
|
-
export { jpgAPng } from './tool/jpgAPng';
|
|
3
|
-
export { webpAPng } from './tool/webpAPng';
|
|
4
|
-
export { webpAJpg } from './tool/webpAJpg';
|
|
5
|
-
export { pngAWebp } from './tool/pngAWebp';
|
|
6
|
-
export { jpgAWebp } from './tool/jpgAWebp';
|
|
7
|
-
export { svgAPng } from './tool/svgAPng';
|
|
8
|
-
export { svgAJpg } from './tool/svgAJpg';
|
|
9
|
-
export { imagenBase64 } from './tool/imagenBase64';
|
|
10
|
-
export { bmpAJpg } from './tool/bmpAJpg';
|
|
11
|
-
export { bmpAPng } from './tool/bmpAPng';
|
|
12
|
-
export { bmpAWebp } from './tool/bmpAWebp';
|
|
13
|
-
export { avifAJpg } from './tool/avifAJpg';
|
|
14
|
-
export { avifAPng } from './tool/avifAPng';
|
|
15
|
-
export { avifAWebp } from './tool/avifAWebp';
|
|
16
|
-
export { gifAJpg } from './tool/gifAJpg';
|
|
17
|
-
export { gifAPng } from './tool/gifAPng';
|
|
18
|
-
export { gifAWebp } from './tool/gifAWebp';
|
|
19
|
-
export { pngAIco } from './tool/pngAIco';
|
|
20
|
-
export { jpgAIco } from './tool/jpgAIco';
|
|
21
|
-
export { webpAIco } from './tool/webpAIco';
|
|
22
1
|
export { convertersCategory } from './category';
|
|
23
2
|
|
|
24
3
|
export type { ImageConverterUI } from './shared/ImageConverter.astro';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
2
|
import "@jjlmoya/utils-shared/theme.css";
|
|
3
3
|
import PreviewToolbar from "../components/PreviewToolbar.astro";
|
|
4
4
|
import type { KnownLocale } from "../types";
|
|
@@ -78,6 +78,7 @@ const { title, currentLocale = "es", localeUrls = {}, hasSidebar = false } = Ast
|
|
|
78
78
|
transition:
|
|
79
79
|
background-color 0.3s ease,
|
|
80
80
|
color 0.3s ease;
|
|
81
|
+
font-family: Inter, sans-serif;
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
main {
|
|
@@ -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 =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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,16 @@ 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
|
+
const cssFiles = import.meta.glob("../../tool/**/*.css", { query: "?raw", import: "default" });
|
|
85
|
+
const cssKey = Object.keys(cssFiles).find((k) => k.endsWith(`/${englishSlug}.css`));
|
|
86
|
+
const toolCss = cssKey ? await cssFiles[cssKey]() as string : "";
|
|
72
87
|
|
|
73
|
-
const seoContent: UtilitySEOContent = { locale, sections: content.seo };
|
|
88
|
+
const seoContent: UtilitySEOContent = { locale, sections: content.seo ?? [] };
|
|
74
89
|
|
|
75
90
|
const words = content.title.split(" ");
|
|
76
91
|
const titleHighlight = words[0] || "";
|
|
@@ -89,8 +104,9 @@ const titleBase = words.slice(1).join(" ") || "";
|
|
|
89
104
|
tools={allToolsNav}
|
|
90
105
|
/>
|
|
91
106
|
<Fragment slot="head">
|
|
107
|
+
{toolCss ? <Fragment set:html={`<style is:inline>${toolCss}</style>`} /> : null}
|
|
92
108
|
{
|
|
93
|
-
content.schemas.map((schema) => (
|
|
109
|
+
( content.schemas ?? []).map((schema) => (
|
|
94
110
|
<script
|
|
95
111
|
is:inline
|
|
96
112
|
type="application/ld+json"
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
function getFiles(dir: string): string[] {
|
|
6
|
+
const results: string[] = [];
|
|
7
|
+
if (!fs.existsSync(dir)) {
|
|
8
|
+
return results;
|
|
9
|
+
}
|
|
10
|
+
const list = fs.readdirSync(dir);
|
|
11
|
+
for (const file of list) {
|
|
12
|
+
const fullPath = path.join(dir, file);
|
|
13
|
+
const stat = fs.statSync(fullPath);
|
|
14
|
+
if (stat && stat.isDirectory()) {
|
|
15
|
+
if (file !== 'tests' && file !== 'node_modules' && file !== '.astro') {
|
|
16
|
+
results.push(...getFiles(fullPath));
|
|
17
|
+
}
|
|
18
|
+
} else {
|
|
19
|
+
results.push(fullPath);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return results;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isContentFile(filePath: string): boolean {
|
|
26
|
+
return /\\i18n\\/.test(filePath) || filePath.endsWith('bibliography.ts');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const srcDir = path.join(process.cwd(), 'src');
|
|
30
|
+
const scriptsDir = path.join(process.cwd(), 'scripts');
|
|
31
|
+
const filesToTest = [
|
|
32
|
+
...getFiles(srcDir).filter(isContentFile),
|
|
33
|
+
...getFiles(scriptsDir).filter(isContentFile),
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const aiTypographyGarbage = [
|
|
37
|
+
'\u2013',
|
|
38
|
+
'\u2014',
|
|
39
|
+
'\u2026',
|
|
40
|
+
'\u201C',
|
|
41
|
+
'\u201D',
|
|
42
|
+
'\u2018',
|
|
43
|
+
'\u2019',
|
|
44
|
+
'\u00AB',
|
|
45
|
+
'\u00BB',
|
|
46
|
+
'\u200B',
|
|
47
|
+
'\u201E',
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
describe('Typography Garbage Character Validation', () => {
|
|
51
|
+
filesToTest.forEach((filePath) => {
|
|
52
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
53
|
+
it(`should not contain typography garbage characters in ${relativePath}`, () => {
|
|
54
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
55
|
+
const hasAiPatterns = aiTypographyGarbage.some(char => content.includes(char));
|
|
56
|
+
expect(hasAiPatterns).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it(`should not contain space before colon in ${relativePath}`, () => {
|
|
60
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
61
|
+
const spaceBeforeColon = / : /.test(content);
|
|
62
|
+
expect(spaceBeforeColon).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it(`should not contain double hyphen in ${relativePath}`, () => {
|
|
66
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
67
|
+
expect(content).not.toContain('--');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|