From 741f66fe144e03fd56442c28cef3f73e609b8800 Mon Sep 17 00:00:00 2001 From: mika kuns Date: Thu, 10 Sep 2026 13:08:43 +0200 Subject: [PATCH] fix(localization): merge duplicate top-level "checks" block in en/de.json en.json and de.json each defined the checks object twice; JSON silently kept the second block's values, so the first block's friendlier titles/hints (e.g. "Permission Mode" over the raw "--permission-mode auto support" flag) were dead text. Merged into one block per key, keeping the friendlier title and the more actionable hint. Added a Utf8JsonReader-based duplicate-top-level-key test, since JsonDocument accepts duplicates without error and the existing parity test can't see this failure mode either. --- src/ClaudeDo.Localization/locales/de.json | 52 ++---------- src/ClaudeDo.Localization/locales/en.json | 52 ++---------- .../DuplicateLocaleKeysTests.cs | 84 +++++++++++++++++++ 3 files changed, 102 insertions(+), 86 deletions(-) create mode 100644 tests/ClaudeDo.Localization.Tests/DuplicateLocaleKeysTests.cs diff --git a/src/ClaudeDo.Localization/locales/de.json b/src/ClaudeDo.Localization/locales/de.json index 38c41343..356d064d 100644 --- a/src/ClaudeDo.Localization/locales/de.json +++ b/src/ClaudeDo.Localization/locales/de.json @@ -670,35 +670,35 @@ "checks": { "git": { "title": "Git", - "hint": "Installiere Git und stelle sicher, dass es im PATH liegt." + "hint": "Installiere Git von https://git-scm.com/downloads und starte den Installer neu." }, "gitIdentity": { "title": "Git-Identität", - "hint": "Lege deine Git-Identität fest: git config --global user.name \"...\" und user.email \"...\"." + "hint": "Führe aus: git config --global user.name \"Dein Name\" und git config --global user.email \"du@example.com\"" }, "ports": { "title": "Ports", - "hint": "Gib den Port frei oder ändere ihn nach der Installation auf der Worker-Seite." + "hint": "Ändere die SignalR-/MCP-Ports auf der Dienste-Seite, oder beende den blockierenden Prozess." }, "writeAccess": { "title": "Schreibzugriff", - "hint": "Wähle ein anderes Installationsverzeichnis oder starte den Installer als Administrator." + "hint": "Wähle ein anderes Installationsverzeichnis oder starte den Installer mit ausreichenden Rechten (z. B. als Administrator)." }, "claudeCli": { "title": "Claude CLI", - "hint": "Installiere die Claude CLI und stelle sicher, dass sie im PATH liegt." + "hint": "Installiere die Claude CLI (npm install -g @anthropic-ai/claude-code) und starte danach dein Terminal/deine Anmeldesitzung neu, damit der aktualisierte PATH greift." }, "claudeVersion": { "title": "Claude-CLI-Version", - "hint": "Aktualisiere die Claude CLI auf eine neuere Version." + "hint": "Aktualisiere die CLI: npm update -g @anthropic-ai/claude-code" }, "claudeAuth": { - "title": "Claude-Anmeldung", - "hint": "Führe 'claude auth login' aus, um dich anzumelden." + "title": "Claude-CLI-Login", + "hint": "Starte 'claude' einmal und schließe den Login-Vorgang ab." }, "permissionModeAuto": { "title": "Berechtigungsmodus", - "hint": "Aktualisiere die Claude CLI auf eine Version, die --permission-mode auto unterstützt." + "hint": "Aktualisiere die CLI: npm update -g @anthropic-ai/claude-code. ClaudeDo funktioniert auch im Modus acceptEdits/default, dann mit mehr Rückfragen." } }, "planning": { @@ -875,39 +875,5 @@ "lastError": "Letzter Fehler: {0}", "throttled": "Gedrosselt: {0}/{1} Slots ({2})" } - }, - "checks": { - "git": { - "title": "Git", - "hint": "Installiere Git von https://git-scm.com/downloads und starte den Installer neu." - }, - "gitIdentity": { - "title": "Git-Identität", - "hint": "Führe aus: git config --global user.name \"Dein Name\" und git config --global user.email \"du@example.com\"" - }, - "ports": { - "title": "Ports", - "hint": "Ändere die SignalR-/MCP-Ports auf der Dienste-Seite, oder beende den blockierenden Prozess." - }, - "writeAccess": { - "title": "Schreibzugriff", - "hint": "Wähle ein anderes Installationsverzeichnis oder starte den Installer mit ausreichenden Rechten (z. B. als Administrator)." - }, - "claudeCli": { - "title": "Claude CLI", - "hint": "Installiere die Claude CLI (npm install -g @anthropic-ai/claude-code) und starte danach dein Terminal/deine Anmeldesitzung neu, damit der aktualisierte PATH greift." - }, - "claudeVersion": { - "title": "Claude-CLI-Version", - "hint": "Aktualisiere die CLI: npm update -g @anthropic-ai/claude-code" - }, - "claudeAuth": { - "title": "Claude-CLI-Login", - "hint": "Starte 'claude' einmal und schließe den Login-Vorgang ab." - }, - "permissionModeAuto": { - "title": "Unterstützung für --permission-mode auto", - "hint": "Aktualisiere die CLI: npm update -g @anthropic-ai/claude-code. ClaudeDo funktioniert auch im Modus acceptEdits/default, dann mit mehr Rückfragen." - } } } \ No newline at end of file diff --git a/src/ClaudeDo.Localization/locales/en.json b/src/ClaudeDo.Localization/locales/en.json index 8d05a8ad..a52b1569 100644 --- a/src/ClaudeDo.Localization/locales/en.json +++ b/src/ClaudeDo.Localization/locales/en.json @@ -670,35 +670,35 @@ "checks": { "git": { "title": "Git", - "hint": "Install Git and make sure it is on PATH." + "hint": "Install Git from https://git-scm.com/downloads, then restart the installer." }, "gitIdentity": { "title": "Git Identity", - "hint": "Set your git identity: git config --global user.name \"...\" and user.email \"...\"." + "hint": "Run: git config --global user.name \"Your Name\" and git config --global user.email \"you@example.com\"" }, "ports": { "title": "Ports", - "hint": "Free the port, or change it on the Worker page after install." + "hint": "Change the SignalR/MCP ports on the Service page, or stop the process using them." }, "writeAccess": { "title": "Write Access", - "hint": "Choose a different install directory, or run the installer as administrator." + "hint": "Choose a different install directory, or run the installer with sufficient permissions (e.g. as administrator)." }, "claudeCli": { "title": "Claude CLI", - "hint": "Install the Claude CLI and make sure it is on PATH." + "hint": "Install the Claude CLI (npm install -g @anthropic-ai/claude-code), then restart your terminal/login session so the updated PATH takes effect." }, "claudeVersion": { "title": "Claude CLI Version", - "hint": "Update the Claude CLI to a newer version." + "hint": "Update the CLI: npm update -g @anthropic-ai/claude-code" }, "claudeAuth": { - "title": "Claude Login", - "hint": "Run 'claude auth login' to sign in." + "title": "Claude CLI Login", + "hint": "Run 'claude' once and complete the login flow." }, "permissionModeAuto": { "title": "Permission Mode", - "hint": "Update the Claude CLI to a version that supports --permission-mode auto." + "hint": "Update the CLI: npm update -g @anthropic-ai/claude-code. ClaudeDo still works in acceptEdits/default mode, with more prompts." } }, "planning": { @@ -875,39 +875,5 @@ "lastError": "Last error: {0}", "throttled": "Throttled: {0}/{1} slots ({2})" } - }, - "checks": { - "git": { - "title": "Git", - "hint": "Install Git from https://git-scm.com/downloads, then restart the installer." - }, - "gitIdentity": { - "title": "Git identity", - "hint": "Run: git config --global user.name \"Your Name\" and git config --global user.email \"you@example.com\"" - }, - "ports": { - "title": "Ports", - "hint": "Change the SignalR/MCP ports on the Service page, or stop the process using them." - }, - "writeAccess": { - "title": "Write access", - "hint": "Choose a different install directory, or run the installer with sufficient permissions (e.g. as administrator)." - }, - "claudeCli": { - "title": "Claude CLI", - "hint": "Install the Claude CLI (npm install -g @anthropic-ai/claude-code), then restart your terminal/login session so the updated PATH takes effect." - }, - "claudeVersion": { - "title": "Claude CLI version", - "hint": "Update the CLI: npm update -g @anthropic-ai/claude-code" - }, - "claudeAuth": { - "title": "Claude CLI login", - "hint": "Run 'claude' once and complete the login flow." - }, - "permissionModeAuto": { - "title": "--permission-mode auto support", - "hint": "Update the CLI: npm update -g @anthropic-ai/claude-code. ClaudeDo still works in acceptEdits/default mode, with more prompts." - } } } \ No newline at end of file diff --git a/tests/ClaudeDo.Localization.Tests/DuplicateLocaleKeysTests.cs b/tests/ClaudeDo.Localization.Tests/DuplicateLocaleKeysTests.cs new file mode 100644 index 00000000..fd13f771 --- /dev/null +++ b/tests/ClaudeDo.Localization.Tests/DuplicateLocaleKeysTests.cs @@ -0,0 +1,84 @@ +using System.Text; +using System.Text.Json; + +namespace ClaudeDo.Localization.Tests; + +/// +/// JsonDocument/JsonElement silently keep the last value on a duplicate key instead of throwing, +/// so a repeated top-level block (e.g. two "checks" objects) is invisible to JSON-validity and +/// parity tests alike. This walks the raw token stream instead. +/// +public class DuplicateLocaleKeysTests +{ + private static string LocalesDir() + { + var dir = AppContext.BaseDirectory; + while (dir is not null) + { + var candidate = Path.Combine(dir, "src", "ClaudeDo.Localization", "locales"); + if (Directory.Exists(candidate)) return candidate; + dir = Path.GetDirectoryName(dir); + } + throw new DirectoryNotFoundException("Could not locate src/ClaudeDo.Localization/locales"); + } + + private static List FindDuplicateTopLevelKeys(string json) + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json)); + var seen = new HashSet(StringComparer.Ordinal); + var duplicates = new List(); + var depth = 0; + + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonTokenType.StartObject: + case JsonTokenType.StartArray: + depth++; + break; + case JsonTokenType.EndObject: + case JsonTokenType.EndArray: + depth--; + break; + case JsonTokenType.PropertyName: + if (depth == 1) + { + var name = reader.GetString()!; + if (!seen.Add(name)) duplicates.Add(name); + } + break; + } + } + + return duplicates; + } + + [Fact] + public void Detects_duplicate_top_level_key_in_fixture() + { + const string json = """ + { + "checks": { "a": "1" }, + "other": { "b": "2" }, + "checks": { "c": "3" } + } + """; + + var duplicates = FindDuplicateTopLevelKeys(json); + + Assert.Contains("checks", duplicates); + } + + [Fact] + public void Locale_files_have_no_duplicate_top_level_keys() + { + var localesDir = LocalesDir(); + foreach (var path in Directory.EnumerateFiles(localesDir, "*.json")) + { + var duplicates = FindDuplicateTopLevelKeys(File.ReadAllText(path)); + Assert.True(duplicates.Count == 0, + $"{Path.GetFileName(path)} has duplicate top-level keys: {string.Join(", ", duplicates)}"); + } + } +}