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.
This commit is contained in:
@@ -670,35 +670,35 @@
|
|||||||
"checks": {
|
"checks": {
|
||||||
"git": {
|
"git": {
|
||||||
"title": "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": {
|
"gitIdentity": {
|
||||||
"title": "Git-Identität",
|
"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": {
|
"ports": {
|
||||||
"title": "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": {
|
"writeAccess": {
|
||||||
"title": "Schreibzugriff",
|
"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": {
|
"claudeCli": {
|
||||||
"title": "Claude CLI",
|
"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": {
|
"claudeVersion": {
|
||||||
"title": "Claude-CLI-Version",
|
"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": {
|
"claudeAuth": {
|
||||||
"title": "Claude-Anmeldung",
|
"title": "Claude-CLI-Login",
|
||||||
"hint": "Führe 'claude auth login' aus, um dich anzumelden."
|
"hint": "Starte 'claude' einmal und schließe den Login-Vorgang ab."
|
||||||
},
|
},
|
||||||
"permissionModeAuto": {
|
"permissionModeAuto": {
|
||||||
"title": "Berechtigungsmodus",
|
"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": {
|
"planning": {
|
||||||
@@ -875,39 +875,5 @@
|
|||||||
"lastError": "Letzter Fehler: {0}",
|
"lastError": "Letzter Fehler: {0}",
|
||||||
"throttled": "Gedrosselt: {0}/{1} Slots ({2})"
|
"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."
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -670,35 +670,35 @@
|
|||||||
"checks": {
|
"checks": {
|
||||||
"git": {
|
"git": {
|
||||||
"title": "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": {
|
"gitIdentity": {
|
||||||
"title": "Git Identity",
|
"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": {
|
"ports": {
|
||||||
"title": "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": {
|
"writeAccess": {
|
||||||
"title": "Write Access",
|
"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": {
|
"claudeCli": {
|
||||||
"title": "Claude CLI",
|
"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": {
|
"claudeVersion": {
|
||||||
"title": "Claude CLI Version",
|
"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": {
|
"claudeAuth": {
|
||||||
"title": "Claude Login",
|
"title": "Claude CLI Login",
|
||||||
"hint": "Run 'claude auth login' to sign in."
|
"hint": "Run 'claude' once and complete the login flow."
|
||||||
},
|
},
|
||||||
"permissionModeAuto": {
|
"permissionModeAuto": {
|
||||||
"title": "Permission Mode",
|
"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": {
|
"planning": {
|
||||||
@@ -875,39 +875,5 @@
|
|||||||
"lastError": "Last error: {0}",
|
"lastError": "Last error: {0}",
|
||||||
"throttled": "Throttled: {0}/{1} slots ({2})"
|
"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."
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Localization.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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<string> FindDuplicateTopLevelKeys(string json)
|
||||||
|
{
|
||||||
|
var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
|
||||||
|
var seen = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
var duplicates = new List<string>();
|
||||||
|
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)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user