@jjlmoya/utils-babies 1.8.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.ts +2 -2
- package/src/pages/[locale]/[slug].astro +3 -4
- package/src/tests/category_validation.test.ts +59 -0
- package/src/tests/slug_language_code_format.test.ts +23 -0
- package/src/tool/baby-feeding-calculator/index.ts +3 -7
- package/src/tool/baby-percentile-calculator/index.ts +3 -7
- package/src/tool/baby-size-converter/index.ts +3 -7
- package/src/tool/fertile-days-estimator/index.ts +3 -7
- package/src/tool/pregnancy-calculator/index.ts +3 -7
- package/src/tool/vaccination-calendar/index.ts +3 -7
- package/src/tools.ts +2 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ export * from './tool/baby-percentile-calculator';
|
|
|
6
6
|
export * from './tool/fertile-days-estimator';
|
|
7
7
|
export * from './tool/pregnancy-calculator';
|
|
8
8
|
export * from './tool/vaccination-calendar';
|
|
9
|
-
export
|
|
9
|
+
export const babiesCategorySEO = () => import('./category/seo.astro').then((m) => m.default);
|
|
10
10
|
|
|
11
11
|
export type {
|
|
12
12
|
KnownLocale,
|
|
@@ -22,5 +22,5 @@ export type {
|
|
|
22
22
|
ToolDefinition,
|
|
23
23
|
} from './types';
|
|
24
24
|
|
|
25
|
-
export { ALL_TOOLS } from './tools';
|
|
25
|
+
export { ALL_ENTRIES, ALL_TOOLS } from './tools';
|
|
26
26
|
|
|
@@ -14,7 +14,8 @@ import type { UtilitySEOContent } from "@jjlmoya/utils-shared";
|
|
|
14
14
|
export async function getStaticPaths() {
|
|
15
15
|
const paths = [];
|
|
16
16
|
|
|
17
|
-
for (const { entry, Component } of ALL_TOOLS) {
|
|
17
|
+
for (const { entry, Component: lazyComp } of ALL_TOOLS) {
|
|
18
|
+
const { default: Component } = await lazyComp();
|
|
18
19
|
const localeEntries = Object.entries(entry.i18n) as [
|
|
19
20
|
KnownLocale,
|
|
20
21
|
() => Promise<ToolLocaleContent>,
|
|
@@ -52,8 +53,6 @@ export async function getStaticPaths() {
|
|
|
52
53
|
return paths;
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
type ToolComponent = (props: { ui: Record<string, string> }) => unknown;
|
|
56
|
-
|
|
57
56
|
interface NavItem {
|
|
58
57
|
id: string;
|
|
59
58
|
title: string;
|
|
@@ -62,7 +61,7 @@ interface NavItem {
|
|
|
62
61
|
}
|
|
63
62
|
|
|
64
63
|
interface Props {
|
|
65
|
-
Component:
|
|
64
|
+
Component: unknown;
|
|
66
65
|
locale: KnownLocale;
|
|
67
66
|
content: ToolLocaleContent;
|
|
68
67
|
localeUrls: Partial<Record<KnownLocale, string>>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { babiesCategory as toolsCategory } from '../category/index';
|
|
3
|
+
import type { CategoryLocaleContent } from '../types';
|
|
4
|
+
|
|
5
|
+
const EXPECTED_LOCALES = [
|
|
6
|
+
'de', 'en', 'es', 'fr', 'id', 'it', 'ja', 'ko', 'nl', 'pl', 'pt', 'ru', 'sv', 'tr', 'zh'
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
const sharingLocales = ['ja', 'ko', 'zh'];
|
|
10
|
+
const allowedDuplicateSlugs = ['bebes', 'babys'];
|
|
11
|
+
|
|
12
|
+
describe('Category Validation', () => {
|
|
13
|
+
it('should have all 15 required locales', () => {
|
|
14
|
+
const registeredLocales = Object.keys(toolsCategory.i18n);
|
|
15
|
+
EXPECTED_LOCALES.forEach((locale) => {
|
|
16
|
+
expect(
|
|
17
|
+
registeredLocales,
|
|
18
|
+
`Category is missing locale "${locale}"`,
|
|
19
|
+
).toContain(locale);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('Category Slug Validation', () => {
|
|
24
|
+
async function getEnSlug(locales: string[]): Promise<string> {
|
|
25
|
+
if (!locales.includes('en')) return '';
|
|
26
|
+
const enLoader = toolsCategory.i18n['en' as keyof typeof toolsCategory.i18n];
|
|
27
|
+
const enContent = (await enLoader?.()) as CategoryLocaleContent;
|
|
28
|
+
return enContent.slug;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function validateNonEnSlug(locale: string, slug: string, enSlug: string, slugs: Map<string, string>) {
|
|
32
|
+
if (sharingLocales.includes(locale)) {
|
|
33
|
+
expect(slug, `Category locale "${locale}" must use the same slug as "en" ("${enSlug}").`).toBe(enSlug);
|
|
34
|
+
} else {
|
|
35
|
+
expect(slug, `Category locale "${locale}" has the same slug as "en" ("${enSlug}"). Cada slug tiene que estar en su propio idioma`).not.toBe(enSlug);
|
|
36
|
+
if (slugs.has(slug) && !allowedDuplicateSlugs.includes(slug)) {
|
|
37
|
+
expect(false, `Category locales "${locale}" and "${slugs.get(slug)}" share the same slug ("${slug}"). Cada slug tiene que estar en su propia idioma`).toBe(true);
|
|
38
|
+
}
|
|
39
|
+
slugs.set(slug, locale);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
it('every locale should have a unique, translated slug and follow format rules', async () => {
|
|
44
|
+
const slugs = new Map<string, string>();
|
|
45
|
+
const locales = Object.keys(toolsCategory.i18n);
|
|
46
|
+
const enSlug = await getEnSlug(locales);
|
|
47
|
+
|
|
48
|
+
for (const locale of locales) {
|
|
49
|
+
const loader = toolsCategory.i18n[locale as keyof typeof toolsCategory.i18n];
|
|
50
|
+
const content = (await loader?.()) as CategoryLocaleContent;
|
|
51
|
+
|
|
52
|
+
expect(content.slug, `Category locale "${locale}" has an invalid slug ("${content.slug}"). Slugs must be transliterated (only a-z, 0-9, and -).`).toMatch(/^[a-z0-9-]+$/);
|
|
53
|
+
expect(content.slug, `Category locale "${locale}" slug ("${content.slug}") cannot end with a 2-letter language code (e.g., -ja, -ru, -ko).`).not.toMatch(/-[a-z]{2}$/);
|
|
54
|
+
|
|
55
|
+
if (locale !== 'en') validateNonEnSlug(locale, content.slug, enSlug, slugs);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ALL_TOOLS } from '../tools';
|
|
3
|
+
import type { ToolLocaleContent } from '../types';
|
|
4
|
+
|
|
5
|
+
describe('Slug Language Code Format Validation', () => {
|
|
6
|
+
ALL_TOOLS.forEach((tool) => {
|
|
7
|
+
describe(`Tool: ${tool.entry.id}`, () => {
|
|
8
|
+
it('slug should not end with 2-letter language codes like -ja, -ru, -ko', async () => {
|
|
9
|
+
const locales = Object.keys(tool.entry.i18n);
|
|
10
|
+
|
|
11
|
+
for (const locale of locales) {
|
|
12
|
+
const loader = tool.entry.i18n[locale as keyof typeof tool.entry.i18n];
|
|
13
|
+
const content = (await loader?.()) as ToolLocaleContent;
|
|
14
|
+
|
|
15
|
+
expect(
|
|
16
|
+
content.slug,
|
|
17
|
+
`Tool "${tool.entry.id}" locale "${locale}" slug ("${content.slug}") cannot end with a 2-letter language code (e.g., -ja, -ru, -ko).`,
|
|
18
|
+
).not.toMatch(/-[a-z]{2}$/);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import type { BabiesToolEntry, ToolDefinition, ToolLocaleContent } from '../../types';
|
|
2
|
-
import BabyFeedingCalculatorComponent from './component.astro';
|
|
3
|
-
import BabyFeedingCalculatorSEO from './seo.astro';
|
|
4
|
-
import BabyFeedingCalculatorBibliography from './bibliography.astro';
|
|
5
2
|
|
|
6
3
|
export interface BabyFeedingCalculatorUI {
|
|
7
4
|
[key: string]: string;
|
|
@@ -49,11 +46,10 @@ export const babyFeedingCalculator: BabiesToolEntry<BabyFeedingCalculatorUI> = {
|
|
|
49
46
|
},
|
|
50
47
|
};
|
|
51
48
|
|
|
52
|
-
export { BabyFeedingCalculatorComponent, BabyFeedingCalculatorSEO, BabyFeedingCalculatorBibliography };
|
|
53
49
|
|
|
54
50
|
export const BABY_FEEDING_CALCULATOR_TOOL: ToolDefinition = {
|
|
55
51
|
entry: babyFeedingCalculator,
|
|
56
|
-
Component:
|
|
57
|
-
SEOComponent:
|
|
58
|
-
BibliographyComponent:
|
|
52
|
+
Component: () => import('./component.astro'),
|
|
53
|
+
SEOComponent: () => import('./seo.astro'),
|
|
54
|
+
BibliographyComponent: () => import('./bibliography.astro'),
|
|
59
55
|
};
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import type { BabiesToolEntry, ToolDefinition, ToolLocaleContent } from '../../types';
|
|
2
|
-
import BabyPercentileCalculatorComponent from './component.astro';
|
|
3
|
-
import BabyPercentileCalculatorSEO from './seo.astro';
|
|
4
|
-
import BabyPercentileCalculatorBibliography from './bibliography.astro';
|
|
5
2
|
|
|
6
3
|
export interface BabyPercentileCalculatorUI {
|
|
7
4
|
[key: string]: string;
|
|
@@ -57,11 +54,10 @@ export const babyPercentileCalculator: BabiesToolEntry<BabyPercentileCalculatorU
|
|
|
57
54
|
},
|
|
58
55
|
};
|
|
59
56
|
|
|
60
|
-
export { BabyPercentileCalculatorComponent, BabyPercentileCalculatorSEO, BabyPercentileCalculatorBibliography };
|
|
61
57
|
|
|
62
58
|
export const BABY_PERCENTILE_CALCULATOR_TOOL: ToolDefinition = {
|
|
63
59
|
entry: babyPercentileCalculator,
|
|
64
|
-
Component:
|
|
65
|
-
SEOComponent:
|
|
66
|
-
BibliographyComponent:
|
|
60
|
+
Component: () => import('./component.astro'),
|
|
61
|
+
SEOComponent: () => import('./seo.astro'),
|
|
62
|
+
BibliographyComponent: () => import('./bibliography.astro'),
|
|
67
63
|
};
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import type { BabiesToolEntry, ToolDefinition, ToolLocaleContent } from '../../types';
|
|
2
|
-
import BabySizeConverterComponent from './component.astro';
|
|
3
|
-
import BabySizeConverterSEO from './seo.astro';
|
|
4
|
-
import BabySizeConverterBibliography from './bibliography.astro';
|
|
5
2
|
|
|
6
3
|
export interface BabySizeConverterUI {
|
|
7
4
|
[key: string]: string;
|
|
@@ -55,11 +52,10 @@ export const babySizeConverter: BabiesToolEntry<BabySizeConverterUI> = {
|
|
|
55
52
|
},
|
|
56
53
|
};
|
|
57
54
|
|
|
58
|
-
export { BabySizeConverterComponent, BabySizeConverterSEO, BabySizeConverterBibliography };
|
|
59
55
|
|
|
60
56
|
export const BABY_SIZE_CONVERTER_TOOL: ToolDefinition = {
|
|
61
57
|
entry: babySizeConverter,
|
|
62
|
-
Component:
|
|
63
|
-
SEOComponent:
|
|
64
|
-
BibliographyComponent:
|
|
58
|
+
Component: () => import('./component.astro'),
|
|
59
|
+
SEOComponent: () => import('./seo.astro'),
|
|
60
|
+
BibliographyComponent: () => import('./bibliography.astro'),
|
|
65
61
|
};
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import type { BabiesToolEntry, ToolDefinition, ToolLocaleContent } from '../../types';
|
|
2
|
-
import FertileDaysEstimatorComponent from './component.astro';
|
|
3
|
-
import FertileDaysEstimatorSEO from './seo.astro';
|
|
4
|
-
import FertileDaysEstimatorBibliography from './bibliography.astro';
|
|
5
2
|
|
|
6
3
|
export interface FertileDaysEstimatorUI {
|
|
7
4
|
[key: string]: string;
|
|
@@ -49,11 +46,10 @@ export const fertileDaysEstimator: BabiesToolEntry<FertileDaysEstimatorUI> = {
|
|
|
49
46
|
},
|
|
50
47
|
};
|
|
51
48
|
|
|
52
|
-
export { FertileDaysEstimatorComponent, FertileDaysEstimatorSEO, FertileDaysEstimatorBibliography };
|
|
53
49
|
|
|
54
50
|
export const FERTILE_DAYS_ESTIMATOR_TOOL: ToolDefinition = {
|
|
55
51
|
entry: fertileDaysEstimator,
|
|
56
|
-
Component:
|
|
57
|
-
SEOComponent:
|
|
58
|
-
BibliographyComponent:
|
|
52
|
+
Component: () => import('./component.astro'),
|
|
53
|
+
SEOComponent: () => import('./seo.astro'),
|
|
54
|
+
BibliographyComponent: () => import('./bibliography.astro'),
|
|
59
55
|
};
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import type { BabiesToolEntry, ToolDefinition, ToolLocaleContent } from '../../types';
|
|
2
|
-
import PregnancyCalculatorComponent from './component.astro';
|
|
3
|
-
import PregnancyCalculatorSEO from './seo.astro';
|
|
4
|
-
import PregnancyCalculatorBibliography from './bibliography.astro';
|
|
5
2
|
|
|
6
3
|
export interface MilestoneI18n {
|
|
7
4
|
analogies: { fruits: string; geek: string; sweets: string };
|
|
@@ -89,11 +86,10 @@ export const pregnancyCalculator: BabiesToolEntry<PregnancyCalculatorUI> = {
|
|
|
89
86
|
},
|
|
90
87
|
};
|
|
91
88
|
|
|
92
|
-
export { PregnancyCalculatorComponent, PregnancyCalculatorSEO, PregnancyCalculatorBibliography };
|
|
93
89
|
|
|
94
90
|
export const PREGNANCY_CALCULATOR_TOOL: ToolDefinition = {
|
|
95
91
|
entry: pregnancyCalculator,
|
|
96
|
-
Component:
|
|
97
|
-
SEOComponent:
|
|
98
|
-
BibliographyComponent:
|
|
92
|
+
Component: () => import('./component.astro'),
|
|
93
|
+
SEOComponent: () => import('./seo.astro'),
|
|
94
|
+
BibliographyComponent: () => import('./bibliography.astro'),
|
|
99
95
|
};
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import type { BabiesToolEntry, ToolDefinition, ToolLocaleContent } from '../../types';
|
|
2
|
-
import VaccinationCalendarComponent from './component.astro';
|
|
3
|
-
import VaccinationCalendarSEO from './seo.astro';
|
|
4
|
-
import VaccinationCalendarBibliography from './bibliography.astro';
|
|
5
2
|
|
|
6
3
|
export interface VaccinationCalendarUI {
|
|
7
4
|
[key: string]: string;
|
|
@@ -69,11 +66,10 @@ export const vaccinationCalendar: BabiesToolEntry<VaccinationCalendarUI> = {
|
|
|
69
66
|
},
|
|
70
67
|
};
|
|
71
68
|
|
|
72
|
-
export { VaccinationCalendarComponent, VaccinationCalendarSEO, VaccinationCalendarBibliography };
|
|
73
69
|
|
|
74
70
|
export const VACCINATION_CALENDAR_TOOL: ToolDefinition = {
|
|
75
71
|
entry: vaccinationCalendar,
|
|
76
|
-
Component:
|
|
77
|
-
SEOComponent:
|
|
78
|
-
BibliographyComponent:
|
|
72
|
+
Component: () => import('./component.astro'),
|
|
73
|
+
SEOComponent: () => import('./seo.astro'),
|
|
74
|
+
BibliographyComponent: () => import('./bibliography.astro'),
|
|
79
75
|
};
|