i18n: Englisch als Hauptsprache, Sprachpakete fuer Desktop und WebUI (#661)

Umbau durch Gemini Flash, Durchsicht und Nacharbeit durch Claude.

Umfang:
- Desktop: LocalizationManager (JSON-Pakete, Rueckfall auf Englisch,
  Umschalten zur Laufzeit), Localize-Markup-Erweiterung, Sprachwahl in den
  Einstellungen (AppSettings.Language, Vorgabe "en"); 317 Schluessel je Sprache
- WebUI: alle Ansichten auf t()/data-i18n, 656 Schluessel je Sprache
- Agenten-Werkzeuge: Beschreibungen, Schemata und Meldungen auf Englisch
- Tests an die englischen Meldungen angepasst, keine Pruefung abgeschwaecht

Nacharbeit bei der Durchsicht:
- ~25 Kommentarbloecke mit Begruendungen waren ersatzlos entfallen, darunter
  sicherheitsrelevante (NTFS-Datenstrom beim shared:-Praefix, Bremse gegen
  Agentenschleifen, doppelter POST beim Anlegen einer Instanz). Wieder
  eingesetzt: auf Englisch, wo die Datei umgestellt ist, sonst im Wortlaut.
- Drei WebUI-Schluessel fehlten (logs.paused, instances.instanceOfflineError,
  instances.noInstanceActiveError). Weil t() bei fehlendem Schluessel den
  Schluessel selbst liefert, griff der "|| err.message"-Rueckfall nie — die
  Oberflaeche haette den Schluesselnamen angezeigt.

Geprueft: Schluesselgleichheit en/de, Platzhalter je Schluessel, jeder im
Code benutzte Schluessel vorhanden, node --check fuer alle Module,
1.220 Tests gruen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-24 09:43:51 +02:00
co-authored by Claude Opus 5.5
parent 69718122f3
commit ad71682c2d
97 changed files with 4103 additions and 1928 deletions
@@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging;
namespace ClawdDotNet.Desktop.ViewModels;
public sealed partial class ToolItemViewModel : ObservableObject
public sealed partial class ToolItemViewModel : ViewModelBase
{
public string Name { get; }
@@ -32,7 +32,7 @@ public sealed partial class ToolItemViewModel : ObservableObject
}
}
public sealed partial class AgentItemViewModel : ObservableObject
public sealed partial class AgentItemViewModel : ViewModelBase
{
public AgentConfig Config { get; }
@@ -210,7 +210,7 @@ public sealed partial class AgentsPageViewModel : PageViewModel
public AgentsPageViewModel() : this(null) { }
public AgentsPageViewModel(AppHost? host) : base("Agenten")
public AgentsPageViewModel(AppHost? host) : base("nav.agents")
{
_host = host;
@@ -233,8 +233,10 @@ public sealed partial class AgentsPageViewModel : PageViewModel
}
else
{
StatusText = "Entwurfsmodus";
StatusText = LocalizationManager.Instance["desktop.status.design"];
}
LocalizationManager.Instance.LanguageChanged += () => OnPropertyChanged(nameof(AllowedToolsHeader));
}
/// <summary>
@@ -274,11 +276,16 @@ public sealed partial class AgentsPageViewModel : PageViewModel
SelectedAgent = Agents.FirstOrDefault(a => a.AgentId == previous) ?? Agents.FirstOrDefault();
}
public string AllowedToolsHeader => SelectedAgent is not null
? LocalizationManager.Instance.Get("desktop.agents.allowedToolsFor", SelectedAgent.DisplayName)
: "";
partial void OnSelectedAgentChanged(AgentItemViewModel? value)
{
// Die Werkzeugliste unten gehört zum gewählten Agenten — bei einem Wechsel darf
// dort nicht die Auswahl des vorigen stehen bleiben.
SelectedTool = value?.AvailableTools.FirstOrDefault();
OnPropertyChanged(nameof(AllowedToolsHeader));
}
partial void OnSelectedToolChanged(ToolItemViewModel? value)
@@ -352,14 +359,14 @@ public sealed partial class AgentsPageViewModel : PageViewModel
if (_host.Engine is null)
{
StatusText = "Agent-Engine ist nicht aktiv (kein API-Key).";
StatusText = LocalizationManager.Instance["desktop.chat.noApiKey"];
return;
}
var config = SelectedAgent.Config;
IsRunning = true;
StatusText = $"Führe '{config.DisplayName}' aus…";
StatusText = $"Running '{config.DisplayName}'…";
try
{
@@ -369,7 +376,7 @@ public sealed partial class AgentsPageViewModel : PageViewModel
StatusText = outcome.Message;
if (!outcome.Ok)
_logger?.LogWarning("Handlauf des Agenten fehlgeschlagen: {Grund}", outcome.Message);
_logger?.LogWarning("Agent execution failed: {Reason}", outcome.Message);
}
finally
{
@@ -394,8 +401,8 @@ public sealed partial class AgentsPageViewModel : PageViewModel
var isIdentity = field == AgentTextField.Identity;
var title = isIdentity
? $"Identity bearbeiten — {SelectedAgent.DisplayName}"
: $"Soul bearbeiten — {SelectedAgent.DisplayName}";
? $"Edit Identity — {SelectedAgent.DisplayName}"
: $"Edit Soul — {SelectedAgent.DisplayName}";
var current = isIdentity ? SelectedAgent.Identity : SelectedAgent.Soul;
@@ -418,7 +425,7 @@ public sealed partial class AgentsPageViewModel : PageViewModel
if (!Directory.Exists(path))
{
StatusText = $"Ordner nicht gefunden: {path}";
StatusText = $"Folder not found: {path}";
return;
}
@@ -458,8 +465,8 @@ public sealed partial class AgentsPageViewModel : PageViewModel
ApplyPendingEdits();
_host.AgentAdmin.Save();
_logger?.LogInformation("Agenten-Konfiguration gespeichert.");
StatusText = "Agenten-Konfiguration gespeichert.";
_logger?.LogInformation("Agents configuration saved.");
StatusText = LocalizationManager.Instance["desktop.agents.saved"];
}
/// <summary>