@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.
Files changed (115) hide show
  1. package/package.json +67 -66
  2. package/src/category/i18n/de.ts +48 -0
  3. package/src/category/i18n/en.ts +10 -10
  4. package/src/category/i18n/fr.ts +10 -10
  5. package/src/category/i18n/id.ts +48 -0
  6. package/src/category/i18n/it.ts +48 -0
  7. package/src/category/i18n/ja.ts +48 -0
  8. package/src/category/i18n/ko.ts +48 -0
  9. package/src/category/i18n/nl.ts +48 -0
  10. package/src/category/i18n/pl.ts +48 -0
  11. package/src/category/i18n/pt.ts +48 -0
  12. package/src/category/i18n/ru.ts +48 -0
  13. package/src/category/i18n/sv.ts +48 -0
  14. package/src/category/i18n/tr.ts +48 -0
  15. package/src/category/i18n/zh.ts +48 -0
  16. package/src/category/index.ts +13 -1
  17. package/src/tests/faq_count.test.ts +1 -1
  18. package/src/tests/i18n_coverage.test.ts +36 -0
  19. package/src/tests/locale_completeness.test.ts +1 -1
  20. package/src/tests/schemas_fulfillment.test.ts +23 -0
  21. package/src/tests/seo_length.test.ts +1 -2
  22. package/src/tests/title_quality.test.ts +55 -0
  23. package/src/tool/baby-feeding-calculator/component.astro +40 -40
  24. package/src/tool/baby-feeding-calculator/i18n/de.ts +162 -0
  25. package/src/tool/baby-feeding-calculator/i18n/id.ts +162 -0
  26. package/src/tool/baby-feeding-calculator/i18n/it.ts +162 -0
  27. package/src/tool/baby-feeding-calculator/i18n/ja.ts +162 -0
  28. package/src/tool/baby-feeding-calculator/i18n/ko.ts +162 -0
  29. package/src/tool/baby-feeding-calculator/i18n/nl.ts +162 -0
  30. package/src/tool/baby-feeding-calculator/i18n/pl.ts +162 -0
  31. package/src/tool/baby-feeding-calculator/i18n/pt.ts +162 -0
  32. package/src/tool/baby-feeding-calculator/i18n/ru.ts +162 -0
  33. package/src/tool/baby-feeding-calculator/i18n/sv.ts +162 -0
  34. package/src/tool/baby-feeding-calculator/i18n/tr.ts +162 -0
  35. package/src/tool/baby-feeding-calculator/i18n/zh.ts +162 -0
  36. package/src/tool/baby-feeding-calculator/index.ts +12 -0
  37. package/src/tool/baby-percentile-calculator/component.astro +38 -38
  38. package/src/tool/baby-percentile-calculator/i18n/de.ts +245 -0
  39. package/src/tool/baby-percentile-calculator/i18n/id.ts +245 -0
  40. package/src/tool/baby-percentile-calculator/i18n/it.ts +245 -0
  41. package/src/tool/baby-percentile-calculator/i18n/ja.ts +245 -0
  42. package/src/tool/baby-percentile-calculator/i18n/ko.ts +245 -0
  43. package/src/tool/baby-percentile-calculator/i18n/nl.ts +245 -0
  44. package/src/tool/baby-percentile-calculator/i18n/pl.ts +245 -0
  45. package/src/tool/baby-percentile-calculator/i18n/pt.ts +245 -0
  46. package/src/tool/baby-percentile-calculator/i18n/ru.ts +245 -0
  47. package/src/tool/baby-percentile-calculator/i18n/sv.ts +245 -0
  48. package/src/tool/baby-percentile-calculator/i18n/tr.ts +245 -0
  49. package/src/tool/baby-percentile-calculator/i18n/zh.ts +245 -0
  50. package/src/tool/baby-percentile-calculator/index.ts +12 -0
  51. package/src/tool/baby-size-converter/component.astro +42 -42
  52. package/src/tool/baby-size-converter/i18n/de.ts +203 -0
  53. package/src/tool/baby-size-converter/i18n/fr.ts +1 -1
  54. package/src/tool/baby-size-converter/i18n/id.ts +203 -0
  55. package/src/tool/baby-size-converter/i18n/it.ts +203 -0
  56. package/src/tool/baby-size-converter/i18n/ja.ts +203 -0
  57. package/src/tool/baby-size-converter/i18n/ko.ts +203 -0
  58. package/src/tool/baby-size-converter/i18n/nl.ts +203 -0
  59. package/src/tool/baby-size-converter/i18n/pl.ts +203 -0
  60. package/src/tool/baby-size-converter/i18n/pt.ts +203 -0
  61. package/src/tool/baby-size-converter/i18n/ru.ts +203 -0
  62. package/src/tool/baby-size-converter/i18n/sv.ts +203 -0
  63. package/src/tool/baby-size-converter/i18n/tr.ts +203 -0
  64. package/src/tool/baby-size-converter/i18n/zh.ts +203 -0
  65. package/src/tool/baby-size-converter/index.ts +12 -0
  66. package/src/tool/fertile-days-estimator/component.astro +14 -9
  67. package/src/tool/fertile-days-estimator/i18n/de.ts +262 -0
  68. package/src/tool/fertile-days-estimator/i18n/id.ts +262 -0
  69. package/src/tool/fertile-days-estimator/i18n/it.ts +262 -0
  70. package/src/tool/fertile-days-estimator/i18n/ja.ts +262 -0
  71. package/src/tool/fertile-days-estimator/i18n/ko.ts +262 -0
  72. package/src/tool/fertile-days-estimator/i18n/nl.ts +262 -0
  73. package/src/tool/fertile-days-estimator/i18n/pl.ts +262 -0
  74. package/src/tool/fertile-days-estimator/i18n/pt.ts +262 -0
  75. package/src/tool/fertile-days-estimator/i18n/ru.ts +262 -0
  76. package/src/tool/fertile-days-estimator/i18n/sv.ts +262 -0
  77. package/src/tool/fertile-days-estimator/i18n/tr.ts +262 -0
  78. package/src/tool/fertile-days-estimator/i18n/zh.ts +262 -0
  79. package/src/tool/fertile-days-estimator/index.ts +12 -0
  80. package/src/tool/pregnancy-calculator/component.astro +55 -48
  81. package/src/tool/pregnancy-calculator/i18n/de.ts +467 -0
  82. package/src/tool/pregnancy-calculator/i18n/en.ts +140 -0
  83. package/src/tool/pregnancy-calculator/i18n/es.ts +143 -3
  84. package/src/tool/pregnancy-calculator/i18n/fr.ts +143 -3
  85. package/src/tool/pregnancy-calculator/i18n/id.ts +467 -0
  86. package/src/tool/pregnancy-calculator/i18n/it.ts +467 -0
  87. package/src/tool/pregnancy-calculator/i18n/ja.ts +467 -0
  88. package/src/tool/pregnancy-calculator/i18n/ko.ts +467 -0
  89. package/src/tool/pregnancy-calculator/i18n/nl.ts +467 -0
  90. package/src/tool/pregnancy-calculator/i18n/pl.ts +467 -0
  91. package/src/tool/pregnancy-calculator/i18n/pt.ts +467 -0
  92. package/src/tool/pregnancy-calculator/i18n/ru.ts +467 -0
  93. package/src/tool/pregnancy-calculator/i18n/sv.ts +467 -0
  94. package/src/tool/pregnancy-calculator/i18n/tr.ts +467 -0
  95. package/src/tool/pregnancy-calculator/i18n/zh.ts +467 -0
  96. package/src/tool/pregnancy-calculator/index.ts +37 -1
  97. package/src/tool/pregnancy-calculator/milestones.ts +2 -146
  98. package/src/tool/vaccination-calendar/component.astro +26 -24
  99. package/src/tool/vaccination-calendar/i18n/de.ts +194 -0
  100. package/src/tool/vaccination-calendar/i18n/en.ts +20 -0
  101. package/src/tool/vaccination-calendar/i18n/es.ts +20 -0
  102. package/src/tool/vaccination-calendar/i18n/fr.ts +99 -75
  103. package/src/tool/vaccination-calendar/i18n/id.ts +194 -0
  104. package/src/tool/vaccination-calendar/i18n/it.ts +194 -0
  105. package/src/tool/vaccination-calendar/i18n/ja.ts +194 -0
  106. package/src/tool/vaccination-calendar/i18n/ko.ts +194 -0
  107. package/src/tool/vaccination-calendar/i18n/nl.ts +194 -0
  108. package/src/tool/vaccination-calendar/i18n/pl.ts +194 -0
  109. package/src/tool/vaccination-calendar/i18n/pt.ts +194 -0
  110. package/src/tool/vaccination-calendar/i18n/ru.ts +194 -0
  111. package/src/tool/vaccination-calendar/i18n/sv.ts +194 -0
  112. package/src/tool/vaccination-calendar/i18n/tr.ts +194 -0
  113. package/src/tool/vaccination-calendar/i18n/zh.ts +194 -0
  114. package/src/tool/vaccination-calendar/index.ts +33 -1
  115. 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
+ };
@@ -17,8 +17,20 @@ export const babiesCategory: BabiesCategoryEntry = {
17
17
  pregnancyCalculator,
18
18
  ],
19
19
  i18n: {
20
- es: () => import('./i18n/es').then((m) => m.content),
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.babiesCategory[] = [];
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
+ });
@@ -35,7 +35,7 @@ describe('Locale Completeness Validation', () => {
35
35
  });
36
36
  });
37
37
 
38
- it('all 6 tools are registered', () => {
38
+ it('all 6 tools registered', () => {
39
39
  expect(ALL_TOOLS.length).toBe(6);
40
40
  });
41
41
  });
@@ -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, (() => Promise<{ seo?: unknown[] }>) | undefined>)[locale];
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) .bfc-card {
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) .bfc-left {
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) .bfc-right {
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) .bfc-input-label {
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) .bfc-unit-nav {
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) .bfc-unit-tab {
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) .bfc-unit-active {
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) .bfc-stepper-box {
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) .bfc-btn-step {
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) .bfc-val-big {
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) .bfc-slider {
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) .bfc-type-tile {
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) .bfc-type-active {
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) .bfc-gauge-area {
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) .bfc-visual-hint {
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) .bfc-res-card-box {
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) .bfc-res-main-val {
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) .bfc-stat-item {
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) .bfc-stat-value {
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) .bfc-pill-hunger {
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) .bfc-pill-fullness {
621
+ :global(.theme-dark .bfc-pill-fullness) {
622
622
  background: rgba(21, 128, 61, 0.1);
623
623
  color: #4ade80;
624
624
  }