@jjlmoya/utils-babies 1.5.0 → 1.7.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 +67 -66
- package/src/category/i18n/de.ts +48 -0
- package/src/category/i18n/en.ts +10 -10
- package/src/category/i18n/fr.ts +10 -10
- package/src/category/i18n/id.ts +48 -0
- package/src/category/i18n/it.ts +48 -0
- package/src/category/i18n/ja.ts +48 -0
- package/src/category/i18n/ko.ts +48 -0
- package/src/category/i18n/nl.ts +48 -0
- package/src/category/i18n/pl.ts +48 -0
- package/src/category/i18n/pt.ts +48 -0
- package/src/category/i18n/ru.ts +48 -0
- package/src/category/i18n/sv.ts +48 -0
- package/src/category/i18n/tr.ts +48 -0
- package/src/category/i18n/zh.ts +48 -0
- package/src/category/index.ts +13 -1
- package/src/tests/faq_count.test.ts +1 -1
- package/src/tests/i18n_coverage.test.ts +36 -0
- package/src/tests/locale_completeness.test.ts +1 -1
- package/src/tests/schemas_fulfillment.test.ts +23 -0
- package/src/tests/seo_length.test.ts +1 -2
- package/src/tests/title_quality.test.ts +55 -0
- package/src/tool/baby-feeding-calculator/component.astro +40 -40
- package/src/tool/baby-feeding-calculator/i18n/de.ts +162 -0
- package/src/tool/baby-feeding-calculator/i18n/id.ts +162 -0
- package/src/tool/baby-feeding-calculator/i18n/it.ts +162 -0
- package/src/tool/baby-feeding-calculator/i18n/ja.ts +162 -0
- package/src/tool/baby-feeding-calculator/i18n/ko.ts +162 -0
- package/src/tool/baby-feeding-calculator/i18n/nl.ts +162 -0
- package/src/tool/baby-feeding-calculator/i18n/pl.ts +162 -0
- package/src/tool/baby-feeding-calculator/i18n/pt.ts +162 -0
- package/src/tool/baby-feeding-calculator/i18n/ru.ts +162 -0
- package/src/tool/baby-feeding-calculator/i18n/sv.ts +162 -0
- package/src/tool/baby-feeding-calculator/i18n/tr.ts +162 -0
- package/src/tool/baby-feeding-calculator/i18n/zh.ts +162 -0
- package/src/tool/baby-feeding-calculator/index.ts +12 -0
- package/src/tool/baby-percentile-calculator/component.astro +38 -38
- package/src/tool/baby-percentile-calculator/i18n/de.ts +245 -0
- package/src/tool/baby-percentile-calculator/i18n/id.ts +245 -0
- package/src/tool/baby-percentile-calculator/i18n/it.ts +245 -0
- package/src/tool/baby-percentile-calculator/i18n/ja.ts +245 -0
- package/src/tool/baby-percentile-calculator/i18n/ko.ts +245 -0
- package/src/tool/baby-percentile-calculator/i18n/nl.ts +245 -0
- package/src/tool/baby-percentile-calculator/i18n/pl.ts +245 -0
- package/src/tool/baby-percentile-calculator/i18n/pt.ts +245 -0
- package/src/tool/baby-percentile-calculator/i18n/ru.ts +245 -0
- package/src/tool/baby-percentile-calculator/i18n/sv.ts +245 -0
- package/src/tool/baby-percentile-calculator/i18n/tr.ts +245 -0
- package/src/tool/baby-percentile-calculator/i18n/zh.ts +245 -0
- package/src/tool/baby-percentile-calculator/index.ts +12 -0
- package/src/tool/baby-size-converter/component.astro +42 -42
- package/src/tool/baby-size-converter/i18n/de.ts +203 -0
- package/src/tool/baby-size-converter/i18n/fr.ts +1 -1
- package/src/tool/baby-size-converter/i18n/id.ts +203 -0
- package/src/tool/baby-size-converter/i18n/it.ts +203 -0
- package/src/tool/baby-size-converter/i18n/ja.ts +203 -0
- package/src/tool/baby-size-converter/i18n/ko.ts +203 -0
- package/src/tool/baby-size-converter/i18n/nl.ts +203 -0
- package/src/tool/baby-size-converter/i18n/pl.ts +203 -0
- package/src/tool/baby-size-converter/i18n/pt.ts +203 -0
- package/src/tool/baby-size-converter/i18n/ru.ts +203 -0
- package/src/tool/baby-size-converter/i18n/sv.ts +203 -0
- package/src/tool/baby-size-converter/i18n/tr.ts +203 -0
- package/src/tool/baby-size-converter/i18n/zh.ts +203 -0
- package/src/tool/baby-size-converter/index.ts +12 -0
- package/src/tool/fertile-days-estimator/component.astro +14 -9
- package/src/tool/fertile-days-estimator/i18n/de.ts +262 -0
- package/src/tool/fertile-days-estimator/i18n/id.ts +262 -0
- package/src/tool/fertile-days-estimator/i18n/it.ts +262 -0
- package/src/tool/fertile-days-estimator/i18n/ja.ts +262 -0
- package/src/tool/fertile-days-estimator/i18n/ko.ts +262 -0
- package/src/tool/fertile-days-estimator/i18n/nl.ts +262 -0
- package/src/tool/fertile-days-estimator/i18n/pl.ts +262 -0
- package/src/tool/fertile-days-estimator/i18n/pt.ts +262 -0
- package/src/tool/fertile-days-estimator/i18n/ru.ts +262 -0
- package/src/tool/fertile-days-estimator/i18n/sv.ts +262 -0
- package/src/tool/fertile-days-estimator/i18n/tr.ts +262 -0
- package/src/tool/fertile-days-estimator/i18n/zh.ts +262 -0
- package/src/tool/fertile-days-estimator/index.ts +12 -0
- package/src/tool/pregnancy-calculator/component.astro +55 -48
- package/src/tool/pregnancy-calculator/i18n/de.ts +467 -0
- package/src/tool/pregnancy-calculator/i18n/en.ts +140 -0
- package/src/tool/pregnancy-calculator/i18n/es.ts +143 -3
- package/src/tool/pregnancy-calculator/i18n/fr.ts +143 -3
- package/src/tool/pregnancy-calculator/i18n/id.ts +467 -0
- package/src/tool/pregnancy-calculator/i18n/it.ts +467 -0
- package/src/tool/pregnancy-calculator/i18n/ja.ts +467 -0
- package/src/tool/pregnancy-calculator/i18n/ko.ts +467 -0
- package/src/tool/pregnancy-calculator/i18n/nl.ts +467 -0
- package/src/tool/pregnancy-calculator/i18n/pl.ts +467 -0
- package/src/tool/pregnancy-calculator/i18n/pt.ts +467 -0
- package/src/tool/pregnancy-calculator/i18n/ru.ts +467 -0
- package/src/tool/pregnancy-calculator/i18n/sv.ts +467 -0
- package/src/tool/pregnancy-calculator/i18n/tr.ts +467 -0
- package/src/tool/pregnancy-calculator/i18n/zh.ts +467 -0
- package/src/tool/pregnancy-calculator/index.ts +37 -1
- package/src/tool/pregnancy-calculator/milestones.ts +2 -146
- package/src/tool/vaccination-calendar/component.astro +26 -24
- package/src/tool/vaccination-calendar/i18n/de.ts +194 -0
- package/src/tool/vaccination-calendar/i18n/en.ts +20 -0
- package/src/tool/vaccination-calendar/i18n/es.ts +20 -0
- package/src/tool/vaccination-calendar/i18n/fr.ts +99 -75
- package/src/tool/vaccination-calendar/i18n/id.ts +194 -0
- package/src/tool/vaccination-calendar/i18n/it.ts +194 -0
- package/src/tool/vaccination-calendar/i18n/ja.ts +194 -0
- package/src/tool/vaccination-calendar/i18n/ko.ts +194 -0
- package/src/tool/vaccination-calendar/i18n/nl.ts +194 -0
- package/src/tool/vaccination-calendar/i18n/pl.ts +194 -0
- package/src/tool/vaccination-calendar/i18n/pt.ts +194 -0
- package/src/tool/vaccination-calendar/i18n/ru.ts +194 -0
- package/src/tool/vaccination-calendar/i18n/sv.ts +194 -0
- package/src/tool/vaccination-calendar/i18n/tr.ts +194 -0
- package/src/tool/vaccination-calendar/i18n/zh.ts +194 -0
- package/src/tool/vaccination-calendar/index.ts +33 -1
- package/src/tool/vaccination-calendar/logic.ts +39 -13
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'mladency',
|
|
5
|
+
title: 'Калькуляторы для Младенцев',
|
|
6
|
+
description: 'Инструменты и калькуляторы для ухода и мониторинга развития вашего ребенка.',
|
|
7
|
+
seo: [
|
|
8
|
+
{
|
|
9
|
+
type: 'summary',
|
|
10
|
+
title: 'Доступные Инструменты',
|
|
11
|
+
items: [
|
|
12
|
+
'Калькулятор кормления по возрасту и весу',
|
|
13
|
+
'Калькулятор процентилей роста (ВОЗ)',
|
|
14
|
+
'Конвертер размеров одежды по брендам',
|
|
15
|
+
'Оценка фертильных дней',
|
|
16
|
+
'Индивидуальный календарь прививок',
|
|
17
|
+
'Калькулятор беременности и срока гестации',
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: 'title',
|
|
22
|
+
text: 'Мониторинг Развития вашего Ребенка',
|
|
23
|
+
level: 2,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
type: 'paragraph',
|
|
27
|
+
html: 'Детские калькуляторы помогут вам точно отслеживать рост и развитие вашего ребенка. От расчета порций молока в зависимости от возраста и веса до проверки процентилей ВОЗ — эти инструменты разработаны для предоставления полезной информации на каждом этапе.',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: 'title',
|
|
31
|
+
text: 'Питание и Нутрициология',
|
|
32
|
+
level: 2,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: 'paragraph',
|
|
36
|
+
html: 'Калькулятор кормления оценивает количество грудного молока или смеси, необходимое вашему ребенку в зависимости от возраста в днях, неделях или месяцах и текущего веса. Расчеты следуют стандартным педиатрическим рекомендациям для обеспечения правильного питания.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'title',
|
|
40
|
+
text: 'Рост и Процентили',
|
|
41
|
+
level: 2,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'paragraph',
|
|
45
|
+
html: 'Калькулятор процентилей использует справочные таблицы Всемирной организации здравоохранения (ВОЗ) для размещения веса, роста и ИМТ вашего ребенка в распределении детского населения. 50-й процентиль указывает на то, что ребенок находится на среднем уровне.',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'bebisar',
|
|
5
|
+
title: 'Babykalkylatorer',
|
|
6
|
+
description: 'Verktyg och kalkylatorer för vård och övervakning av din bebis utveckling.',
|
|
7
|
+
seo: [
|
|
8
|
+
{
|
|
9
|
+
type: 'summary',
|
|
10
|
+
title: 'Tillgängliga Verktyg',
|
|
11
|
+
items: [
|
|
12
|
+
'Matningskalkylator efter ålder och vikt',
|
|
13
|
+
'Groeicentil-kalkylator (WHO)',
|
|
14
|
+
'Klädstorleks-omvandlare per märke',
|
|
15
|
+
'Ägglossnings-simulator',
|
|
16
|
+
'Personligt vaccinationsschema',
|
|
17
|
+
'Kalkylator för graviditet och veckor',
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: 'title',
|
|
22
|
+
text: 'Övervakning av din Bebis Utveckling',
|
|
23
|
+
level: 2,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
type: 'paragraph',
|
|
27
|
+
html: 'Babykalkylatorerna hjälper dig att noggrant följa ditt barns tillväxt och utveckling. Från att beräkna mjölkmatningar efter ålder och vikt till att kontrollera WHO-centiler, dessa verktyg är utformade för att ge användbar information i varje steg.',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: 'title',
|
|
31
|
+
text: 'Matning och Nutrition',
|
|
32
|
+
level: 2,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: 'paragraph',
|
|
36
|
+
html: 'Matningskalkylatorn uppskattar mängden bröstmjölk eller ersättning din bebis behöver baserat på ålder i dagar, veckor eller månader och aktuell vikt. Beräkningarna följer pediatriska riktlinjer för att säkerställa korrekt näring.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'title',
|
|
40
|
+
text: 'Tillväxt och Centiler',
|
|
41
|
+
level: 2,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'paragraph',
|
|
45
|
+
html: 'Centilkalkylatorn använder Världshälsoorganisationens (WHO) referensdiagram för att plotta din bebis vikt, längd och BMI inom barnpopulationens fördelning. En 50:e centil indikerar att bebisen är vid medianen.',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'bebekler',
|
|
5
|
+
title: 'Bebek Hesaplayıcıları',
|
|
6
|
+
description: 'Bebeğinizin gelişimini izlemek ve bakımı için araçlar ve hesaplayıcılar.',
|
|
7
|
+
seo: [
|
|
8
|
+
{
|
|
9
|
+
type: 'summary',
|
|
10
|
+
title: 'Mevcut Araçlar',
|
|
11
|
+
items: [
|
|
12
|
+
'Yaş ve ağırlığa göre beslenme hesaplayıcı',
|
|
13
|
+
'Büyüme persentil hesaplayıcı (WHO)',
|
|
14
|
+
'Markaya göre kıyafet bedeni dönüştürücü',
|
|
15
|
+
'Doğurganlık günleri tahmini',
|
|
16
|
+
'Kişiselleştirilmiş aşılama takvimi',
|
|
17
|
+
'Hamilelik ve gestasyon haftası hesaplayıcı',
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: 'title',
|
|
22
|
+
text: 'Bebeğinizin Gelişimini İzleme',
|
|
23
|
+
level: 2,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
type: 'paragraph',
|
|
27
|
+
html: 'Bebek hesaplayıcıları, çocuğunuzun büyümesini ve gelişimini doğru bir şekilde takip etmenize yardımcı olur. Yaş ve ağırlığa göre süt miktarını hesaplamaktan WHO persentillerini kontrol etmeye kadar bu araçlar, her aşamada yararlı bilgiler sağlamak üzere tasarlanmıştır.',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: 'title',
|
|
31
|
+
text: 'Beslenme ve Nutrisyon',
|
|
32
|
+
level: 2,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: 'paragraph',
|
|
36
|
+
html: 'Beslenme hesaplayıcı, bebeğinizin gün, hafta veya ay cinsinden yaşına ve güncel ağırlığına göre ihtiyaç duyduğu anne sütü veya mama miktarını tahmin eder. Hesaplamalar, uygun beslenmeyi sağlamak için standart pediatrik yönergeleri takip eder.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'title',
|
|
40
|
+
text: 'Büyüme ve Persentiller',
|
|
41
|
+
level: 2,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'paragraph',
|
|
45
|
+
html: 'Persentil hesaplayıcı, bebeğinizin ağırlığını, boyunu ve BMI\'sini (Vücut Kitle İndeksi) çocuk popülasyonu dağılımı içinde konumlandırmak için Dünya Sağlık Örgütü (WHO) referans tablolarını kullanır. 50. persentil, bebeğin ortalamada olduğunu gösterir.',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { CategoryLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const content: CategoryLocaleContent = {
|
|
4
|
+
slug: 'babies',
|
|
5
|
+
title: '婴儿计算器',
|
|
6
|
+
description: '用于宝宝护理和发育监测的工具及计算器。',
|
|
7
|
+
seo: [
|
|
8
|
+
{
|
|
9
|
+
type: 'summary',
|
|
10
|
+
title: '可用工具',
|
|
11
|
+
items: [
|
|
12
|
+
'按月龄和体重计算喂养量',
|
|
13
|
+
'生长百分位数计算器 (WHO)',
|
|
14
|
+
'按品牌转换衣服尺码',
|
|
15
|
+
'排卵日估算',
|
|
16
|
+
'个性化疫苗接种时间表',
|
|
17
|
+
'怀孕和孕周计算器',
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: 'title',
|
|
22
|
+
text: '监测宝宝的发育',
|
|
23
|
+
level: 2,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
type: 'paragraph',
|
|
27
|
+
html: '婴儿计算器帮助您准确追踪孩子的成长和发育。从根据月龄和体重计算奶量到查询世卫组织 (WHO) 百分位数,这些工具旨在为每个阶段提供有用的信息。',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: 'title',
|
|
31
|
+
text: '喂养与营养',
|
|
32
|
+
level: 2,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: 'paragraph',
|
|
36
|
+
html: '喂养计算器根据宝宝按天、周或月计算的月龄及当前体重估算所需的母乳或配方奶量。计算结果遵循标准儿科指南,以确保充足的营养。',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'title',
|
|
40
|
+
text: '成长与百分位数',
|
|
41
|
+
level: 2,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'paragraph',
|
|
45
|
+
html: '百分位数计算器使用世界卫生组织 (WHO) 的参考图表,将宝宝的体重、身高和 BMI 置于儿童人口分布中。第 50 百分位数表示宝宝处于平均水平。',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
package/src/category/index.ts
CHANGED
|
@@ -17,8 +17,20 @@ export const babiesCategory: BabiesCategoryEntry = {
|
|
|
17
17
|
pregnancyCalculator,
|
|
18
18
|
],
|
|
19
19
|
i18n: {
|
|
20
|
-
|
|
20
|
+
de: () => import('./i18n/de').then((m) => m.content),
|
|
21
21
|
en: () => import('./i18n/en').then((m) => m.content),
|
|
22
|
+
es: () => import('./i18n/es').then((m) => m.content),
|
|
22
23
|
fr: () => import('./i18n/fr').then((m) => m.content),
|
|
24
|
+
id: () => import('./i18n/id').then((m) => m.content),
|
|
25
|
+
it: () => import('./i18n/it').then((m) => m.content),
|
|
26
|
+
ja: () => import('./i18n/ja').then((m) => m.content),
|
|
27
|
+
ko: () => import('./i18n/ko').then((m) => m.content),
|
|
28
|
+
nl: () => import('./i18n/nl').then((m) => m.content),
|
|
29
|
+
pl: () => import('./i18n/pl').then((m) => m.content),
|
|
30
|
+
pt: () => import('./i18n/pt').then((m) => m.content),
|
|
31
|
+
ru: () => import('./i18n/ru').then((m) => m.content),
|
|
32
|
+
sv: () => import('./i18n/sv').then((m) => m.content),
|
|
33
|
+
tr: () => import('./i18n/tr').then((m) => m.content),
|
|
34
|
+
zh: () => import('./i18n/zh').then((m) => m.content),
|
|
23
35
|
},
|
|
24
36
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import type * as DATA from '../data';
|
|
3
3
|
|
|
4
|
-
const TOOLS: typeof DATA.
|
|
4
|
+
const TOOLS: typeof DATA.audiovisualCategory[] = [];
|
|
5
5
|
|
|
6
6
|
describe('FAQ Content Validation', () => {
|
|
7
7
|
TOOLS.forEach((entry) => {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ALL_TOOLS } from '../tools';
|
|
3
|
+
|
|
4
|
+
const EXPECTED_LOCALES = [
|
|
5
|
+
'de', 'en', 'es', 'fr', 'id', 'it', 'ja', 'ko', 'nl', 'pl', 'pt', 'ru', 'sv', 'tr', 'zh'
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
describe('I18n Coverage Validation', () => {
|
|
9
|
+
it('all tools should be registered', () => {
|
|
10
|
+
expect(ALL_TOOLS.length).toBeGreaterThan(0);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
ALL_TOOLS.forEach(({ entry }: { entry: any }) => {
|
|
14
|
+
describe(`Tool: ${entry.id}`, () => {
|
|
15
|
+
it('should have all 15 required locales', () => {
|
|
16
|
+
const registeredLocales = Object.keys(entry.i18n);
|
|
17
|
+
EXPECTED_LOCALES.forEach((locale) => {
|
|
18
|
+
expect(
|
|
19
|
+
registeredLocales,
|
|
20
|
+
`Tool "${entry.id}" is missing locale "${locale}"`,
|
|
21
|
+
).toContain(locale);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('all locale loaders should be functions', () => {
|
|
26
|
+
EXPECTED_LOCALES.forEach((locale) => {
|
|
27
|
+
const loader = entry.i18n[locale as keyof typeof entry.i18n];
|
|
28
|
+
expect(
|
|
29
|
+
typeof loader,
|
|
30
|
+
`Tool "${entry.id}" locale "${locale}" loader is not a function`,
|
|
31
|
+
).toBe('function');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -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('Schemas Fulfillment Validation', () => {
|
|
6
|
+
ALL_TOOLS.forEach((tool) => {
|
|
7
|
+
describe(`Tool: ${tool.entry.id}`, () => {
|
|
8
|
+
Object.keys(tool.entry.i18n).forEach((locale) => {
|
|
9
|
+
it(`Locale: ${locale} should have faqSchema, appSchema and howToSchema`, async () => {
|
|
10
|
+
const loader = tool.entry.i18n[locale as keyof typeof tool.entry.i18n];
|
|
11
|
+
if (!loader) return;
|
|
12
|
+
const content = (await loader()) as ToolLocaleContent;
|
|
13
|
+
|
|
14
|
+
const schemaTypes = content.schemas.map((s: any) => s['@type']);
|
|
15
|
+
|
|
16
|
+
expect(schemaTypes, `Tool "${tool.entry.id}" locale "${locale}" is missing FAQPage schema`).toContain('FAQPage');
|
|
17
|
+
expect(schemaTypes, `Tool "${tool.entry.id}" locale "${locale}" is missing SoftwareApplication schema`).toContain('SoftwareApplication');
|
|
18
|
+
expect(schemaTypes, `Tool "${tool.entry.id}" locale "${locale}" is missing HowTo schema`).toContain('HowTo');
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -10,8 +10,7 @@ describe('SEO Content Length Validation', () => {
|
|
|
10
10
|
describe(`Tool: ${entry.id}`, () => {
|
|
11
11
|
Object.keys(entry.i18n).forEach((locale) => {
|
|
12
12
|
it(`${locale}: SEO section should exist`, async () => {
|
|
13
|
-
const loader = (entry.i18n as Record<string, (
|
|
14
|
-
if (!loader) return;
|
|
13
|
+
const loader = (entry.i18n as Record<string, () => Promise<{ seo?: unknown[] }>>)[locale];
|
|
15
14
|
const content = await loader();
|
|
16
15
|
if (!content.seo) return;
|
|
17
16
|
expect(Array.isArray(content.seo)).toBe(true);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it } from 'vitest';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
function getFiles(dir: string, ext: string[]): string[] {
|
|
6
|
+
const results: string[] = [];
|
|
7
|
+
if (!fs.existsSync(dir)) return results;
|
|
8
|
+
const list = fs.readdirSync(dir);
|
|
9
|
+
for (const file of list) {
|
|
10
|
+
const fullPath = path.join(dir, file);
|
|
11
|
+
const stat = fs.statSync(fullPath);
|
|
12
|
+
if (stat && stat.isDirectory()) {
|
|
13
|
+
results.push(...getFiles(fullPath, ext));
|
|
14
|
+
} else if (ext.some((e) => file.endsWith(e))) {
|
|
15
|
+
results.push(fullPath);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return results;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const SRC_DIR = path.join(process.cwd(), 'src');
|
|
22
|
+
|
|
23
|
+
describe('Project Titles - Separator Validation', () => {
|
|
24
|
+
const files = [
|
|
25
|
+
...getFiles(path.join(SRC_DIR, 'tool'), ['.ts']),
|
|
26
|
+
...getFiles(path.join(SRC_DIR, 'category'), ['.ts']),
|
|
27
|
+
].filter(f => f.includes('i18n'));
|
|
28
|
+
|
|
29
|
+
it.each(files)('Verify that titles in %s do not contain | or -', (filePath) => {
|
|
30
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
31
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
32
|
+
|
|
33
|
+
const titlePatterns = [
|
|
34
|
+
/const\s+title\s*=\s*['"]([^'"]+)['"]/g,
|
|
35
|
+
/title\s*:\s*['"]([^'"]+)['"]/g,
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const findings: string[] = [];
|
|
39
|
+
|
|
40
|
+
for (const pattern of titlePatterns) {
|
|
41
|
+
let match;
|
|
42
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
43
|
+
const title = match[1];
|
|
44
|
+
if (title.includes('|') || title.includes('-')) {
|
|
45
|
+
findings.push(title);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (findings.length > 0) {
|
|
51
|
+
const list = findings.map((f) => ` - "${f}"`).join('\n');
|
|
52
|
+
throw new Error(`Forbidden separators (| or -) found in titles in ${relativePath}:\n${list}`);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -240,7 +240,7 @@ const { ui } = Astro.props;
|
|
|
240
240
|
</script>
|
|
241
241
|
|
|
242
242
|
<style>
|
|
243
|
-
.bfc-card {
|
|
243
|
+
:global(.bfc-card) {
|
|
244
244
|
background: #fff;
|
|
245
245
|
border: 1px solid #e2e8f0;
|
|
246
246
|
border-radius: 28px;
|
|
@@ -250,7 +250,7 @@ const { ui } = Astro.props;
|
|
|
250
250
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.08);
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
:global(.theme-dark
|
|
253
|
+
:global(.theme-dark .bfc-card) {
|
|
254
254
|
background: #111827;
|
|
255
255
|
border-color: #1f2937;
|
|
256
256
|
}
|
|
@@ -266,17 +266,17 @@ const { ui } = Astro.props;
|
|
|
266
266
|
border-right: 1px solid #e2e8f0;
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
:global(.theme-dark
|
|
269
|
+
:global(.theme-dark .bfc-left) {
|
|
270
270
|
background: #1f2937;
|
|
271
271
|
border-right-color: #374151;
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
-
.bfc-right {
|
|
274
|
+
:global(.bfc-right) {
|
|
275
275
|
background: #fff;
|
|
276
276
|
padding: 40px;
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
-
:global(.theme-dark
|
|
279
|
+
:global(.theme-dark .bfc-right) {
|
|
280
280
|
background: #111827;
|
|
281
281
|
}
|
|
282
282
|
|
|
@@ -290,7 +290,7 @@ const { ui } = Astro.props;
|
|
|
290
290
|
margin-bottom: 32px;
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
-
.bfc-input-label {
|
|
293
|
+
:global(.bfc-input-label) {
|
|
294
294
|
display: block;
|
|
295
295
|
font-size: 0.95rem;
|
|
296
296
|
font-weight: 700;
|
|
@@ -298,7 +298,7 @@ const { ui } = Astro.props;
|
|
|
298
298
|
margin-bottom: 12px;
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
-
:global(.theme-dark
|
|
301
|
+
:global(.theme-dark .bfc-input-label) {
|
|
302
302
|
color: #e2e8f0;
|
|
303
303
|
}
|
|
304
304
|
|
|
@@ -314,11 +314,11 @@ const { ui } = Astro.props;
|
|
|
314
314
|
margin-bottom: 24px;
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
-
:global(.theme-dark
|
|
317
|
+
:global(.theme-dark .bfc-unit-nav) {
|
|
318
318
|
background: #374151;
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
.bfc-unit-tab {
|
|
321
|
+
:global(.bfc-unit-tab) {
|
|
322
322
|
flex: 1;
|
|
323
323
|
padding: 8px;
|
|
324
324
|
border: none;
|
|
@@ -332,17 +332,17 @@ const { ui } = Astro.props;
|
|
|
332
332
|
margin: 2px;
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
-
:global(.theme-dark
|
|
335
|
+
:global(.theme-dark .bfc-unit-tab) {
|
|
336
336
|
background: #1f2937;
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
-
.bfc-unit-active {
|
|
339
|
+
:global(.bfc-unit-active) {
|
|
340
340
|
background: #fff;
|
|
341
341
|
color: #0d9488;
|
|
342
342
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
-
:global(.theme-dark
|
|
345
|
+
:global(.theme-dark .bfc-unit-active) {
|
|
346
346
|
background: #4b5563;
|
|
347
347
|
color: #2dd4bf;
|
|
348
348
|
}
|
|
@@ -358,12 +358,12 @@ const { ui } = Astro.props;
|
|
|
358
358
|
margin-bottom: 8px;
|
|
359
359
|
}
|
|
360
360
|
|
|
361
|
-
:global(.theme-dark
|
|
361
|
+
:global(.theme-dark .bfc-stepper-box) {
|
|
362
362
|
background: #111827;
|
|
363
363
|
border-color: #4b5563;
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
-
.bfc-btn-step {
|
|
366
|
+
:global(.bfc-btn-step) {
|
|
367
367
|
width: 44px;
|
|
368
368
|
height: 44px;
|
|
369
369
|
border-radius: 12px;
|
|
@@ -376,12 +376,12 @@ const { ui } = Astro.props;
|
|
|
376
376
|
transition: all 0.2s ease;
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
-
:global(.theme-dark
|
|
379
|
+
:global(.theme-dark .bfc-btn-step) {
|
|
380
380
|
background: #374151;
|
|
381
381
|
color: #f9fafb;
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
-
.bfc-btn-step:hover {
|
|
384
|
+
:global(.bfc-btn-step:hover) {
|
|
385
385
|
background: #0d9488;
|
|
386
386
|
color: #fff;
|
|
387
387
|
}
|
|
@@ -390,18 +390,18 @@ const { ui } = Astro.props;
|
|
|
390
390
|
text-align: center;
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
.bfc-val-big {
|
|
393
|
+
:global(.bfc-val-big) {
|
|
394
394
|
display: block;
|
|
395
395
|
font-size: 2rem;
|
|
396
396
|
font-weight: 800;
|
|
397
397
|
color: #0f172a;
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
-
:global(.theme-dark
|
|
400
|
+
:global(.theme-dark .bfc-val-big) {
|
|
401
401
|
color: #fff;
|
|
402
402
|
}
|
|
403
403
|
|
|
404
|
-
.bfc-val-sub {
|
|
404
|
+
:global(.bfc-val-sub) {
|
|
405
405
|
display: block;
|
|
406
406
|
font-size: 0.85rem;
|
|
407
407
|
color: #64748b;
|
|
@@ -423,11 +423,11 @@ const { ui } = Astro.props;
|
|
|
423
423
|
outline: none;
|
|
424
424
|
}
|
|
425
425
|
|
|
426
|
-
:global(.theme-dark
|
|
426
|
+
:global(.theme-dark .bfc-slider) {
|
|
427
427
|
background: #374151;
|
|
428
428
|
}
|
|
429
429
|
|
|
430
|
-
.bfc-slider::-webkit-slider-thumb {
|
|
430
|
+
:global(.bfc-slider::-webkit-slider-thumb) {
|
|
431
431
|
-webkit-appearance: none;
|
|
432
432
|
appearance: none;
|
|
433
433
|
width: 22px;
|
|
@@ -445,7 +445,7 @@ const { ui } = Astro.props;
|
|
|
445
445
|
gap: 12px;
|
|
446
446
|
}
|
|
447
447
|
|
|
448
|
-
.bfc-type-tile {
|
|
448
|
+
:global(.bfc-type-tile) {
|
|
449
449
|
background: #f1f5f9;
|
|
450
450
|
border: 1px solid #cbd5e1;
|
|
451
451
|
border-radius: 14px;
|
|
@@ -458,19 +458,19 @@ const { ui } = Astro.props;
|
|
|
458
458
|
color: #64748b;
|
|
459
459
|
}
|
|
460
460
|
|
|
461
|
-
:global(.theme-dark
|
|
461
|
+
:global(.theme-dark .bfc-type-tile) {
|
|
462
462
|
background: #1f2937;
|
|
463
463
|
border-color: #4b5563;
|
|
464
464
|
}
|
|
465
465
|
|
|
466
|
-
.bfc-type-active {
|
|
466
|
+
:global(.bfc-type-active) {
|
|
467
467
|
background: #f0fdfa;
|
|
468
468
|
border-color: #0d9488;
|
|
469
469
|
color: #0f766e;
|
|
470
470
|
box-shadow: 0 2px 8px rgba(13, 148, 136, 0.1);
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
-
:global(.theme-dark
|
|
473
|
+
:global(.theme-dark .bfc-type-active) {
|
|
474
474
|
background: rgba(13, 148, 136, 0.1);
|
|
475
475
|
color: #2dd4bf;
|
|
476
476
|
border-color: #2dd4bf;
|
|
@@ -484,7 +484,7 @@ const { ui } = Astro.props;
|
|
|
484
484
|
border: 1px solid #e2e8f0;
|
|
485
485
|
}
|
|
486
486
|
|
|
487
|
-
:global(.theme-dark
|
|
487
|
+
:global(.theme-dark .bfc-gauge-area) {
|
|
488
488
|
background: #111827;
|
|
489
489
|
border-color: #374151;
|
|
490
490
|
}
|
|
@@ -506,14 +506,14 @@ const { ui } = Astro.props;
|
|
|
506
506
|
box-shadow: 0 10px 25px rgba(13, 148, 136, 0.2);
|
|
507
507
|
}
|
|
508
508
|
|
|
509
|
-
.bfc-visual-hint {
|
|
509
|
+
:global(.bfc-visual-hint) {
|
|
510
510
|
font-size: 0.95rem;
|
|
511
511
|
font-weight: 600;
|
|
512
512
|
color: #0f766e;
|
|
513
513
|
margin: 0;
|
|
514
514
|
}
|
|
515
515
|
|
|
516
|
-
:global(.theme-dark
|
|
516
|
+
:global(.theme-dark .bfc-visual-hint) {
|
|
517
517
|
color: #2dd4bf;
|
|
518
518
|
}
|
|
519
519
|
|
|
@@ -525,11 +525,11 @@ const { ui } = Astro.props;
|
|
|
525
525
|
margin-bottom: 24px;
|
|
526
526
|
}
|
|
527
527
|
|
|
528
|
-
:global(.theme-dark
|
|
528
|
+
:global(.theme-dark .bfc-res-card-box) {
|
|
529
529
|
background: rgba(13, 148, 136, 0.05);
|
|
530
530
|
}
|
|
531
531
|
|
|
532
|
-
.bfc-res-main-val {
|
|
532
|
+
:global(.bfc-res-main-val) {
|
|
533
533
|
display: block;
|
|
534
534
|
font-size: 3.5rem;
|
|
535
535
|
font-weight: 950;
|
|
@@ -538,11 +538,11 @@ const { ui } = Astro.props;
|
|
|
538
538
|
line-height: 1;
|
|
539
539
|
}
|
|
540
540
|
|
|
541
|
-
:global(.theme-dark
|
|
541
|
+
:global(.theme-dark .bfc-res-main-val) {
|
|
542
542
|
color: #2dd4bf;
|
|
543
543
|
}
|
|
544
544
|
|
|
545
|
-
.bfc-res-label {
|
|
545
|
+
:global(.bfc-res-label) {
|
|
546
546
|
display: block;
|
|
547
547
|
margin-top: 8px;
|
|
548
548
|
font-size: 1rem;
|
|
@@ -562,11 +562,11 @@ const { ui } = Astro.props;
|
|
|
562
562
|
border-bottom: 2px solid #f1f5f9;
|
|
563
563
|
}
|
|
564
564
|
|
|
565
|
-
:global(.theme-dark
|
|
565
|
+
:global(.theme-dark .bfc-stat-item) {
|
|
566
566
|
border-bottom-color: #374151;
|
|
567
567
|
}
|
|
568
568
|
|
|
569
|
-
.bfc-stat-label {
|
|
569
|
+
:global(.bfc-stat-label) {
|
|
570
570
|
display: block;
|
|
571
571
|
font-size: 0.7rem;
|
|
572
572
|
font-weight: 800;
|
|
@@ -575,13 +575,13 @@ const { ui } = Astro.props;
|
|
|
575
575
|
margin-bottom: 2px;
|
|
576
576
|
}
|
|
577
577
|
|
|
578
|
-
.bfc-stat-value {
|
|
578
|
+
:global(.bfc-stat-value) {
|
|
579
579
|
font-size: 1.15rem;
|
|
580
580
|
font-weight: 800;
|
|
581
581
|
color: #334155;
|
|
582
582
|
}
|
|
583
583
|
|
|
584
|
-
:global(.theme-dark
|
|
584
|
+
:global(.theme-dark .bfc-stat-value) {
|
|
585
585
|
color: #e2e8f0;
|
|
586
586
|
}
|
|
587
587
|
|
|
@@ -603,22 +603,22 @@ const { ui } = Astro.props;
|
|
|
603
603
|
border-radius: 100px;
|
|
604
604
|
}
|
|
605
605
|
|
|
606
|
-
.bfc-pill-hunger {
|
|
606
|
+
:global(.bfc-pill-hunger) {
|
|
607
607
|
background: #fff7ed;
|
|
608
608
|
color: #c2410c;
|
|
609
609
|
}
|
|
610
610
|
|
|
611
|
-
:global(.theme-dark
|
|
611
|
+
:global(.theme-dark .bfc-pill-hunger) {
|
|
612
612
|
background: rgba(194, 65, 12, 0.1);
|
|
613
613
|
color: #fdba74;
|
|
614
614
|
}
|
|
615
615
|
|
|
616
|
-
.bfc-pill-fullness {
|
|
616
|
+
:global(.bfc-pill-fullness) {
|
|
617
617
|
background: #f0fdf4;
|
|
618
618
|
color: #15803d;
|
|
619
619
|
}
|
|
620
620
|
|
|
621
|
-
:global(.theme-dark
|
|
621
|
+
:global(.theme-dark .bfc-pill-fullness) {
|
|
622
622
|
background: rgba(21, 128, 61, 0.1);
|
|
623
623
|
color: #4ade80;
|
|
624
624
|
}
|