@jjlmoya/utils-home 1.6.0 → 1.8.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 +61 -60
- package/src/tests/i18n_coverage.test.ts +36 -0
- package/src/tests/schemas_fulfillment.test.ts +23 -0
- package/src/tests/slug_language_code_format.test.ts +27 -0
- package/src/tests/slug_uniqueness.test.ts +81 -0
- package/src/tests/title_quality.test.ts +55 -0
- package/src/tool/dewPointCalculator/i18n/de.ts +178 -0
- package/src/tool/dewPointCalculator/i18n/en.ts +1 -1
- package/src/tool/dewPointCalculator/i18n/es.ts +1 -1
- package/src/tool/dewPointCalculator/i18n/fr.ts +1 -1
- package/src/tool/dewPointCalculator/i18n/id.ts +178 -0
- package/src/tool/dewPointCalculator/i18n/it.ts +178 -0
- package/src/tool/dewPointCalculator/i18n/ja.ts +178 -0
- package/src/tool/dewPointCalculator/i18n/ko.ts +178 -0
- package/src/tool/dewPointCalculator/i18n/nl.ts +178 -0
- package/src/tool/dewPointCalculator/i18n/pl.ts +178 -0
- package/src/tool/dewPointCalculator/i18n/pt.ts +178 -0
- package/src/tool/dewPointCalculator/i18n/ru.ts +178 -0
- package/src/tool/dewPointCalculator/i18n/sv.ts +178 -0
- package/src/tool/dewPointCalculator/i18n/tr.ts +178 -0
- package/src/tool/dewPointCalculator/i18n/zh.ts +178 -0
- package/src/tool/dewPointCalculator/index.ts +16 -6
- package/src/tool/heatingComparator/i18n/de.ts +340 -0
- package/src/tool/heatingComparator/i18n/id.ts +324 -0
- package/src/tool/heatingComparator/i18n/it.ts +336 -0
- package/src/tool/heatingComparator/i18n/ja.ts +311 -0
- package/src/tool/heatingComparator/i18n/ko.ts +307 -0
- package/src/tool/heatingComparator/i18n/nl.ts +336 -0
- package/src/tool/heatingComparator/i18n/pl.ts +311 -0
- package/src/tool/heatingComparator/i18n/pt.ts +336 -0
- package/src/tool/heatingComparator/i18n/ru.ts +307 -0
- package/src/tool/heatingComparator/i18n/sv.ts +311 -0
- package/src/tool/heatingComparator/i18n/tr.ts +324 -0
- package/src/tool/heatingComparator/i18n/zh.ts +307 -0
- package/src/tool/heatingComparator/index.ts +13 -1
- package/src/tool/ledSavingCalculator/i18n/de.ts +208 -0
- package/src/tool/ledSavingCalculator/i18n/fr.ts +1 -1
- package/src/tool/ledSavingCalculator/i18n/id.ts +208 -0
- package/src/tool/ledSavingCalculator/i18n/it.ts +208 -0
- package/src/tool/ledSavingCalculator/i18n/ja.ts +208 -0
- package/src/tool/ledSavingCalculator/i18n/ko.ts +208 -0
- package/src/tool/ledSavingCalculator/i18n/nl.ts +208 -0
- package/src/tool/ledSavingCalculator/i18n/pl.ts +208 -0
- package/src/tool/ledSavingCalculator/i18n/pt.ts +208 -0
- package/src/tool/ledSavingCalculator/i18n/ru.ts +208 -0
- package/src/tool/ledSavingCalculator/i18n/sv.ts +208 -0
- package/src/tool/ledSavingCalculator/i18n/tr.ts +208 -0
- package/src/tool/ledSavingCalculator/i18n/zh.ts +208 -0
- package/src/tool/ledSavingCalculator/index.ts +16 -7
- package/src/tool/projectorCalculator/i18n/de.ts +180 -0
- package/src/tool/projectorCalculator/i18n/fr.ts +1 -1
- package/src/tool/projectorCalculator/i18n/id.ts +181 -0
- package/src/tool/projectorCalculator/i18n/it.ts +180 -0
- package/src/tool/projectorCalculator/i18n/ja.ts +180 -0
- package/src/tool/projectorCalculator/i18n/ko.ts +180 -0
- package/src/tool/projectorCalculator/i18n/nl.ts +180 -0
- package/src/tool/projectorCalculator/i18n/pl.ts +180 -0
- package/src/tool/projectorCalculator/i18n/pt.ts +180 -0
- package/src/tool/projectorCalculator/i18n/ru.ts +180 -0
- package/src/tool/projectorCalculator/i18n/sv.ts +180 -0
- package/src/tool/projectorCalculator/i18n/tr.ts +180 -0
- package/src/tool/projectorCalculator/i18n/zh.ts +180 -0
- package/src/tool/projectorCalculator/index.ts +15 -6
- package/src/tool/qrGenerator/i18n/de.ts +203 -0
- package/src/tool/qrGenerator/i18n/id.ts +151 -0
- package/src/tool/qrGenerator/i18n/it.ts +174 -0
- package/src/tool/qrGenerator/i18n/ja.ts +151 -0
- package/src/tool/qrGenerator/i18n/ko.ts +151 -0
- package/src/tool/qrGenerator/i18n/nl.ts +151 -0
- package/src/tool/qrGenerator/i18n/pl.ts +151 -0
- package/src/tool/qrGenerator/i18n/pt.ts +151 -0
- package/src/tool/qrGenerator/i18n/ru.ts +151 -0
- package/src/tool/qrGenerator/i18n/sv.ts +151 -0
- package/src/tool/qrGenerator/i18n/tr.ts +151 -0
- package/src/tool/qrGenerator/i18n/zh.ts +151 -0
- package/src/tool/qrGenerator/index.ts +17 -9
- package/src/tool/solarCalculator/i18n/de.ts +146 -0
- package/src/tool/solarCalculator/i18n/id.ts +126 -0
- package/src/tool/solarCalculator/i18n/it.ts +126 -0
- package/src/tool/solarCalculator/i18n/ja.ts +126 -0
- package/src/tool/solarCalculator/i18n/ko.ts +121 -0
- package/src/tool/solarCalculator/i18n/nl.ts +120 -0
- package/src/tool/solarCalculator/i18n/pl.ts +121 -0
- package/src/tool/solarCalculator/i18n/pt.ts +126 -0
- package/src/tool/solarCalculator/i18n/ru.ts +110 -0
- package/src/tool/solarCalculator/i18n/sv.ts +110 -0
- package/src/tool/solarCalculator/i18n/tr.ts +120 -0
- package/src/tool/solarCalculator/i18n/zh.ts +121 -0
- package/src/tool/solarCalculator/index.ts +17 -9
- package/src/tool/tariffComparator/i18n/de.ts +133 -0
- package/src/tool/tariffComparator/i18n/id.ts +133 -0
- package/src/tool/tariffComparator/i18n/it.ts +133 -0
- package/src/tool/tariffComparator/i18n/ja.ts +133 -0
- package/src/tool/tariffComparator/i18n/ko.ts +133 -0
- package/src/tool/tariffComparator/i18n/nl.ts +133 -0
- package/src/tool/tariffComparator/i18n/pl.ts +133 -0
- package/src/tool/tariffComparator/i18n/pt.ts +133 -0
- package/src/tool/tariffComparator/i18n/ru.ts +106 -0
- package/src/tool/tariffComparator/i18n/sv.ts +111 -0
- package/src/tool/tariffComparator/i18n/tr.ts +133 -0
- package/src/tool/tariffComparator/i18n/zh.ts +133 -0
- package/src/tool/tariffComparator/index.ts +16 -7
- package/src/types.ts +1 -1
package/package.json
CHANGED
|
@@ -1,62 +1,63 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
2
|
+
"name": "@jjlmoya/utils-home",
|
|
3
|
+
"version": "1.8.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts",
|
|
9
|
+
"./data": "./src/data.ts"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"dev": "astro dev",
|
|
19
|
+
"start": "astro dev",
|
|
20
|
+
"build": "astro build",
|
|
21
|
+
"preview": "astro preview",
|
|
22
|
+
"astro": "astro",
|
|
23
|
+
"lint": "eslint src/ --max-warnings 0 && stylelint \"src/**/*.{css,astro}\"",
|
|
24
|
+
"check": "astro check",
|
|
25
|
+
"type-check": "astro check",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"preversion": "npm run lint && npm run test",
|
|
28
|
+
"postversion": "git push && git push --tags",
|
|
29
|
+
"patch": "npm version patch",
|
|
30
|
+
"minor": "npm version minor",
|
|
31
|
+
"major": "npm version major"
|
|
32
|
+
},
|
|
33
|
+
"lint-staged": {
|
|
34
|
+
"*.{ts,tsx,astro}": [
|
|
35
|
+
"eslint --fix"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@iconify-json/mdi": "^1.2.3",
|
|
40
|
+
"@jjlmoya/prompagate": "^1.1.0",
|
|
41
|
+
"@jjlmoya/utils-shared": "1.2.0",
|
|
42
|
+
"astro": "^6.1.2",
|
|
43
|
+
"astro-icon": "^1.1.0",
|
|
44
|
+
"qrcode": "^1.5.4"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@astrojs/check": "^0.9.8",
|
|
48
|
+
"@types/qrcode": "^1.5.6",
|
|
49
|
+
"eslint": "^9.39.4",
|
|
50
|
+
"eslint-plugin-astro": "^1.6.0",
|
|
51
|
+
"eslint-plugin-no-comments": "^1.1.10",
|
|
52
|
+
"husky": "^9.1.7",
|
|
53
|
+
"lint-staged": "^16.4.0",
|
|
54
|
+
"postcss-html": "^1.8.1",
|
|
55
|
+
"schema-dts": "^1.1.2",
|
|
56
|
+
"stylelint": "^17.6.0",
|
|
57
|
+
"stylelint-config-standard": "^40.0.0",
|
|
58
|
+
"stylelint-declaration-strict-value": "^1.11.1",
|
|
59
|
+
"typescript": "^5.4.0",
|
|
60
|
+
"typescript-eslint": "^8.58.0",
|
|
61
|
+
"vitest": "^4.1.2"
|
|
62
|
+
}
|
|
62
63
|
}
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
const languageCodes = ['de', 'en', 'es', 'fr', 'id', 'it', 'ja', 'ko', 'nl', 'pl', 'pt', 'ru', 'sv', 'tr', 'zh'];
|
|
7
|
+
|
|
8
|
+
ALL_TOOLS.forEach((tool) => {
|
|
9
|
+
describe(`Tool: ${tool.entry.id}`, () => {
|
|
10
|
+
it('slug should not end with 2-letter language codes like -ja, -ru, -ko', async () => {
|
|
11
|
+
const locales = Object.keys(tool.entry.i18n);
|
|
12
|
+
|
|
13
|
+
for (const locale of locales) {
|
|
14
|
+
const loader = tool.entry.i18n[locale as keyof typeof tool.entry.i18n];
|
|
15
|
+
const content = (await loader?.()) as ToolLocaleContent;
|
|
16
|
+
|
|
17
|
+
const endsWithLanguageCode = languageCodes.some(code => content.slug.endsWith(`-${code}`));
|
|
18
|
+
|
|
19
|
+
expect(
|
|
20
|
+
endsWithLanguageCode,
|
|
21
|
+
`Tool "${tool.entry.id}" locale "${locale}" slug ("${content.slug}") cannot end with a language code like -ja, -ru, -ko, -de, etc.`,
|
|
22
|
+
).toBe(false);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -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
|
+
});
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import type { DewPointCalculatorUI } from '../ui';
|
|
4
|
+
|
|
5
|
+
const slug = 'taupunkt-rechner';
|
|
6
|
+
const title = 'Taupunkt Rechner';
|
|
7
|
+
const description = 'Berechnen Sie die Kondensationstemperatur an Ihren Wänden basierend auf Luftfeuchtigkeit und Raumtemperatur. Ein wichtiges Werkzeug zur Vermeidung von Schimmel und zum Schutz der Bausubstanz.';
|
|
8
|
+
|
|
9
|
+
const faqData = [
|
|
10
|
+
{
|
|
11
|
+
question: 'Was genau ist der Taupunkt?',
|
|
12
|
+
answer: 'Der Taupunkt ist die Temperatur, auf die Luft abgekühlt werden muss, damit der darin enthaltene Wasserdampf zu flüssigem Wasser kondensiert. Je höher die relative Luftfeuchtigkeit ist, desto näher liegt der Taupunkt an der aktuellen Temperatur.',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
question: 'Warum bildet sich Schimmel in den Ecken meiner Wohnung?',
|
|
16
|
+
answer: 'Ecken sind oft Wärmebrücken, an denen die Wand kälter ist. Wenn die Temperatur dieser Oberfläche unter den Taupunkt fällt, bildet sich flüssiges Wasser (Kondensat). Schimmel benötigt diese konstante Feuchtigkeit zum Wachsen.',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
question: 'Wie kann ich die Luftfeuchtigkeit zu Hause senken?',
|
|
20
|
+
answer: 'Die effektivste Methode ist regelmäßiges Stoßlüften (besonders nach dem Duschen oder Kochen) und der Einsatz von Luftentfeuchtern. Eine konstante Raumtemperatur hilft ebenfalls, da warme Luft mehr Feuchtigkeit aufnehmen kann, ohne zu kondensieren.',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
question: 'Ist Schimmel durch Kondensation gefährlich?',
|
|
24
|
+
answer: 'Ja. Schimmel setzt Sporen frei, die Atemwegsprobleme, Allergien und Asthma verursachen können. Das Erkennen des Kondensationsrisikos mit diesem Rechner ist der erste Schritt zu einem gesunden Zuhause.',
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const howToData = [
|
|
29
|
+
{
|
|
30
|
+
name: 'Temperatur und Feuchtigkeit messen',
|
|
31
|
+
text: 'Verwenden Sie ein Thermometer und ein Hygrometer, um die aktuellen Werte im Raum zu ermitteln.',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'Werte eingeben',
|
|
35
|
+
text: 'Stellen Sie die Temperatur in Grad Celsius und die Luftfeuchtigkeit in Prozent im Rechner ein.',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'Kritische Temperatur ermitteln',
|
|
39
|
+
text: 'Das Tool zeigt Ihnen genau an, ab welcher Oberflächentemperatur Wasser zu kondensieren beginnt.',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'Oberflächen prüfen',
|
|
43
|
+
text: 'Messen Sie mit einem Infrarot-Thermometer die Temperatur Ihrer Wände. Liegt sie am oder unter dem Taupunkt, müssen Sie lüften oder dämmen.',
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const faqSchema: WithContext<FAQPage> = {
|
|
48
|
+
'@context': 'https://schema.org',
|
|
49
|
+
'@type': 'FAQPage',
|
|
50
|
+
mainEntity: faqData.map((item) => ({
|
|
51
|
+
'@type': 'Question',
|
|
52
|
+
name: item.question,
|
|
53
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
54
|
+
})),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const howToSchema: WithContext<HowTo> = {
|
|
58
|
+
'@context': 'https://schema.org',
|
|
59
|
+
'@type': 'HowTo',
|
|
60
|
+
name: title,
|
|
61
|
+
description,
|
|
62
|
+
step: howToData.map((step) => ({
|
|
63
|
+
'@type': 'HowToStep',
|
|
64
|
+
name: step.name,
|
|
65
|
+
text: step.text,
|
|
66
|
+
})),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const appSchema: WithContext<SoftwareApplication> = {
|
|
70
|
+
'@context': 'https://schema.org',
|
|
71
|
+
'@type': 'SoftwareApplication',
|
|
72
|
+
name: title,
|
|
73
|
+
description,
|
|
74
|
+
applicationCategory: 'UtilityApplication',
|
|
75
|
+
operatingSystem: 'All',
|
|
76
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
77
|
+
inLanguage: 'de',
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const content: ToolLocaleContent<DewPointCalculatorUI> = {
|
|
81
|
+
slug,
|
|
82
|
+
title,
|
|
83
|
+
description,
|
|
84
|
+
faqTitle: 'Häufig gestellte Fragen',
|
|
85
|
+
faq: faqData,
|
|
86
|
+
bibliographyTitle: 'Literaturhinweise',
|
|
87
|
+
bibliography: [
|
|
88
|
+
{
|
|
89
|
+
name: 'Magnus-Formel zur Berechnung des Taupunkts',
|
|
90
|
+
url: 'https://de.wikipedia.org/wiki/Taupunkt',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'WMO Guide to Meteorological Instruments',
|
|
94
|
+
url: 'https://community.wmo.int/site/knowledge-hub/programmes-and-initiatives/instruments-and-methods-of-observation-programme-imop/guide-instruments-and-methods-of-observation-wmo-no-8',
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
howTo: howToData,
|
|
98
|
+
schemas: [faqSchema, howToSchema, appSchema],
|
|
99
|
+
seo: [
|
|
100
|
+
{
|
|
101
|
+
type: 'title',
|
|
102
|
+
text: 'Was ist der Taupunkt und warum ist er für Ihr Zuhause wichtig?',
|
|
103
|
+
level: 2,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
type: 'paragraph',
|
|
107
|
+
html: 'Der Taupunkt markiert die Temperatur, bei der Luftfeuchtigkeit zu Wasser wird. In Wohnräumen ist dieser Wert entscheidend für die Vermeidung von Bauschäden. Wenn die Temperatur einer Oberfläche (z. B. einer schlecht gedämmten Außenwand) unter den Taupunkt sinkt, entsteht Kondenswasser – der ideale Nährboden für <em>Aspergillus</em> und andere gesundheitsschädliche Pilze.',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
type: 'stats',
|
|
111
|
+
items: [
|
|
112
|
+
{ value: '> 5°C', label: 'Sicherer Bereich', icon: 'mdi:shield-check' },
|
|
113
|
+
{ value: '40–60%', label: 'Ideale Feuchte', icon: 'mdi:water-percent' },
|
|
114
|
+
{ value: '< 1°C', label: 'Akute Gefahr', icon: 'mdi:alert' },
|
|
115
|
+
],
|
|
116
|
+
columns: 3,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
type: 'comparative',
|
|
120
|
+
items: [
|
|
121
|
+
{
|
|
122
|
+
title: 'Magnus Tetens Formel',
|
|
123
|
+
description: 'Zur präzisen Berechnung nutzen wir die Magnus-Tetens-Näherung. Diese Formel wird von der Weltorganisation für Meteorologie (WMO) für Temperaturen zwischen 0°C und 50°C empfohlen.',
|
|
124
|
+
icon: 'mdi:calculator',
|
|
125
|
+
points: ['Wissenschaftlich validierte Präzision', 'Optimiert für Wohnraumtemperaturen'],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
title: 'Wärmebrücken und Schimmel',
|
|
129
|
+
description: 'Ecken, Fensterrahmen und ungenügend gedämmte Wände sind die kältesten Stellen im Haus. Sinkt dort die Temperatur unter den Taupunkt, ist Schimmelbildung vorprogrammiert.',
|
|
130
|
+
icon: 'mdi:home-thermometer',
|
|
131
|
+
points: ['Ecken sind besonders anfällig', 'Wärmedämmung verhindert Kondensation'],
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
columns: 2,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
type: 'title',
|
|
138
|
+
text: 'Risikostufen',
|
|
139
|
+
level: 3,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
type: 'paragraph',
|
|
143
|
+
html: '<strong>Geringes Risiko (Diff. > 5°C):</strong> Die Wände sind sicher. <strong>Mittleres Risiko (3–5°C):</strong> Achten Sie auf Ecken und Wärmebrücken. <strong>Hohes Risiko (1–3°C):</strong> Kondensation an Scheiben wahrscheinlich – sofort lüften. <strong>Akute Gefahr (< 1°C):</strong> Aktive Kondensation mit sofortigem Risiko für schwarzen Schimmel.',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
type: 'diagnostic',
|
|
147
|
+
variant: 'warning',
|
|
148
|
+
title: 'Die goldene Regel',
|
|
149
|
+
icon: 'mdi:thermometer-alert',
|
|
150
|
+
badge: 'Experten-Tipp',
|
|
151
|
+
html: '<p>Wenn die Wandtemperatur weniger als <strong>3°C über dem Taupunkt</strong> liegt, besteht unmittelbare Kondensationsgefahr. Lüften Sie regelmäßig und halten Sie die Luftfeuchtigkeit zwischen 40 % und 60 %.</p>',
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
type: 'summary',
|
|
155
|
+
title: 'So vermeiden Sie Kondenswasser',
|
|
156
|
+
items: [
|
|
157
|
+
'Täglich mehrmals stoßlüften, besonders nach dem Duschen oder Kochen.',
|
|
158
|
+
'Relative Luftfeuchtigkeit zwischen 40 % und 60 % halten.',
|
|
159
|
+
'Dunstabzugshauben in der Küche konsequent nutzen.',
|
|
160
|
+
'Wäsche nicht in der Wohnung trocknen (oder nur bei guter Belüftung).',
|
|
161
|
+
'Außenwände dämmen, um kalte Oberflächen zu vermeiden.',
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
ui: {
|
|
166
|
+
labelTemperature: 'Raumtemperatur',
|
|
167
|
+
labelHumidity: 'Rel. Luftfeuchtigkeit',
|
|
168
|
+
labelDewPoint: 'Taupunkt',
|
|
169
|
+
riskLow: 'Geringes Risiko',
|
|
170
|
+
riskMedium: 'Mittleres Risiko',
|
|
171
|
+
riskHigh: 'Hohes Risiko',
|
|
172
|
+
riskExtreme: 'Akute Gefahr',
|
|
173
|
+
riskLowDesc: 'Differenz > 5°C. Wände sind sicher.',
|
|
174
|
+
riskMediumDesc: 'Differenz 3–5°C. Ecken prüfen.',
|
|
175
|
+
riskHighDesc: 'Differenz 1–3°C. Sofort lüften!',
|
|
176
|
+
riskExtremeDesc: 'Differenz < 1°C. Aktive Kondensation.',
|
|
177
|
+
},
|
|
178
|
+
};
|
|
@@ -124,7 +124,7 @@ export const content: ToolLocaleContent<DewPointCalculatorUI> = {
|
|
|
124
124
|
type: 'comparative',
|
|
125
125
|
items: [
|
|
126
126
|
{
|
|
127
|
-
title: 'The Magnus
|
|
127
|
+
title: 'The Magnus Tetens Formula',
|
|
128
128
|
description: 'To calculate the dew point with scientific accuracy we use the Magnus-Tetens approximation, with constants b=17.625 and c=243.04°C recommended by the World Meteorological Organization for temperatures between 0°C and 50°C.',
|
|
129
129
|
icon: 'mdi:calculator',
|
|
130
130
|
points: ['Scientific accuracy validated by the WMO', 'Valid for residential temperature ranges'],
|
|
@@ -124,7 +124,7 @@ export const content: ToolLocaleContent<DewPointCalculatorUI> = {
|
|
|
124
124
|
type: 'comparative',
|
|
125
125
|
items: [
|
|
126
126
|
{
|
|
127
|
-
title: 'La Fórmula de Magnus
|
|
127
|
+
title: 'La Fórmula de Magnus Tetens',
|
|
128
128
|
description: 'Para calcular el punto de rocío con precisión científica utilizamos la aproximación de Magnus-Tetens, con constantes b=17.625 y c=243.04°C recomendadas por la Organización Meteorológica Mundial para temperaturas entre 0°C y 50°C.',
|
|
129
129
|
icon: 'mdi:calculator',
|
|
130
130
|
points: ['Precisión científica validada por la OMM', 'Válida para rangos de temperatura habitacional'],
|
|
@@ -124,7 +124,7 @@ export const content: ToolLocaleContent<DewPointCalculatorUI> = {
|
|
|
124
124
|
type: 'comparative',
|
|
125
125
|
items: [
|
|
126
126
|
{
|
|
127
|
-
title: 'La Formule de Magnus
|
|
127
|
+
title: 'La Formule de Magnus Tetens',
|
|
128
128
|
description: "Pour calculer le point de rosée avec précision scientifique, nous utilisons l'approximation de Magnus-Tetens, avec les constantes b=17,625 et c=243,04°C recommandées par l'Organisation Météorologique Mondiale pour des températures entre 0°C et 50°C.",
|
|
129
129
|
icon: 'mdi:calculator',
|
|
130
130
|
points: ["Précision scientifique validée par l'OMM", 'Valide pour les plages de température résidentielles'],
|