mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
Introduce a shared SettingsSectionFrame (the settings twin of the admin renderer's SectionFrame) that emits the canonical visible heading + subheading, and route all 19 settings sections through it. Every settings page now shows a real h1 + subtitle in the reference Übersicht style instead of an invisible sr-only title; the section bodies drop their own headings and off-style intro paragraphs (the explanatory copy folds into the frame subtitle). - Route the admin About page through SectionFrame so it carries the same visible heading as every other admin section. - Rename the AI settings section from KI-Auswertung to KI-Anbieter across six locales: the page configures the provider and key, not an analysis. - Add a heading-block spacer to the settings and admin sidebars so the first menu item lines up with the top of the first card. - Remove the always-on core-domains card from Module: a tracking area that can't be turned off doesn't need listing. - Move the proactive-coach control out of Benachrichtigungen into Coach. - Move admin module availability out of the Coach section into its own page: it gates every module, not just the coach. - Give the Layout Insights editor a proper Insights heading. Adds settings.sections.<slug>.subtitle and the new admin keys across all six locales; removes the now-unused module core-domain strings.
94 lines
3.1 KiB
TypeScript
94 lines
3.1 KiB
TypeScript
/**
|
|
* v1.4.16 phase B6 — settings i18n parity.
|
|
*
|
|
* Every slug declared in `SETTINGS_SECTION_SLUGS` MUST have a
|
|
* `settings.sections.<slug>.title` AND a `settings.sections.<slug>.subtitle`
|
|
* resolved in both English and German. The sidebar reads `.title`; the shared
|
|
* `<SettingsSectionFrame>` reads `.title` + `.subtitle` for the visible page
|
|
* heading + subheading (v1.18.6 W9), so a missing key paints a raw key in
|
|
* production.
|
|
*/
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
import en from "../../../../messages/en.json" with { type: "json" };
|
|
import de from "../../../../messages/de.json" with { type: "json" };
|
|
import { SETTINGS_SECTION_SLUGS } from "../section-slugs";
|
|
|
|
type Bag = Record<string, unknown>;
|
|
|
|
function lookup(bag: Bag, path: string[]): string | null {
|
|
let cursor: unknown = bag;
|
|
for (const segment of path) {
|
|
if (typeof cursor !== "object" || cursor === null) return null;
|
|
cursor = (cursor as Bag)[segment];
|
|
}
|
|
return typeof cursor === "string" ? cursor : null;
|
|
}
|
|
|
|
describe("settings sections — i18n parity (B6)", () => {
|
|
for (const slug of SETTINGS_SECTION_SLUGS) {
|
|
it(`every locale resolves settings.sections.${slug}.title`, () => {
|
|
const enValue = lookup(en as Bag, [
|
|
"settings",
|
|
"sections",
|
|
slug,
|
|
"title",
|
|
]);
|
|
const deValue = lookup(de as Bag, [
|
|
"settings",
|
|
"sections",
|
|
slug,
|
|
"title",
|
|
]);
|
|
expect(enValue, `EN missing title for ${slug}`).toBeTypeOf("string");
|
|
expect(enValue?.length ?? 0).toBeGreaterThan(0);
|
|
expect(deValue, `DE missing title for ${slug}`).toBeTypeOf("string");
|
|
expect(deValue?.length ?? 0).toBeGreaterThan(0);
|
|
});
|
|
|
|
it(`every locale resolves settings.sections.${slug}.subtitle`, () => {
|
|
const enValue = lookup(en as Bag, [
|
|
"settings",
|
|
"sections",
|
|
slug,
|
|
"subtitle",
|
|
]);
|
|
const deValue = lookup(de as Bag, [
|
|
"settings",
|
|
"sections",
|
|
slug,
|
|
"subtitle",
|
|
]);
|
|
expect(enValue, `EN missing subtitle for ${slug}`).toBeTypeOf("string");
|
|
expect(enValue?.length ?? 0).toBeGreaterThan(0);
|
|
expect(deValue, `DE missing subtitle for ${slug}`).toBeTypeOf("string");
|
|
expect(deValue?.length ?? 0).toBeGreaterThan(0);
|
|
});
|
|
}
|
|
|
|
it("EN and DE subtitle for every slug are different (catches accidental copy-paste)", () => {
|
|
for (const slug of SETTINGS_SECTION_SLUGS) {
|
|
const enValue = lookup(en as Bag, [
|
|
"settings",
|
|
"sections",
|
|
slug,
|
|
"subtitle",
|
|
]);
|
|
const deValue = lookup(de as Bag, [
|
|
"settings",
|
|
"sections",
|
|
slug,
|
|
"subtitle",
|
|
]);
|
|
// It's legitimate for some titles to coincide between locales when
|
|
// the proper noun is the same (e.g. "Dashboard", "API & Tokens").
|
|
// Descriptions, however, MUST be translated — a copy-paste of the
|
|
// EN value into the DE bag is a bug.
|
|
expect(
|
|
deValue,
|
|
`DE subtitle for ${slug} is identical to EN — needs translation`,
|
|
).not.toBe(enValue);
|
|
}
|
|
});
|
|
});
|