From 7a6bc812c946a5da55702b4ec35218df30213e45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 10 May 2026 13:32:19 +0200 Subject: [PATCH] i18n(achievements): replace insulting badge titles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- messages/de.json | 4 +- messages/en.json | 4 +- .../__tests__/achievements-no-insults.test.ts | 57 +++++++++++++++++++ 3 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 src/lib/__tests__/achievements-no-insults.test.ts diff --git a/messages/de.json b/messages/de.json index 21da79b1a..adeb372d7 100644 --- a/messages/de.json +++ b/messages/de.json @@ -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": { diff --git a/messages/en.json b/messages/en.json index 593256a07..10a953834 100644 --- a/messages/en.json +++ b/messages/en.json @@ -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": { diff --git a/src/lib/__tests__/achievements-no-insults.test.ts b/src/lib/__tests__/achievements-no-insults.test.ts new file mode 100644 index 000000000..9c6e30423 --- /dev/null +++ b/src/lib/__tests__/achievements-no-insults.test.ts @@ -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; + }; +} + +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"); + }, + ); +});