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)}");
+ }
+ }
+}