i18n(achievements): replace insulting badge titles

The over-intake and skipped-intake achievements shipped with the
literal titles "Idiot" / "Lazy Boy" in both EN and DE bundles —
the German file never translated them. For a personal health app
the user gets called an idiot any time they double-dose because
they forgot they already took it. Replaced with neutral,
descriptive titles ("Double take" / "Stepped back" — "Doppelt
genommen" / "Pause eingelegt"). Locked in by a regression suite
that scans every badge title against a banned-word list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-10 13:32:19 +02:00
1 parent dc75fe846a
commit 7a6bc812c9
3 files changed
+61 -4

No files matched your search

+2 -2
View File
@@ -2011,11 +2011,11 @@
"description": "Dokumentiere 300 eingenommene Medikamente."
},
"overIntake1": {
"title": "Idiot",
"title": "Doppelt genommen",
"description": "Nimm mindestens einmal mehr ein als vorgesehen."
},
"skippedIntake1": {
"title": "Lazy Boy",
"title": "Pause eingelegt",
"description": "Überspringe mindestens eine geplante Einnahme."
},
"passkeyCreated1": {
+2 -2
View File
@@ -2011,11 +2011,11 @@
"description": "Record 300 taken medication events."
},
"overIntake1": {
"title": "Idiot",
"title": "Double take",
"description": "Take medication at least once more than planned."
},
"skippedIntake1": {
"title": "Lazy Boy",
"title": "Stepped back",
"description": "Skip at least one planned intake."
},
"passkeyCreated1": {
@@ -0,0 +1,57 @@
import { describe, expect, test } from "vitest";
import deMessages from "../../../messages/de.json";
import enMessages from "../../../messages/en.json";
/**
* v1.4.19 A8 / F-03 — `achievements.list.overIntake1.title === "Idiot"` and
* `achievements.list.skippedIntake1.title === "Lazy Boy"` were shipping in
* both EN and DE bundles. For a personal health app that translates to a
* literal insult any time the user double-doses (e.g. forgot they already
* took it) or skips a dose. This guard pins the fix and prevents copy-paste
* regressions when adding new badges.
*/
const BANNED = ["idiot", "lazy boy", "lazyboy", "loser", "stupid", "dummy"];
interface MessagesShape {
achievements?: {
badges?: Record<string, { title?: string; description?: string }>;
};
}
function readBadgeTitles(
messages: MessagesShape,
): { key: string; title: string }[] {
const badges = messages.achievements?.badges ?? {};
return Object.entries(badges).map(([key, entry]) => ({
key,
title: entry.title ?? "",
}));
}
describe("achievement titles", () => {
test.each([
["en", enMessages as unknown as MessagesShape],
["de", deMessages as unknown as MessagesShape],
] as const)("%s bundle contains no insulting titles", (_locale, bundle) => {
const offending = readBadgeTitles(bundle).filter((entry) =>
BANNED.some((b) => entry.title.toLowerCase().includes(b)),
);
expect(offending).toEqual([]);
});
test.each([
["en", enMessages as unknown as MessagesShape],
["de", deMessages as unknown as MessagesShape],
] as const)(
"%s overIntake1/skippedIntake1 are translated, not the literal English shipped before",
(_locale, bundle) => {
const badges = bundle.achievements?.badges ?? {};
expect(badges.overIntake1?.title).toBeDefined();
expect(badges.skippedIntake1?.title).toBeDefined();
expect(badges.overIntake1?.title).not.toBe("Idiot");
expect(badges.skippedIntake1?.title).not.toBe("Lazy Boy");
},
);
});