@jjlmoya/utils-forensic-science 1.13.0 → 1.14.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 +2 -1
- package/src/category/index.ts +61 -59
- package/src/category/seo.astro +1 -1
- package/src/components/PreviewNavSidebar.astro +1 -1
- package/src/components/PreviewToolbar.astro +1 -1
- package/src/data.ts +1 -1
- package/src/entries.ts +70 -68
- package/src/env.d.ts +1 -1
- package/src/index.ts +32 -31
- package/src/pages/[locale]/[slug].astro +161 -159
- package/src/pages/[locale].astro +8 -5
- package/src/pages/index.astro +1 -1
- package/src/tests/bibliography_wellformed_export.test.ts +46 -0
- package/src/tests/diacritics_density.test.ts +1 -1
- package/src/tests/faq_count.test.ts +19 -19
- package/src/tests/i18n_coverage.test.ts +36 -36
- package/src/tests/inverted_punctuation.test.ts +1 -1
- package/src/tests/locale_completeness.test.ts +2 -2
- package/src/tests/mocks/astro_mock.js +1 -1
- package/src/tests/no_h1_in_components.test.ts +1 -1
- package/src/tests/script_density.test.ts +94 -94
- package/src/tests/seo_length.test.ts +46 -46
- package/src/tests/seo_parity.test.ts +2 -7
- package/src/tests/seo_translation_completeness.test.ts +68 -0
- package/src/tests/seo_wellformed_export.test.ts +65 -0
- package/src/tests/tool_validation.test.ts +2 -2
- package/src/tests/translation_copy.test.ts +23 -24
- package/src/tool/bloodstain-pattern-origin-analyzer/component.astro +9 -9
- package/src/tool/bloodstain-pattern-origin-analyzer/i18n/zh.ts +1 -1
- package/src/tool/dna-profile-match-probability-lab/bibliography.astro +14 -0
- package/src/tool/dna-profile-match-probability-lab/bibliography.ts +16 -0
- package/src/tool/dna-profile-match-probability-lab/component.astro +121 -0
- package/src/tool/dna-profile-match-probability-lab/controller.ts +159 -0
- package/src/tool/dna-profile-match-probability-lab/dna-profile-match-probability-lab.css +485 -0
- package/src/tool/dna-profile-match-probability-lab/dom-views.ts +93 -0
- package/src/tool/dna-profile-match-probability-lab/entry.ts +29 -0
- package/src/tool/dna-profile-match-probability-lab/evaluator.ts +19 -0
- package/src/tool/dna-profile-match-probability-lab/i18n/de.ts +43 -0
- package/src/tool/dna-profile-match-probability-lab/i18n/en.ts +196 -0
- package/src/tool/dna-profile-match-probability-lab/i18n/es.ts +40 -0
- package/src/tool/dna-profile-match-probability-lab/i18n/fr.ts +10 -0
- package/src/tool/dna-profile-match-probability-lab/i18n/id.ts +41 -0
- package/src/tool/dna-profile-match-probability-lab/i18n/it.ts +10 -0
- package/src/tool/dna-profile-match-probability-lab/i18n/ja.ts +40 -0
- package/src/tool/dna-profile-match-probability-lab/i18n/ko.ts +40 -0
- package/src/tool/dna-profile-match-probability-lab/i18n/nl.ts +29 -0
- package/src/tool/dna-profile-match-probability-lab/i18n/pl.ts +10 -0
- package/src/tool/dna-profile-match-probability-lab/i18n/pt.ts +41 -0
- package/src/tool/dna-profile-match-probability-lab/i18n/ru.ts +29 -0
- package/src/tool/dna-profile-match-probability-lab/i18n/sv.ts +41 -0
- package/src/tool/dna-profile-match-probability-lab/i18n/tr.ts +10 -0
- package/src/tool/dna-profile-match-probability-lab/i18n/zh.ts +29 -0
- package/src/tool/dna-profile-match-probability-lab/index.ts +11 -0
- package/src/tool/dna-profile-match-probability-lab/localize.ts +107 -0
- package/src/tool/dna-profile-match-probability-lab/logic.test.ts +56 -0
- package/src/tool/dna-profile-match-probability-lab/logic.ts +139 -0
- package/src/tool/dna-profile-match-probability-lab/seo.astro +15 -0
- package/src/tool/dna-profile-match-probability-lab/storage.ts +26 -0
- package/src/tool/dna-profile-match-probability-lab/ui.ts +3 -0
- package/src/tool/fire-pattern-origin-analyzer/component.astro +1 -1
- package/src/tool/fire-pattern-origin-analyzer/i18n/zh.ts +5 -1
- package/src/tool/fire-pattern-origin-analyzer/logic.ts +9 -2
- package/src/tool/fire-pattern-origin-analyzer/view-bindings.ts +8 -4
- package/src/tool/fire-pattern-origin-analyzer/view-model.ts +6 -2
- package/src/tool/forensic-age-estimator/bibliography.astro +1 -1
- package/src/tool/forensic-age-estimator/component.astro +1 -1
- package/src/tool/forensic-age-estimator/entry.ts +17 -17
- package/src/tool/forensic-age-estimator/i18n/de.ts +244 -244
- package/src/tool/forensic-age-estimator/i18n/en.ts +244 -244
- package/src/tool/forensic-age-estimator/i18n/es.ts +244 -244
- package/src/tool/forensic-age-estimator/i18n/fr.ts +244 -244
- package/src/tool/forensic-age-estimator/i18n/id.ts +244 -244
- package/src/tool/forensic-age-estimator/i18n/it.ts +244 -244
- package/src/tool/forensic-age-estimator/i18n/nl.ts +244 -244
- package/src/tool/forensic-age-estimator/i18n/pl.ts +244 -244
- package/src/tool/forensic-age-estimator/i18n/pt.ts +244 -244
- package/src/tool/forensic-age-estimator/i18n/ru.ts +244 -244
- package/src/tool/forensic-age-estimator/i18n/sv.ts +244 -244
- package/src/tool/forensic-age-estimator/i18n/tr.ts +244 -244
- package/src/tool/forensic-age-estimator/i18n/zh.ts +245 -245
- package/src/tool/forensic-age-estimator/index.ts +1 -1
- package/src/tool/forensic-age-estimator/seo.astro +1 -1
- package/src/tool/forensic-blood-test-simulator/component.astro +1 -1
- package/src/tool/forensic-fiber-comparison-microscope/component.astro +1 -1
- package/src/tool/forensic-fiber-comparison-microscope/render.ts +1 -1
- package/src/tool/forensic-fiber-comparison-microscope/view.ts +1 -1
- package/src/tool/forensic-fingerprint-minutiae-identifier/component.astro +2 -2
- package/src/tool/forensic-fingerprint-minutiae-identifier/i18n/ja.ts +2 -1
- package/src/tool/forensic-fingerprint-minutiae-identifier/i18n/zh.ts +2 -0
- package/src/tool/forensic-fingerprint-minutiae-identifier/renderer.ts +9 -8
- package/src/tool/forensic-glass-becke-line-simulator/component.astro +1 -1
- package/src/tool/forensic-glass-becke-line-simulator/view.ts +8 -6
- package/src/tool/forensic-image-authenticity-analyzer/component.astro +1 -1
- package/src/tool/forensic-image-authenticity-analyzer/i18n/de.ts +105 -105
- package/src/tool/forensic-image-authenticity-analyzer/i18n/en.ts +105 -105
- package/src/tool/forensic-image-authenticity-analyzer/i18n/es.ts +105 -105
- package/src/tool/forensic-image-authenticity-analyzer/i18n/fr.ts +105 -105
- package/src/tool/forensic-image-authenticity-analyzer/i18n/id.ts +95 -90
- package/src/tool/forensic-image-authenticity-analyzer/i18n/it.ts +105 -105
- package/src/tool/forensic-image-authenticity-analyzer/i18n/ja.ts +7 -2
- package/src/tool/forensic-image-authenticity-analyzer/i18n/ko.ts +4 -1
- package/src/tool/forensic-image-authenticity-analyzer/i18n/nl.ts +95 -92
- package/src/tool/forensic-image-authenticity-analyzer/i18n/pl.ts +95 -92
- package/src/tool/forensic-image-authenticity-analyzer/i18n/pt.ts +105 -105
- package/src/tool/forensic-image-authenticity-analyzer/i18n/ru.ts +97 -92
- package/src/tool/forensic-image-authenticity-analyzer/i18n/sv.ts +97 -93
- package/src/tool/forensic-image-authenticity-analyzer/i18n/tr.ts +98 -93
- package/src/tool/forensic-image-authenticity-analyzer/i18n/zh.ts +11 -0
- package/src/tool/forensic-microcrystal-drug-simulator/component.astro +1 -1
- package/src/tool/forensic-sex-determinator/component.astro +3 -2
- package/src/tool/forensic-stature-estimator/component.astro +1 -1
- package/src/tool/forensic-stature-estimator/i18n/id.ts +4 -0
- package/src/tool/forensic-stature-estimator/i18n/nl.ts +4 -0
- package/src/tool/forensic-stature-estimator/i18n/ru.ts +4 -0
- package/src/tool/forensic-tlc-ink-simulator/component.astro +1 -1
- package/src/tool/forensic-tlc-ink-simulator/i18n/zh.ts +1 -0
- package/src/tool/forensic-toolmark-striation-matcher/component.astro +1 -1
- package/src/tool/forensic-toolmark-striation-matcher/logic.ts +1 -1
- package/src/tool/forensic-toolmark-striation-matcher/renderer.ts +7 -6
- package/src/tool/forensic-toolmark-striation-matcher/view.ts +19 -20
- package/src/tool/gsr-dispersion-calculator/component.astro +1 -1
- package/src/tool/gsr-dispersion-calculator/logic.ts +1 -1
- package/src/tool/time-of-death-algor-mortis-calculator/entry.ts +2 -0
- package/src/tool/widmark-alcohol-simulator/component.astro +1 -1
- package/src/tool/widmark-alcohol-simulator/i18n/de.ts +5 -0
- package/src/tool/widmark-alcohol-simulator/i18n/fr.ts +2 -1
- package/src/tool/widmark-alcohol-simulator/i18n/id.ts +1 -0
- package/src/tool/widmark-alcohol-simulator/i18n/it.ts +4 -1
- package/src/tool/widmark-alcohol-simulator/i18n/ja.ts +4 -1
- package/src/tool/widmark-alcohol-simulator/i18n/nl.ts +5 -1
- package/src/tool/widmark-alcohol-simulator/i18n/pl.ts +5 -1
- package/src/tool/widmark-alcohol-simulator/i18n/pt.ts +5 -1
- package/src/tool/widmark-alcohol-simulator/i18n/ru.ts +4 -1
- package/src/tool/widmark-alcohol-simulator/i18n/sv.ts +5 -1
- package/src/tool/widmark-alcohol-simulator/i18n/tr.ts +4 -1
- package/src/tool/widmark-alcohol-simulator/i18n/zh.ts +4 -1
- package/src/tools.ts +37 -35
- package/src/types.ts +4 -4
|
@@ -1,162 +1,164 @@
|
|
|
1
|
-
---
|
|
2
|
-
import PreviewLayout from "../../layouts/PreviewLayout.astro";
|
|
3
|
-
import PreviewNavSidebar from "../../components/PreviewNavSidebar.astro";
|
|
4
|
-
import { ALL_TOOLS } from "../../index";
|
|
5
|
-
import {
|
|
6
|
-
UtilityHeader,
|
|
7
|
-
FAQSection,
|
|
8
|
-
Bibliography,
|
|
9
|
-
SEORenderer,
|
|
10
|
-
} from "@jjlmoya/utils-shared";
|
|
1
|
+
---
|
|
2
|
+
import PreviewLayout from "../../layouts/PreviewLayout.astro";
|
|
3
|
+
import PreviewNavSidebar from "../../components/PreviewNavSidebar.astro";
|
|
4
|
+
import { ALL_TOOLS } from "../../index";
|
|
5
|
+
import {
|
|
6
|
+
UtilityHeader,
|
|
7
|
+
FAQSection,
|
|
8
|
+
Bibliography,
|
|
9
|
+
SEORenderer,
|
|
10
|
+
} from "@jjlmoya/utils-shared";
|
|
11
11
|
import type { KnownLocale, ToolLocaleContent } from "../../types";
|
|
12
12
|
import type { UtilitySEOContent } from "@jjlmoya/utils-shared";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
13
|
+
import type { AstroComponentFactory } from "astro/runtime/server/index.js";
|
|
14
|
+
|
|
15
|
+
export async function getStaticPaths() {
|
|
16
|
+
const paths = [];
|
|
17
|
+
|
|
18
|
+
for (const { entry, Component: lazyComp } of ALL_TOOLS) {
|
|
19
|
+
const { default: Component } = await (lazyComp as () => Promise<{ default: unknown }>)();
|
|
20
|
+
const localeEntries = Object.entries(entry.i18n) as [
|
|
21
|
+
KnownLocale,
|
|
22
|
+
() => Promise<ToolLocaleContent>,
|
|
23
|
+
][];
|
|
24
|
+
const localeContents = await Promise.all(
|
|
25
|
+
localeEntries.map(async ([locale, loader]) => ({
|
|
26
|
+
locale,
|
|
27
|
+
content: await loader(),
|
|
28
|
+
})),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const localeUrls = Object.fromEntries(
|
|
32
|
+
localeContents.map(({ locale, content }) => [
|
|
33
|
+
locale,
|
|
34
|
+
`/${locale}/${content.slug}`,
|
|
35
|
+
]),
|
|
36
|
+
) as Partial<Record<KnownLocale, string>>;
|
|
37
|
+
|
|
38
|
+
const firstLoader = entry.i18n.en ?? Object.values(entry.i18n)[0];
|
|
39
|
+
const englishSlug = firstLoader ? (await firstLoader()).slug : entry.id;
|
|
40
|
+
|
|
41
|
+
for (const { locale, content } of localeContents) {
|
|
42
|
+
const allToolsNav = (
|
|
43
|
+
await Promise.all(
|
|
44
|
+
ALL_TOOLS.map(async ({ entry: navEntry }) => {
|
|
45
|
+
const loader = navEntry.i18n[locale] ?? navEntry.i18n.en;
|
|
46
|
+
if (!loader) return null;
|
|
47
|
+
const navContent = await loader();
|
|
48
|
+
return {
|
|
49
|
+
id: navEntry.id,
|
|
50
|
+
title: navContent.title,
|
|
51
|
+
href: `/${locale}/${navContent.slug}`,
|
|
52
|
+
isActive: navEntry.id === entry.id,
|
|
53
|
+
};
|
|
54
|
+
}),
|
|
55
|
+
)
|
|
56
|
+
).filter(Boolean) as NavItem[];
|
|
57
|
+
paths.push({
|
|
58
|
+
params: { locale, slug: content.slug },
|
|
59
|
+
props: { Component, locale, content, localeUrls, allToolsNav, englishSlug },
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return paths;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface NavItem {
|
|
68
|
+
id: string;
|
|
69
|
+
title: string;
|
|
70
|
+
href: string;
|
|
71
|
+
isActive?: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface Props {
|
|
75
|
+
Component: unknown;
|
|
76
|
+
locale: KnownLocale;
|
|
77
|
+
content: ToolLocaleContent;
|
|
78
|
+
localeUrls: Partial<Record<KnownLocale, string>>;
|
|
79
|
+
allToolsNav: NavItem[];
|
|
80
|
+
englishSlug: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
82
83
|
const { Component, locale, content, localeUrls, allToolsNav, englishSlug } = Astro.props as Props;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
{
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
</
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
.section-
|
|
158
|
-
.section-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
84
|
+
const ToolComponent = Component as AstroComponentFactory;
|
|
85
|
+
|
|
86
|
+
const cssFiles = import.meta.glob("../../tool/*/*.css", { query: "?raw", import: "default" });
|
|
87
|
+
const cssKey = Object.keys(cssFiles).find((k) => k.endsWith(`/${englishSlug}.css`));
|
|
88
|
+
const cssLoader = cssKey ? cssFiles[cssKey] : null;
|
|
89
|
+
const toolCss = cssLoader ? await cssLoader() as string : "";
|
|
90
|
+
|
|
91
|
+
const seoContent: UtilitySEOContent = { locale, sections: content.seo ?? [] };
|
|
92
|
+
|
|
93
|
+
const words = content.title.split(" ");
|
|
94
|
+
const titleHighlight = words[0] || "";
|
|
95
|
+
const titleBase = words.slice(1).join(" ") || "";
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
<PreviewLayout
|
|
99
|
+
title={content.title}
|
|
100
|
+
currentLocale={locale}
|
|
101
|
+
localeUrls={localeUrls}
|
|
102
|
+
hasSidebar={true}
|
|
103
|
+
>
|
|
104
|
+
<PreviewNavSidebar
|
|
105
|
+
slot="sidebar"
|
|
106
|
+
categoryTitle="Tools"
|
|
107
|
+
tools={allToolsNav}
|
|
108
|
+
/>
|
|
109
|
+
<Fragment slot="head">
|
|
110
|
+
{toolCss ? <Fragment set:html={`<style is:inline>${toolCss}</style>`} /> : null}
|
|
111
|
+
{
|
|
112
|
+
( content.schemas ?? []).map((schema: unknown) => (
|
|
113
|
+
<script
|
|
114
|
+
is:inline
|
|
115
|
+
type="application/ld+json"
|
|
116
|
+
set:html={JSON.stringify(schema)}
|
|
117
|
+
/>
|
|
118
|
+
))
|
|
119
|
+
}
|
|
120
|
+
</Fragment>
|
|
121
|
+
|
|
122
|
+
<div class="tool-page">
|
|
123
|
+
<UtilityHeader
|
|
124
|
+
titleHighlight={titleHighlight}
|
|
125
|
+
titleBase={titleBase}
|
|
126
|
+
description={content.description}
|
|
127
|
+
/>
|
|
128
|
+
|
|
129
|
+
<section class="section-tool">
|
|
130
|
+
<ToolComponent ui={content.ui} />
|
|
131
|
+
</section>
|
|
132
|
+
|
|
133
|
+
<section class="section-seo">
|
|
134
|
+
<SEORenderer content={seoContent} />
|
|
135
|
+
</section>
|
|
136
|
+
|
|
137
|
+
<section class="section-faq">
|
|
138
|
+
<FAQSection items={content.faq} />
|
|
139
|
+
</section>
|
|
140
|
+
|
|
141
|
+
<section class="section-bibliography">
|
|
142
|
+
<Bibliography links={content.bibliography} />
|
|
143
|
+
</section>
|
|
144
|
+
</div>
|
|
145
|
+
</PreviewLayout>
|
|
146
|
+
|
|
147
|
+
<style>
|
|
148
|
+
.tool-page {
|
|
149
|
+
display: flex;
|
|
150
|
+
flex-direction: column;
|
|
151
|
+
gap: 2rem;
|
|
152
|
+
}
|
|
153
|
+
.section-tool {
|
|
154
|
+
max-width: 1200px;
|
|
155
|
+
margin: 0 auto;
|
|
156
|
+
width: 100%;
|
|
157
|
+
}
|
|
158
|
+
.section-seo,
|
|
159
|
+
.section-faq,
|
|
160
|
+
.section-bibliography {
|
|
161
|
+
padding-top: 2rem;
|
|
162
|
+
border-top: 1px solid var(--border-color);
|
|
163
|
+
}
|
|
164
|
+
</style>
|
package/src/pages/[locale].astro
CHANGED
|
@@ -28,7 +28,7 @@ const toolsWithContent = tools.length > 0
|
|
|
28
28
|
tools.map(async ({ entry, Component }) => {
|
|
29
29
|
const languages = Object.keys(entry.i18n) as KnownLocale[];
|
|
30
30
|
const localeEntries = await Promise.all(
|
|
31
|
-
|
|
31
|
+
languages.map(async (l) => {
|
|
32
32
|
const loader = entry.i18n[l];
|
|
33
33
|
if (!loader) return null;
|
|
34
34
|
const content = await loader();
|
|
@@ -36,7 +36,10 @@ const toolsWithContent = tools.length > 0
|
|
|
36
36
|
})
|
|
37
37
|
);
|
|
38
38
|
|
|
39
|
-
const
|
|
39
|
+
const availableLocaleEntries = localeEntries.filter(
|
|
40
|
+
(localeEntry): localeEntry is readonly [KnownLocale, ToolLocaleContent<Record<string, string>>] => localeEntry !== null,
|
|
41
|
+
);
|
|
42
|
+
const localeContents = Object.fromEntries(availableLocaleEntries) as Record<string, ToolLocaleContent<Record<string, string>>>;
|
|
40
43
|
const currentLocaleContent = localeContents[currentLocale] || localeContents.en;
|
|
41
44
|
const availableLocales: Record<string, string> = {};
|
|
42
45
|
|
|
@@ -256,6 +259,6 @@ const toolsWithContent = tools.length > 0
|
|
|
256
259
|
border-color: var(--accent);
|
|
257
260
|
background: rgba(244, 63, 94, 0.1);
|
|
258
261
|
}
|
|
259
|
-
</style>
|
|
260
|
-
|
|
261
|
-
|
|
262
|
+
</style>
|
|
263
|
+
|
|
264
|
+
|
package/src/pages/index.astro
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { readdirSync, readFileSync, existsSync } from 'fs';
|
|
3
|
+
import { join, relative } from 'path';
|
|
4
|
+
|
|
5
|
+
function findBibliographyAstroFiles(dir: string): string[] {
|
|
6
|
+
const files: string[] = [];
|
|
7
|
+
if (!existsSync(dir)) return files;
|
|
8
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
9
|
+
|
|
10
|
+
for (const entry of entries) {
|
|
11
|
+
const fullPath = join(dir, entry.name);
|
|
12
|
+
if (entry.isDirectory()) {
|
|
13
|
+
files.push(...findBibliographyAstroFiles(fullPath));
|
|
14
|
+
} else if (entry.isFile() && entry.name === 'bibliography.astro') {
|
|
15
|
+
files.push(fullPath);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return files;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const toolDir = join(process.cwd(), 'src', 'tool');
|
|
23
|
+
const bibliographyFiles = findBibliographyAstroFiles(toolDir);
|
|
24
|
+
|
|
25
|
+
describe('Bibliography Component Wellformed Export', () => {
|
|
26
|
+
it('found tool bibliography.astro components', () => {
|
|
27
|
+
expect(bibliographyFiles.length).toBeGreaterThan(0);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
bibliographyFiles.forEach((file) => {
|
|
31
|
+
const relativePath = relative(process.cwd(), file);
|
|
32
|
+
|
|
33
|
+
it(`${relativePath} should use SharedBibliography from @jjlmoya/utils-shared`, () => {
|
|
34
|
+
const content = readFileSync(file, 'utf-8');
|
|
35
|
+
|
|
36
|
+
const usesSharedComponent =
|
|
37
|
+
content.includes('@jjlmoya/utils-shared') &&
|
|
38
|
+
(content.includes('Bibliography') || content.includes('SharedBibliography'));
|
|
39
|
+
|
|
40
|
+
expect(
|
|
41
|
+
usesSharedComponent,
|
|
42
|
+
`File "${relativePath}" does not use the standard Bibliography component from @jjlmoya/utils-shared, resulting in unstyled or raw bibliography output.`,
|
|
43
|
+
).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -98,7 +98,7 @@ describe('Diacritics density validation', () => {
|
|
|
98
98
|
if (!loader) return;
|
|
99
99
|
|
|
100
100
|
const content = await loader();
|
|
101
|
-
const text = normalizeText(translatableContent(content as Record<string, unknown>));
|
|
101
|
+
const text = normalizeText(translatableContent(content as unknown as Record<string, unknown>));
|
|
102
102
|
const rule = DIACRITIC_RULES[typedLocale];
|
|
103
103
|
const letters = letterCount(text);
|
|
104
104
|
const matches = diacriticCount(text, typedLocale);
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import type * as DATA from '../data';
|
|
3
|
-
|
|
4
|
-
const TOOLS: typeof DATA.scienceCategory[] = [];
|
|
5
|
-
|
|
6
|
-
describe('FAQ Content Validation', () => {
|
|
7
|
-
TOOLS.forEach((entry) => {
|
|
8
|
-
describe(`Tool: ${entry.icon}`, () => {
|
|
9
|
-
it('placeholder', () => {
|
|
10
|
-
expect(true).toBe(true);
|
|
11
|
-
});
|
|
12
|
-
});
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('no tools registered yet', () => {
|
|
16
|
-
expect(TOOLS.length).toBe(0);
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import type * as DATA from '../data';
|
|
3
|
+
|
|
4
|
+
const TOOLS: typeof DATA.scienceCategory[] = [];
|
|
5
|
+
|
|
6
|
+
describe('FAQ Content Validation', () => {
|
|
7
|
+
TOOLS.forEach((entry) => {
|
|
8
|
+
describe(`Tool: ${entry.icon}`, () => {
|
|
9
|
+
it('placeholder', () => {
|
|
10
|
+
expect(true).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('no tools registered yet', () => {
|
|
16
|
+
expect(TOOLS.length).toBe(0);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
@@ -1,36 +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
|
-
});
|
|
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
|
+
});
|
|
@@ -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
|
);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export default function () { return null; }
|
|
2
|
-
|
|
2
|
+
|