fix(settings): English addresses for the health record and checkups pages

Two Settings pages had German words in their address in every
language: /settings/gesundheitsakte and /settings/layout/vorsorge.
They are /settings/health-record and /settings/layout/checkups now,
and the old addresses (and the older /settings/sharing and
/settings/vorsorge) redirect permanently, so bookmarks and links in
old reports keep working; the report range a since-last-visit link
carries in its query passes through. The in-app links, the tour, the
command palette, the anchor forwards and the e2e specs use the new
addresses, and the two message groups follow the new slugs.
This commit is contained in:
Marc-André Bombeck committed 2026-11-11 22:47:40 +01:00
1 parent 3b79c81379
commit f17819f1c2
34 files changed
+110 -78

No files matched your search

+1 -1
View File
@@ -321,7 +321,7 @@ export function SinceLastVisit({
className="min-h-11 sm:min-h-9"
>
<Link
href={`/settings/gesundheitsakte?reportFrom=${fromDay}&reportTo=${nextDay < today ? nextDay : today}`}
href={`/settings/health-record?reportFrom=${fromDay}&reportTo=${nextDay < today ? nextDay : today}`}
data-slot="since-last-visit-report"
>
<FileText className="size-4" aria-hidden="true" />
@@ -200,7 +200,7 @@ export function VorsorgeSection({
className="min-h-11 min-w-11 sm:min-h-9 sm:min-w-9"
>
<Link
href="/settings/layout/vorsorge"
href="/settings/layout/checkups"
aria-label={t("measurementReminders.customize")}
title={t("measurementReminders.customize")}
>
@@ -10,7 +10,7 @@ import { renderToStaticMarkup } from "react-dom/server";
vi.mock("next/navigation", () => ({
useRouter: () => ({ push: vi.fn(), replace: vi.fn() }),
usePathname: () => "/settings/gesundheitsakte",
usePathname: () => "/settings/health-record",
}));
vi.mock("@tanstack/react-query", () => ({
@@ -43,19 +43,19 @@ describe("<GesundheitsakteSection> — SSR smoke", () => {
// v1.18.6 — the visible heading (with the historic id) comes from
// the shared frame the route wraps the section in.
const html = render(
<SettingsSectionFrame slug="gesundheitsakte">
<SettingsSectionFrame slug="health-record">
<GesundheitsakteSection />
</SettingsSectionFrame>,
);
expect(html).toContain('id="settings-section-gesundheitsakte-title"');
expect(html).toContain('id="settings-section-health-record-title"');
expect(html).toContain('data-testid="health-record-export-panel"');
// Raw key never leaks past i18n.
expect(html).not.toContain("settings.sections.gesundheitsakte.");
expect(html).not.toContain("settings.sections.health-record.");
});
it("German locale resolves without leaking the raw key", () => {
const html = render(<GesundheitsakteSection />, "de");
expect(html).toContain('data-testid="health-record-export-panel"');
expect(html).not.toContain("settings.sections.gesundheitsakte.");
expect(html).not.toContain("settings.sections.health-record.");
});
});
@@ -38,7 +38,7 @@ describe("layout-groups registry", () => {
"labs",
"illness",
"documents",
"vorsorge",
"checkups",
]);
// generateStaticParams() shape.
expect(LAYOUT_GROUP_IDS.map((module) => ({ module }))).toEqual([
@@ -48,13 +48,13 @@ describe("layout-groups registry", () => {
{ module: "labs" },
{ module: "illness" },
{ module: "documents" },
{ module: "vorsorge" },
{ module: "checkups" },
]);
});
it("guards unknown segments", () => {
expect(isLayoutGroupId("medications")).toBe(true);
expect(isLayoutGroupId("vorsorge")).toBe(true);
expect(isLayoutGroupId("checkups")).toBe(true);
expect(isLayoutGroupId("nope")).toBe(false);
expect(isLayoutGroupId("")).toBe(false);
});
@@ -68,10 +68,10 @@ describe("layout-groups registry", () => {
expect(gate("mood")).toBe("not-a-group");
expect(gate("labs")).toBe("labs");
expect(gate("illness")).toBe("illness");
// dashboard / insights / vorsorge are never gated.
// dashboard / insights / checkups are never gated.
expect(gate("dashboard")).toBeUndefined();
expect(gate("insights")).toBeUndefined();
expect(gate("vorsorge")).toBeUndefined();
expect(gate("checkups")).toBeUndefined();
});
it("maps every id to a distinct section body and the canonical i18n keys", () => {
@@ -56,7 +56,7 @@ describe("<LayoutSection> hub", () => {
"medications",
"labs",
"illness",
"vorsorge",
"checkups",
]) {
expect(html).toContain(`href="/settings/layout/${id}"`);
}
@@ -66,12 +66,12 @@ describe("<LayoutSection> hub", () => {
const html = render({ medications: false, labs: false });
expect(html).not.toContain('href="/settings/layout/medications"');
expect(html).not.toContain('href="/settings/layout/labs"');
// Other modules unaffected; Vorsorge is never gated.
// Other modules unaffected; checkups are never gated.
expect(html).toContain('href="/settings/layout/illness"');
expect(html).toContain('href="/settings/layout/vorsorge"');
expect(html).toContain('href="/settings/layout/checkups"');
});
it("always lists dashboard, insights, and vorsorge (no module gate)", () => {
it("always lists dashboard, insights, and checkups (no module gate)", () => {
const html = render({
medications: false,
mood: false,
@@ -80,7 +80,7 @@ describe("<LayoutSection> hub", () => {
});
expect(html).toContain('href="/settings/layout/dashboard"');
expect(html).toContain('href="/settings/layout/insights"');
expect(html).toContain('href="/settings/layout/vorsorge"');
expect(html).toContain('href="/settings/layout/checkups"');
// v1.44 — mood is tag management and left Arrangement for Modules.
expect(html).not.toContain('href="/settings/layout/mood"');
});
@@ -28,7 +28,7 @@ const LAYOUT_CHILD_SLUGS = new Set([
"mood",
"labs",
"illness",
"vorsorge",
"checkups",
]);
export function SettingsSectionFrame({
@@ -120,7 +120,7 @@ describe("SETTINGS_SECTION_SLUGS", () => {
// 301-redirects to `/settings/notifications`.
// v1.18.0 — `modules` ("Was du trackst") sits right after `account` as
// the single front door for enabling/disabling secondary domains.
// v1.18.0 — `gesundheitsakte` (the full health-record export) lifts
// v1.18.0 — `health-record` (the full health-record export) lifts
// out of Export & Import into its own top-level entry, before `export`;
// `coach` gathers the Coach preference cards out of the AI section and
// sits right after `ai`.
@@ -133,7 +133,7 @@ describe("SETTINGS_SECTION_SLUGS", () => {
// first-class sections, right after `mood` (the three were standalone
// `ModuleSettingsFrame` pages).
// v1.18.7 — `sharing` (clinician share links) sits directly after
// `gesundheitsakte`, before `export`: minting a read-only link to the
// `health-record`, before `export`: minting a read-only link to the
// health record belongs next to the health-record export.
expect([...SETTINGS_SECTION_SLUGS]).toEqual([
"account",
@@ -164,7 +164,7 @@ describe("SETTINGS_SECTION_SLUGS", () => {
"coach",
"api",
"mcp",
"gesundheitsakte",
"health-record",
"export",
"advanced",
// v1.23 — "Data & Privacy" assembles the export / deletion / retention /
@@ -246,7 +246,7 @@ describe("<SettingsShell>", () => {
api: "connections",
mcp: "connections",
export: "data",
gesundheitsakte: "data",
"health-record": "data",
privacy: "data",
advanced: "data",
about: "system",
@@ -397,16 +397,16 @@ describe("<SettingsShell>", () => {
// reachable. The server-side `/api/export/health-record` route remains
// the hard enforcement of the opt-out.
expect(renderShell({ active: "account" })).toContain(
'href="/settings/gesundheitsakte"',
'href="/settings/health-record"',
);
expect(renderShell({ active: "account", modules: {} })).toContain(
'href="/settings/gesundheitsakte"',
'href="/settings/health-record"',
);
const optedOut = renderShell({
active: "account",
modules: { doctorReport: false },
});
expect(optedOut).toContain('href="/settings/gesundheitsakte"');
expect(optedOut).toContain('href="/settings/health-record"');
});
describe("inside somebody else's record", () => {
@@ -507,7 +507,7 @@ describe("<SettingsShell>", () => {
// form so we don't accidentally match a parser that double-escapes.
expect(html).toContain("API &amp; tokens");
// v1.18.0 — the health record is its own top-level entry.
expect(html).toContain('href="/settings/gesundheitsakte"');
expect(html).toContain('href="/settings/health-record"');
expect(html).toContain("Health record");
// v1.4.16: the consolidated Export section is a top-level
// entry in the sidebar; the link must be present in both locales.
@@ -20,7 +20,7 @@
* "Freigabe" section) folds in here as a labelled "Sharing" group below the
* export panel: a time-boxed read-only link is a sharing face of the SAME
* health record. `/settings/sharing` 301-redirects to
* `/settings/gesundheitsakte#sharing`. The backing model, the
* `/settings/health-record#sharing`. The backing model, the
* `/api/share-links` routes, and the public `/c/[token]` view are unchanged.
*/
+4 -3
View File
@@ -99,9 +99,10 @@ export const LAYOUT_GROUPS: ReadonlyArray<LayoutGroup> = [
Body: DocumentsSection,
},
{
id: "vorsorge",
titleKey: "settings.sections.layout.vorsorge.title",
descriptionKey: "settings.sections.layout.vorsorge.description",
// The address read `vorsorge` in every language; that one redirects.
id: "checkups",
titleKey: "settings.sections.layout.checkups.title",
descriptionKey: "settings.sections.layout.checkups.description",
Body: VorsorgeSection,
},
];
@@ -41,7 +41,7 @@ export const SETTINGS_ANCHOR_FORWARDS: Readonly<
"/settings/layout/medications": {
"injection-sites": "/settings/modules/medications#injection-sites",
},
"/settings/layout/vorsorge": {
"/settings/layout/checkups": {
"screening-hints": "/settings/anamnesis#screening-hints",
"vorsorge-reminders": "/settings/notifications#vorsorge-reminders",
},
@@ -110,7 +110,7 @@ const LOADERS: Record<
import("@/components/settings/api-section").then((m) => m.ApiSection),
mcp: () =>
import("@/components/settings/mcp-section").then((m) => m.McpSection),
gesundheitsakte: () =>
"health-record": () =>
import("@/components/settings/gesundheitsakte-section").then(
(m) => m.GesundheitsakteSection,
),
@@ -152,7 +152,7 @@ const SECTIONS: Record<SettingsSectionSlug, ComponentType<SectionProps>> = {
}),
api: dynamic(LOADERS.api, { loading: () => <SectionLoading /> }),
mcp: dynamic(LOADERS.mcp, { loading: () => <SectionLoading /> }),
gesundheitsakte: dynamic(LOADERS.gesundheitsakte, {
"health-record": dynamic(LOADERS["health-record"], {
loading: () => <SectionLoading />,
}),
export: dynamic(LOADERS.export, { loading: () => <SectionLoading /> }),
+2 -2
View File
@@ -256,8 +256,8 @@ export const SETTINGS_SECTIONS: readonly SettingsSection[] = [
// Not module-gated: the health record is a flagship export, and its only
// entry point must not vanish behind the `doctorReport` opt-out.
{
slug: "gesundheitsakte",
titleKey: "settings.sections.gesundheitsakte.title",
slug: "health-record",
titleKey: "settings.sections.health-record.title",
icon: FileHeart,
group: "data",
},