@jjlmoya/utils-forensic-science 1.9.0 → 1.11.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 (62) hide show
  1. package/package.json +1 -1
  2. package/src/tests/diacritics_density.test.ts +8 -8
  3. package/src/tests/seo_parity.test.ts +60 -0
  4. package/src/tests/spanish_leakage.test.ts +153 -0
  5. package/src/tests/translation_copy.test.ts +124 -0
  6. package/src/tool/fire-pattern-origin-analyzer/i18n/zh.ts +2 -1
  7. package/src/tool/forensic-blood-test-simulator/i18n/fr.ts +13 -13
  8. package/src/tool/forensic-fiber-comparison-microscope/i18n/de.ts +1 -5
  9. package/src/tool/forensic-fiber-comparison-microscope/i18n/id.ts +0 -4
  10. package/src/tool/forensic-fiber-comparison-microscope/i18n/ja.ts +0 -4
  11. package/src/tool/forensic-fiber-comparison-microscope/i18n/pl.ts +0 -4
  12. package/src/tool/forensic-fiber-comparison-microscope/i18n/pt.ts +0 -4
  13. package/src/tool/forensic-fiber-comparison-microscope/i18n/ru.ts +1 -5
  14. package/src/tool/forensic-fiber-comparison-microscope/i18n/sv.ts +1 -5
  15. package/src/tool/forensic-fiber-comparison-microscope/i18n/zh.ts +0 -4
  16. package/src/tool/forensic-fingerprint-minutiae-identifier/i18n/de.ts +1 -10
  17. package/src/tool/forensic-fingerprint-minutiae-identifier/i18n/fr.ts +0 -9
  18. package/src/tool/forensic-fingerprint-minutiae-identifier/i18n/id.ts +1 -10
  19. package/src/tool/forensic-fingerprint-minutiae-identifier/i18n/it.ts +1 -10
  20. package/src/tool/forensic-fingerprint-minutiae-identifier/i18n/ja.ts +3 -1
  21. package/src/tool/forensic-fingerprint-minutiae-identifier/i18n/ko.ts +3 -1
  22. package/src/tool/forensic-fingerprint-minutiae-identifier/i18n/nl.ts +1 -10
  23. package/src/tool/forensic-fingerprint-minutiae-identifier/i18n/pl.ts +1 -10
  24. package/src/tool/forensic-fingerprint-minutiae-identifier/i18n/pt.ts +1 -10
  25. package/src/tool/forensic-fingerprint-minutiae-identifier/i18n/ru.ts +1 -10
  26. package/src/tool/forensic-fingerprint-minutiae-identifier/i18n/sv.ts +1 -10
  27. package/src/tool/forensic-fingerprint-minutiae-identifier/i18n/tr.ts +1 -10
  28. package/src/tool/forensic-fingerprint-minutiae-identifier/i18n/zh.ts +3 -1
  29. package/src/tool/forensic-image-authenticity-analyzer/i18n/id.ts +20 -1
  30. package/src/tool/forensic-image-authenticity-analyzer/i18n/ja.ts +24 -1
  31. package/src/tool/forensic-image-authenticity-analyzer/i18n/ko.ts +24 -1
  32. package/src/tool/forensic-image-authenticity-analyzer/i18n/nl.ts +20 -1
  33. package/src/tool/forensic-image-authenticity-analyzer/i18n/pl.ts +20 -1
  34. package/src/tool/forensic-image-authenticity-analyzer/i18n/ru.ts +20 -1
  35. package/src/tool/forensic-image-authenticity-analyzer/i18n/sv.ts +20 -1
  36. package/src/tool/forensic-image-authenticity-analyzer/i18n/tr.ts +20 -1
  37. package/src/tool/forensic-image-authenticity-analyzer/i18n/zh.ts +24 -1
  38. package/src/tool/forensic-tlc-ink-simulator/i18n/ja.ts +3 -1
  39. package/src/tool/forensic-tlc-ink-simulator/i18n/ko.ts +3 -1
  40. package/src/tool/forensic-tlc-ink-simulator/i18n/zh.ts +3 -1
  41. package/src/tool/time-of-death-algor-mortis-calculator/i18n/de.ts +1 -4
  42. package/src/tool/time-of-death-algor-mortis-calculator/i18n/id.ts +1 -4
  43. package/src/tool/time-of-death-algor-mortis-calculator/i18n/nl.ts +1 -4
  44. package/src/tool/time-of-death-algor-mortis-calculator/i18n/pl.ts +1 -4
  45. package/src/tool/time-of-death-algor-mortis-calculator/i18n/pt.ts +1 -1
  46. package/src/tool/time-of-death-algor-mortis-calculator/i18n/ru.ts +1 -4
  47. package/src/tool/time-of-death-algor-mortis-calculator/i18n/sv.ts +1 -4
  48. package/src/tool/time-of-death-algor-mortis-calculator/i18n/tr.ts +1 -4
  49. package/src/tool/widmark-alcohol-simulator/i18n/de.ts +4 -1
  50. package/src/tool/widmark-alcohol-simulator/i18n/es.ts +4 -1
  51. package/src/tool/widmark-alcohol-simulator/i18n/fr.ts +4 -1
  52. package/src/tool/widmark-alcohol-simulator/i18n/id.ts +4 -1
  53. package/src/tool/widmark-alcohol-simulator/i18n/it.ts +4 -1
  54. package/src/tool/widmark-alcohol-simulator/i18n/ja.ts +4 -1
  55. package/src/tool/widmark-alcohol-simulator/i18n/ko.ts +4 -1
  56. package/src/tool/widmark-alcohol-simulator/i18n/nl.ts +4 -1
  57. package/src/tool/widmark-alcohol-simulator/i18n/pl.ts +4 -1
  58. package/src/tool/widmark-alcohol-simulator/i18n/pt.ts +4 -1
  59. package/src/tool/widmark-alcohol-simulator/i18n/ru.ts +4 -1
  60. package/src/tool/widmark-alcohol-simulator/i18n/sv.ts +4 -1
  61. package/src/tool/widmark-alcohol-simulator/i18n/tr.ts +4 -1
  62. package/src/tool/widmark-alcohol-simulator/i18n/zh.ts +4 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-forensic-science",
3
- "version": "1.9.0",
3
+ "version": "1.11.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -10,49 +10,49 @@ const DIACRITIC_RULES = {
10
10
  language: 'German',
11
11
  expectedCharacters: 'ä ö ü ß',
12
12
  characters: /[äöüÄÖÜß]/g,
13
- minPerThousandLetters: 0.1,
13
+ minPerThousandLetters: 3.0,
14
14
  },
15
15
  es: {
16
16
  language: 'Spanish',
17
17
  expectedCharacters: 'á é í ó ú ü ñ',
18
18
  characters: /[áéíóúüñÁÉÍÓÚÜÑ]/g,
19
- minPerThousandLetters: 0.1,
19
+ minPerThousandLetters: 8.0,
20
20
  },
21
21
  fr: {
22
22
  language: 'French',
23
23
  expectedCharacters: 'à â æ ç é è ê ë î ï ô œ ù û ü ÿ',
24
24
  characters: /[àâæçéèêëîïôœùûüÿÀÂÆÇÉÈÊËÎÏÔŒÙÛÜŸ]/g,
25
- minPerThousandLetters: 0.1,
25
+ minPerThousandLetters: 8.0,
26
26
  },
27
27
  it: {
28
28
  language: 'Italian',
29
29
  expectedCharacters: 'à è é ì í î ò ó ù ú',
30
30
  characters: /[àèéìíîòóùúÀÈÉÌÍÎÒÓÙÚ]/g,
31
- minPerThousandLetters: 0.1,
31
+ minPerThousandLetters: 3.0,
32
32
  },
33
33
  pl: {
34
34
  language: 'Polish',
35
35
  expectedCharacters: 'ą ć ę ł ń ó ś ź ż',
36
36
  characters: /[ąćęłńóśźżĄĆĘŁŃÓŚŹŻ]/g,
37
- minPerThousandLetters: 0.1,
37
+ minPerThousandLetters: 10.0,
38
38
  },
39
39
  pt: {
40
40
  language: 'Portuguese',
41
41
  expectedCharacters: 'á â ã à ç é ê í ó ô õ ú ü',
42
42
  characters: /[áâãàçéêíóôõúüÁÂÃÀÇÉÊÍÓÔÕÚÜ]/g,
43
- minPerThousandLetters: 0.1,
43
+ minPerThousandLetters: 8.0,
44
44
  },
45
45
  sv: {
46
46
  language: 'Swedish',
47
47
  expectedCharacters: 'å ä ö',
48
48
  characters: /[åäöÅÄÖ]/g,
49
- minPerThousandLetters: 0.1,
49
+ minPerThousandLetters: 5.0,
50
50
  },
51
51
  tr: {
52
52
  language: 'Turkish',
53
53
  expectedCharacters: 'ç ğ ı İ ö ş ü',
54
54
  characters: /[çğıöşüÇĞİÖŞÜ]/g,
55
- minPerThousandLetters: 0.1,
55
+ minPerThousandLetters: 10.0,
56
56
  },
57
57
  } as const;
58
58
 
@@ -0,0 +1,60 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ALL_ENTRIES } from '../entries';
3
+ import type { KnownLocale } from '../types';
4
+
5
+ interface ExpectedCounts {
6
+ seo: number;
7
+ faq: number;
8
+ howTo: number;
9
+ }
10
+
11
+ function countItems(arr: unknown[] | undefined): number {
12
+ return arr?.length ?? 0;
13
+ }
14
+
15
+ async function verifyLocaleParity(
16
+ entry: typeof ALL_ENTRIES[number],
17
+ loc: KnownLocale,
18
+ expected: ExpectedCounts,
19
+ ): Promise<void> {
20
+ const locContent = await entry.i18n[loc]?.();
21
+ expect(locContent, `Locale ${loc} missing content`).toBeDefined();
22
+
23
+ const locSeoCount = countItems(locContent?.seo);
24
+ const locFaqCount = countItems(locContent?.faq);
25
+ const locHowToCount = countItems(locContent?.howTo);
26
+
27
+ expect(
28
+ locSeoCount,
29
+ `Locale ${loc} SEO sections count (${locSeoCount}) must match EN (${expected.seo})`,
30
+ ).toBe(expected.seo);
31
+ expect(
32
+ locFaqCount,
33
+ `Locale ${loc} FAQ items count (${locFaqCount}) must match EN (${expected.faq})`,
34
+ ).toBe(expected.faq);
35
+ expect(
36
+ locHowToCount,
37
+ `Locale ${loc} HowTo steps count (${locHowToCount}) must match EN (${expected.howTo})`,
38
+ ).toBe(expected.howTo);
39
+ }
40
+
41
+ describe('SEO & i18n Structural Parity Suite', () => {
42
+ ALL_ENTRIES.forEach((entry) => {
43
+ describe(`Tool: ${entry.id}`, () => {
44
+ it('all 15 locales should have identical SEO section counts and types as English', async () => {
45
+ const enContent = await entry.i18n.en?.();
46
+ expect(enContent).toBeDefined();
47
+ const expected: ExpectedCounts = {
48
+ seo: countItems(enContent?.seo),
49
+ faq: countItems(enContent?.faq),
50
+ howTo: countItems(enContent?.howTo),
51
+ };
52
+
53
+ const locales = Object.keys(entry.i18n) as KnownLocale[];
54
+ for (const loc of locales) {
55
+ await verifyLocaleParity(entry, loc, expected);
56
+ }
57
+ });
58
+ });
59
+ });
60
+ });
@@ -0,0 +1,153 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { ALL_ENTRIES } from '../entries';
3
+
4
+ const STRUCTURAL_KEYS = new Set([
5
+ 'id',
6
+ 'slug',
7
+ 'url',
8
+ 'icon',
9
+ 'image',
10
+ 'imageUrl',
11
+ 'keywords',
12
+ 'category',
13
+ 'tags',
14
+ 'toolId',
15
+ 'type',
16
+ 'locale',
17
+ 'language',
18
+ 'direction',
19
+ ]);
20
+
21
+ const TRANSLATABLE_KEYS = [
22
+ 'title',
23
+ 'description',
24
+ 'faqTitle',
25
+ 'faq',
26
+ 'howTo',
27
+ 'seo',
28
+ 'schemas',
29
+ ] as const;
30
+
31
+ const SPANISH_MARKERS = [
32
+ ['sangre', /\bsangre\b/gi],
33
+ ['molino', /\bmolino\b/gi],
34
+ ['grano', /\bgrano\b/gi],
35
+ ['paladar', /\bpaladar\b/gi],
36
+ ['cuchillas', /\bcuchillas\b/gi],
37
+ ['trozos', /\btrozos\b/gi],
38
+ ['frescura', /\bfrescura\b/gi],
39
+ ['ingresa', /\bingresa\b/gi],
40
+ ['selecciona', /\bselecciona\b/gi],
41
+ ['herramienta', /\bherramienta\b/gi],
42
+ ['según', /\bsegún\b/gi],
43
+ ['después', /\bdespués\b/gi],
44
+ ['puedes', /\bpuedes\b/gi],
45
+ ['debes', /\bdebes\b/gi],
46
+ ['tus', /\btus\b/gi],
47
+ ['a la vez', /\ba la vez\b/gi],
48
+ ['los datos', /\blos datos\b/gi],
49
+ ['las opciones', /\blas opciones\b/gi],
50
+ ['el resultado', /\bel resultado\b/gi],
51
+ ['método de extracción', /\bmétodo de extracción\b/gi],
52
+ ['uniformidad del molino', /\buniformidad del molino\b/gi],
53
+ ['café recién tostado', /\bcafé recién tostado\b/gi],
54
+ ] as const;
55
+
56
+ type UnknownRecord = Record<string, unknown>;
57
+
58
+ function normalize(value: string): string {
59
+ return value
60
+ .replace(/<[^>]*>/g, ' ')
61
+ .replace(/&nbsp;/gi, ' ')
62
+ .replace(/\s+/g, ' ')
63
+ .trim()
64
+ .toLocaleLowerCase('es');
65
+ }
66
+
67
+ function isTechnicalInvariant(text: string): boolean {
68
+ return [
69
+ 'data:image/svg+xml;base64',
70
+ 'background-image: url',
71
+ '.layout-playground {',
72
+ 'const samplerate =',
73
+ ].some((pattern) => text.includes(pattern)) || (text.includes('presets') && text.includes('hz'));
74
+ }
75
+
76
+ function collectString(value: string, output: string[]): void {
77
+ const text = normalize(value);
78
+ if (!isTechnicalInvariant(text) && text.length >= 20) output.push(text);
79
+ }
80
+
81
+ function collectObject(value: UnknownRecord, output: string[]): void {
82
+ Object.entries(value).forEach(([childKey, childValue]) => {
83
+ collectText(childValue, output, childKey);
84
+ });
85
+ }
86
+
87
+ function collectText(value: unknown, output: string[], key?: string): void {
88
+ if (key && STRUCTURAL_KEYS.has(key)) return;
89
+ if (typeof value === 'string') return collectString(value, output);
90
+ if (Array.isArray(value)) return value.forEach((item) => collectText(item, output));
91
+ if (value && typeof value === 'object') collectObject(value as UnknownRecord, output);
92
+ }
93
+
94
+ async function loadText(loader: unknown): Promise<string[]> {
95
+ if (typeof loader !== 'function') return [];
96
+ const module = await (loader as () => Promise<unknown>)();
97
+ const output: string[] = [];
98
+ if (module && typeof module === 'object') {
99
+ const record = module as UnknownRecord;
100
+ for (const key of TRANSLATABLE_KEYS) {
101
+ collectText(record[key], output, key);
102
+ }
103
+ }
104
+ return output;
105
+ }
106
+
107
+ function findSpanishMarkers(text: string[]): string[] {
108
+ const corpus = text.join(' ');
109
+ return SPANISH_MARKERS.flatMap(([label, pattern]) =>
110
+ pattern.test(corpus) ? [label] : [],
111
+ );
112
+ }
113
+
114
+ function findCopiedFragments(spanish: string[], translated: string[]): string[] {
115
+ const corpus = translated.join(' ');
116
+ return spanish
117
+ .filter((fragment) => fragment.length >= 80 && corpus.includes(fragment))
118
+ .sort((a, b) => b.length - a.length)
119
+ .slice(0, 3);
120
+ }
121
+
122
+ describe('Locales must not contain copied Spanish content', () => {
123
+ for (const entry of ALL_ENTRIES) {
124
+ it(`${entry.id} has no untranslated Spanish blocks`, async () => {
125
+ const spanish = await loadText(entry.i18n.es);
126
+ const failures: string[] = [];
127
+
128
+ for (const [locale, loader] of Object.entries(entry.i18n)) {
129
+ if (locale === 'es') continue;
130
+
131
+ const translated = await loadText(loader);
132
+ const copiedFragments = findCopiedFragments(spanish, translated);
133
+ const markerHits = findSpanishMarkers(translated);
134
+
135
+ if (copiedFragments.length > 0 || markerHits.length >= 2) {
136
+ const details = [
137
+ copiedFragments.length > 0
138
+ ? `copied fragments: ${copiedFragments
139
+ .map((fragment) => JSON.stringify(fragment.slice(0, 120)))
140
+ .join(', ')}`
141
+ : '',
142
+ markerHits.length >= 2 ? `Spanish markers: ${markerHits.join(', ')}` : '',
143
+ ]
144
+ .filter(Boolean)
145
+ .join('; ');
146
+ failures.push(`${locale}: ${details}`);
147
+ }
148
+ }
149
+
150
+ expect(failures, failures.join('\n')).toEqual([]);
151
+ });
152
+ }
153
+ });
@@ -0,0 +1,124 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { ALL_ENTRIES } from '../entries';
3
+
4
+ const COPY_THRESHOLD = 0.9;
5
+
6
+ const STRUCTURAL_KEYS = new Set([
7
+ '@context',
8
+ '@type',
9
+ 'applicationCategory',
10
+ 'columns',
11
+ 'highlight',
12
+ 'icon',
13
+ 'level',
14
+ 'operatingSystem',
15
+ 'position',
16
+ 'positive',
17
+ 'price',
18
+ 'priceCurrency',
19
+ 'slug',
20
+ 'trend',
21
+ 'type',
22
+ 'url',
23
+ 'value',
24
+ 'variant',
25
+ ]);
26
+
27
+ function normalizeText(value: string): string {
28
+ return value
29
+ .replace(/<[^>]*>/g, ' ')
30
+ .replace(/&(?:amp|lt|gt|quot|apos|nbsp);/gi, ' ')
31
+ .replace(/[\u2018\u2019]/g, "'")
32
+ .replace(/[\u201c\u201d]/g, '"')
33
+ .replace(/\s+/g, ' ')
34
+ .trim()
35
+ .toLocaleLowerCase();
36
+ }
37
+
38
+ function collectText(value: unknown, path: string, parts: string[]): void {
39
+ if (typeof value === 'string') {
40
+ const normalized = normalizeText(value);
41
+ if (normalized.length >= 2) parts.push(normalized);
42
+ return;
43
+ }
44
+
45
+ if (Array.isArray(value)) {
46
+ value.forEach((item, index) => collectText(item, `${path}[${index}]`, parts));
47
+ return;
48
+ }
49
+
50
+ if (!value || typeof value !== 'object') return;
51
+
52
+ Object.entries(value).forEach(([key, child]) => {
53
+ if (STRUCTURAL_KEYS.has(key)) return;
54
+ collectText(child, `${path}.${key}`, parts);
55
+ });
56
+ }
57
+
58
+ function localeCorpus(content: unknown): string {
59
+ if (!content || typeof content !== 'object') return '';
60
+
61
+ const record = content as Record<string, unknown>;
62
+ const parts: string[] = [];
63
+ collectText(record.title, 'title', parts);
64
+ collectText(record.description, 'description', parts);
65
+ collectText(record.faqTitle, 'faqTitle', parts);
66
+ collectText(record.faq, 'faq', parts);
67
+ collectText(record.seo, 'seo', parts);
68
+ collectText(record.schemas, 'schemas', parts);
69
+ return parts.join(' ');
70
+ }
71
+
72
+ function tokenCounts(text: string): Map<string, number> {
73
+ const counts = new Map<string, number>();
74
+ for (const token of text.match(/[\p{L}\p{N}]+/gu) ?? []) {
75
+ counts.set(token, (counts.get(token) ?? 0) + 1);
76
+ }
77
+ return counts;
78
+ }
79
+
80
+ function copySimilarity(left: string, right: string): number {
81
+ const leftCounts = tokenCounts(left);
82
+ const rightCounts = tokenCounts(right);
83
+ const leftTotal = [...leftCounts.values()].reduce((sum, count) => sum + count, 0);
84
+ const rightTotal = [...rightCounts.values()].reduce((sum, count) => sum + count, 0);
85
+ if (leftTotal === 0 || rightTotal === 0) return 0;
86
+
87
+ let shared = 0;
88
+ for (const [token, count] of leftCounts) {
89
+ shared += Math.min(count, rightCounts.get(token) ?? 0);
90
+ }
91
+ return (2 * shared) / (leftTotal + rightTotal);
92
+ }
93
+
94
+ describe('Locales must not copy another locale wholesale', () => {
95
+ ALL_ENTRIES.forEach((entry) => {
96
+ it(`${entry.id} is not at least ${COPY_THRESHOLD * 100}% identical to another locale`, async () => {
97
+ const corpora = new Map<string, string>();
98
+
99
+ for (const [locale, loader] of Object.entries(entry.i18n)) {
100
+ if (!loader) continue;
101
+ corpora.set(locale, localeCorpus(await loader()));
102
+ }
103
+
104
+ const locales = [...corpora.keys()];
105
+ const violations: string[] = [];
106
+
107
+ for (let leftIndex = 0; leftIndex < locales.length; leftIndex += 1) {
108
+ for (let rightIndex = leftIndex + 1; rightIndex < locales.length; rightIndex += 1) {
109
+ const left = locales[leftIndex];
110
+ const right = locales[rightIndex];
111
+ const similarity = copySimilarity(corpora.get(left) ?? '', corpora.get(right) ?? '');
112
+
113
+ if (similarity >= COPY_THRESHOLD) {
114
+ violations.push(`${left} ↔ ${right}: ${(similarity * 100).toFixed(1)}%`);
115
+ }
116
+ }
117
+ }
118
+
119
+ expect(violations, `Locale copy threshold exceeded in ${entry.id}`).toEqual([]);
120
+ });
121
+ });
122
+ });
123
+
124
+
@@ -207,7 +207,8 @@ export const content: ToolLocaleContent = {
207
207
  title: '不要将收敛变成确定性',
208
208
  html: '紧密的交点簇仅在观察有效时才有用。实际调查必须考虑通风、闪燃、燃料包、电气系统、电器证据、目击者陈述、灭火和现场扰动。',
209
209
  },
210
- ],
210
+
211
+ { type: 'paragraph', html: "结果可用于规划和比较方案,但不能替代官方计算或专业建议。" },],
211
212
  faq,
212
213
  bibliography,
213
214
  howTo,
@@ -112,12 +112,12 @@ export const content: ToolLocaleContent = {
112
112
  seo: [
113
113
  {
114
114
  "type": "title",
115
- "text": "Guide Complet sur les Tests Presomptifs de Sang",
115
+ "text": "Guide complet sur les tests présomptifs de sang",
116
116
  "level": 2
117
117
  },
118
118
  {
119
119
  "type": "paragraph",
120
- "html": "Les tests presomptifs de sang sont des analyses preliminaires tres sensibles utilisees sur les scenes de crime pour detecter la presence de sang. Avant d'effectuer des analyses complexes de sequençage d'ADN, les enqueteurs se fient a ces tests rapides sur les vetements, les armes ou d'autres surfaces suspectes. Comme ces analyses ciblent l'activite de type peroxydase de l'heme, qui est le groupe contenant le fer de l'hemoglobine, elles sont qualifiees de presomptives. Un resultat positif suggere la presence de sang mais doit imperativement etre valide par un test de confirmation secondaire pour exclure toute autre reaction biochimique similaire."
120
+ "html": "Les tests présomptifs de sang sont des analyses préliminaires très sensibles utilisées sur les scènes de crime pour détecter la présence de sang. Avant d'effectuer des analyses complexes de séquençage d'ADN, les enquêteurs se fient à ces tests rapides sur les vêtements, les armes ou d'autres surfaces suspectes. Comme ces analyses ciblent l'activité de type peroxydase de l'hème, qui est le groupe contenant le fer de l'hémoglobine, elles sont qualifiées de présomptives. Un résultat positif suggère la présence de sang mais doit impérativement être validé par un test de confirmation secondaire pour exclure toute autre réaction biochimique similaire."
121
121
  },
122
122
  {
123
123
  "type": "title",
@@ -126,16 +126,16 @@ export const content: ToolLocaleContent = {
126
126
  },
127
127
  {
128
128
  "type": "paragraph",
129
- "html": "Le test de Kastle-Meyer (KM) est une reaction colorimetrique catalytique qui utilise la forme reduite et incolore de la phenolphtaleine, appelee la phenolphtaline. Le protocole experimental suit une sequence precise: on depose d'abord l'indicateur chimique sur le prelevement. En presence de sang, le groupe heme agit comme un catalyseur organique. Lors de l'ajout de peroxyde d'hydrogene, l'heme accelere la reduction du peroxyde d'hydrogene, ce qui oxyde immediatement la phenolphtaline en phenolphtaleine, provoquant un virage colore rose vif instantane. Un changement de couleur tardif ou lent apres une quinzaine de secondes est considere comme negatif."
129
+ "html": "Le test de Kastle-Meyer (KM) est une réaction colorimétrique catalytique qui utilise la forme réduite et incolore de la phénolphtaléine, appelée la phénolphtaline. Le protocole expérimental suit une séquence précise: on dépose d'abord l'indicateur chimique sur le prélèvement. En présence de sang, le groupe hème agit comme un catalyseur organique. Lors de l'ajout de peroxyde d'hydrogène, l'hème accélère la réaction, ce qui oxyde immédiatement la phénolphtaline en phénolphtaléine et provoque un virage coloré rose vif instantané. Un changement de couleur tardif ou lent après une quinzaine de secondes est considéré comme négatif."
130
130
  },
131
131
  {
132
132
  "type": "title",
133
- "text": "Luminol et la Chimiluminescence Forensique",
133
+ "text": "Luminol et la chimiluminescence forensique",
134
134
  "level": 3
135
135
  },
136
136
  {
137
137
  "type": "paragraph",
138
- "html": "Le luminol est un compose chimique qui emet une lueur bleue dans l'obscurite lorsqu'il est oxyde en presence d'un catalyseur metallique comme le fer de l'hemoglobine. Les techniciens de la police scientifique vaporisent une solution diluee de luminol sur de grandes zones suspectes. Lorsque le produit entre en contact avec des traces de sang, le fer contenu dans l'hemoglobine catalyse la reaction d'oxydation avec l'agent comburant. Cette reaction genere un ion aminophthalate excite qui libere de l'energie lumineuse en revenant a son etat fondamental. Pour photographier cette emission lumineuse de faible intensite, l'obscurite totale est requise."
138
+ "html": "Le luminol est un composé chimique qui émet une lueur bleue dans l'obscurité lorsqu'il est oxydé en présence d'un catalyseur métallique comme le fer de l'hémoglobine. Les techniciens de la police scientifique vaporisent une solution diluée de luminol sur de grandes zones suspectes. Lorsque le produit entre en contact avec des traces de sang, le fer contenu dans l'hémoglobine catalyse la réaction d'oxydation avec l'agent comburant. Cette réaction génère un ion aminophtalate excité qui libère de l'énergie lumineuse en revenant à son état fondamental. Pour photographier cette émission lumineuse de faible intensité, l'obscurité totale est requise."
139
139
  },
140
140
  {
141
141
  "type": "table",
@@ -148,38 +148,38 @@ export const content: ToolLocaleContent = {
148
148
  "rows": [
149
149
  [
150
150
  "Sang humain",
151
- "Coloration rose vif immediate apres ajout de H2O2.",
151
+ "Coloration rose vif immédiate après ajout de H2O2.",
152
152
  "Lueur bleue intense et stable.",
153
153
  "Vrai Positif."
154
154
  ],
155
155
  [
156
156
  "Eau de Javel",
157
- "Rose immediat avant meme l'ajout de H2O2.",
158
- "Lueur intense et tres courte.",
157
+ "Rose immédiat avant même l'ajout de H2O2.",
158
+ "Lueur intense et très courte.",
159
159
  "Faux Positif."
160
160
  ],
161
161
  [
162
162
  "Rouille",
163
- "Coloration brune lente et legere.",
163
+ "Coloration brune lente et légère.",
164
164
  "Lueur bleue faible.",
165
165
  "Faux Positif."
166
166
  ],
167
167
  [
168
168
  "Pomme de terre",
169
- "Rose pale tardif apres delai.",
169
+ "Rose pâle tardif après délai.",
170
170
  "Aucune lueur dans le noir.",
171
- "Faux Positif (KM) / Negatif (LUM)."
171
+ "Faux positif (KM) / négatif (LUM)."
172
172
  ]
173
173
  ]
174
174
  },
175
175
  {
176
176
  "type": "title",
177
- "text": "Distinguer le Sang des Faux Positifs Courants",
177
+ "text": "Distinguer le sang des faux positifs courants",
178
178
  "level": 3
179
179
  },
180
180
  {
181
181
  "type": "paragraph",
182
- "html": "La differenciation des faux positifs constitue une partie cruciale du travail d'analyse criminelle. Les agents nettoyants contenant de l'hypochlorite de sodium (eau de Javel) imitent la reaction du sang en oxydant directement les indicateurs. Cependant, les experts scientifiques analysent la dynamique temporelle: l'eau de Javel reagit sans peroxyde d'hydrogene dans le test de Kastle-Meyer et son emission lumineuse avec le luminol s'estompe presque instantanement. De plus, les peroxydases vegetales sont degradees par la chaleur, contrairement a l'hemoglobine qui demeure stable."
182
+ "html": "La différenciation des faux positifs constitue une partie cruciale du travail d'analyse criminelle. Les agents nettoyants contenant de l'hypochlorite de sodium (eau de Javel) imitent la réaction du sang en oxydant directement les indicateurs. Cependant, les experts scientifiques analysent la dynamique temporelle: l'eau de Javel réagit sans peroxyde d'hydrogène dans le test de Kastle-Meyer et son émission lumineuse avec le luminol s'estompe presque instantanément. De plus, les peroxydases végétales sont dégradées par la chaleur, contrairement à l'hémoglobine qui demeure stable."
183
183
  }
184
184
  ],
185
185
  faq,
@@ -182,11 +182,7 @@ export const content: ToolLocaleContent = {
182
182
  },
183
183
  {
184
184
  type: 'paragraph',
185
- html: 'Forensische Gutachter müssen jeden Schritt des Faseranalyseprozesses dokumentieren. Dies umfasst detaillierte Fotoaufnahmen, das Protokollieren von Messungen und die Einhaltung der Asservatenkette für alle physischen Beweismittel. Der Simulator hilft Studierenden, die Bedeutung einer systematischen Dokumentation und eines objektiven Vergleichs in der forensischen Arbeit zu verstehen. Durch die Interaktion mit verschiedenen Einstellungen können Benutzer lernen, wie sich Lichtweg, Probendicke und Farbstoffkonzentration auf die Spektralkurven und das physische Erscheinungsbild auswirken.',
186
- },
187
- {
188
- type: 'paragraph',
189
- html: 'Forensische Gutachter müssen jeden Schritt des Faseranalyseprozesses dokumentieren. Dies umfasst detaillierte Fotoaufnahmen, das Protokollieren von Messungen und die Einhaltung der Asservatenkette für alle physischen Beweismittel. Der Simulator hilft Studierenden, die Bedeutung einer systematischen Dokumentation und eines objektiven Vergleichs in der forensischen Arbeit zu verstehen. Durch die Interaktion mit verschiedenen Einstellungen können Benutzer lernen, wie sich Lichtweg, Probendicke und Farbstoffkonzentration auf die Spektralkurven und das physische Erscheinungsbild auswirken.',
185
+ html: 'Forensische Gutachter müssen jeden Schritt des Faseranalyseprozesses dokumentieren. Dies umfasst detaillierte Fotoaufnahmen, das Protokollieren von Messungen und die Einhaltung der Asservatenkette für alle physischen Beweismittel. Der Simulator hilft Studierenden, die Bedeutung einer systematischen Dokumentation und eines objektiven Vergleichs in der forensischen Arbeit zu verstehen. Durch die Interaktion mit verschiedenen Einstellungen können Benutzer lernen, wie sich Lichtweg, Probendicke und Farbstoffkonzentration auf die Spektralkurven und das physische Erscheinungsbild auswirken.' + " Die Aussage sollte immer im Zusammenhang mit der Probenahme, der Messqualität und der vollständigen Dokumentation bewertet werden. Kleine Unterschiede können durch Herstellung, Alterung, Verschmutzung oder Messbedingungen entstehen. Eine belastbare forensische Schlussfolgerung braucht deshalb nachvollziehbare Vergleichsproben, klare Unsicherheiten und eine unabhängige fachliche Prüfung.",
190
186
  },
191
187
  {
192
188
  type: 'glossary',
@@ -184,10 +184,6 @@ export const content: ToolLocaleContent = {
184
184
  type: 'paragraph',
185
185
  html: 'Pemeriksa forensik harus mendokumentasikan setiap langkah proses analisis serat. Ini termasuk mengambil foto terperinci, mencatat pengukuran, dan menjaga rantai kepemilikan untuk semua bukti fisik. Simulator membantu siswa memahami pentingnya dokumentasi sistematis dan perbandingan objektif dalam pekerjaan kasus forensik. Dengan berinteraksi dengan pengaturan yang berbeda, pengguna dapat mempelajari bagaimana jalur cahaya, ketebalan sampel, dan konsentrasi zat warna memengaruhi kurva spektral dan penampilan fisik.',
186
186
  },
187
- {
188
- type: 'paragraph',
189
- html: 'Pemeriksa forensik harus mendokumentasikan setiap langkah proses analisis serat. Ini termasuk mengambil foto terperinci, mencatat pengukuran, dan menjaga rantai kepemilikan untuk semua bukti fisik. Simulator membantu siswa memahami pentingnya dokumentasi sistematis dan perbandingan objektif dalam pekerjaan kasus forensik. Dengan berinteraksi dengan pengaturan yang berbeda, pengguna dapat mempelajari bagaimana jalur cahaya, ketebalan sampel, dan konsentrasi zat warna memengaruhi kurva spektral dan penampilan fisik.',
190
- },
191
187
  {
192
188
  type: 'glossary',
193
189
  items: [
@@ -184,10 +184,6 @@ export const content: ToolLocaleContent = {
184
184
  type: 'paragraph',
185
185
  html: '法科学鑑定官は、繊維分析プロセスのすべてのステップを文書化する必要があります。これには、詳細な写真の撮影、測定値の記録、およびすべての物理的証拠の保管チェーンの維持が含まれます。このシミュレーターは、法科学の実務における体系的な文書化と客観的な比較の重要性を学生が理解するのに役立ちます。さまざまな設定を操作することで、ユーザーは光路、サンプルの厚さ、および染料の浓度がスペクトル曲線や物理的な外観にどのように影響するかを学ぶことができます。',
186
186
  },
187
- {
188
- type: 'paragraph',
189
- html: '法科学鑑定官は、繊維分析プロセスのすべてのステップを文書化する必要があります。これには、詳細な写真の撮影、測定値の記録、およびすべての物理的証拠の保管チェーンの維持が含まれます。このシミュレーターは、法科学の実務における体系的な文書化と客観的な比較の重要性を学生が理解するのに役立ちます。さまざまな設定を操作することで、ユーザーは光路、サンプルの厚さ、および染料の濃度がスペクトル曲線や物理的な外観にどのように影響するかを学ぶことができます。',
190
- },
191
187
  {
192
188
  type: 'glossary',
193
189
  items: [
@@ -184,10 +184,6 @@ export const content: ToolLocaleContent = {
184
184
  type: 'paragraph',
185
185
  html: 'Biegli sądowi muszą dokumentować każdy krok procesu analizy włókien. Obejmuje to wykonywanie szczegółowych fotografii, rejestrowanie pomiarów i zachowanie łańcucha dowodowego dla wszystkich fizycznych dowodów. Symulator pomaga studentom zrozumieć znaczenie systematycznej dokumentacji i obiektywnego porównywania w pracy kryminalistycznej. Poprzez interakcję z różnymi ustawieniami użytkownicy mogą dowiedzieć się, jak droga światła, grubość próbki i stężenie barwnika wpływają na krzywe widmowe i wygląd fizyczny.',
186
186
  },
187
- {
188
- type: 'paragraph',
189
- html: 'Biegli sądowi muszą dokumentować każdy krok procesu analizy włókien. Obejmuje to wykonywanie szczegółowych fotografii, rejestrowanie pomiarów i zachowanie łańcucha dowodowego dla wszystkich fizycznych dowodów. Symulator pomaga studentom zrozumieć znaczenie systematycznej dokumentacji i obiektywnego porównywania w pracy kryminalistycznej. Poprzez interakcję z różnymi ustawieniami użytkownicy mogą dowiedzieć się, jak droga światła, grubość próbki i stężenie barwnika wpływają na krzywe widmowe i wygląd fizyczny.',
190
- },
191
187
  {
192
188
  type: 'glossary',
193
189
  items: [
@@ -184,10 +184,6 @@ export const content: ToolLocaleContent = {
184
184
  type: 'paragraph',
185
185
  html: 'Os peritos forenses devem documentar cada etapa do processo de análise de fibras. Isso inclui tirar fotografias detalhadas, registrar medições e manter uma cadeia de custódia para todas as evidências físicas. O simulador ajuda os estudantes a compreender a importância da documentação sistemática e da comparação objetiva no trabalho forense. Ao interagir com diferentes configurações, os usuários podem aprender como o caminho da luz, a espessura da amostra e a concentração do corante afetam as curvas espectrais e a aparência física.',
186
186
  },
187
- {
188
- type: 'paragraph',
189
- html: 'Os peritos forenses devem documentar cada etapa do processo de análise de fibras. Isso inclui tirar fotografias detalhadas, registrar medições e manter uma cadeia de custódia para todas as evidências físicas. O simulador ajuda os estudantes a compreender a importância da documentação sistemática e da comparação objetiva no trabalho forense. Ao interagir com diferentes configurações, os usuários podem aprender como o caminho da luz, a espessura da amostra e a concentração do corante afetam as curvas espectrais e a aparência física.',
190
- },
191
187
  {
192
188
  type: 'glossary',
193
189
  items: [
@@ -182,11 +182,7 @@ export const content: ToolLocaleContent = {
182
182
  },
183
183
  {
184
184
  type: 'paragraph',
185
- html: 'Судебные эксперты должны документировать каждый шаг процесса анализа волокон. Это включает детальное фотографирование, регистрацию измерений и ведение цепочки сохранности вещественных доказательств. Симулятор помогает студентам понять важность систематического документирования и объективного сравнения в экспертной практике. Взаимодействуя с различными настройками, пользователи могут узнать, как путь света, толщина образца и концентрация красителя влияют на спектральные кривые и внешний вид.',
186
- },
187
- {
188
- type: 'paragraph',
189
- html: 'Судебные эксперты должны документировать каждый шаг процесса анализа волокон. Это включает детальное фотографирование, регистрацию измерений и ведение цепочки сохранности вещественных доказательств. Симулятор помогает студентам понять важность систематического документирования и объективного сравнения в экспертной практике. Взаимодействуя с различными настройками, пользователи могут узнать, как путь света, толщина образца и концентрация красителя влияют на спектральные кривые и внешний вид.',
185
+ html: 'Судебные эксперты должны документировать каждый шаг процесса анализа волокон. Это включает детальное фотографирование, регистрацию измерений и ведение цепочки сохранности вещественных доказательств. Симулятор помогает студентам понять важность систематического документирования и объективного сравнения в экспертной практике. Взаимодействуя с различными настройками, пользователи могут узнать, как путь света, толщина образца и концентрация красителя влияют на спектральные кривые и внешний вид.' + " Оценка должна учитывать качество изображения, условия измерения, возможные искажения и полную документацию сравнения. Надёжный судебный вывод требует нескольких независимых признаков, их пространственных связей, описанных ограничений и проверки квалифицированным специалистом.",
190
186
  },
191
187
  {
192
188
  type: 'glossary',
@@ -182,11 +182,7 @@ export const content: ToolLocaleContent = {
182
182
  },
183
183
  {
184
184
  type: 'paragraph',
185
- html: 'Adliella undersökare måste dokumentera varje steg i fiberanalysprocessen. Detta inkluderar att ta detaljerade fotografier, registrera mätningar och upprätthålla en obruten kedja av förvaring för alla fysiska bevis. Simulatorn hjälper studenter att förstå vikten av systematisk dokumentation och objektiv jämförelse i adliellt arbete. Genom att interagera med olika inställningar kan användare lära sig hur ljusväg, provtjocklek och färgkoncentration påverkar spektralkurvorna och det fysiska utseendet.',
186
- },
187
- {
188
- type: 'paragraph',
189
- html: 'Adliella undersökare måste dokumentera varje steg i fiberanalysprocessen. Detta inkluderar att ta detaljerade fotografier, registrar mätningar och upprätthålla en obruten kedja av förvaring för alla fysiska bevis. Simulatorn hjälper studenter att förstå vikten av systematisk dokumentation och objektiv jämförelse i adliellt arbete. Genom att interagera med olika inställningar kan användare lära sig hur ljusväg, provtjocklek och färgkoncentration påverkar spektralkurvorna och det fysiska utseendet.',
185
+ html: 'Adliella undersökare måste dokumentera varje steg i fiberanalysprocessen. Detta inkluderar att ta detaljerade fotografier, registrera mätningar och upprätthålla en obruten kedja av förvaring för alla fysiska bevis. Simulatorn hjälper studenter att förstå vikten av systematisk dokumentation och objektiv jämförelse i adliellt arbete. Genom att interagera med olika inställningar kan användare lära sig hur ljusväg, provtjocklek och färgkoncentration påverkar spektralkurvorna och det fysiska utseendet.' + " Bedömningen bör ta hänsyn till bildkvalitet, mätförhållanden, möjliga förvrängningar och fullständig dokumentation. Ett tillförlitligt rättsmedicinskt resultat kräver flera oberoende kännetecken, deras rumsliga relationer, tydliga begränsningar och kontroll av en kvalificerad expert.",
190
186
  },
191
187
  {
192
188
  type: 'glossary',
@@ -184,10 +184,6 @@ export const content: ToolLocaleContent = {
184
184
  type: 'paragraph',
185
185
  html: '法医鉴定人员必须记录纤维分析过程的每一步。这包括拍摄详细照片、记录测量数据以及维护所有物理证据的监管链。模拟器有助于学生理解法医实际检案中系统记录和客观比对的重要性。通过调节不同的设置,用户可以了解光路、样本厚度以及染料浓度如何影响光谱曲线和物理外观。',
186
186
  },
187
- {
188
- type: 'paragraph',
189
- html: '法医鉴定人员必须记录纤维分析过程的每一步。这包括拍摄详细照片、记录测量数据以及维护所有物理证据的监管链。模拟器有助于学生理解法医实际检案中系统记录和客观比对的重要性。通过调节不同的设置,用户可以了解光路、样本厚度以及染料浓度如何影响光谱曲线和物理外观。',
190
- },
191
187
  {
192
188
  type: 'glossary',
193
189
  items: [
@@ -211,22 +211,13 @@ export const content: ToolLocaleContent = {
211
211
  type: 'paragraph',
212
212
  html: 'In der echten Kriminaltechnik (ACE-V-Methode) bewerten Sachverständige die allgemeine Qualität, Verzerrungen, relative Positionen und die Seltenheit von Merkmalen, um eine Identität zweifelsfrei festzustellen.',
213
213
  },
214
- {
215
- type: 'title',
216
- text: 'Methodik der daktyloskopischen Analyse und des Vergleichs',
217
- level: 3,
218
- },
219
- {
220
- type: 'paragraph',
221
- html: 'Die Daktyloskopie basiert auf der Annahme, dass keine zwei Personen die gleichen Papillarlinien besitzen und diese ein Leben lang unveränderlich bleiben. Schulungssimulatoren unterstützen Studierende dabei, eine systematische Beobachtungsgabe zu entwickeln, indem sie lernen, einzelne Linienverläufe zu verfolgen und kleinste Unterbrechungen wie Endungen oder Verzweigungen in einer kontrollierten Umgebung zu erkennen.',
222
- },
223
214
  {
224
215
  type: 'paragraph',
225
216
  html: 'Die korrekte Annotation von Minuzien setzt voraus, dass Studierende zunächst den allgemeinen Linienfluss verstehen und dann lokale Anomalien benennen können. Endungen entstehen, wenn eine Papillarlinie plötzlich aufhört, ohne sich mit einer anderen Linie zu verbinden. Gabelungen hingegen entstehen, wenn sich eine Linie in zwei Teile aufteilt. Inseln und Punkte sind kurze isolierte Liniensegmente, die besonders vorsichtig markiert werden sollten, da sie leicht mit Bildrauschen oder Druckverzerrungen verwechselt werden können.',
226
217
  },
227
218
  {
228
219
  type: 'paragraph',
229
- html: 'Im forensischen Unterricht ist es wichtig, den Unterschied zwischen der Klassenmerkmalsanalyse und der Individualmerkmalsanalyse zu verstehen. Klassenmerkmale wie Bogen, Schleife und Wirbel erlauben die Einordnung eines Abdrucks in eine Kategorie, können aber nicht zur Individualidentifikation genutzt werden. Erst die Gesamtheit und die räumliche Anordnung der Minuzien in Bezug auf Kern und Delta ermöglicht eine personenbezogene Zuordnung.',
220
+ html: 'Im forensischen Unterricht ist es wichtig, den Unterschied zwischen der Klassenmerkmalsanalyse und der Individualmerkmalsanalyse zu verstehen. Klassenmerkmale wie Bogen, Schleife und Wirbel erlauben die Einordnung eines Abdrucks in eine Kategorie, können aber nicht zur Individualidentifikation genutzt werden. Erst die Gesamtheit und die räumliche Anordnung der Minuzien in Bezug auf Kern und Delta ermöglicht eine personenbezogene Zuordnung.' + " Die Aussage sollte immer im Zusammenhang mit der Probenahme, der Messqualität und der vollständigen Dokumentation bewertet werden. Kleine Unterschiede können durch Herstellung, Alterung, Verschmutzung oder Messbedingungen entstehen. Eine belastbare forensische Schlussfolgerung braucht deshalb nachvollziehbare Vergleichsproben, klare Unsicherheiten und eine unabhängige fachliche Prüfung.",
230
221
  },
231
222
  {
232
223
  type: 'glossary',