@jjlmoya/utils-nature 1.20.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/category/index.ts +3 -2
- package/src/entries.ts +5 -1
- package/src/index.ts +4 -0
- package/src/pages/[locale]/[slug].astro +5 -4
- package/src/tests/diacritics_density.test.ts +1 -1
- package/src/tests/inverted_punctuation.test.ts +1 -1
- package/src/tests/script_density.test.ts +1 -1
- package/src/tests/seo_length.test.ts +1 -0
- package/src/tests/seo_translation_completeness.test.ts +69 -0
- package/src/tests/title_quality.test.ts +1 -1
- package/src/tests/tool_validation.test.ts +2 -3
- package/src/tests/translation_copy.test.ts +2 -2
- package/src/tool/compostBinVolumeRatioCalculator/bibliography.astro +18 -0
- package/src/tool/compostBinVolumeRatioCalculator/bibliography.ts +12 -0
- package/src/tool/compostBinVolumeRatioCalculator/component.astro +77 -0
- package/src/tool/compostBinVolumeRatioCalculator/compost-bin-volume-ratio-calculator.css +509 -0
- package/src/tool/compostBinVolumeRatioCalculator/controller.ts +189 -0
- package/src/tool/compostBinVolumeRatioCalculator/dom-views.ts +162 -0
- package/src/tool/compostBinVolumeRatioCalculator/entry.ts +28 -0
- package/src/tool/compostBinVolumeRatioCalculator/evaluator.ts +26 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/de.ts +193 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/en.ts +193 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/es.ts +193 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/fr.ts +193 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/id.ts +185 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/it.ts +193 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/ja.ts +190 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/ko.ts +190 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/nl.ts +190 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/pl.ts +190 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/pt.ts +190 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/ru.ts +190 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/sv.ts +190 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/tr.ts +190 -0
- package/src/tool/compostBinVolumeRatioCalculator/i18n/zh.ts +190 -0
- package/src/tool/compostBinVolumeRatioCalculator/index.ts +11 -0
- package/src/tool/compostBinVolumeRatioCalculator/logic.test.ts +55 -0
- package/src/tool/compostBinVolumeRatioCalculator/logic.ts +165 -0
- package/src/tool/compostBinVolumeRatioCalculator/seo.astro +23 -0
- package/src/tool/compostBinVolumeRatioCalculator/storage.ts +25 -0
- package/src/tool/compostBinVolumeRatioCalculator/ui.ts +54 -0
- package/src/tool/cricketThermometer/component.astro +1 -1
- package/src/tool/cricketThermometer/i18n/de.ts +2 -2
- package/src/tool/cricketThermometer/i18n/en.ts +2 -2
- package/src/tool/cricketThermometer/i18n/es.ts +2 -2
- package/src/tool/cricketThermometer/i18n/fr.ts +2 -2
- package/src/tool/cricketThermometer/i18n/id.ts +2 -2
- package/src/tool/cricketThermometer/i18n/it.ts +2 -2
- package/src/tool/cricketThermometer/i18n/ja.ts +2 -2
- package/src/tool/cricketThermometer/i18n/ko.ts +2 -2
- package/src/tool/cricketThermometer/i18n/nl.ts +2 -2
- package/src/tool/cricketThermometer/i18n/pl.ts +2 -2
- package/src/tool/cricketThermometer/i18n/pt.ts +2 -2
- package/src/tool/cricketThermometer/i18n/ru.ts +2 -2
- package/src/tool/cricketThermometer/i18n/sv.ts +2 -2
- package/src/tool/cricketThermometer/i18n/tr.ts +2 -2
- package/src/tool/cricketThermometer/i18n/zh.ts +2 -2
- package/src/tool/cricketThermometer/index.ts +1 -0
- package/src/tool/digitalCarbon/component.astro +3 -3
- package/src/tool/digitalCarbon/i18n/de.ts +2 -2
- package/src/tool/digitalCarbon/i18n/en.ts +2 -2
- package/src/tool/digitalCarbon/i18n/es.ts +2 -2
- package/src/tool/digitalCarbon/i18n/fr.ts +2 -2
- package/src/tool/digitalCarbon/i18n/id.ts +2 -2
- package/src/tool/digitalCarbon/i18n/it.ts +2 -2
- package/src/tool/digitalCarbon/i18n/ja.ts +2 -2
- package/src/tool/digitalCarbon/i18n/ko.ts +2 -2
- package/src/tool/digitalCarbon/i18n/nl.ts +2 -2
- package/src/tool/digitalCarbon/i18n/pl.ts +2 -2
- package/src/tool/digitalCarbon/i18n/pt.ts +2 -2
- package/src/tool/digitalCarbon/i18n/ru.ts +2 -2
- package/src/tool/digitalCarbon/i18n/sv.ts +2 -2
- package/src/tool/digitalCarbon/i18n/tr.ts +2 -2
- package/src/tool/digitalCarbon/i18n/zh.ts +2 -2
- package/src/tool/digitalCarbon/index.ts +1 -0
- package/src/tool/rainHarvester/i18n/de.ts +2 -2
- package/src/tool/rainHarvester/i18n/en.ts +2 -2
- package/src/tool/rainHarvester/i18n/es.ts +2 -2
- package/src/tool/rainHarvester/i18n/fr.ts +2 -2
- package/src/tool/rainHarvester/i18n/id.ts +2 -2
- package/src/tool/rainHarvester/i18n/it.ts +2 -2
- package/src/tool/rainHarvester/i18n/ja.ts +2 -2
- package/src/tool/rainHarvester/i18n/ko.ts +2 -2
- package/src/tool/rainHarvester/i18n/nl.ts +2 -2
- package/src/tool/rainHarvester/i18n/pl.ts +2 -2
- package/src/tool/rainHarvester/i18n/pt.ts +2 -2
- package/src/tool/rainHarvester/i18n/ru.ts +2 -2
- package/src/tool/rainHarvester/i18n/sv.ts +2 -2
- package/src/tool/rainHarvester/i18n/tr.ts +2 -2
- package/src/tool/rainHarvester/i18n/zh.ts +2 -2
- package/src/tool/rainHarvester/index.ts +1 -0
- package/src/tool/seedCalculator/i18n/de.ts +2 -2
- package/src/tool/seedCalculator/i18n/en.ts +2 -2
- package/src/tool/seedCalculator/i18n/es.ts +2 -2
- package/src/tool/seedCalculator/i18n/fr.ts +2 -2
- package/src/tool/seedCalculator/i18n/id.ts +2 -2
- package/src/tool/seedCalculator/i18n/it.ts +2 -2
- package/src/tool/seedCalculator/i18n/ja.ts +2 -2
- package/src/tool/seedCalculator/i18n/ko.ts +2 -2
- package/src/tool/seedCalculator/i18n/nl.ts +2 -2
- package/src/tool/seedCalculator/i18n/pl.ts +2 -2
- package/src/tool/seedCalculator/i18n/pt.ts +2 -2
- package/src/tool/seedCalculator/i18n/ru.ts +2 -2
- package/src/tool/seedCalculator/i18n/sv.ts +2 -2
- package/src/tool/seedCalculator/i18n/tr.ts +2 -2
- package/src/tool/seedCalculator/i18n/zh.ts +2 -2
- package/src/tool/seedCalculator/index.ts +1 -0
- package/src/tool/urbanGardenPlanner/bibliography.astro +9 -0
- package/src/tool/urbanGardenPlanner/bibliography.ts +16 -0
- package/src/tool/urbanGardenPlanner/component.astro +131 -0
- package/src/tool/urbanGardenPlanner/controller.ts +88 -0
- package/src/tool/urbanGardenPlanner/dom-views.ts +84 -0
- package/src/tool/urbanGardenPlanner/entry.ts +29 -0
- package/src/tool/urbanGardenPlanner/evaluator.ts +27 -0
- package/src/tool/urbanGardenPlanner/i18n/de.ts +50 -0
- package/src/tool/urbanGardenPlanner/i18n/en.ts +179 -0
- package/src/tool/urbanGardenPlanner/i18n/es.ts +50 -0
- package/src/tool/urbanGardenPlanner/i18n/fr.ts +50 -0
- package/src/tool/urbanGardenPlanner/i18n/id.ts +50 -0
- package/src/tool/urbanGardenPlanner/i18n/it.ts +45 -0
- package/src/tool/urbanGardenPlanner/i18n/ja.ts +39 -0
- package/src/tool/urbanGardenPlanner/i18n/ko.ts +39 -0
- package/src/tool/urbanGardenPlanner/i18n/nl.ts +39 -0
- package/src/tool/urbanGardenPlanner/i18n/pl.ts +39 -0
- package/src/tool/urbanGardenPlanner/i18n/pt.ts +39 -0
- package/src/tool/urbanGardenPlanner/i18n/ru.ts +39 -0
- package/src/tool/urbanGardenPlanner/i18n/sv.ts +39 -0
- package/src/tool/urbanGardenPlanner/i18n/tr.ts +104 -0
- package/src/tool/urbanGardenPlanner/i18n/zh.ts +104 -0
- package/src/tool/urbanGardenPlanner/index.ts +11 -0
- package/src/tool/urbanGardenPlanner/logic.test.ts +45 -0
- package/src/tool/urbanGardenPlanner/logic.ts +55 -0
- package/src/tool/urbanGardenPlanner/seo.astro +15 -0
- package/src/tool/urbanGardenPlanner/storage.ts +25 -0
- package/src/tool/urbanGardenPlanner/ui.ts +38 -0
- package/src/tool/urbanGardenPlanner/urban-garden-rainwater-and-soil-planner.css +531 -0
- package/src/tools.ts +5 -2
- package/src/types.ts +4 -4
package/package.json
CHANGED
package/src/category/index.ts
CHANGED
|
@@ -3,10 +3,12 @@ import { cricketThermometer } from '../tool/cricketThermometer/entry';
|
|
|
3
3
|
import { seedCalculator } from '../tool/seedCalculator/entry';
|
|
4
4
|
import { rainHarvester } from '../tool/rainHarvester/entry';
|
|
5
5
|
import { digitalCarbon } from '../tool/digitalCarbon/entry';
|
|
6
|
+
import { urbanGardenPlanner } from '../tool/urbanGardenPlanner/entry';
|
|
7
|
+
import { compostBinVolumeRatioCalculator } from '../tool/compostBinVolumeRatioCalculator/entry';
|
|
6
8
|
|
|
7
9
|
export const natureCategory: NatureCategoryEntry = {
|
|
8
10
|
icon: 'mdi:leaf',
|
|
9
|
-
tools: [cricketThermometer, seedCalculator, rainHarvester, digitalCarbon],
|
|
11
|
+
tools: [cricketThermometer, seedCalculator, rainHarvester, digitalCarbon, urbanGardenPlanner, compostBinVolumeRatioCalculator],
|
|
10
12
|
i18n: {
|
|
11
13
|
en: () => import('./i18n/en').then((m) => m.content),
|
|
12
14
|
es: () => import('./i18n/es').then((m) => m.content),
|
|
@@ -25,4 +27,3 @@ export const natureCategory: NatureCategoryEntry = {
|
|
|
25
27
|
zh: () => import('./i18n/zh').then((m) => m.content),
|
|
26
28
|
},
|
|
27
29
|
};
|
|
28
|
-
|
package/src/entries.ts
CHANGED
|
@@ -6,9 +6,13 @@ export { rainHarvester } from './tool/rainHarvester/entry';
|
|
|
6
6
|
export type { RainHarvesterLocaleContent } from './tool/rainHarvester/entry';
|
|
7
7
|
export { seedCalculator } from './tool/seedCalculator/entry';
|
|
8
8
|
export type { SeedCalculatorLocaleContent } from './tool/seedCalculator/entry';
|
|
9
|
+
export { urbanGardenPlanner } from './tool/urbanGardenPlanner/entry';
|
|
10
|
+
export type { UrbanGardenPlannerLocaleContent } from './tool/urbanGardenPlanner/entry';
|
|
9
11
|
export { natureCategory } from './category';
|
|
10
12
|
import { cricketThermometer } from './tool/cricketThermometer/entry';
|
|
11
13
|
import { digitalCarbon } from './tool/digitalCarbon/entry';
|
|
12
14
|
import { rainHarvester } from './tool/rainHarvester/entry';
|
|
13
15
|
import { seedCalculator } from './tool/seedCalculator/entry';
|
|
14
|
-
|
|
16
|
+
import { urbanGardenPlanner } from './tool/urbanGardenPlanner/entry';
|
|
17
|
+
import { compostBinVolumeRatioCalculator } from './tool/compostBinVolumeRatioCalculator/entry';
|
|
18
|
+
export const ALL_ENTRIES = [cricketThermometer, digitalCarbon, rainHarvester, seedCalculator, urbanGardenPlanner, compostBinVolumeRatioCalculator];
|
package/src/index.ts
CHANGED
|
@@ -24,3 +24,7 @@ export { SEED_CALCULATOR_TOOL, seedCalculator } from './tool/seedCalculator/inde
|
|
|
24
24
|
export { RAIN_HARVESTER_TOOL, rainHarvester } from './tool/rainHarvester/index';
|
|
25
25
|
|
|
26
26
|
export { DIGITAL_CARBON_TOOL, digitalCarbon } from './tool/digitalCarbon/index';
|
|
27
|
+
|
|
28
|
+
export { URBAN_GARDEN_PLANNER_TOOL, urbanGardenPlanner } from './tool/urbanGardenPlanner/index';
|
|
29
|
+
|
|
30
|
+
export { COMPOST_BIN_VOLUME_RATIO_CALCULATOR_TOOL, compostBinVolumeRatioCalculator } from './tool/compostBinVolumeRatioCalculator/index';
|
|
@@ -15,7 +15,7 @@ export async function getStaticPaths() {
|
|
|
15
15
|
const paths = [];
|
|
16
16
|
|
|
17
17
|
for (const { entry, Component: lazyComp } of ALL_TOOLS) {
|
|
18
|
-
const
|
|
18
|
+
const Component = (await (lazyComp as () => Promise<{ default: unknown }>)()).default;
|
|
19
19
|
const localeEntries = Object.entries(entry.i18n) as [
|
|
20
20
|
KnownLocale,
|
|
21
21
|
() => Promise<ToolLocaleContent>,
|
|
@@ -70,7 +70,7 @@ interface NavItem {
|
|
|
70
70
|
isActive?: boolean;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
interface Props {
|
|
73
|
+
export interface Props {
|
|
74
74
|
Component: unknown;
|
|
75
75
|
locale: KnownLocale;
|
|
76
76
|
content: ToolLocaleContent;
|
|
@@ -83,7 +83,8 @@ const { Component, locale, content, localeUrls, allToolsNav, englishSlug } = Ast
|
|
|
83
83
|
|
|
84
84
|
const cssFiles = import.meta.glob("../../tool/**/*.css", { query: "?raw", import: "default" });
|
|
85
85
|
const cssKey = Object.keys(cssFiles).find((k) => k.endsWith(`/${englishSlug}.css`));
|
|
86
|
-
const
|
|
86
|
+
const cssLoader = cssKey ? cssFiles[cssKey] : undefined;
|
|
87
|
+
const toolCss = cssLoader ? await cssLoader() as string : "";
|
|
87
88
|
|
|
88
89
|
const seoContent: UtilitySEOContent = { locale, sections: content.seo ?? [] };
|
|
89
90
|
|
|
@@ -132,7 +133,7 @@ const titleBase = words.slice(1).join(" ") || "";
|
|
|
132
133
|
</section>
|
|
133
134
|
|
|
134
135
|
<section class="section-faq">
|
|
135
|
-
<FAQSection items={content.faq}
|
|
136
|
+
<FAQSection items={content.faq} />
|
|
136
137
|
</section>
|
|
137
138
|
|
|
138
139
|
<section class="section-bibliography">
|
|
@@ -96,7 +96,7 @@ describe('Diacritics density validation', () => {
|
|
|
96
96
|
if (!loader) return;
|
|
97
97
|
|
|
98
98
|
const content = await loader();
|
|
99
|
-
const text = normalizeText(translatableContent(content as Record<string, unknown>));
|
|
99
|
+
const text = normalizeText(translatableContent(content as unknown as Record<string, unknown>));
|
|
100
100
|
const rule = DIACRITIC_RULES[typedLocale];
|
|
101
101
|
const letters = letterCount(text);
|
|
102
102
|
const matches = diacriticCount(text, typedLocale);
|
|
@@ -60,7 +60,7 @@ describe('Inverted punctuation validation', () => {
|
|
|
60
60
|
|
|
61
61
|
const rule = INVERTED_PUNCTUATION_LOCALES[typedLocale];
|
|
62
62
|
const content = await loader();
|
|
63
|
-
const strings = translatableStrings(content as Record<string, unknown>);
|
|
63
|
+
const strings = translatableStrings(content as unknown as Record<string, unknown>);
|
|
64
64
|
const missingQuestions = strings.flatMap((text) =>
|
|
65
65
|
findMissingInvertedMarks(text, rule.questionStart, rule.questionEnd)
|
|
66
66
|
);
|
|
@@ -73,7 +73,7 @@ describe('Native script density validation', () => {
|
|
|
73
73
|
|
|
74
74
|
const content = await loader();
|
|
75
75
|
const rule = SCRIPT_RULES[typedLocale];
|
|
76
|
-
const text = normalizeText(translatableContent(content as Record<string, unknown>));
|
|
76
|
+
const text = normalizeText(translatableContent(content as unknown as Record<string, unknown>));
|
|
77
77
|
const letters = letterCount(text);
|
|
78
78
|
const matches = scriptCount(text, typedLocale);
|
|
79
79
|
const ratio = scriptRatio(text, typedLocale);
|
|
@@ -11,6 +11,7 @@ describe('SEO Content Length Validation', () => {
|
|
|
11
11
|
Object.keys(entry.i18n).forEach((locale) => {
|
|
12
12
|
it(`${locale}: SEO section should exist`, async () => {
|
|
13
13
|
const loader = (entry.i18n as Record<string, () => Promise<{ seo?: unknown[] }>>)[locale];
|
|
14
|
+
if (!loader) return;
|
|
14
15
|
const content = await loader();
|
|
15
16
|
if (!content.seo) return;
|
|
16
17
|
expect(Array.isArray(content.seo)).toBe(true);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ALL_TOOLS } from '../tools';
|
|
3
|
+
|
|
4
|
+
const ASIAN_LOCALES = ['ja', 'ko', 'zh'];
|
|
5
|
+
|
|
6
|
+
function getSimpleSectionLength(section: any): number {
|
|
7
|
+
if (section.type === 'title') return section.text ? section.text.length : 0;
|
|
8
|
+
if (section.type === 'paragraph') return section.html ? section.html.replace(/<[^>]*>/g, '').length : 0;
|
|
9
|
+
if (section.type === 'list') return section.items ? section.items.join('').length : 0;
|
|
10
|
+
return 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getComplexSectionLength(section: any): number {
|
|
14
|
+
if (section.type === 'diagnostic' || section.type === 'tip') {
|
|
15
|
+
return (section.title ? section.title.length : 0) + (section.html ? section.html.replace(/<[^>]*>/g, '').length : 0);
|
|
16
|
+
}
|
|
17
|
+
if (section.type === 'table') {
|
|
18
|
+
return ((section.headers || []).join('').length) + ((section.rows || []).flat().join('').length);
|
|
19
|
+
}
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getSectionLength(section: any): number {
|
|
24
|
+
return getSimpleSectionLength(section) || getComplexSectionLength(section);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function calculateSeoTextLength(seoSections: any[]): number {
|
|
28
|
+
return seoSections.reduce((acc, section) => acc + getSectionLength(section), 0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function checkLocaleSeoLength(toolId: string, locale: string, localeLen: number, enLen: number): void {
|
|
32
|
+
const isAsian = ASIAN_LOCALES.includes(locale);
|
|
33
|
+
const minLength = Math.floor(enLen * (isAsian ? 0.25 : 0.70));
|
|
34
|
+
const msgType = isAsian ? 'suspiciously short' : 'truncated/lazy';
|
|
35
|
+
|
|
36
|
+
expect(
|
|
37
|
+
localeLen,
|
|
38
|
+
`[LAZY SEO TRANSLATION] Tool "${toolId}" locale "${locale}" SEO text is ${msgType} (${localeLen} chars vs EN ${enLen} chars, expected min ${minLength})`,
|
|
39
|
+
).toBeGreaterThanOrEqual(minLength);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function auditSingleLocale(toolId: string, locale: string, loader: any, enSeoLength: number): Promise<void> {
|
|
43
|
+
if (locale === 'en' || !loader) return;
|
|
44
|
+
const content = await loader();
|
|
45
|
+
if (!content.seo || !Array.isArray(content.seo)) return;
|
|
46
|
+
|
|
47
|
+
checkLocaleSeoLength(toolId, locale, calculateSeoTextLength(content.seo), enSeoLength);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe('SEO Translation Completeness & Laziness Audit', () => {
|
|
51
|
+
ALL_TOOLS.forEach(({ entry }) => {
|
|
52
|
+
describe(`Tool: ${entry.id}`, () => {
|
|
53
|
+
it('should not have lazy or truncated SEO content compared to English reference', async () => {
|
|
54
|
+
const enLoader = entry.i18n['en' as keyof typeof entry.i18n];
|
|
55
|
+
if (!enLoader) return;
|
|
56
|
+
|
|
57
|
+
const enContent = await enLoader();
|
|
58
|
+
if (!enContent.seo || !Array.isArray(enContent.seo)) return;
|
|
59
|
+
|
|
60
|
+
const enSeoLength = calculateSeoTextLength(enContent.seo);
|
|
61
|
+
|
|
62
|
+
for (const [locale, loader] of Object.entries(entry.i18n)) {
|
|
63
|
+
await auditSingleLocale(entry.id, locale, loader, enSeoLength);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
@@ -41,7 +41,7 @@ describe('Project Titles - Separator Validation', () => {
|
|
|
41
41
|
let match;
|
|
42
42
|
while ((match = pattern.exec(content)) !== null) {
|
|
43
43
|
const title = match[1];
|
|
44
|
-
if (title.includes('|') || title.includes('-')) {
|
|
44
|
+
if (title && (title.includes('|') || title.includes('-'))) {
|
|
45
45
|
findings.push(title);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -3,8 +3,8 @@ import { ALL_TOOLS, natureCategory } from '../index';
|
|
|
3
3
|
|
|
4
4
|
describe('Tool Validation Suite', () => {
|
|
5
5
|
describe('Library Registration', () => {
|
|
6
|
-
it('should have
|
|
7
|
-
expect(ALL_TOOLS.length).toBe(
|
|
6
|
+
it('should have 6 tools in ALL_TOOLS', () => {
|
|
7
|
+
expect(ALL_TOOLS.length).toBe(6);
|
|
8
8
|
});
|
|
9
9
|
|
|
10
10
|
it('natureCategory should be defined', () => {
|
|
@@ -13,4 +13,3 @@ describe('Tool Validation Suite', () => {
|
|
|
13
13
|
});
|
|
14
14
|
});
|
|
15
15
|
});
|
|
16
|
-
|
|
@@ -106,8 +106,8 @@ describe('Locales must not copy another locale wholesale', () => {
|
|
|
106
106
|
|
|
107
107
|
for (let leftIndex = 0; leftIndex < locales.length; leftIndex += 1) {
|
|
108
108
|
for (let rightIndex = leftIndex + 1; rightIndex < locales.length; rightIndex += 1) {
|
|
109
|
-
const left = locales[leftIndex]
|
|
110
|
-
const right = locales[rightIndex]
|
|
109
|
+
const left = locales[leftIndex]!;
|
|
110
|
+
const right = locales[rightIndex]!;
|
|
111
111
|
const similarity = copySimilarity(corpora.get(left) ?? '', corpora.get(right) ?? '');
|
|
112
112
|
|
|
113
113
|
if (similarity >= COPY_THRESHOLD) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { BibliographyEntry } from '../../types';
|
|
3
|
+
import { bibliography } from './bibliography';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
locale?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { locale = 'en' } = Astro.props as Props;
|
|
10
|
+
const entries = bibliography;
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<section class="compost-references" aria-labelledby="compost-references-title" data-locale={locale}>
|
|
14
|
+
<h2 id="compost-references-title">References</h2>
|
|
15
|
+
<ul>
|
|
16
|
+
{entries.map((entry: BibliographyEntry) => <li><a href={entry.url} target="_blank" rel="noreferrer">{entry.name}</a></li>)}
|
|
17
|
+
</ul>
|
|
18
|
+
</section>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BibliographyEntry } from "../../types";
|
|
2
|
+
|
|
3
|
+
export const bibliography: BibliographyEntry[] = [
|
|
4
|
+
{
|
|
5
|
+
name: "U.S. Environmental Protection Agency, Composting At Home",
|
|
6
|
+
url: "https://www.epa.gov/recycle/composting-home",
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
name: "Ministerio de Medio Ambiente y Medio Rural y Marino y Amigos de la Tierra España, Manual de compostaje. Experiencias realizadas 2004-2008",
|
|
10
|
+
url: "https://www.miteco.gob.es/content/dam/miteco/es/calidad-y-evaluacion-ambiental/publicaciones/manual_compostaje_tcm30-185063.pdf",
|
|
11
|
+
},
|
|
12
|
+
];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
import './compost-bin-volume-ratio-calculator.css';
|
|
3
|
+
import type { CompostBinVolumeRatioCalculatorUI } from './ui';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
ui: CompostBinVolumeRatioCalculatorUI;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { ui } = Astro.props as Props;
|
|
10
|
+
const initialState = {
|
|
11
|
+
unitSystem: 'metric',
|
|
12
|
+
shape: 'rectangular',
|
|
13
|
+
width: 80,
|
|
14
|
+
depth: 60,
|
|
15
|
+
diameter: 60,
|
|
16
|
+
height: 80,
|
|
17
|
+
browns: 36,
|
|
18
|
+
greens: 14,
|
|
19
|
+
moisture: 'sponge',
|
|
20
|
+
};
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
<div class="compost-tool" data-compost-tool>
|
|
24
|
+
<div class="compost-tool-bar">
|
|
25
|
+
<div class="compost-tool-units" aria-label="Units">
|
|
26
|
+
<button type="button" data-unit="metric" data-unit-metric aria-pressed="true">{ui.unitMetric}</button>
|
|
27
|
+
<button type="button" data-unit="imperial" data-unit-imperial aria-pressed="false">{ui.unitImperial}</button>
|
|
28
|
+
</div>
|
|
29
|
+
<button type="button" class="compost-tool-reset" data-reset>{ui.resetLabel}</button>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="compost-tool-body">
|
|
32
|
+
<section class="compost-controls" aria-labelledby="compost-controls-title">
|
|
33
|
+
<div class="compost-section-heading"><span>01</span><h2 id="compost-controls-title">Build the bin</h2></div>
|
|
34
|
+
<div class="compost-choice-group" role="group" aria-label={ui.shapeTitle}>
|
|
35
|
+
<span class="compost-choice-group-label">{ui.shapeTitle}</span>
|
|
36
|
+
<div class="compost-choice-group-buttons">
|
|
37
|
+
<button type="button" data-shape="rectangular" data-shape-rectangular aria-pressed="true" class="is-active"><span class="compost-shape-icon compost-shape-icon-box"></span>{ui.shapeRectangular}</button>
|
|
38
|
+
<button type="button" data-shape="cylindrical" data-shape-cylindrical aria-pressed="false"><span class="compost-shape-icon compost-shape-icon-cylinder"></span>{ui.shapeCylindrical}</button>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="compost-dimensions" data-dimensions data-shape="rectangular">
|
|
42
|
+
<label data-rectangular-only><span>{ui.widthLabel}</span><div><input data-input="width" type="number" min="0" step="1" value="80" inputmode="decimal" /><em data-dimension-unit>{ui.dimensionUnitCm}</em></div></label>
|
|
43
|
+
<label data-rectangular-only><span>{ui.depthLabel}</span><div><input data-input="depth" type="number" min="0" step="1" value="60" inputmode="decimal" /><em data-dimension-unit>{ui.dimensionUnitCm}</em></div></label>
|
|
44
|
+
<label data-cylinder-only><span>{ui.diameterLabel}</span><div><input data-input="diameter" type="number" min="0" step="1" value="60" inputmode="decimal" /><em data-dimension-unit>{ui.dimensionUnitCm}</em></div></label>
|
|
45
|
+
<label><span>{ui.heightLabel}</span><div><input data-input="height" type="number" min="0" step="1" value="80" inputmode="decimal" /><em data-dimension-unit>{ui.dimensionUnitCm}</em></div></label>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="compost-section-heading compost-section-heading-materials"><span>02</span><h2>{ui.recipeTitle}</h2></div>
|
|
48
|
+
<div class="compost-volume-inputs">
|
|
49
|
+
<label><span class="compost-label-dot compost-label-dot-brown"></span>{ui.brownLabel}<div><input data-input="browns" type="number" min="0" step="1" value="36" inputmode="decimal" /><em data-volume-unit>{ui.volumeUnitLiters}</em></div></label>
|
|
50
|
+
<label><span class="compost-label-dot compost-label-dot-green"></span>{ui.greenLabel}<div><input data-input="greens" type="number" min="0" step="1" value="14" inputmode="decimal" /><em data-volume-unit>{ui.volumeUnitLiters}</em></div></label>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="compost-presets" aria-label={ui.recipeTitle}>
|
|
53
|
+
<button type="button" data-preset="kitchen">{ui.recipeKitchen}</button>
|
|
54
|
+
<button type="button" data-preset="garden">{ui.recipeGarden}</button>
|
|
55
|
+
<button type="button" data-preset="leafy">{ui.recipeLeafy}</button>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="compost-section-heading compost-section-heading-moisture"><span>03</span><h2>{ui.moistureTitle}</h2></div>
|
|
58
|
+
<div class="compost-moisture" role="group" aria-label={ui.moistureTitle}>
|
|
59
|
+
<button type="button" data-moisture="dry" aria-pressed="false">{ui.moistureDry}</button>
|
|
60
|
+
<button type="button" data-moisture="sponge" aria-pressed="true">{ui.moistureSponge}</button>
|
|
61
|
+
<button type="button" data-moisture="wet" aria-pressed="false">{ui.moistureWet}</button>
|
|
62
|
+
</div>
|
|
63
|
+
</section>
|
|
64
|
+
<section class="compost-scene" data-compost-scene aria-label={ui.sceneTitle}></section>
|
|
65
|
+
<section class="compost-result" data-compost-results aria-live="polite"></section>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="compost-tool-note"><span aria-hidden="true">◌</span>{ui.localNote}</div>
|
|
68
|
+
<div hidden data-ui={JSON.stringify(ui)}></div>
|
|
69
|
+
<script is:inline type="application/json" id="compost-initial-state" set:html={JSON.stringify(initialState)}></script>
|
|
70
|
+
<script>
|
|
71
|
+
import { mountCompostCalculator } from './controller';
|
|
72
|
+
const root = document.querySelector<HTMLElement>('[data-compost-tool]');
|
|
73
|
+
const uiNode = root?.querySelector('[data-ui]');
|
|
74
|
+
const ui = uiNode ? JSON.parse(uiNode.getAttribute('data-ui') ?? '{}') : {};
|
|
75
|
+
if (root) mountCompostCalculator(root, ui);
|
|
76
|
+
</script>
|
|
77
|
+
</div>
|