@jjlmoya/utils-nature 1.3.0 → 1.5.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 (70) hide show
  1. package/package.json +2 -1
  2. package/src/category/i18n/de.ts +110 -0
  3. package/src/category/i18n/id.ts +110 -0
  4. package/src/category/i18n/it.ts +110 -0
  5. package/src/category/i18n/ja.ts +110 -0
  6. package/src/category/i18n/ko.ts +110 -0
  7. package/src/category/i18n/nl.ts +110 -0
  8. package/src/category/i18n/pl.ts +110 -0
  9. package/src/category/i18n/pt.ts +110 -0
  10. package/src/category/i18n/ru.ts +110 -0
  11. package/src/category/i18n/sv.ts +110 -0
  12. package/src/category/i18n/tr.ts +110 -0
  13. package/src/category/i18n/zh.ts +110 -0
  14. package/src/category/index.ts +13 -1
  15. package/src/tests/i18n_coverage.test.ts +36 -0
  16. package/src/tests/slug_language_code_format.test.ts +23 -0
  17. package/src/tests/slug_uniqueness.test.ts +81 -0
  18. package/src/tests/tool_validation.test.ts +1 -2
  19. package/src/tool/cricketThermometer/i18n/de.ts +181 -0
  20. package/src/tool/cricketThermometer/i18n/id.ts +181 -0
  21. package/src/tool/cricketThermometer/i18n/it.ts +181 -0
  22. package/src/tool/cricketThermometer/i18n/ja.ts +181 -0
  23. package/src/tool/cricketThermometer/i18n/ko.ts +181 -0
  24. package/src/tool/cricketThermometer/i18n/nl.ts +181 -0
  25. package/src/tool/cricketThermometer/i18n/pl.ts +181 -0
  26. package/src/tool/cricketThermometer/i18n/pt.ts +181 -0
  27. package/src/tool/cricketThermometer/i18n/ru.ts +181 -0
  28. package/src/tool/cricketThermometer/i18n/sv.ts +181 -0
  29. package/src/tool/cricketThermometer/i18n/tr.ts +181 -0
  30. package/src/tool/cricketThermometer/i18n/zh.ts +181 -0
  31. package/src/tool/cricketThermometer/index.ts +15 -7
  32. package/src/tool/digitalCarbon/i18n/de.ts +235 -0
  33. package/src/tool/digitalCarbon/i18n/id.ts +235 -0
  34. package/src/tool/digitalCarbon/i18n/it.ts +235 -0
  35. package/src/tool/digitalCarbon/i18n/ja.ts +235 -0
  36. package/src/tool/digitalCarbon/i18n/ko.ts +235 -0
  37. package/src/tool/digitalCarbon/i18n/nl.ts +235 -0
  38. package/src/tool/digitalCarbon/i18n/pl.ts +235 -0
  39. package/src/tool/digitalCarbon/i18n/pt.ts +235 -0
  40. package/src/tool/digitalCarbon/i18n/ru.ts +235 -0
  41. package/src/tool/digitalCarbon/i18n/sv.ts +235 -0
  42. package/src/tool/digitalCarbon/i18n/tr.ts +235 -0
  43. package/src/tool/digitalCarbon/i18n/zh.ts +235 -0
  44. package/src/tool/digitalCarbon/index.ts +15 -7
  45. package/src/tool/rainHarvester/i18n/de.ts +185 -0
  46. package/src/tool/rainHarvester/i18n/id.ts +185 -0
  47. package/src/tool/rainHarvester/i18n/it.ts +185 -0
  48. package/src/tool/rainHarvester/i18n/ja.ts +185 -0
  49. package/src/tool/rainHarvester/i18n/ko.ts +185 -0
  50. package/src/tool/rainHarvester/i18n/nl.ts +185 -0
  51. package/src/tool/rainHarvester/i18n/pl.ts +185 -0
  52. package/src/tool/rainHarvester/i18n/pt.ts +185 -0
  53. package/src/tool/rainHarvester/i18n/ru.ts +185 -0
  54. package/src/tool/rainHarvester/i18n/sv.ts +185 -0
  55. package/src/tool/rainHarvester/i18n/tr.ts +185 -0
  56. package/src/tool/rainHarvester/i18n/zh.ts +185 -0
  57. package/src/tool/rainHarvester/index.ts +15 -7
  58. package/src/tool/seedCalculator/i18n/de.ts +213 -0
  59. package/src/tool/seedCalculator/i18n/id.ts +213 -0
  60. package/src/tool/seedCalculator/i18n/it.ts +213 -0
  61. package/src/tool/seedCalculator/i18n/ja.ts +213 -0
  62. package/src/tool/seedCalculator/i18n/ko.ts +213 -0
  63. package/src/tool/seedCalculator/i18n/nl.ts +213 -0
  64. package/src/tool/seedCalculator/i18n/pl.ts +213 -0
  65. package/src/tool/seedCalculator/i18n/pt.ts +213 -0
  66. package/src/tool/seedCalculator/i18n/ru.ts +213 -0
  67. package/src/tool/seedCalculator/i18n/sv.ts +213 -0
  68. package/src/tool/seedCalculator/i18n/tr.ts +213 -0
  69. package/src/tool/seedCalculator/i18n/zh.ts +213 -0
  70. package/src/tool/seedCalculator/index.ts +15 -7
@@ -0,0 +1,110 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'doga',
5
+ title: 'Doğa & Tarım Araçları',
6
+ description:
7
+ 'Ücretsiz çevrimiçi araçlarla doğayla ilişkinizi optimize edin. Ekim hesaplayıcıları, yağmur suyu hasadı, cırcır böceği termometresi ve dijital karbon ayak izi tahmin araçları.',
8
+ seo: [
9
+ {
10
+ type: 'title',
11
+ text: 'Biyomühendislik ve Sürdürülebilirlik: Gezegen için Veri',
12
+ level: 2,
13
+ },
14
+ {
15
+ type: 'paragraph',
16
+ html: '2026\'da doğayla bağlantı, kaynaklarımız hakkında daha derin bir teknik anlayışla sağlanmaktadır. Bu bölümde, küçük ve büyük ölçekte tarımsal üretimi optimize etmek, hayati su kaynaklarını yönetmek ve küresel ekosistem üzerindeki görünmez etkimizi ölçmek için tasarlanmış <strong>ücretsiz çevrimiçi araçlar</strong> sunuyoruz. <strong>Hassas tarım</strong> ve sürdürülebilirlik yalnızca kavramlar değil, kesin hesaplamalara dayanan eylemlerdir.',
17
+ },
18
+ {
19
+ type: 'paragraph',
20
+ html: 'Ekim makinelerinin kalibrasyonundan internet taramanızın çevresel etkisinin hesaplanmasına kadar, araçlarımız biyolojik ve fiziksel değişkenleri daha yeşil bir gelecek için uygulanabilir verilere dönüştürür.',
21
+ },
22
+ {
23
+ type: 'title',
24
+ text: 'Hassas Tarım: Ekim Kalibrasyonu ve Yetiştiricilik',
25
+ level: 2,
26
+ },
27
+ {
28
+ type: 'paragraph',
29
+ html: 'Tarlada verimlilik, tohumların tam dağılımıyla başlar. <strong>Tohum hesaplayıcımız</strong>, mekanik ve pnömatik ekim makinelerinin kalibre edilmesine olanak tanır ve hektar başına istenen bitki nüfusunu tohumlar arasındaki tam aralığa dönüştürür. Çimlenme oranını ve ekim yoğunluğunu anlamak, mahsul verimini maksimize etmek ve girdi israfını azaltmak için temeldir.',
30
+ },
31
+ {
32
+ type: 'title',
33
+ text: 'Su Kaynağı Yönetimi: Yağmur Suyu Hasadı',
34
+ level: 2,
35
+ },
36
+ {
37
+ type: 'paragraph',
38
+ html: 'Su sınırlı bir kaynaktır. <strong>Yağmur suyu hasat aracı</strong>, çatı yüzey alanınıza ve bölgenizin yağış geçmişine dayalı olarak yıllık kaç litre su toplayabileceğinizi hesaplar. Bu araç, depolama tanklarının boyutlandırılması ve kentsel bahçeler ile kırsal çiftlikler için tamamlayıcı sulama sistemlerinin uygulanması için hayati öneme sahiptir.',
39
+ },
40
+ {
41
+ type: 'title',
42
+ text: 'Doğal Meraklar ve Dolbear Yasası',
43
+ level: 2,
44
+ },
45
+ {
46
+ type: 'paragraph',
47
+ html: 'Doğa büyüleyici matematiksel yasalara uyar. Cırcır böceklerinin biyolojik termometreler olarak işlev gördüğünü biliyor muydunuz? <strong>Dolbear Yasası</strong>na dayanan hesaplayıcımız, cırcır böceklerinin cırtlama frekansını şaşırtıcı bir doğrulukla santigrat dereceye çevirir; hayvan fizyolojisi ile çevresel termodinamik arasındaki bağlantıyı gösterir.',
48
+ },
49
+ {
50
+ type: 'title',
51
+ text: 'Dijital Sürdürülebilirlik: Web Karbon Ayak İzi',
52
+ level: 2,
53
+ },
54
+ {
55
+ type: 'paragraph',
56
+ html: 'İnternet madde dışı değildir; bir enerji maliyeti vardır. <strong>Dijital karbon ayak izi</strong> hesaplayıcımız, bir web sitesinin veri ağırlığını analiz eder ve sunucu tüketimini ve dağıtım ağını göz önünde bulundurarak ziyaret başına oluşturulan CO2 emisyonlarını tahmin eder. Sürdürülebilir tasarım uygulamak isteyen bilinçli geliştiriciler ve markalar için vazgeçilmez bir araçtır.',
57
+ },
58
+ {
59
+ type: 'list',
60
+ items: [
61
+ '<strong>Kaynak Optimizasyonu:</strong> Veri odaklı tarımsal planlama yoluyla su ve tohum tüketimini azaltın.',
62
+ '<strong>Çevresel Farkındalık:</strong> Dijital aktivitelerinizin küresel çevre üzerindeki gizli etkisini görselleştirin.',
63
+ '<strong>Biyoçeşitlilik ve Bilim:</strong> Yaban hayatı ritimlerinin gözlemlenmesini ve anlaşılmasını teşvik eden araçlar.',
64
+ '<strong>Öz Yeterlilik:</strong> Daha dirençli bir off-grid yaşam için toplama ve yetiştirme altyapısını boyutlandırın.',
65
+ ],
66
+ },
67
+ {
68
+ type: 'tip',
69
+ title: 'Su Sürdürülebilirliği İpucu',
70
+ html: '<p><strong>Sulama Stratejisi:</strong> Toplanan suyu sabahın ilk saatlerinde veya öğleden sonranın son saatlerinde kullanın. Buharlaşmayı %30 azaltacak ve böylece daha az miktarda suyun bitkilerinizin gelişimi üzerinde çok daha büyük bir etkiye sahip olmasını sağlayacaksınız.</p>',
71
+ },
72
+ {
73
+ type: 'title',
74
+ text: 'Ekim Nöbeti ve Biyoçeşitlilik: Binlerce Yıllık Tarım Bilimi',
75
+ level: 2,
76
+ },
77
+ {
78
+ type: 'paragraph',
79
+ html: 'Ekim nöbeti ortaçağ geleneği değildir; bitki fizyolojisidir. Farklı bitkiler farklı toprak besinlerini tüketir ve kök bölgesinde farklı bakteri bileşimleri oluşturur. Buğday azotu tüketir; yonca onu bağlar. Mısır çok potasyum gerektirir; yulaf onu kullanılabilir bırakır. Akıllı rotasyon (buğday-yonca-mısır-baklagiller), pahalı sentetik gübreler olmadan doğal verimliliği geri kazandırır.',
80
+ },
81
+ {
82
+ type: 'title',
83
+ text: 'Toprak ve Besin Korunumu: Pedolojik Analiz',
84
+ level: 2,
85
+ },
86
+ {
87
+ type: 'paragraph',
88
+ html: 'Toprak canlıdır. Yapı ve verimlilik yaratan trilyon mikrobu, mantarları ve organizmaları içerir. Aşırı sürümden kaynaklanan erozyon bu yapıyı yok eder; sıkıştırılmış toprak su emmez; düşük organik madde su tutma kapasitesini azaltır. Toprak analizi (pH, besinler, organik madde, doku), herhangi bir müdahaleden önceki tanıdır.',
89
+ },
90
+ {
91
+ type: 'title',
92
+ text: 'Rejeneratif Tarımın Geleceği 2026',
93
+ level: 2,
94
+ },
95
+ {
96
+ type: 'paragraph',
97
+ html: '2026 yılının trendi <strong>Doğal Nesnelerin İnterneti (IoNT)</strong>dir. Nem sensörleri, izleme insansız hava araçları ve çevre yönetim yazılımı rejeneratif tarımı demokratikleştiriyor. Bu araçlar, çevrenizi sömürülecek bir kaynak olarak değil, teknik zeka aracılığıyla beslenmesi ve korunması gereken bir ekosistem olarak yönetme yeteneği verir.',
98
+ },
99
+ {
100
+ type: 'stats',
101
+ columns: 2,
102
+ items: [
103
+ { label: 'Ekim', value: 'Hassasiyet', icon: 'mdi:sprout' },
104
+ { label: 'Su', value: 'Kapasite', icon: 'mdi:water' },
105
+ { label: 'Karbon', value: 'Net Sıfır', icon: 'mdi:leaf' },
106
+ { label: 'Yaban Hayatı', value: 'Biyoritim', icon: 'mdi:bug' },
107
+ ],
108
+ },
109
+ ],
110
+ };
@@ -0,0 +1,110 @@
1
+ import type { CategoryLocaleContent } from '../../types';
2
+
3
+ export const content: CategoryLocaleContent = {
4
+ slug: 'nature',
5
+ title: '自然与农业工具',
6
+ description:
7
+ '使用免费在线工具优化您与环境的关系。播种计算器、雨水收集、蟋蟀温度计和数字碳足迹估算器。',
8
+ seo: [
9
+ {
10
+ type: 'title',
11
+ text: '生物工程与可持续发展:地球的数据',
12
+ level: 2,
13
+ },
14
+ {
15
+ type: 'paragraph',
16
+ html: '2026年,我们与自然的联系通过对资源更深入的技术理解来实现。在本节中,我们提供<strong>免费在线工具</strong>,旨在优化大小规模的农业生产,管理重要的水资源,并衡量我们对全球生态系统的隐形影响。<strong>精准农业</strong>和可持续发展不仅仅是概念,而是基于精确计算的行动。',
17
+ },
18
+ {
19
+ type: 'paragraph',
20
+ html: '从校准播种机械到计算您上网浏览的环境影响,我们的实用工具将生物和物理变量转化为更绿色未来的可行动数据。',
21
+ },
22
+ {
23
+ type: 'title',
24
+ text: '精准农业:播种校准与种植',
25
+ level: 2,
26
+ },
27
+ {
28
+ type: 'paragraph',
29
+ html: '农场效率始于种子的精确分配。我们的<strong>种子计算器</strong>可以校准机械和气动播种机,将每公顷所需的植株数量转换为种子之间的精确间距。了解发芽率和种植密度对于最大化作物产量和减少投入浪费至关重要。',
30
+ },
31
+ {
32
+ type: 'title',
33
+ text: '水资源管理:雨水收集',
34
+ level: 2,
35
+ },
36
+ {
37
+ type: 'paragraph',
38
+ html: '水是有限的资源。<strong>雨水收集器</strong>根据您的屋顶面积和您所在地区的降雨历史计算您每年可以收集多少升水。这个工具对于确定储水箱大小和为城市花园和农村农场实施补充灌溉系统至关重要。',
39
+ },
40
+ {
41
+ type: 'title',
42
+ text: '自然奇妙与多尔贝尔定律',
43
+ level: 2,
44
+ },
45
+ {
46
+ type: 'paragraph',
47
+ html: '自然遵循迷人的数学定律。您知道蟋蟀充当生物温度计吗?基于<strong>多尔贝尔定律</strong>,我们的计算器以令人惊讶的精度将蟋蟀鸣叫的频率转换为摄氏度,展示了动物生理学与环境热力学之间的相互关联。',
48
+ },
49
+ {
50
+ type: 'title',
51
+ text: '数字可持续性:网络碳足迹',
52
+ level: 2,
53
+ },
54
+ {
55
+ type: 'paragraph',
56
+ html: '互联网并非无形;它有能源成本。我们的<strong>数字碳足迹</strong>计算器分析网站的数据重量,并估算每次访问产生的CO2排放量,考虑服务器消耗和分销网络。这是寻求实施可持续设计的有意识开发者和品牌的重要工具。',
57
+ },
58
+ {
59
+ type: 'list',
60
+ items: [
61
+ '<strong>资源优化:</strong>通过数据驱动的农业规划减少水和种子消耗。',
62
+ '<strong>环境意识:</strong>可视化您的数字活动对全球环境的隐藏影响。',
63
+ '<strong>生物多样性和科学:</strong>促进观察和了解野生动物节律的工具。',
64
+ '<strong>自给自足:</strong>为更具弹性的离网生活确定收集和种植基础设施的规模。',
65
+ ],
66
+ },
67
+ {
68
+ type: 'tip',
69
+ title: '水资源可持续性提示',
70
+ html: '<p><strong>灌溉策略:</strong>在清晨或傍晚最后几小时使用收集的水。您将减少30%的蒸发,使较少量的水对植物发育产生更大的影响。</p>',
71
+ },
72
+ {
73
+ type: 'title',
74
+ text: '作物轮作与生物多样性:千年农业科学',
75
+ level: 2,
76
+ },
77
+ {
78
+ type: 'paragraph',
79
+ html: '作物轮作不是中世纪传统;它是植物生理学。不同作物消耗不同的土壤营养素,并在根系圈中产生不同的细菌组成。小麦消耗氮;苜蓿固定它。玉米需要大量钾;燕麦使其可用。智能轮作(小麦-苜蓿-玉米-豆类)无需昂贵的合成肥料即可恢复自然肥力。',
80
+ },
81
+ {
82
+ type: 'title',
83
+ text: '土壤与营养素保护:土壤学分析',
84
+ level: 2,
85
+ },
86
+ {
87
+ type: 'paragraph',
88
+ html: '土壤是活的。它含有数以万亿计的微生物、真菌和有机体,创造结构和肥力。过度耕作导致的侵蚀会破坏这种结构;压实的土壤不吸收水分;低有机质降低保水能力。土壤分析(pH、营养素、有机质、质地)是任何干预前的诊断。',
89
+ },
90
+ {
91
+ type: 'title',
92
+ text: '再生农业的未来 2026',
93
+ level: 2,
94
+ },
95
+ {
96
+ type: 'paragraph',
97
+ html: '2026年的趋势是<strong>自然物联网(IoNT)</strong>。湿度传感器、监控无人机和环境管理软件正在使再生农业民主化。这些工具让您能够将环境不作为开采资源来管理,而是作为通过技术智慧来培育和保护的生态系统。',
98
+ },
99
+ {
100
+ type: 'stats',
101
+ columns: 2,
102
+ items: [
103
+ { label: '播种', value: '精准', icon: 'mdi:sprout' },
104
+ { label: '水', value: '容量', icon: 'mdi:water' },
105
+ { label: '碳', value: '净零', icon: 'mdi:leaf' },
106
+ { label: '野生动物', value: '生物节律', icon: 'mdi:bug' },
107
+ ],
108
+ },
109
+ ],
110
+ };
@@ -8,9 +8,21 @@ export const natureCategory: NatureCategoryEntry = {
8
8
  icon: 'mdi:leaf',
9
9
  tools: [cricketThermometer, seedCalculator, rainHarvester, digitalCarbon],
10
10
  i18n: {
11
- es: () => import('./i18n/es').then((m) => m.content),
12
11
  en: () => import('./i18n/en').then((m) => m.content),
12
+ es: () => import('./i18n/es').then((m) => m.content),
13
13
  fr: () => import('./i18n/fr').then((m) => m.content),
14
+ de: () => import('./i18n/de').then((m) => m.content),
15
+ id: () => import('./i18n/id').then((m) => m.content),
16
+ it: () => import('./i18n/it').then((m) => m.content),
17
+ ja: () => import('./i18n/ja').then((m) => m.content),
18
+ ko: () => import('./i18n/ko').then((m) => m.content),
19
+ nl: () => import('./i18n/nl').then((m) => m.content),
20
+ pl: () => import('./i18n/pl').then((m) => m.content),
21
+ pt: () => import('./i18n/pt').then((m) => m.content),
22
+ ru: () => import('./i18n/ru').then((m) => m.content),
23
+ sv: () => import('./i18n/sv').then((m) => m.content),
24
+ tr: () => import('./i18n/tr').then((m) => m.content),
25
+ zh: () => import('./i18n/zh').then((m) => m.content),
14
26
  },
15
27
  };
16
28
 
@@ -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('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
+ });
@@ -0,0 +1,81 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ALL_TOOLS } from '../tools';
3
+ import type { ToolLocaleContent } from '../types';
4
+
5
+ const sharingLocales = ['ja', 'ko', 'zh'];
6
+
7
+ interface ValidateParams {
8
+ toolId: string;
9
+ locale: string;
10
+ content: ToolLocaleContent;
11
+ enSlug: string;
12
+ slugs: Map<string, string>;
13
+ }
14
+
15
+ const validateLocaleSlug = ({
16
+ toolId,
17
+ locale,
18
+ content,
19
+ enSlug,
20
+ slugs,
21
+ }: ValidateParams) => {
22
+ expect(
23
+ content.slug,
24
+ `Tool "${toolId}" locale "${locale}" has an invalid slug ("${content.slug}"). Slugs must be transliterated (only a-z, 0-9, and -).`,
25
+ ).toMatch(/^[a-z0-9-]+$/);
26
+
27
+ if (locale === 'en') {
28
+ return;
29
+ }
30
+
31
+ if (sharingLocales.includes(locale)) {
32
+ expect(
33
+ content.slug,
34
+ `Tool "${toolId}" locale "${locale}" must use the same slug as "en" ("${enSlug}").`,
35
+ ).toBe(enSlug);
36
+ } else {
37
+ expect(
38
+ content.slug,
39
+ `Tool "${toolId}" locale "${locale}" has the same slug as "en" ("${enSlug}"). Cada slug tiene que estar en su propia idioma`,
40
+ ).not.toBe(enSlug);
41
+
42
+ if (slugs.has(content.slug)) {
43
+ const previousLocale = slugs.get(content.slug);
44
+ expect(
45
+ false,
46
+ `Tool "${toolId}" locales "${locale}" and "${previousLocale}" share the same slug ("${content.slug}"). Cada slug tiene que estar en su propia idioma`,
47
+ ).toBe(true);
48
+ }
49
+ slugs.set(content.slug, locale);
50
+ }
51
+ };
52
+
53
+ describe('Slug Localization and Uniqueness Validation', () => {
54
+ ALL_TOOLS.forEach((tool) => {
55
+ describe(`Tool: ${tool.entry.id}`, () => {
56
+ it('every locale should have a unique, translated slug', async () => {
57
+ const slugs = new Map<string, string>();
58
+ const locales = Object.keys(tool.entry.i18n);
59
+
60
+ let enSlug = '';
61
+ if (locales.includes('en')) {
62
+ const enLoader = tool.entry.i18n['en' as keyof typeof tool.entry.i18n];
63
+ const enContent = (await enLoader?.()) as ToolLocaleContent;
64
+ enSlug = enContent.slug;
65
+ }
66
+
67
+ for (const locale of locales) {
68
+ const loader = tool.entry.i18n[locale as keyof typeof tool.entry.i18n];
69
+ const content = (await loader?.()) as ToolLocaleContent;
70
+ validateLocaleSlug({
71
+ toolId: tool.entry.id,
72
+ locale,
73
+ content,
74
+ enSlug,
75
+ slugs,
76
+ });
77
+ }
78
+ });
79
+ });
80
+ });
81
+ });
@@ -1,6 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { ALL_TOOLS } from '../tools';
3
- import { natureCategory } from '../data';
2
+ import { ALL_TOOLS, natureCategory } from '../index';
4
3
 
5
4
  describe('Tool Validation Suite', () => {
6
5
  describe('Library Registration', () => {
@@ -0,0 +1,181 @@
1
+ import type { WithContext, FAQPage, HowToThing, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { CricketThermometerUI } from '../ui';
4
+
5
+ const slug = 'grillen-thermometer';
6
+ const title = 'Grillen Thermometer – Dolbearsches Gesetz Temperaturrechner';
7
+ const description =
8
+ 'Kein Thermometer zur Hand? Hören Sie den Grillen zu. Berechnen Sie die exakte Temperatur, indem Sie das Zirpen mit unserem Dolbearsches Gesetz Rechner zählen.';
9
+
10
+ const faqData = [
11
+ {
12
+ question: 'Was ist das Dolbearsche Gesetz?',
13
+ answer:
14
+ 'Es wurde 1897 von Amos Dolbear formuliert und ist eine wissenschaftliche Beobachtung, die eine lineare Korrelation zwischen der Zirp-Rate von Grillen und der Umgebungslufttemperatur feststellte.',
15
+ },
16
+ {
17
+ question: 'Warum zirpen Grillen bei Hitze schneller?',
18
+ answer:
19
+ 'Grillen sind wechselwarme Tiere (Ektothermen). Die Geschwindigkeit ihrer Stoffwechselprozesse und Muskelkontraktionen hängt von der Außentemperatur ab; je wärmer es ist, desto mehr Energie haben sie, um Geräusche schnell hintereinander zu erzeugen.',
20
+ },
21
+ {
22
+ question: 'Ist diese Messung genau?',
23
+ answer:
24
+ 'Sie ist überraschend genau für Arten wie die Schneegrille (Oecanthus fultoni), mit einer Fehlermarge von etwa 0,5°C bei korrekter Zählung. Faktoren wie Luftfeuchtigkeit oder Wind können das Ergebnis jedoch beeinflussen.',
25
+ },
26
+ {
27
+ question: 'Welche Grille sollte ich für die Berechnung nutzen?',
28
+ answer:
29
+ 'Die ursprüngliche Formel basiert auf der Schneegrille. Für die gewöhnliche Feldgrille ist das Verhältnis ähnlich, aber die Rate tendiert dazu, etwas langsamer zu sein.',
30
+ },
31
+ ];
32
+
33
+ const howToData = [
34
+ {
35
+ name: 'Finden Sie eine einsame Grille',
36
+ text: 'Suchen Sie sich nachts einen ruhigen Ort, an dem Sie das Zirpen einer einzelnen Grille deutlich hören können, um die Rhythmen nicht zu verwechseln.',
37
+ },
38
+ {
39
+ name: 'Zählen Sie das Zirpen für 15 Sekunden',
40
+ text: 'Nutzen Sie eine Stoppuhr und zählen Sie, wie viele Stridulationen das Insekt in genau 15 Sekunden von sich gibt.',
41
+ },
42
+ {
43
+ name: 'Geben Sie den Wert ein',
44
+ text: 'Tippen Sie einige Sekunden lang im Rhythmus des Zirpens auf die TAP-Schaltfläche, damit der Rechner automatisch die BPM berechnet.',
45
+ },
46
+ {
47
+ name: 'Überprüfen Sie die Temperatur',
48
+ text: 'Das System wendet die Formel T(°C) = 10 + (N - 40) / 7 an, um Ihnen eine Schätzung der Umgebungswärme in Grad Celsius zu geben.',
49
+ },
50
+ ];
51
+
52
+ const faqSchema: WithContext<FAQPage> = {
53
+ '@context': 'https://schema.org',
54
+ '@type': 'FAQPage',
55
+ mainEntity: faqData.map((item) => ({
56
+ '@type': 'Question',
57
+ name: item.question,
58
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
59
+ })),
60
+ };
61
+
62
+ const howToSchema: WithContext<HowToThing> = {
63
+ '@context': 'https://schema.org',
64
+ '@type': 'HowTo',
65
+ name: title,
66
+ description,
67
+ step: howToData.map((step, i) => ({
68
+ '@type': 'HowToStep',
69
+ position: i + 1,
70
+ name: step.name,
71
+ text: step.text,
72
+ })),
73
+ };
74
+
75
+ const appSchema: WithContext<SoftwareApplication> = {
76
+ '@context': 'https://schema.org',
77
+ '@type': 'SoftwareApplication',
78
+ name: title,
79
+ description,
80
+ applicationCategory: 'UtilityApplication',
81
+ operatingSystem: 'All',
82
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
83
+ inLanguage: 'de',
84
+ };
85
+
86
+ export const content: ToolLocaleContent<CricketThermometerUI> = {
87
+ slug,
88
+ title,
89
+ description,
90
+ faqTitle: 'Häufig gestellte Fragen',
91
+ faq: faqData,
92
+ bibliographyTitle: 'Wissenschaftliche Referenzen',
93
+ bibliography: [
94
+ {
95
+ name: 'The American Naturalist - Die Grille als Thermometer',
96
+ url: 'https://www.jstor.org/stable/2453256',
97
+ },
98
+ {
99
+ name: 'Wikipedia - Dolbearsches Gesetz',
100
+ url: 'https://de.wikipedia.org/wiki/Dolbearsches_Gesetz',
101
+ },
102
+ ],
103
+ howTo: howToData,
104
+ schemas: [faqSchema, howToSchema, appSchema],
105
+ seo: [
106
+ {
107
+ type: 'title',
108
+ text: 'Vollständiger Leitfaden: So nutzen Sie das Dolbearsche Gesetz zur Temperaturberechnung',
109
+ level: 2,
110
+ },
111
+ {
112
+ type: 'paragraph',
113
+ html: 'Wussten Sie, dass Sie die exakte Temperatur allein durch das Zuhören in der Natur bestimmen können? 1897 entdeckte der Physiker Amos Dolbear einen präzisen mathematischen Zusammenhang zwischen der Zirp-Rate von Grillen und der Umgebungswärme. Dieses Tool digitalisiert diese Entdeckung, um Ihr Smartphone in ein natürliches Thermometer zu verwandeln.',
114
+ },
115
+ {
116
+ type: 'tip',
117
+ title: 'Warum singen Grillen?',
118
+ html: '<p>Der „Gesang“ der Grille, auch <strong>Stridulation</strong> genannt, ist eigentlich ein Paarungsruf. Die Männchen reiben ihre Flügel (nicht ihre Beine) aneinander, um diesen Laut zu erzeugen. Faszinierenderweise hängt die Geschwindigkeit dieses Reibens direkt von der Wärmeenergie der Luft ab, da Grillen wechselwarme Tiere (Ektothermen) sind.</p>',
119
+ },
120
+ {
121
+ type: 'title',
122
+ text: 'Die Wissenschaft: Ektothermie und Stoffwechsel',
123
+ level: 3,
124
+ },
125
+ {
126
+ type: 'paragraph',
127
+ html: 'Im Gegensatz zu Säugetieren, die eine konstante Körpertemperatur aufrechterhalten, sind Insekten von äußerer Wärme abhängig. Ihre biochemischen Reaktionen folgen der <strong>Arrhenius-Gleichung</strong>: Je mehr Wärme, desto schneller die Reaktion.',
128
+ },
129
+ {
130
+ type: 'paragraph',
131
+ html: 'Das bedeutet, dass die Grillenmuskeln für die Kontraktion und das Reiben der Flügel enzymatische Reaktionen benötigen. Wenn es kalt ist, sind diese Reaktionen langsam und das Zirpen ist träge. Wenn es warm ist, beschleunigt sich der Stoffwechsel und der Gesang wird zu einem hektischen Triller.',
132
+ },
133
+ {
134
+ type: 'title',
135
+ text: 'Die Dolbear-Formel',
136
+ level: 3,
137
+ },
138
+ {
139
+ type: 'paragraph',
140
+ html: 'Obwohl es Variationen für verschiedene Arten gibt, ist die bekannteste Formel die für die Schneegrille (<em>Oecanthus fultoni</em>). Um die Temperatur in Grad Celsius zu erhalten:',
141
+ },
142
+ {
143
+ type: 'code',
144
+ code: 'T(°C) = 10 + (N - 40) / 7\n\nMit N = Anzahl der Zirps pro Minute.',
145
+ ariaLabel: 'Dolbearsche Formel zur Berechnung der Temperatur aus dem Grillenzirpen',
146
+ },
147
+ {
148
+ type: 'paragraph',
149
+ html: 'Unser Tool erledigt das automatisch: Es misst die Zeit zwischen Ihren Taps, berechnet die Zirps pro Minute (BPM) und wendet die Formel sofort an.',
150
+ },
151
+ {
152
+ type: 'title',
153
+ text: 'Faszinierende Fakten',
154
+ level: 3,
155
+ },
156
+ {
157
+ type: 'list',
158
+ items: [
159
+ '<strong>Thermometer der Liebe:</strong> Einige Theorien besagen, dass Weibchen Männchen bevorzugen, die in der „korrekten“ Frequenz für die aktuelle Temperatur singen, da dies darauf hindeutet, dass das Männchen gesund ist und einen starken Stoffwechsel hat.',
160
+ '<strong>Kältegrenze:</strong> Unterhalb von 10°C (50°F) hören die meisten Grillen auf zu singen, da ihr Stoffwechsel zu langsam ist, um die muskuläre Anstrengung aufrechtzuerhalten.',
161
+ '<strong>Synchronisation:</strong> In warmen Nächten können tausende Grillen ihr Zirpen synchronisieren und so einen beeindruckenden „Wellen“-Klangeffekt erzeugen.',
162
+ ],
163
+ },
164
+ {
165
+ type: 'tip',
166
+ title: 'Hinweis zur Genauigkeit',
167
+ html: '<p>Die Genauigkeit hängt von der Grillenart ab. Dieses Tool ist für die gewöhnliche Feldgrille und die Baumgrille kalibriert. Faktoren wie Luftfeuchtigkeit oder Wind können das Ergebnis um ±0,5°C verändern.</p>',
168
+ },
169
+ ],
170
+ ui: {
171
+ labelWaiting: 'Warten...',
172
+ labelTapping: 'Weiter tippen...',
173
+ tapInstruction: 'Jedes Mal, wenn Sie ein Zirpen hören',
174
+ btnReset: 'Zurücksetzen',
175
+ btnSoundOn: 'Ton: Ein',
176
+ btnSoundOff: 'Ton: Aus',
177
+ unitChirpsMin: 'Zirp/Min',
178
+ faqTitle: 'Häufig gestellte Fragen',
179
+ bibliographyTitle: 'Wissenschaftliche Referenzen',
180
+ },
181
+ };