@jjlmoya/utils-developer 1.29.1 → 1.31.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 +70 -70
- package/src/category/seo.astro +1 -1
- package/src/components/PreviewNavSidebar.astro +1 -1
- package/src/components/PreviewToolbar.astro +1 -1
- package/src/data.ts +1 -1
- package/src/env.d.ts +1 -1
- package/src/layouts/PreviewLayout.astro +1 -1
- package/src/pages/[locale]/[slug].astro +162 -162
- package/src/pages/index.astro +1 -1
- package/src/tests/entry_contract.test.ts +8 -2
- package/src/tests/faq_count.test.ts +1 -1
- package/src/tests/i18n_coverage.test.ts +2 -6
- package/src/tests/inverted_punctuation.test.ts +113 -113
- package/src/tests/mocks/astro_mock.js +1 -1
- package/src/tests/no_h1_in_components.test.ts +1 -1
- package/src/tests/promote_this_website_i18n.test.ts +33 -0
- package/src/tests/slug_language_code_format.test.ts +25 -25
- package/src/tests/slug_uniqueness.test.ts +86 -86
- package/src/tests/tool_validation.test.ts +15 -15
- package/src/tool/aspectRatio/i18n/de.ts +164 -164
- package/src/tool/aspectRatio/i18n/ja.ts +1 -1
- package/src/tool/aspectRatio/i18n/ko.ts +1 -1
- package/src/tool/aspectRatio/i18n/pl.ts +164 -164
- package/src/tool/aspectRatio/i18n/ru.ts +164 -164
- package/src/tool/aspectRatio/i18n/zh.ts +165 -165
- package/src/tool/aspectRatio/seo.astro +15 -15
- package/src/tool/calculadoraTiempoDatos/i18n/de.ts +143 -143
- package/src/tool/calculadoraTiempoDatos/i18n/ja.ts +1 -1
- package/src/tool/calculadoraTiempoDatos/i18n/ko.ts +1 -1
- package/src/tool/calculadoraTiempoDatos/i18n/pl.ts +143 -143
- package/src/tool/calculadoraTiempoDatos/i18n/ru.ts +144 -144
- package/src/tool/calculadoraTiempoDatos/i18n/sv.ts +143 -143
- package/src/tool/calculadoraTiempoDatos/i18n/zh.ts +144 -144
- package/src/tool/calculadoraTiempoDatos/seo.astro +15 -15
- package/src/tool/colorConverter/i18n/de.ts +120 -120
- package/src/tool/colorConverter/i18n/ja.ts +1 -1
- package/src/tool/colorConverter/i18n/ko.ts +1 -1
- package/src/tool/colorConverter/i18n/ru.ts +120 -120
- package/src/tool/colorConverter/i18n/zh.ts +1 -1
- package/src/tool/colorConverter/seo.astro +15 -15
- package/src/tool/conversorExcelCsvHtml/i18n/ja.ts +1 -1
- package/src/tool/conversorExcelCsvHtml/i18n/ko.ts +1 -1
- package/src/tool/conversorExcelCsvHtml/i18n/pl.ts +124 -124
- package/src/tool/conversorExcelCsvHtml/i18n/ru.ts +124 -124
- package/src/tool/conversorExcelCsvHtml/i18n/zh.ts +125 -125
- package/src/tool/conversorExcelCsvHtml/seo.astro +15 -15
- package/src/tool/cronGenerator/i18n/ja.ts +1 -1
- package/src/tool/cronGenerator/i18n/ko.ts +1 -1
- package/src/tool/cronGenerator/i18n/zh.ts +183 -183
- package/src/tool/cronGenerator/seo.astro +15 -15
- package/src/tool/cssSpecificityCalculator/i18n/ja.ts +1 -1
- package/src/tool/cssSpecificityCalculator/i18n/ko.ts +1 -1
- package/src/tool/cssSpecificityCalculator/i18n/zh.ts +1 -1
- package/src/tool/cssSpecificityCalculator/seo.astro +15 -15
- package/src/tool/cssToInlineConverter/i18n/ja.ts +1 -1
- package/src/tool/cssToInlineConverter/i18n/ko.ts +1 -1
- package/src/tool/cssToInlineConverter/i18n/zh.ts +1 -1
- package/src/tool/cssToInlineConverter/seo.astro +15 -15
- package/src/tool/duplicateCssRemover/i18n/ja.ts +1 -1
- package/src/tool/duplicateCssRemover/i18n/ko.ts +1 -1
- package/src/tool/duplicateCssRemover/i18n/zh.ts +1 -1
- package/src/tool/duplicateCssRemover/seo.astro +15 -15
- package/src/tool/generadorSecurityTxt/i18n/ja.ts +1 -1
- package/src/tool/generadorSecurityTxt/i18n/ko.ts +1 -1
- package/src/tool/generadorSecurityTxt/i18n/zh.ts +1 -1
- package/src/tool/generadorSecurityTxt/seo.astro +15 -15
- package/src/tool/hashGenerator/i18n/ja.ts +1 -1
- package/src/tool/hashGenerator/i18n/ko.ts +1 -1
- package/src/tool/hashGenerator/seo.astro +15 -15
- package/src/tool/inspectorCertificadosSsl/i18n/ja.ts +1 -1
- package/src/tool/inspectorCertificadosSsl/i18n/ko.ts +1 -1
- package/src/tool/inspectorCertificadosSsl/i18n/zh.ts +1 -1
- package/src/tool/inspectorCertificadosSsl/seo.astro +15 -15
- package/src/tool/jsonFormatter/seo.astro +15 -15
- package/src/tool/jwtDecoder/i18n/es.ts +248 -248
- package/src/tool/jwtDecoder/i18n/fr.ts +248 -248
- package/src/tool/jwtDecoder/i18n/tr.ts +248 -248
- package/src/tool/keycode/seo.astro +15 -15
- package/src/tool/llmCostCalculator/i18n/fr.ts +149 -149
- package/src/tool/llmCostCalculator/i18n/ja.ts +1 -1
- package/src/tool/llmCostCalculator/i18n/ko.ts +1 -1
- package/src/tool/llmCostCalculator/seo.astro +15 -15
- package/src/tool/mobileMockupGenerator/i18n/ja.ts +1 -1
- package/src/tool/mobileMockupGenerator/i18n/ko.ts +1 -1
- package/src/tool/mobileMockupGenerator/i18n/zh.ts +1 -1
- package/src/tool/mobileMockupGenerator/seo.astro +15 -15
- package/src/tool/musicalTypography/i18n/ja.ts +1 -1
- package/src/tool/musicalTypography/i18n/ko.ts +1 -1
- package/src/tool/musicalTypography/i18n/zh.ts +1 -1
- package/src/tool/musicalTypography/seo.astro +15 -15
- package/src/tool/placeholderGenerator/i18n/ja.ts +1 -1
- package/src/tool/placeholderGenerator/i18n/ko.ts +1 -1
- package/src/tool/placeholderGenerator/i18n/zh.ts +1 -1
- package/src/tool/placeholderGenerator/seo.astro +15 -15
- package/src/tool/promoteThisWebsite/entry.ts +13 -0
- package/src/tool/promoteThisWebsite/i18n/de.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/fr.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/id.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/it.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/ja.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/ko.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/nl.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/pl.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/pt.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/ru.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/sv.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/tr.ts +95 -0
- package/src/tool/promoteThisWebsite/i18n/zh.ts +95 -0
- package/src/tool/promptLibrary/i18n/ja.ts +1 -1
- package/src/tool/promptLibrary/i18n/ko.ts +1 -1
- package/src/tool/promptLibrary/i18n/zh.ts +1 -1
- package/src/tool/promptLibrary/seo.astro +15 -15
- package/src/tool/readabilityCalculator/i18n/ja.ts +1 -1
- package/src/tool/readabilityCalculator/i18n/ko.ts +1 -1
- package/src/tool/readabilityCalculator/i18n/zh.ts +1 -1
- package/src/tool/readabilityCalculator/seo.astro +15 -15
- package/src/tool/serpPixelSimulator/entry.ts +30 -30
- package/src/tool/serpPixelSimulator/i18n/en.ts +211 -211
- package/src/tool/serpPixelSimulator/i18n/fr.ts +214 -214
- package/src/tool/serpPixelSimulator/i18n/ru.ts +214 -214
- package/src/tool/serpPixelSimulator/logic.ts +18 -18
- package/src/tool/serpPixelSimulator/serp-pixel-simulator.css +195 -195
- package/src/tool/svgSanitizer/i18n/ja.ts +1 -1
- package/src/tool/svgSanitizer/i18n/ko.ts +1 -1
- package/src/tool/svgSanitizer/seo.astro +15 -15
- package/src/tool/svgToCss/i18n/ja.ts +1 -1
- package/src/tool/svgToCss/i18n/ko.ts +1 -1
- package/src/tool/svgToCss/seo.astro +15 -15
- package/src/tool/urlCleaner/i18n/ja.ts +1 -1
- package/src/tool/urlCleaner/i18n/ko.ts +1 -1
- package/src/tool/urlCleaner/i18n/zh.ts +1 -1
- package/src/tool/urlCleaner/seo.astro +15 -15
- package/src/tool/urlEncoderDecoder/i18n/es.ts +146 -146
- package/src/tool/urlEncoderDecoder/i18n/ko.ts +1 -1
- package/src/tool/urlEncoderDecoder/seo.astro +15 -15
- package/src/tool/utmGenerator/i18n/ja.ts +1 -1
- package/src/tool/utmGenerator/i18n/ko.ts +1 -1
- package/src/tool/utmGenerator/i18n/zh.ts +1 -1
- package/src/tool/utmGenerator/seo.astro +15 -15
- package/src/types.ts +1 -1
package/package.json
CHANGED
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@jjlmoya/utils-developer",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"main": "./src/index.ts",
|
|
6
|
-
"types": "./src/index.ts",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": "./src/index.ts",
|
|
9
|
-
"./data": "./src/data.ts",
|
|
10
|
-
"./entries": "./src/entries.ts",
|
|
11
|
-
"./runtime/*": "./src/tool/*/index.ts",
|
|
12
|
-
"./category-seo": "./src/category/seo.astro"
|
|
13
|
-
},
|
|
14
|
-
"files": [
|
|
15
|
-
"src",
|
|
16
|
-
"scripts"
|
|
17
|
-
],
|
|
18
|
-
"publishConfig": {
|
|
19
|
-
"access": "public"
|
|
20
|
-
},
|
|
21
|
-
"scripts": {
|
|
22
|
-
"dev": "astro dev",
|
|
23
|
-
"start": "astro dev",
|
|
24
|
-
"build": "astro build",
|
|
25
|
-
"preview": "astro preview",
|
|
26
|
-
"astro": "astro",
|
|
27
|
-
"lint": "eslint src/ --max-warnings 0 && stylelint \"src/**/*.{css,astro}\"",
|
|
28
|
-
"check": "astro check",
|
|
29
|
-
"type-check": "astro check",
|
|
30
|
-
"test": "vitest run",
|
|
31
|
-
"preversion": "npm run lint && npm run test && npm run build",
|
|
32
|
-
"postversion": "git push && git push --tags",
|
|
33
|
-
"patch": "npm version patch",
|
|
34
|
-
"minor": "npm version minor",
|
|
35
|
-
"major": "npm version major",
|
|
36
|
-
"postinstall": "node scripts/postinstall.mjs"
|
|
37
|
-
},
|
|
38
|
-
"lint-staged": {
|
|
39
|
-
"*.{ts,tsx,astro}": [
|
|
40
|
-
"eslint --fix"
|
|
41
|
-
]
|
|
42
|
-
},
|
|
43
|
-
"dependencies": {
|
|
44
|
-
"@iconify-json/mdi": "^1.2.3",
|
|
45
|
-
"@jjlmoya/prompagate": "^1.1.0",
|
|
46
|
-
"@jjlmoya/utils-shared": "1.2.0",
|
|
47
|
-
"@types/file-saver": "^2.0.7",
|
|
48
|
-
"astro": "^6.1.2",
|
|
49
|
-
"astro-icon": "^1.1.0",
|
|
50
|
-
"file-saver": "^2.0.5",
|
|
51
|
-
"jszip": "^3.10.1"
|
|
52
|
-
},
|
|
53
|
-
"devDependencies": {
|
|
54
|
-
"@astrojs/check": "^0.9.8",
|
|
55
|
-
"@types/jszip": "^3.4.0",
|
|
56
|
-
"eslint": "^9.39.4",
|
|
57
|
-
"eslint-plugin-astro": "^1.6.0",
|
|
58
|
-
"eslint-plugin-no-comments": "^1.1.10",
|
|
59
|
-
"husky": "^9.1.7",
|
|
60
|
-
"lint-staged": "^16.4.0",
|
|
61
|
-
"postcss-html": "^1.8.1",
|
|
62
|
-
"schema-dts": "^1.1.2",
|
|
63
|
-
"stylelint": "^17.6.0",
|
|
64
|
-
"stylelint-config-standard": "^40.0.0",
|
|
65
|
-
"stylelint-declaration-strict-value": "^1.11.1",
|
|
66
|
-
"typescript": "^5.4.0",
|
|
67
|
-
"typescript-eslint": "^8.58.0",
|
|
68
|
-
"vitest": "^4.1.2"
|
|
69
|
-
}
|
|
70
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@jjlmoya/utils-developer",
|
|
3
|
+
"version": "1.31.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts",
|
|
9
|
+
"./data": "./src/data.ts",
|
|
10
|
+
"./entries": "./src/entries.ts",
|
|
11
|
+
"./runtime/*": "./src/tool/*/index.ts",
|
|
12
|
+
"./category-seo": "./src/category/seo.astro"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"src",
|
|
16
|
+
"scripts"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"dev": "astro dev",
|
|
23
|
+
"start": "astro dev",
|
|
24
|
+
"build": "astro build",
|
|
25
|
+
"preview": "astro preview",
|
|
26
|
+
"astro": "astro",
|
|
27
|
+
"lint": "eslint src/ --max-warnings 0 && stylelint \"src/**/*.{css,astro}\"",
|
|
28
|
+
"check": "astro check",
|
|
29
|
+
"type-check": "astro check",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"preversion": "npm run lint && npm run test && npm run build",
|
|
32
|
+
"postversion": "git push && git push --tags",
|
|
33
|
+
"patch": "npm version patch",
|
|
34
|
+
"minor": "npm version minor",
|
|
35
|
+
"major": "npm version major",
|
|
36
|
+
"postinstall": "node scripts/postinstall.mjs"
|
|
37
|
+
},
|
|
38
|
+
"lint-staged": {
|
|
39
|
+
"*.{ts,tsx,astro}": [
|
|
40
|
+
"eslint --fix"
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@iconify-json/mdi": "^1.2.3",
|
|
45
|
+
"@jjlmoya/prompagate": "^1.1.0",
|
|
46
|
+
"@jjlmoya/utils-shared": "1.2.0",
|
|
47
|
+
"@types/file-saver": "^2.0.7",
|
|
48
|
+
"astro": "^6.1.2",
|
|
49
|
+
"astro-icon": "^1.1.0",
|
|
50
|
+
"file-saver": "^2.0.5",
|
|
51
|
+
"jszip": "^3.10.1"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@astrojs/check": "^0.9.8",
|
|
55
|
+
"@types/jszip": "^3.4.0",
|
|
56
|
+
"eslint": "^9.39.4",
|
|
57
|
+
"eslint-plugin-astro": "^1.6.0",
|
|
58
|
+
"eslint-plugin-no-comments": "^1.1.10",
|
|
59
|
+
"husky": "^9.1.7",
|
|
60
|
+
"lint-staged": "^16.4.0",
|
|
61
|
+
"postcss-html": "^1.8.1",
|
|
62
|
+
"schema-dts": "^1.1.2",
|
|
63
|
+
"stylelint": "^17.6.0",
|
|
64
|
+
"stylelint-config-standard": "^40.0.0",
|
|
65
|
+
"stylelint-declaration-strict-value": "^1.11.1",
|
|
66
|
+
"typescript": "^5.4.0",
|
|
67
|
+
"typescript-eslint": "^8.58.0",
|
|
68
|
+
"vitest": "^4.1.2"
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/category/seo.astro
CHANGED
package/src/data.ts
CHANGED
package/src/env.d.ts
CHANGED
|
@@ -1,162 +1,162 @@
|
|
|
1
|
-
---
|
|
2
|
-
import PreviewLayout from "../../layouts/PreviewLayout.astro";
|
|
3
|
-
import PreviewNavSidebar from "../../components/PreviewNavSidebar.astro";
|
|
4
|
-
import { ALL_TOOLS } from "../../index";
|
|
5
|
-
import {
|
|
6
|
-
UtilityHeader,
|
|
7
|
-
FAQSection,
|
|
8
|
-
Bibliography,
|
|
9
|
-
SEORenderer,
|
|
10
|
-
} from "@jjlmoya/utils-shared";
|
|
11
|
-
import type { KnownLocale, ToolLocaleContent } from "../../types";
|
|
12
|
-
import type { UtilitySEOContent } from "@jjlmoya/utils-shared";
|
|
13
|
-
|
|
14
|
-
export async function getStaticPaths() {
|
|
15
|
-
const paths = [];
|
|
16
|
-
|
|
17
|
-
for (const { entry, Component: lazyComp } of ALL_TOOLS) {
|
|
18
|
-
const { default: Component } = await lazyComp();
|
|
19
|
-
const localeEntries = Object.entries(entry.i18n) as [
|
|
20
|
-
KnownLocale,
|
|
21
|
-
() => Promise<ToolLocaleContent>,
|
|
22
|
-
][];
|
|
23
|
-
const localeContents = await Promise.all(
|
|
24
|
-
localeEntries.map(async ([locale, loader]) => ({
|
|
25
|
-
locale,
|
|
26
|
-
content: await loader(),
|
|
27
|
-
})),
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
const localeUrls = Object.fromEntries(
|
|
31
|
-
localeContents.map(({ locale, content }) => [
|
|
32
|
-
locale,
|
|
33
|
-
`/${locale}/${content.slug}`,
|
|
34
|
-
]),
|
|
35
|
-
) as Partial<Record<KnownLocale, string>>;
|
|
36
|
-
|
|
37
|
-
const firstLoader = entry.i18n.en ?? Object.values(entry.i18n)[0];
|
|
38
|
-
const englishSlug = firstLoader ? (await firstLoader()).slug : entry.id;
|
|
39
|
-
|
|
40
|
-
for (const { locale, content } of localeContents) {
|
|
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[];
|
|
56
|
-
paths.push({
|
|
57
|
-
params: { locale, slug: content.slug },
|
|
58
|
-
props: { Component, locale, content, localeUrls, allToolsNav, englishSlug },
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return paths;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
interface NavItem {
|
|
67
|
-
id: string;
|
|
68
|
-
title: string;
|
|
69
|
-
href: string;
|
|
70
|
-
isActive?: boolean;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
interface Props {
|
|
74
|
-
Component: unknown;
|
|
75
|
-
locale: KnownLocale;
|
|
76
|
-
content: ToolLocaleContent;
|
|
77
|
-
localeUrls: Partial<Record<KnownLocale, string>>;
|
|
78
|
-
allToolsNav: NavItem[];
|
|
79
|
-
englishSlug: string;
|
|
80
|
-
}
|
|
81
|
-
|
|
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 cssLoader = cssKey ? cssFiles[cssKey] : undefined;
|
|
87
|
-
const toolCss = cssLoader ? await cssLoader() as string : "";
|
|
88
|
-
|
|
89
|
-
const seoContent: UtilitySEOContent = { locale, sections: content.seo ?? [] };
|
|
90
|
-
|
|
91
|
-
const words = content.title.split(" ");
|
|
92
|
-
const titleHighlight = words[0] || "";
|
|
93
|
-
const titleBase = words.slice(1).join(" ") || "";
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
<PreviewLayout
|
|
97
|
-
title={content.title}
|
|
98
|
-
currentLocale={locale}
|
|
99
|
-
localeUrls={localeUrls}
|
|
100
|
-
hasSidebar={true}
|
|
101
|
-
>
|
|
102
|
-
<PreviewNavSidebar
|
|
103
|
-
slot="sidebar"
|
|
104
|
-
categoryTitle="Tools"
|
|
105
|
-
tools={allToolsNav}
|
|
106
|
-
/>
|
|
107
|
-
<Fragment slot="head">
|
|
108
|
-
{toolCss ? <Fragment set:html={`<style is:inline>${toolCss}</style>`} /> : null}
|
|
109
|
-
{
|
|
110
|
-
( content.schemas ?? []).map((schema: unknown) => (
|
|
111
|
-
<script
|
|
112
|
-
is:inline
|
|
113
|
-
type="application/ld+json"
|
|
114
|
-
set:html={JSON.stringify(schema)}
|
|
115
|
-
/>
|
|
116
|
-
))
|
|
117
|
-
}
|
|
118
|
-
</Fragment>
|
|
119
|
-
|
|
120
|
-
<div class="tool-page">
|
|
121
|
-
<UtilityHeader
|
|
122
|
-
titleHighlight={titleHighlight}
|
|
123
|
-
titleBase={titleBase}
|
|
124
|
-
description={content.description}
|
|
125
|
-
/>
|
|
126
|
-
|
|
127
|
-
<section class="section-tool">
|
|
128
|
-
<Component ui={content.ui} />
|
|
129
|
-
</section>
|
|
130
|
-
|
|
131
|
-
<section class="section-seo">
|
|
132
|
-
<SEORenderer content={seoContent} />
|
|
133
|
-
</section>
|
|
134
|
-
|
|
135
|
-
<section class="section-faq">
|
|
136
|
-
<FAQSection items={content.faq} title={content.ui.faqTitle} inLanguage={locale} />
|
|
137
|
-
</section>
|
|
138
|
-
|
|
139
|
-
<section class="section-bibliography">
|
|
140
|
-
<Bibliography links={content.bibliography} title={content.ui.bibliographyTitle} />
|
|
141
|
-
</section>
|
|
142
|
-
</div>
|
|
143
|
-
</PreviewLayout>
|
|
144
|
-
|
|
145
|
-
<style>
|
|
146
|
-
.tool-page {
|
|
147
|
-
display: flex;
|
|
148
|
-
flex-direction: column;
|
|
149
|
-
gap: 2rem;
|
|
150
|
-
}
|
|
151
|
-
.section-tool {
|
|
152
|
-
max-width: 1200px;
|
|
153
|
-
margin: 0 auto;
|
|
154
|
-
width: 100%;
|
|
155
|
-
}
|
|
156
|
-
.section-seo,
|
|
157
|
-
.section-faq,
|
|
158
|
-
.section-bibliography {
|
|
159
|
-
padding-top: 2rem;
|
|
160
|
-
border-top: 1px solid var(--border-color);
|
|
161
|
-
}
|
|
162
|
-
</style>
|
|
1
|
+
---
|
|
2
|
+
import PreviewLayout from "../../layouts/PreviewLayout.astro";
|
|
3
|
+
import PreviewNavSidebar from "../../components/PreviewNavSidebar.astro";
|
|
4
|
+
import { ALL_TOOLS } from "../../index";
|
|
5
|
+
import {
|
|
6
|
+
UtilityHeader,
|
|
7
|
+
FAQSection,
|
|
8
|
+
Bibliography,
|
|
9
|
+
SEORenderer,
|
|
10
|
+
} from "@jjlmoya/utils-shared";
|
|
11
|
+
import type { KnownLocale, ToolLocaleContent } from "../../types";
|
|
12
|
+
import type { UtilitySEOContent } from "@jjlmoya/utils-shared";
|
|
13
|
+
|
|
14
|
+
export async function getStaticPaths() {
|
|
15
|
+
const paths = [];
|
|
16
|
+
|
|
17
|
+
for (const { entry, Component: lazyComp } of ALL_TOOLS) {
|
|
18
|
+
const { default: Component } = await lazyComp();
|
|
19
|
+
const localeEntries = Object.entries(entry.i18n) as [
|
|
20
|
+
KnownLocale,
|
|
21
|
+
() => Promise<ToolLocaleContent>,
|
|
22
|
+
][];
|
|
23
|
+
const localeContents = await Promise.all(
|
|
24
|
+
localeEntries.map(async ([locale, loader]) => ({
|
|
25
|
+
locale,
|
|
26
|
+
content: await loader(),
|
|
27
|
+
})),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const localeUrls = Object.fromEntries(
|
|
31
|
+
localeContents.map(({ locale, content }) => [
|
|
32
|
+
locale,
|
|
33
|
+
`/${locale}/${content.slug}`,
|
|
34
|
+
]),
|
|
35
|
+
) as Partial<Record<KnownLocale, string>>;
|
|
36
|
+
|
|
37
|
+
const firstLoader = entry.i18n.en ?? Object.values(entry.i18n)[0];
|
|
38
|
+
const englishSlug = firstLoader ? (await firstLoader()).slug : entry.id;
|
|
39
|
+
|
|
40
|
+
for (const { locale, content } of localeContents) {
|
|
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[];
|
|
56
|
+
paths.push({
|
|
57
|
+
params: { locale, slug: content.slug },
|
|
58
|
+
props: { Component, locale, content, localeUrls, allToolsNav, englishSlug },
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return paths;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface NavItem {
|
|
67
|
+
id: string;
|
|
68
|
+
title: string;
|
|
69
|
+
href: string;
|
|
70
|
+
isActive?: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface Props {
|
|
74
|
+
Component: unknown;
|
|
75
|
+
locale: KnownLocale;
|
|
76
|
+
content: ToolLocaleContent;
|
|
77
|
+
localeUrls: Partial<Record<KnownLocale, string>>;
|
|
78
|
+
allToolsNav: NavItem[];
|
|
79
|
+
englishSlug: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
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 cssLoader = cssKey ? cssFiles[cssKey] : undefined;
|
|
87
|
+
const toolCss = cssLoader ? await cssLoader() as string : "";
|
|
88
|
+
|
|
89
|
+
const seoContent: UtilitySEOContent = { locale, sections: content.seo ?? [] };
|
|
90
|
+
|
|
91
|
+
const words = content.title.split(" ");
|
|
92
|
+
const titleHighlight = words[0] || "";
|
|
93
|
+
const titleBase = words.slice(1).join(" ") || "";
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
<PreviewLayout
|
|
97
|
+
title={content.title}
|
|
98
|
+
currentLocale={locale}
|
|
99
|
+
localeUrls={localeUrls}
|
|
100
|
+
hasSidebar={true}
|
|
101
|
+
>
|
|
102
|
+
<PreviewNavSidebar
|
|
103
|
+
slot="sidebar"
|
|
104
|
+
categoryTitle="Tools"
|
|
105
|
+
tools={allToolsNav}
|
|
106
|
+
/>
|
|
107
|
+
<Fragment slot="head">
|
|
108
|
+
{toolCss ? <Fragment set:html={`<style is:inline>${toolCss}</style>`} /> : null}
|
|
109
|
+
{
|
|
110
|
+
( content.schemas ?? []).map((schema: unknown) => (
|
|
111
|
+
<script
|
|
112
|
+
is:inline
|
|
113
|
+
type="application/ld+json"
|
|
114
|
+
set:html={JSON.stringify(schema)}
|
|
115
|
+
/>
|
|
116
|
+
))
|
|
117
|
+
}
|
|
118
|
+
</Fragment>
|
|
119
|
+
|
|
120
|
+
<div class="tool-page">
|
|
121
|
+
<UtilityHeader
|
|
122
|
+
titleHighlight={titleHighlight}
|
|
123
|
+
titleBase={titleBase}
|
|
124
|
+
description={content.description}
|
|
125
|
+
/>
|
|
126
|
+
|
|
127
|
+
<section class="section-tool">
|
|
128
|
+
<Component ui={content.ui} />
|
|
129
|
+
</section>
|
|
130
|
+
|
|
131
|
+
<section class="section-seo">
|
|
132
|
+
<SEORenderer content={seoContent} />
|
|
133
|
+
</section>
|
|
134
|
+
|
|
135
|
+
<section class="section-faq">
|
|
136
|
+
<FAQSection items={content.faq} title={content.ui.faqTitle} inLanguage={locale} />
|
|
137
|
+
</section>
|
|
138
|
+
|
|
139
|
+
<section class="section-bibliography">
|
|
140
|
+
<Bibliography links={content.bibliography} title={content.ui.bibliographyTitle} />
|
|
141
|
+
</section>
|
|
142
|
+
</div>
|
|
143
|
+
</PreviewLayout>
|
|
144
|
+
|
|
145
|
+
<style>
|
|
146
|
+
.tool-page {
|
|
147
|
+
display: flex;
|
|
148
|
+
flex-direction: column;
|
|
149
|
+
gap: 2rem;
|
|
150
|
+
}
|
|
151
|
+
.section-tool {
|
|
152
|
+
max-width: 1200px;
|
|
153
|
+
margin: 0 auto;
|
|
154
|
+
width: 100%;
|
|
155
|
+
}
|
|
156
|
+
.section-seo,
|
|
157
|
+
.section-faq,
|
|
158
|
+
.section-bibliography {
|
|
159
|
+
padding-top: 2rem;
|
|
160
|
+
border-top: 1px solid var(--border-color);
|
|
161
|
+
}
|
|
162
|
+
</style>
|
package/src/pages/index.astro
CHANGED
|
@@ -2,9 +2,15 @@ import { describe, expect, it } from 'vitest';
|
|
|
2
2
|
import { ALL_ENTRIES } from '../entries';
|
|
3
3
|
|
|
4
4
|
describe('Tool entry consumer contract', () => {
|
|
5
|
-
|
|
5
|
+
const requiredLocales = [
|
|
6
|
+
'de', 'en', 'es', 'fr', 'id', 'it', 'ja', 'ko', 'nl', 'pl', 'pt', 'ru', 'sv', 'tr', 'zh',
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
it('every published tool exposes all required locale loaders', () => {
|
|
6
10
|
for (const entry of ALL_ENTRIES) {
|
|
7
|
-
|
|
11
|
+
for (const locale of requiredLocales) {
|
|
12
|
+
expect(typeof entry.i18n[locale as keyof typeof entry.i18n], `${entry.id} must expose i18n.${locale}`).toBe('function');
|
|
13
|
+
}
|
|
8
14
|
}
|
|
9
15
|
});
|
|
10
16
|
});
|
|
@@ -4,8 +4,6 @@ import { ALL_TOOLS } from '../tools';
|
|
|
4
4
|
const EXPECTED_LOCALES = [
|
|
5
5
|
'de', 'en', 'es', 'fr', 'id', 'it', 'ja', 'ko', 'nl', 'pl', 'pt', 'ru', 'sv', 'tr', 'zh'
|
|
6
6
|
];
|
|
7
|
-
const ENGLISH_FIRST_TOOLS = new Set(['promote-this-website']);
|
|
8
|
-
|
|
9
7
|
describe('I18n Coverage Validation', () => {
|
|
10
8
|
it('all tools should be registered', () => {
|
|
11
9
|
expect(ALL_TOOLS.length).toBeGreaterThan(0);
|
|
@@ -15,8 +13,7 @@ describe('I18n Coverage Validation', () => {
|
|
|
15
13
|
describe(`Tool: ${entry.id}`, () => {
|
|
16
14
|
it('should have all 15 required locales', () => {
|
|
17
15
|
const registeredLocales = Object.keys(entry.i18n);
|
|
18
|
-
|
|
19
|
-
requiredLocales.forEach((locale) => {
|
|
16
|
+
EXPECTED_LOCALES.forEach((locale) => {
|
|
20
17
|
expect(
|
|
21
18
|
registeredLocales,
|
|
22
19
|
`Tool "${entry.id}" is missing locale "${locale}"`,
|
|
@@ -25,8 +22,7 @@ describe('I18n Coverage Validation', () => {
|
|
|
25
22
|
});
|
|
26
23
|
|
|
27
24
|
it('all locale loaders should be functions', () => {
|
|
28
|
-
|
|
29
|
-
requiredLocales.forEach((locale) => {
|
|
25
|
+
EXPECTED_LOCALES.forEach((locale) => {
|
|
30
26
|
const loader = entry.i18n[locale as keyof typeof entry.i18n];
|
|
31
27
|
expect(
|
|
32
28
|
typeof loader,
|