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
+34 -20
View File
@@ -111,6 +111,7 @@ public partial class App : Application
_managed = CommandLine.IsManaged(desktop.Args);
ApplySavedThemeEarly();
ApplySavedLanguageEarly();
desktop.ShutdownRequested += async (_, _) =>
{
@@ -156,6 +157,23 @@ public partial class App : Application
}
}
/// <summary>
/// Setzt die gespeicherte Sprache, bevor das erste Fenster erscheint.
/// </summary>
private static void ApplySavedLanguageEarly()
{
try
{
var settings = new SettingsManager();
settings.Load();
LocalizationManager.Instance.SetLanguage(settings.AppSettings.Language);
}
catch (Exception)
{
LocalizationManager.Instance.SetLanguage(LocalizationManager.DefaultLanguage);
}
}
private async Task StartAsync(IClassicDesktopStyleApplicationLifetime desktop)
{
var result = await AppHost.StartAsync(new AppHost.Callbacks
@@ -183,6 +201,7 @@ public partial class App : Application
}
_host = result.Host;
LocalizationManager.Instance.SetLanguage(_host.Settings.AppSettings.Language);
HookErrorReporting(_host);
HookLicenseWatch(_host, desktop);
@@ -664,27 +683,28 @@ public partial class App : Application
=> await Dispatcher.UIThread.InvokeAsync(async () =>
{
var completion = new TaskCompletionSource<StandaloneSwitchChoice>();
var l = LocalizationManager.Instance;
var managerBtn = new Button
{
Content = "Instanzmanager starten (Empfohlen)",
Content = l["desktop.startMode.managerBtn"],
Classes = { "primary" }
};
var switchBtn = new Button
{
Content = "Instanz trotzdem wechseln"
Content = l["desktop.standaloneSwitch.switchAnyway"]
};
var cancelBtn = new Button
{
Content = "Abbrechen",
Content = l["common.cancel"],
IsCancel = true
};
var window = new Window
{
Title = "ClawdDotNet – Instanzwechsel (Standalone)",
Title = l["desktop.standaloneSwitch.title"],
Width = 540,
SizeToContent = SizeToContent.Height,
CanResize = false,
@@ -698,15 +718,12 @@ public partial class App : Application
{
new TextBlock
{
Text = "Achtung: Standalone-Modus",
Text = l["desktop.standaloneSwitch.warning"],
Classes = { "h1" }
},
new TextBlock
{
Text = "Diese Instanz läuft eigenständig ohne Instanzmanager.\n\n"
+ "Wenn du die Instanz hier wechselst, wird diese Instanz vollständig beendet "
+ "und alle laufenden Agenten und Hintergrunddienste werden gestoppt.\n\n"
+ "Empfehlung: Starte stattdessen den Instanzmanager, um mehrere Instanzen gleichzeitig und unabhängig voneinander zu betreiben.",
Text = l["desktop.standaloneSwitch.text"],
TextWrapping = TextWrapping.Wrap
},
new StackPanel
@@ -752,27 +769,28 @@ public partial class App : Application
=> await Dispatcher.UIThread.InvokeAsync(async () =>
{
var completion = new TaskCompletionSource<StartModeChoice>();
var l = LocalizationManager.Instance;
var managerBtn = new Button
{
Content = "Instanzmanager starten (Empfohlen)",
Content = l["desktop.startMode.managerBtn"],
Classes = { "primary" }
};
var standaloneBtn = new Button
{
Content = "Als Standalone-Instanz starten"
Content = l["desktop.startMode.standaloneBtn"]
};
var cancelBtn = new Button
{
Content = "Abbrechen",
Content = l["common.cancel"],
IsCancel = true
};
var window = new Window
{
Title = "ClawdDotNet – Startmodus wählen",
Title = l["desktop.startMode.title"],
Width = 540,
SizeToContent = SizeToContent.Height,
CanResize = false,
@@ -785,16 +803,12 @@ public partial class App : Application
{
new TextBlock
{
Text = "Wie möchtest du ClawdDotNet starten?",
Text = l["desktop.startMode.question"],
Classes = { "h1" }
},
new TextBlock
{
Text = "• Instanzmanager (Empfohlen):\n"
+ " Startet den zentralen Hintergrund-Manager und öffnet die Verwaltung im Browser. "
+ "Ermöglicht den stabilen Dauerbetrieb mehrerer Instanzen parallel ohne Absturzrisiko.\n\n"
+ "• Standalone-Modus:\n"
+ " Startet genau eine Instanz direkt als einzelnes Desktop-Fenster.",
Text = l["desktop.startMode.explanation"],
TextWrapping = TextWrapping.Wrap
},
new StackPanel
@@ -948,7 +962,7 @@ public partial class App : Application
var completion = new TaskCompletionSource<string>();
var input = new TextBox();
var ok = new Button { Content = "OK", IsDefault = true };
var ok = new Button { Content = LocalizationManager.Instance["common.ok"], IsDefault = true };
var window = new Window
{
@@ -18,6 +18,10 @@
<BuiltInComInteropSupport>false</BuiltInComInteropSupport>
</PropertyGroup>
<ItemGroup>
<Using Include="ClawdDotNet.Desktop.Services" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="12.1.1" />
<PackageReference Include="Avalonia.Desktop" Version="12.1.1" />
@@ -38,6 +42,13 @@
<Content Include="setup.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="i18n\*.json" />
<None Update="i18n\*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ClawdDotNet.App\ClawdDotNet.App.csproj" />
@@ -0,0 +1,199 @@
using System.ComponentModel;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json;
namespace ClawdDotNet.Desktop.Services;
public sealed record LanguageOption(string Code, string Name)
{
public override string ToString() => Name;
}
/// <summary>
/// Manages application localization for Avalonia UI.
/// Supports JSON language packs with fallback to English, dynamic language switching,
/// and live UI re-binding via indexer property notification.
/// </summary>
public sealed class LocalizationManager : INotifyPropertyChanged
{
public static LocalizationManager Instance { get; } = new();
public const string DefaultLanguage = "en";
public const string FallbackLanguage = "en";
private readonly Dictionary<string, string> _fallbackStrings = new(StringComparer.OrdinalIgnoreCase);
private Dictionary<string, string> _activeStrings = new(StringComparer.OrdinalIgnoreCase);
private string _currentLanguage = DefaultLanguage;
public IReadOnlyList<LanguageOption> AvailableLanguages { get; } =
[
new("en", "English"),
new("de", "Deutsch")
];
public event PropertyChangedEventHandler? PropertyChanged;
public event Action? LanguageChanged;
public string CurrentLanguage
{
get => _currentLanguage;
private set
{
if (_currentLanguage != value)
{
_currentLanguage = value;
OnPropertyChanged();
}
}
}
[IndexerName("Item")]
public string this[string key] => Get(key);
private LocalizationManager()
{
LoadFallbackPack();
SetLanguage(DefaultLanguage);
}
private void LoadFallbackPack()
{
_fallbackStrings.Clear();
var json = LoadLanguageJson(FallbackLanguage);
if (!string.IsNullOrWhiteSpace(json))
{
ParseJsonIntoDictionary(json, _fallbackStrings);
}
}
public void SetLanguage(string? languageCode)
{
var target = string.IsNullOrWhiteSpace(languageCode) ? DefaultLanguage : languageCode.Trim().ToLowerInvariant();
if (target == FallbackLanguage)
{
_activeStrings = _fallbackStrings;
}
else
{
var pack = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var json = LoadLanguageJson(target);
if (!string.IsNullOrWhiteSpace(json))
{
ParseJsonIntoDictionary(json, pack);
}
_activeStrings = pack;
}
CurrentLanguage = target;
// Notify indexer bindings so Avalonia refreshes all localized strings on screen
OnPropertyChanged("Item");
OnPropertyChanged("Item[]");
LanguageChanged?.Invoke();
}
public string Get(string key, params object[] args)
{
if (string.IsNullOrEmpty(key)) return "";
string? template = null;
if (!_activeStrings.TryGetValue(key, out template))
{
_fallbackStrings.TryGetValue(key, out template);
}
template ??= key;
if (args.Length > 0)
{
try
{
return string.Format(template, args);
}
catch
{
return template;
}
}
return template;
}
private static string? LoadLanguageJson(string langCode)
{
// 1. Try file on disk in AppContext.BaseDirectory/i18n/{langCode}.json
var localPath = Path.Combine(AppContext.BaseDirectory, "i18n", $"{langCode}.json");
if (File.Exists(localPath))
{
try
{
return File.ReadAllText(localPath);
}
catch
{
// Fall through to embedded resource
}
}
// 2. Try embedded resource in ClawdDotNet assembly
var assembly = Assembly.GetExecutingAssembly();
var resourceName = assembly.GetManifestResourceNames()
.FirstOrDefault(r => r.EndsWith($"i18n.{langCode}.json", StringComparison.OrdinalIgnoreCase));
if (resourceName is not null)
{
using var stream = assembly.GetManifestResourceStream(resourceName);
if (stream is not null)
{
using var reader = new StreamReader(stream);
return reader.ReadToEnd();
}
}
return null;
}
private static void ParseJsonIntoDictionary(string json, Dictionary<string, string> dict)
{
try
{
using var doc = JsonDocument.Parse(json);
FlattenElement("", doc.RootElement, dict);
}
catch
{
// Invalid JSON ignored
}
}
private static void FlattenElement(string prefix, JsonElement element, Dictionary<string, string> dict)
{
switch (element.ValueKind)
{
case JsonValueKind.Object:
foreach (var prop in element.EnumerateObject())
{
var childPrefix = string.IsNullOrEmpty(prefix) ? prop.Name : $"{prefix}.{prop.Name}";
FlattenElement(childPrefix, prop.Value, dict);
}
break;
case JsonValueKind.String:
dict[prefix] = element.GetString() ?? "";
break;
case JsonValueKind.Number:
case JsonValueKind.True:
case JsonValueKind.False:
dict[prefix] = element.ToString();
break;
}
}
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
@@ -0,0 +1,30 @@
using Avalonia.Data;
using Avalonia.Markup.Xaml;
namespace ClawdDotNet.Desktop.Services;
/// <summary>
/// Markup extension for Avalonia XAML:
/// Usage: Text="{services:Localize nav.chat}"
/// Updates automatically when language changes.
/// </summary>
public sealed class LocalizeExtension : MarkupExtension
{
public string Key { get; set; } = "";
public LocalizeExtension() { }
public LocalizeExtension(string key)
{
Key = key;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return new Binding($"[{Key}]")
{
Source = LocalizationManager.Instance,
Mode = BindingMode.OneWay
};
}
}
@@ -47,13 +47,13 @@ public sealed partial class AddAgentViewModel : ViewModelBase
{
if (string.IsNullOrWhiteSpace(AgentId))
{
StatusMessage = "Bitte eine Agenten-ID eingeben.";
StatusMessage = "Please enter an Agent ID.";
return;
}
if (string.IsNullOrWhiteSpace(DisplayName))
{
StatusMessage = "Bitte einen Anzeigenamen eingeben.";
StatusMessage = "Please enter a display name.";
return;
}
@@ -37,7 +37,7 @@ public sealed partial class AddJobViewModel : ViewModelBase
private string _cronExpression = "0 * * * *";
[ObservableProperty]
private string _taskMessage = "Führe deine zugewiesenen Aufgaben aus.";
private string _taskMessage = "Execute your assigned tasks.";
[ObservableProperty]
private bool _runOnStart;
@@ -79,13 +79,13 @@ public sealed partial class AddJobViewModel : ViewModelBase
{
if (SelectedAgent is null)
{
StatusMessage = "Bitte einen Agenten auswählen.";
StatusMessage = "Please select an agent.";
return;
}
if (string.IsNullOrWhiteSpace(CronExpression))
{
StatusMessage = "Bitte einen Cron-Ausdruck angeben.";
StatusMessage = "Please enter a cron expression.";
return;
}
@@ -38,13 +38,13 @@ public sealed partial class AddServiceViewModel : ViewModelBase
{
if (string.IsNullOrWhiteSpace(ServiceName))
{
StatusMessage = "Bitte einen Dienstnamen eingeben.";
StatusMessage = "Please enter a service name.";
return;
}
if (ServicePort <= 0 || ServicePort > 65535)
{
StatusMessage = "Gültigen Port zwischen 1 und 65535 eingeben.";
StatusMessage = "Please enter a valid port between 1 and 65535.";
return;
}
@@ -53,7 +53,7 @@ public sealed partial class AgentChatsPageViewModel : PageViewModel
public AgentChatsPageViewModel() : this(null) { }
public AgentChatsPageViewModel(AppHost? host) : base("Agenten-Chats")
public AgentChatsPageViewModel(AppHost? host) : base("nav.agentChats")
{
if (host is null)
{
@@ -85,10 +85,8 @@ public sealed partial class AgentChatsPageViewModel : PageViewModel
Messages.Clear();
EmptyText = Agents.Count < 2
? "Für einen Verlauf braucht es mindestens zwei Agenten in dieser Instanz."
: "Noch keine Nachrichten aufgezeichnet. Agenten können sich bereits gegenseitig "
+ "erreichen; mitgeschrieben wird der Wortlaut erst, wenn der Nachrichtenspeicher "
+ "aus docs/Agentenkommunikation-Konzept.md steht.";
? LocalizationManager.Instance["desktop.agentChats.needTwo"]
: LocalizationManager.Instance["desktop.agentChats.empty"];
IsEmpty = Messages.Count == 0;
}
@@ -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>
@@ -74,7 +74,7 @@ public sealed partial class BackupPageViewModel : PageViewModel
private int _keepCount = 10;
[ObservableProperty]
private string _statusText = "Bereit";
private string _statusText = LocalizationManager.Instance["common.ready"];
[ObservableProperty]
private bool _isBusy;
@@ -88,7 +88,7 @@ public sealed partial class BackupPageViewModel : PageViewModel
public BackupPageViewModel() : this(null) { }
public BackupPageViewModel(AppHost? host) : base("Sicherung")
public BackupPageViewModel(AppHost? host) : base("nav.backup")
{
_host = host;
@@ -35,11 +35,11 @@ public sealed partial class ChatPageViewModel : PageViewModel
private bool _isSending;
[ObservableProperty]
private string _statusText = "Bereit";
private string _statusText = "Ready";
public ChatPageViewModel() : this(null) { }
public ChatPageViewModel(AppHost? host) : base("Chat")
public ChatPageViewModel(AppHost? host) : base("nav.chat")
{
_host = host;
@@ -57,7 +57,7 @@ public sealed partial class ChatPageViewModel : PageViewModel
}
else
{
StatusText = "Entwurfsmodus";
StatusText = "Design Mode";
}
}
@@ -77,14 +77,14 @@ public sealed partial class ChatPageViewModel : PageViewModel
var isUser = entry.Role.Equals("user", StringComparison.OrdinalIgnoreCase);
ChatEntries.Add(new ChatEntryItemViewModel(
entry.Role,
isUser ? "Du" : SelectedAgent.DisplayName,
isUser ? "You" : SelectedAgent.DisplayName,
entry.Content,
entry.Timestamp,
isUser
));
}
StatusText = $"Chat-Verlauf für '{SelectedAgent.DisplayName}' geladen ({ChatEntries.Count} Nachrichten).";
StatusText = $"Chat history for '{SelectedAgent.DisplayName}' loaded ({ChatEntries.Count} messages).";
}
private void OnEngineChatEntryAdded(string agentId, string role, string text, string? toolName)
@@ -94,7 +94,7 @@ public sealed partial class ChatPageViewModel : PageViewModel
Dispatcher.UIThread.Post(() =>
{
var isUser = role.Equals("user", StringComparison.OrdinalIgnoreCase);
var sender = isUser ? "Du" : (string.IsNullOrWhiteSpace(toolName) ? SelectedAgent.DisplayName : $"Tool: {toolName}");
var sender = isUser ? "You" : (string.IsNullOrWhiteSpace(toolName) ? SelectedAgent.DisplayName : $"Tool: {toolName}");
ChatEntries.Add(new ChatEntryItemViewModel(
role, sender, text, DateTime.Now, isUser
@@ -107,7 +107,7 @@ public sealed partial class ChatPageViewModel : PageViewModel
{
if (_host?.Engine is null || SelectedAgent is null)
{
StatusText = "Engine nicht aktiv oder kein Agent ausgewählt.";
StatusText = "Engine not active or no agent selected.";
return;
}
@@ -116,7 +116,7 @@ public sealed partial class ChatPageViewModel : PageViewModel
InputPrompt = "";
IsSending = true;
StatusText = $"{SelectedAgent.DisplayName} denkt nach…";
StatusText = $"{SelectedAgent.DisplayName} is thinking…";
try
{
@@ -126,11 +126,11 @@ public sealed partial class ChatPageViewModel : PageViewModel
var result = await Task.Run(() =>
_host.Engine.ChatAsync(agent, text, instanceId, CancellationToken.None));
StatusText = $"{agent.DisplayName} fertig ({result.Status}, {result.TokensUsed:N0} Tokens).";
StatusText = $"{agent.DisplayName} finished ({result.Status}, {result.TokensUsed:N0} tokens).";
}
catch (Exception ex)
{
StatusText = $"Fehler: {ex.Message}";
StatusText = $"Error: {ex.Message}";
}
finally
{
@@ -39,7 +39,7 @@ public sealed partial class InfoPageViewModel : PageViewModel
public InfoPageViewModel() : this(null) { }
public InfoPageViewModel(AppHost? host) : base("Info")
public InfoPageViewModel(AppHost? host) : base("nav.info")
{
AppVersion = AppHost.AppVersion;
BuildSummary = AppHost.BuildSummary;
@@ -51,16 +51,16 @@ public sealed partial class InfoPageViewModel : PageViewModel
InstancePath = host.InstancePath;
LogDirectory = host.LogDirectory;
OpenRouterStatus = host.Engine is null
? "Deaktiviert (kein API-Key hinterlegt)"
: "Aktiv (AgentEngine bereit)";
? "Disabled (no API key configured)"
: "Active (AgentEngine ready)";
}
else
{
InstanceName = "Entwurfsmodus";
InstanceName = "Design Mode";
InstanceId = "00000000-0000-0000-0000-000000000000";
InstancePath = @"C:\ClawdDotNet\Instances\Default";
LogDirectory = @"C:\ClawdDotNet\Logs";
OpenRouterStatus = "Entwurf";
OpenRouterStatus = "Design";
}
LoadAssemblyBuildInfos();
@@ -28,10 +28,23 @@ public sealed class InstanceChoiceViewModel(InstanceInfo info, bool isCurrent)
? Info.InstanceName[..2].ToUpperInvariant()
: Info.InstanceName.ToUpperInvariant();
public string Summary => $"{Info.AgentCount} Agenten · API-Key {Info.ApiKeyStatus.ToLowerInvariant()}";
public string Summary
{
get
{
var keyStatus = IsReady
? LocalizationManager.Instance["desktop.instancePicker.apiKeyConfigured"]
: LocalizationManager.Instance["desktop.instancePicker.apiKeyMissing"];
return string.Format(
LocalizationManager.Instance["desktop.instancePicker.agentsCount"],
Info.AgentCount,
keyStatus);
}
}
/// <summary>Ohne API-Key kann die Instanz keine Agenten starten.</summary>
public bool IsReady => Info.ApiKeyStatus.Equals("Konfiguriert", StringComparison.OrdinalIgnoreCase);
public bool IsReady => Info.ApiKeyStatus.Equals("Konfiguriert", StringComparison.OrdinalIgnoreCase)
|| Info.ApiKeyStatus.Equals("Configured", StringComparison.OrdinalIgnoreCase);
/// <summary>Die gerade laufende Instanz — traegt im Entwurf das Etikett „Aktiv".</summary>
public bool IsCurrent { get; } = isCurrent;
@@ -75,16 +88,19 @@ public sealed partial class InstancePickerViewModel : ViewModelBase
/// </summary>
public bool IsSwitchMode => _currentInstancePath is not null;
public string ConfirmLabel => IsSwitchMode ? "Wechseln" : "Starten";
public string ConfirmLabel => IsSwitchMode
? LocalizationManager.Instance["desktop.instancePicker.confirmSwitch"]
: LocalizationManager.Instance["desktop.instancePicker.confirmStart"];
/// <summary>Ueberschrift des Dialogs — nicht aus ConfirmLabel zusammengesetzt,
/// sonst stuende dort „Instanz Starten".</summary>
public string Title => IsSwitchMode ? "Instanz wechseln" : "Instanz wählen";
public string Title => IsSwitchMode
? LocalizationManager.Instance["desktop.instancePicker.titleSwitch"]
: LocalizationManager.Instance["desktop.instancePicker.titleSelect"];
public string Hint => IsSwitchMode
? "Jede Instanz läuft unabhängig mit eigenen Agenten und Daten. "
+ "Wechseln startet ClawdDotNet mit der gewählten Instanz neu."
: "Jede Instanz läuft unabhängig mit eigenen Agenten und Daten.";
? LocalizationManager.Instance["desktop.instancePicker.hintSwitch"]
: LocalizationManager.Instance["desktop.instancePicker.hintSelect"];
/// <param name="currentInstancePath">
/// Pfad der laufenden Instanz, oder <c>null</c> beim Start. Steuert Beschriftung und
@@ -141,7 +157,7 @@ public sealed partial class InstancePickerViewModel : ViewModelBase
if (string.IsNullOrWhiteSpace(name))
{
ErrorText = "Bitte einen Instanznamen eingeben.";
ErrorText = LocalizationManager.Instance["desktop.instancePicker.nameRequired"];
return;
}
@@ -159,7 +175,9 @@ public sealed partial class InstancePickerViewModel : ViewModelBase
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException
or ArgumentException)
{
ErrorText = $"Instanz konnte nicht angelegt werden: {ex.Message}";
ErrorText = string.Format(
LocalizationManager.Instance["desktop.instancePicker.createFailed"],
ex.Message);
}
}
@@ -33,15 +33,15 @@ public sealed partial class LogPageViewModel : PageViewModel
public ObservableCollection<LogLine> Lines { get; } = [];
public ObservableCollection<string> Modules { get; } = ["Alle"];
public ObservableCollection<string> Modules { get; } = [LocalizationManager.Instance["common.all"]];
public IReadOnlyList<string> Levels { get; } = ["Alle", "Info", "Warn", "Error"];
public IReadOnlyList<string> Levels { get; } = [LocalizationManager.Instance["common.all"], "Info", "Warn", "Error"];
[ObservableProperty]
private string _selectedModule = "Alle";
private string _selectedModule = LocalizationManager.Instance["common.all"];
[ObservableProperty]
private string _selectedLevel = "Alle";
private string _selectedLevel = LocalizationManager.Instance["common.all"];
/// <summary>
/// Hängt die Ansicht am unteren Ende? Sobald jemand nach oben scrollt, soll nicht
@@ -50,7 +50,7 @@ public sealed partial class LogPageViewModel : PageViewModel
[ObservableProperty]
private bool _followTail = true;
public LogPageViewModel(AppHost? host) : base("Logs")
public LogPageViewModel(AppHost? host) : base("nav.logs")
{
_logDirectory = host?.LogDirectory ?? "";
@@ -68,7 +68,10 @@ public sealed partial class LogPageViewModel : PageViewModel
{
if (_tail is null) return;
var module = SelectedModule == "Alle" ? null : SelectedModule;
var isAllModule = string.Equals(SelectedModule, LocalizationManager.Instance["common.all"], StringComparison.OrdinalIgnoreCase) ||
string.Equals(SelectedModule, "All", StringComparison.OrdinalIgnoreCase) ||
string.Equals(SelectedModule, "Alle", StringComparison.OrdinalIgnoreCase);
var module = isAllModule ? null : SelectedModule;
var level = SelectedLevel switch
{
"Info" => LogLevelFilter.Info,
@@ -96,15 +99,16 @@ public sealed partial class LogPageViewModel : PageViewModel
if (_tail is null) return;
var current = SelectedModule;
var allText = LocalizationManager.Instance["common.all"];
Modules.Clear();
Modules.Add("Alle");
Modules.Add(allText);
foreach (var module in _tail.AvailableModules())
Modules.Add(module);
// Auswahl halten, wenn es das Modul noch gibt.
SelectedModule = Modules.Contains(current) ? current : "Alle";
SelectedModule = Modules.Contains(current) ? current : allText;
}
[RelayCommand]
@@ -46,14 +46,19 @@ public sealed partial class MainWindowViewModel : ViewModelBase
// ─── Statusleiste ───
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(InstanceStatusLabel))]
private string _instanceName = "—";
[ObservableProperty]
private bool _isApiReachable;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ApiStatusDisplay))]
private string _apiStatusText = "unbekannt";
public string InstanceStatusLabel => LocalizationManager.Instance.Get("desktop.mainWindow.instanceLabel", InstanceName);
public string ApiStatusDisplay => LocalizationManager.Instance.Get("desktop.mainWindow.apiStatus", ApiStatusText);
/// <summary>Kurzinfo zu Tokens und Kosten der letzten Stunde bzw. 24 Stunden.</summary>
[ObservableProperty]
private string _usageText = "—";
@@ -86,7 +91,9 @@ public sealed partial class MainWindowViewModel : ViewModelBase
public string PageTitle => SelectedPage?.Title ?? "";
public string ThemeLabel => IsDarkTheme ? "Dunkelmodus" : "Hellmodus";
public string ThemeLabel => IsDarkTheme
? LocalizationManager.Instance["desktop.mainWindow.themeDark"]
: LocalizationManager.Instance["desktop.mainWindow.themeLight"];
/// <summary>Fusszeile der Seitenleiste.</summary>
public string VersionText => $"ClawdDotNet {AppHost.AppVersion} · Avalonia";
@@ -128,20 +135,20 @@ public sealed partial class MainWindowViewModel : ViewModelBase
{ chat, agents, tasks, tokens, agentChats, backup, logs, settings, info })
Pages.Add(page);
NavGroups.Add(new NavGroupViewModel("Arbeiten",
NavGroups.Add(new NavGroupViewModel("desktop.nav.work",
[
new NavItemViewModel(this, chat, "IconChat"),
new NavItemViewModel(this, agents, "IconAgents"),
new NavItemViewModel(this, tasks, "IconTasks")
]));
NavGroups.Add(new NavGroupViewModel("Analyse",
NavGroups.Add(new NavGroupViewModel("desktop.nav.analytics",
[
new NavItemViewModel(this, tokens, "IconChart"),
new NavItemViewModel(this, agentChats, "IconAgentChats")
]));
NavGroups.Add(new NavGroupViewModel("System",
NavGroups.Add(new NavGroupViewModel("desktop.nav.system",
[
new NavItemViewModel(this, backup, "IconBackup"),
new NavItemViewModel(this, logs, "IconLogs"),
@@ -149,6 +156,19 @@ public sealed partial class MainWindowViewModel : ViewModelBase
new NavItemViewModel(this, info, "IconInfo")
]));
LocalizationManager.Instance.LanguageChanged += () =>
{
OnPropertyChanged(nameof(PageTitle));
OnPropertyChanged(nameof(ThemeLabel));
OnPropertyChanged(nameof(InstanceStatusLabel));
OnPropertyChanged(nameof(ApiStatusDisplay));
if (_host is null)
{
InstanceName = LocalizationManager.Instance["desktop.status.design"];
ApiStatusText = LocalizationManager.Instance["desktop.status.design"];
}
};
// Die Leiste blendet sich aus, solange nichts anliegt — dafür muss die Ansicht
// von Zu- und Abgängen erfahren, nicht nur von der Sammlung selbst.
Notifications.CollectionChanged += (_, _) => OnPropertyChanged(nameof(HasNotifications));
@@ -191,8 +211,8 @@ public sealed partial class MainWindowViewModel : ViewModelBase
{
if (host.Status is not { } status)
{
ApiStatusText = "kein API-Key";
UsageText = "keine Nutzungsdaten";
ApiStatusText = LocalizationManager.Instance["desktop.status.noApiKey"];
UsageText = LocalizationManager.Instance["desktop.status.noUsageData"];
CreditsText = "";
return;
}
@@ -200,13 +220,15 @@ public sealed partial class MainWindowViewModel : ViewModelBase
void Update()
{
IsApiReachable = status.IsApiReachable;
ApiStatusText = status.IsApiReachable ? "OK" : "nicht erreichbar";
ApiStatusText = status.IsApiReachable
? LocalizationManager.Instance["desktop.status.reachable"]
: LocalizationManager.Instance["desktop.status.unreachable"];
UsageText = status.CreditsText;
UsageTooltip = status.CreditsTooltip;
CreditsText = status.CreditRemaining is { } remaining && status.CreditBalance is { } total
? $"Credits: ${remaining:F2} von ${total:F2}"
: "Credits: —";
? LocalizationManager.Instance.Get("desktop.mainWindow.credits", remaining, total)
: LocalizationManager.Instance["desktop.mainWindow.creditsNone"];
}
status.OnStatusUpdated += () => Dispatcher.UIThread.Post(Update);
@@ -294,15 +316,22 @@ public sealed partial class MainWindowViewModel : ViewModelBase
}
/// <summary>Eine Gruppe der Navigation („Arbeiten", „Analyse", „System").</summary>
public sealed partial class NavGroupViewModel(string title, IReadOnlyList<NavItemViewModel> items)
: ObservableObject
public sealed partial class NavGroupViewModel : ObservableObject
{
public string Title { get; } = title;
public IReadOnlyList<NavItemViewModel> Items { get; } = items;
private readonly string _titleKey;
public string Title => LocalizationManager.Instance.Get(_titleKey);
public IReadOnlyList<NavItemViewModel> Items { get; }
/// <summary>Im eingeklappten Zustand entfällt die Gruppenüberschrift.</summary>
[ObservableProperty]
private bool _isTitleVisible = true;
public NavGroupViewModel(string titleKey, IReadOnlyList<NavItemViewModel> items)
{
_titleKey = titleKey;
Items = items;
LocalizationManager.Instance.LanguageChanged += () => OnPropertyChanged(nameof(Title));
}
}
/// <summary>
@@ -313,14 +342,14 @@ public sealed partial class NavGroupViewModel(string title, IReadOnlyList<NavIte
/// zwar, ist aber bei übersetzten Bindungen ein Ausdruck mit Typumwandlung, den man beim
/// nächsten Lesen erst wieder entziffern muss.</para>
/// </summary>
public sealed partial class NavItemViewModel(
MainWindowViewModel owner, PageViewModel page, string iconKey) : ObservableObject
public sealed partial class NavItemViewModel : ObservableObject
{
public PageViewModel Page { get; } = page;
private readonly MainWindowViewModel _owner;
public PageViewModel Page { get; }
public string Title => Page.Title;
/// <summary>Name der Geometrie in <c>Styles/Icons.axaml</c>.</summary>
public string IconKey { get; } = iconKey;
public string IconKey { get; }
[ObservableProperty]
private bool _isSelected;
@@ -328,8 +357,16 @@ public sealed partial class NavItemViewModel(
[ObservableProperty]
private bool _isCollapsed;
public NavItemViewModel(MainWindowViewModel owner, PageViewModel page, string iconKey)
{
_owner = owner;
Page = page;
IconKey = iconKey;
LocalizationManager.Instance.LanguageChanged += () => OnPropertyChanged(nameof(Title));
}
[RelayCommand]
private void Select() => owner.SelectedPage = Page;
private void Select() => _owner.SelectedPage = Page;
}
/// <summary>Eine Zeile der Benachrichtigungsleiste.</summary>
@@ -346,7 +383,14 @@ public sealed partial class NotificationViewModel(
}
/// <summary>Ein Bereich des Hauptfensters.</summary>
public abstract partial class PageViewModel(string title) : ViewModelBase
public abstract partial class PageViewModel : ViewModelBase
{
public string Title { get; } = title;
private readonly string _titleKey;
public string Title => LocalizationManager.Instance.Get(_titleKey);
protected PageViewModel(string titleKey)
{
_titleKey = titleKey;
LocalizationManager.Instance.LanguageChanged += () => OnPropertyChanged(nameof(Title));
}
}
@@ -16,10 +16,15 @@ public sealed partial class RestoreBackupViewModel : ViewModelBase
public bool NeedsPassphrase => _manifest.HasSecrets;
public string SecretsDescription => _manifest.HasSecrets
? $"{_manifest.SecretCount}, mit Passphrase geschützt"
? $"{_manifest.SecretCount}, passphrase protected"
: _manifest.SecretCount > 0
? $"{_manifest.SecretCount} entfernt"
: "keine";
? $"{_manifest.SecretCount} excluded"
: "none";
public string InstanceInfoText => string.Format(L["desktop.dialog.backupInstance"], InstanceName);
public string CreatedAtText => string.Format(L["desktop.dialog.backupCreated"], CreatedAt);
public string FileCountText => string.Format(L["desktop.dialog.backupFiles"], FileCount);
public string SecretsText => string.Format(L["desktop.dialog.backupSecrets"], SecretsDescription);
[ObservableProperty]
private string _targetDirectory = "";
@@ -50,13 +55,13 @@ public sealed partial class RestoreBackupViewModel : ViewModelBase
{
if (string.IsNullOrWhiteSpace(TargetDirectory))
{
StatusMessage = "Bitte einen Zielordner angeben.";
StatusMessage = "Please specify a target directory.";
return;
}
if (NeedsPassphrase && string.IsNullOrEmpty(Passphrase))
{
StatusMessage = "Bitte Passphrase für geschützte Zugangsdaten eingeben.";
StatusMessage = "Please enter the passphrase for protected credentials.";
return;
}
@@ -3,6 +3,7 @@ using ClawdDotNet.App.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Logging;
using ClawdDotNet.Desktop.Services;
namespace ClawdDotNet.Desktop.ViewModels;
@@ -107,13 +108,32 @@ public sealed partial class SettingsPageViewModel : PageViewModel
[ObservableProperty]
private string _statusText = "Bereit";
[ObservableProperty]
private string _selectedLanguage = LocalizationManager.DefaultLanguage;
public IReadOnlyList<LanguageOption> AvailableLanguages => LocalizationManager.Instance.AvailableLanguages;
public LanguageOption? SelectedLanguageOption
{
get => AvailableLanguages.FirstOrDefault(l => l.Code.Equals(SelectedLanguage, StringComparison.OrdinalIgnoreCase)) ?? AvailableLanguages.FirstOrDefault();
set
{
if (value is not null && value.Code != SelectedLanguage)
{
SelectedLanguage = value.Code;
OnPropertyChanged();
LocalizationManager.Instance.SetLanguage(value.Code);
}
}
}
public List<string> LogLevels { get; } = ["Debug", "Info", "Warn", "Error"];
public List<string> UpdateChannels { get; } = ["prod", "beta", "dev"];
public List<string> Environments { get; } = ["production", "development", "staging"];
public SettingsPageViewModel() : this(null) { }
public SettingsPageViewModel(AppHost? host) : base("Einstellungen")
public SettingsPageViewModel(AppHost? host) : base("nav.settings")
{
_host = host;
@@ -124,7 +144,7 @@ public sealed partial class SettingsPageViewModel : PageViewModel
}
else
{
StatusText = "Entwurfsmodus";
StatusText = LocalizationManager.Instance["desktop.status.design"];
}
}
@@ -147,6 +167,8 @@ public sealed partial class SettingsPageViewModel : PageViewModel
UpdateCheckEnabled = s.UpdateCheckEnabled;
UpdateChannel = s.UpdateChannel;
LicenseKey = s.LicenseKey;
SelectedLanguage = s.Language ?? LocalizationManager.DefaultLanguage;
OnPropertyChanged(nameof(SelectedLanguageOption));
var inst = _host.Instance;
InstanceName = inst.InstanceName;
@@ -177,8 +199,10 @@ public sealed partial class SettingsPageViewModel : PageViewModel
s.UpdateCheckEnabled = UpdateCheckEnabled;
s.UpdateChannel = UpdateChannel;
s.LicenseKey = LicenseKey.Trim();
s.Language = SelectedLanguage;
_host.Settings.Save();
LocalizationManager.Instance.SetLanguage(SelectedLanguage);
var inst = _host.Instance;
inst.InstanceName = InstanceName.Trim();
@@ -190,13 +214,13 @@ public sealed partial class SettingsPageViewModel : PageViewModel
_host.SaveInstance(new InstanceChange(InstanceChangeKind.Settings));
_logger?.LogInformation("Einstellungen und Instanz-Konfiguration gespeichert.");
_logger?.LogInformation("Settings and instance configuration saved.");
// Das Protokoll-Verzeichnis greift erst beim naechsten Start: Die
// Protokollierung steht seit dem Aufbau des AppHost und schreibt bereits.
StatusText = string.Equals(inst.LogDirectory, _logDirectoryAtLoad, StringComparison.Ordinal)
? "Einstellungen erfolgreich gespeichert."
: "Gespeichert. Das Protokoll-Verzeichnis gilt ab dem nächsten Start.";
? LocalizationManager.Instance["desktop.settings.saved"]
: LocalizationManager.Instance["desktop.settings.savedLogRestart"];
_logDirectoryAtLoad = inst.LogDirectory;
}
@@ -86,7 +86,7 @@ public sealed partial class TasksPageViewModel : PageViewModel
public TasksPageViewModel() : this(null) { }
public TasksPageViewModel(AppHost? host) : base("Aufgaben")
public TasksPageViewModel(AppHost? host) : base("nav.tasks")
{
_host = host;
@@ -122,7 +122,7 @@ public sealed partial class TasksPageViewModel : PageViewModel
}
else
{
StatusText = "Entwurfsmodus";
StatusText = LocalizationManager.Instance["desktop.status.design"];
}
}
@@ -73,15 +73,15 @@ public sealed partial class TokenUsagePageViewModel : PageViewModel
public TokenUsagePageViewModel() : this(null) { }
public TokenUsagePageViewModel(AppHost? host) : base("Tokennutzung")
public TokenUsagePageViewModel(AppHost? host) : base("nav.usage")
{
_host = host;
if (host?.Usage is null)
{
StatusText = host is null
? "Entwurfsmodus"
: "Keine Verbrauchsdatenbank für diese Instanz.";
? "Design mode"
: "No usage database for this instance.";
return;
}
@@ -184,7 +184,7 @@ public sealed partial class TokenUsagePageViewModel : PageViewModel
TotalRuns = daily.Sum(d => d.RunCount).ToString("N0");
IsEmpty = tokens == 0 && runs.Count == 0;
StatusText = IsEmpty ? "In den letzten sieben Tagen ist kein Lauf verzeichnet." : "";
StatusText = IsEmpty ? LocalizationManager.Instance["desktop.usage.empty"] : "";
}
/// <summary>
@@ -1,3 +1,4 @@
using ClawdDotNet.Desktop.Services;
using CommunityToolkit.Mvvm.ComponentModel;
namespace ClawdDotNet.Desktop.ViewModels;
@@ -11,5 +12,11 @@ namespace ClawdDotNet.Desktop.ViewModels;
/// mit Benachrichtigung. Das spart gegenüber der WinForms-Fassung, in der die
/// Oberfläche bei jeder Änderung von Hand nachgezogen wurde
/// (38 Stellen mit <c>Invoke</c>/<c>BeginInvoke</c>).
///
/// <para><see cref="L"/> macht die Sprachpakete in jeder Ansicht als
/// <c>{Binding L[schluessel]}</c> erreichbar.</para>
/// </summary>
public abstract class ViewModelBase : ObservableObject;
public abstract class ViewModelBase : ObservableObject
{
public LocalizationManager L => LocalizationManager.Instance;
}
@@ -8,7 +8,7 @@
Padding="16"
WindowStartupLocation="CenterOwner"
CanResize="False"
Title="Neuen Agenten anlegen"
Title="{Binding L[desktop.dialog.addAgent]}"
x:Class="ClawdDotNet.Desktop.Views.AddAgentWindow"
x:DataType="vm:AddAgentViewModel">
@@ -16,8 +16,8 @@
<!-- Bottom Buttons -->
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8" Margin="0,16,0,0">
<Button Content="Abbrechen" Command="{Binding CancelCommand}" />
<Button Content="Anlegen" Command="{Binding ConfirmCommand}" Classes="accent" />
<Button Content="{Binding L[common.cancel]}" Command="{Binding CancelCommand}" />
<Button Content="{Binding L[common.create]}" Command="{Binding ConfirmCommand}" Classes="accent" />
</StackPanel>
<TextBlock DockPanel.Dock="Bottom" Text="{Binding StatusMessage}" Foreground="Red" Margin="0,8,0,0" />
@@ -25,23 +25,23 @@
<StackPanel Spacing="12">
<StackPanel Spacing="4">
<TextBlock Text="Agent-ID (eindeutiger Schlüssel):" FontWeight="Bold" />
<TextBlock Text="{Binding L[desktop.dialog.agentId]}" FontWeight="Bold" />
<TextBox Text="{Binding AgentId}" PlaceholderText="z.B. dev-assistant" />
</StackPanel>
<StackPanel Spacing="4">
<TextBlock Text="Anzeigename:" FontWeight="Bold" />
<TextBlock Text="{Binding L[desktop.dialog.agentDisplayName]}" FontWeight="Bold" />
<TextBox Text="{Binding DisplayName}" PlaceholderText="z.B. Dev Assistant" />
</StackPanel>
<StackPanel Spacing="4">
<TextBlock Text="Standard-Modell:" FontWeight="Bold" />
<TextBlock Text="{Binding L[desktop.dialog.defaultModel]}" FontWeight="Bold" />
<ComboBox ItemsSource="{Binding AvailableModels}" SelectedItem="{Binding SelectedModel}" HorizontalAlignment="Stretch" />
</StackPanel>
<StackPanel Spacing="4">
<TextBlock Text="Beschreibung / Rolle:" FontWeight="Bold" />
<TextBox Text="{Binding Description}" PlaceholderText="Kurze Beschreibung der Rolle..." AcceptsReturn="True" Height="60" />
<TextBlock Text="{Binding L[desktop.dialog.roleDescription]}" FontWeight="Bold" />
<TextBox Text="{Binding Description}" PlaceholderText="Role description..." AcceptsReturn="True" Height="60" />
</StackPanel>
</StackPanel>
@@ -8,7 +8,7 @@
Padding="16"
WindowStartupLocation="CenterOwner"
CanResize="False"
Title="Job hinzufügen / bearbeiten"
Title="{Binding L[desktop.dialog.addJobTitle]}"
x:Class="ClawdDotNet.Desktop.Views.AddJobWindow"
x:DataType="vm:AddJobViewModel">
@@ -16,8 +16,8 @@
<!-- Bottom Buttons -->
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8" Margin="0,16,0,0">
<Button Content="Abbrechen" Command="{Binding CancelCommand}" />
<Button Content="Speichern" Command="{Binding ConfirmCommand}" Classes="accent" />
<Button Content="{Binding L[common.cancel]}" Command="{Binding CancelCommand}" />
<Button Content="{Binding L[common.save]}" Command="{Binding ConfirmCommand}" Classes="accent" />
</StackPanel>
<TextBlock DockPanel.Dock="Bottom" Text="{Binding StatusMessage}" Foreground="Red" Margin="0,8,0,0" />
@@ -27,7 +27,7 @@
<!-- Agent Selection -->
<StackPanel Spacing="4">
<TextBlock Text="Agent:" FontWeight="Bold" />
<TextBlock Text="{Binding L[desktop.tasks.thAgent]}" FontWeight="Bold" />
<ComboBox ItemsSource="{Binding AvailableAgents}" SelectedItem="{Binding SelectedAgent}" HorizontalAlignment="Stretch">
<ComboBox.ItemTemplate>
<DataTemplate>
@@ -39,38 +39,38 @@
<!-- Job Type RadioButtons -->
<StackPanel Spacing="4" Margin="0,4,0,0">
<TextBlock Text="Job-Typ:" FontWeight="Bold" />
<RadioButton Content="Agent Wakeup (Periodische Ausführung)" IsChecked="{Binding IsAgentWakeup}" />
<RadioButton Content="Tool Job (Werkzeug-spezifischer Job)" IsChecked="{Binding IsToolJob}" />
<TextBlock Text="{Binding L[desktop.dialog.jobType]}" FontWeight="Bold" />
<RadioButton Content="{Binding L[desktop.dialog.agentWakeup]}" IsChecked="{Binding IsAgentWakeup}" />
<RadioButton Content="{Binding L[desktop.dialog.toolJob]}" IsChecked="{Binding IsToolJob}" />
</StackPanel>
<!-- Tool Job Details -->
<StackPanel Spacing="8" IsVisible="{Binding IsToolJob}" Margin="12,4,0,0">
<StackPanel Spacing="4">
<TextBlock Text="Tool:" FontWeight="Bold" />
<TextBlock Text="{Binding L[desktop.agents.thTool]}" FontWeight="Bold" />
<ComboBox ItemsSource="{Binding AvailableTools}" SelectedItem="{Binding SelectedToolName}" HorizontalAlignment="Stretch" />
</StackPanel>
<StackPanel Spacing="4">
<TextBlock Text="Job-Typ-ID / Name:" FontWeight="Bold" />
<TextBlock Text="{Binding L[desktop.dialog.jobTypeId]}" FontWeight="Bold" />
<TextBox Text="{Binding JobTypeId}" PlaceholderText="z.B. PollMail" />
</StackPanel>
</StackPanel>
<!-- Agent Wakeup Details -->
<StackPanel Spacing="4" IsVisible="{Binding IsAgentWakeup}">
<TextBlock Text="Aufgabenbeschreibung / Nachricht:" FontWeight="Bold" />
<TextBox Text="{Binding TaskMessage}" PlaceholderText="Nachricht an den Agenten..." AcceptsReturn="True" Height="60" />
<TextBlock Text="{Binding L[desktop.dialog.taskMessage]}" FontWeight="Bold" />
<TextBox Text="{Binding TaskMessage}" PlaceholderText="Message to agent..." AcceptsReturn="True" Height="60" />
</StackPanel>
<!-- Cron & Options -->
<StackPanel Spacing="4">
<TextBlock Text="Cron-Ausdruck:" FontWeight="Bold" />
<TextBlock Text="{Binding L[desktop.dialog.cronExpression]}" FontWeight="Bold" />
<TextBox Text="{Binding CronExpression}" PlaceholderText="0 * * * *" />
<TextBlock Text="Syntax: Min Std Tag Mon Wochentag (z.B. '*/15 * * * *' alle 15 Minuten)" Classes="caption" />
<TextBlock Text="{Binding L[desktop.dialog.cronHint]}" Classes="caption" />
</StackPanel>
<CheckBox Content="Beim Anwendungsstart einmalig sofort ausführen" IsChecked="{Binding RunOnStart}" Margin="0,4,0,0" />
<CheckBox Content="{Binding L[desktop.dialog.runOnStart]}" IsChecked="{Binding RunOnStart}" Margin="0,4,0,0" />
</StackPanel>
</ScrollViewer>
@@ -8,7 +8,7 @@
Padding="16"
WindowStartupLocation="CenterOwner"
CanResize="False"
Title="Dienst (Service) hinzufügen"
Title="{Binding L[desktop.dialog.addServiceTitle]}"
x:Class="ClawdDotNet.Desktop.Views.AddServiceWindow"
x:DataType="vm:AddServiceViewModel">
@@ -16,8 +16,8 @@
<!-- Bottom Buttons -->
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8" Margin="0,16,0,0">
<Button Content="Abbrechen" Command="{Binding CancelCommand}" />
<Button Content="Hinzufügen" Command="{Binding ConfirmCommand}" Classes="accent" />
<Button Content="{Binding L[common.cancel]}" Command="{Binding CancelCommand}" />
<Button Content="{Binding L[common.add]}" Command="{Binding ConfirmCommand}" Classes="accent" />
</StackPanel>
<TextBlock DockPanel.Dock="Bottom" Text="{Binding StatusMessage}" Foreground="Red" Margin="0,8,0,0" />
@@ -25,25 +25,25 @@
<StackPanel Spacing="12">
<StackPanel Spacing="4">
<TextBlock Text="Name des Dienstes:" FontWeight="Bold" />
<TextBlock Text="{Binding L[desktop.dialog.serviceName]}" FontWeight="Bold" />
<TextBox Text="{Binding ServiceName}" PlaceholderText="z.B. WebHookService" />
</StackPanel>
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,Auto">
<StackPanel Grid.Column="0" Spacing="4" Margin="0,0,4,0">
<TextBlock Text="Typ:" FontWeight="Bold" />
<TextBlock Text="{Binding L[desktop.dialog.serviceType]}" FontWeight="Bold" />
<ComboBox ItemsSource="{Binding AvailableServiceTypes}" SelectedItem="{Binding ServiceType}" HorizontalAlignment="Stretch" />
</StackPanel>
<StackPanel Grid.Column="1" Spacing="4" Margin="4,0,0,0">
<TextBlock Text="Port:" FontWeight="Bold" />
<TextBlock Text="{Binding L[desktop.dialog.servicePort]}" FontWeight="Bold" />
<NumericUpDown Value="{Binding ServicePort}" Minimum="1" Maximum="65535" FormatString="0" />
</StackPanel>
</Grid>
<StackPanel Spacing="4">
<TextBlock Text="Beschreibung:" FontWeight="Bold" />
<TextBox Text="{Binding ServiceDescription}" PlaceholderText="Optionale Beschreibung..." AcceptsReturn="True" Height="60" />
<TextBlock Text="{Binding L[desktop.dialog.serviceDesc]}" FontWeight="Bold" />
<TextBox Text="{Binding ServiceDescription}" PlaceholderText="Service description..." AcceptsReturn="True" Height="60" />
</StackPanel>
</StackPanel>
@@ -13,10 +13,10 @@
<!-- Wer mit wem -->
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="10"
Margin="0,0,0,16" VerticalAlignment="Center">
<TextBlock Text="Agent A:" FontWeight="SemiBold" VerticalAlignment="Center" />
<TextBlock Text="{Binding L[desktop.agentChats.agentA]}" FontWeight="SemiBold" VerticalAlignment="Center" />
<ComboBox Width="220" ItemsSource="{Binding Agents}" SelectedItem="{Binding AgentA}" />
<TextBlock Text="Agent B:" FontWeight="SemiBold" VerticalAlignment="Center"
<TextBlock Text="{Binding L[desktop.agentChats.agentB]}" FontWeight="SemiBold" VerticalAlignment="Center"
Margin="8,0,0,0" />
<ComboBox Width="220" ItemsSource="{Binding Agents}" SelectedItem="{Binding AgentB}" />
</StackPanel>
@@ -34,7 +34,7 @@
<Button Classes="toolbar" Command="{Binding AddAgentCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPlus}" Width="14" Height="14" />
<TextBlock Text="Agent" />
<TextBlock Text="{Binding L[desktop.agents.addAgent]}" />
</StackPanel>
</Button>
@@ -42,7 +42,7 @@
IsEnabled="{Binding SelectedAgent, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconMinus}" Width="14" Height="14" />
<TextBlock Text="Agent" />
<TextBlock Text="{Binding L[desktop.agents.removeAgent]}" />
</StackPanel>
</Button>
@@ -52,7 +52,7 @@
IsEnabled="{Binding !IsRunning}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPlay}" Width="12" Height="12" IsFilled="True" />
<TextBlock Text="Jetzt ausf." />
<TextBlock Text="{Binding L[desktop.agents.runNow]}" />
</StackPanel>
</Button>
@@ -62,7 +62,7 @@
IsEnabled="{Binding SelectedAgent, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPencil}" Width="14" Height="14" />
<TextBlock Text="Identity" />
<TextBlock Text="{Binding L[desktop.agents.identity]}" />
</StackPanel>
</Button>
@@ -70,7 +70,7 @@
IsEnabled="{Binding SelectedAgent, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPencil}" Width="14" Height="14" />
<TextBlock Text="Soul" />
<TextBlock Text="{Binding L[desktop.agents.soul]}" />
</StackPanel>
</Button>
@@ -80,23 +80,23 @@
IsEnabled="{Binding SelectedAgent, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconFolder}" Width="14" Height="14" />
<TextBlock Text="Ordner" />
<TextBlock Text="{Binding L[desktop.agents.folder]}" />
</StackPanel>
</Button>
<Button Classes="toolbar" Command="{Binding SaveAgentsCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconSave}" Width="14" Height="14" />
<TextBlock Text="Speichern" />
<TextBlock Text="{Binding L[desktop.agents.save]}" />
</StackPanel>
</Button>
</WrapPanel>
<Border DockPanel.Dock="Top" Classes="thead">
<Grid ColumnDefinitions="1.4*,1.6*,2*">
<TextBlock Grid.Column="0" Classes="th" Text="ANZEIGENAME" />
<TextBlock Grid.Column="1" Classes="th" Text="MODELL" />
<TextBlock Grid.Column="2" Classes="th" Text="KURZINFO" />
<TextBlock Grid.Column="0" Classes="th" Text="{Binding L[desktop.agents.thDisplayName]}" />
<TextBlock Grid.Column="1" Classes="th" Text="{Binding L[desktop.agents.thModel]}" />
<TextBlock Grid.Column="2" Classes="th" Text="{Binding L[desktop.agents.thSummary]}" />
</Grid>
</Border>
@@ -128,29 +128,29 @@
<StackPanel Spacing="20" MaxWidth="640" HorizontalAlignment="Left">
<StackPanel Spacing="12">
<TextBlock Classes="kicker" Text="ALLGEMEIN &amp; MODELL" />
<TextBlock Classes="kicker" Text="{Binding L[desktop.agents.kickerGeneralModel]}" />
<TextBlock Classes="h2" Text="{Binding DisplayName}" />
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Agent-ID" />
<TextBlock Classes="label" Text="{Binding L[desktop.agents.agentId]}" />
<TextBox Text="{Binding AgentId}" IsReadOnly="True" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Anzeigename" />
<TextBlock Classes="label" Text="{Binding L[desktop.agents.displayName]}" />
<TextBox Text="{Binding DisplayName}" />
</StackPanel>
</Grid>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Modell" />
<TextBlock Classes="label" Text="{Binding L[desktop.agents.model]}" />
<ComboBox HorizontalAlignment="Stretch"
ItemsSource="{Binding $parent[UserControl].((vm:AgentsPageViewModel)DataContext).KnownModels}"
SelectedItem="{Binding Model}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Prompt Caching" />
<TextBlock Classes="label" Text="{Binding L[desktop.agents.promptCaching]}" />
<ComboBox HorizontalAlignment="Stretch"
ItemsSource="{Binding $parent[UserControl].((vm:AgentsPageViewModel)DataContext).CachingOptions}"
SelectedItem="{Binding PromptCaching}" />
@@ -158,13 +158,13 @@
</Grid>
<StackPanel>
<TextBlock Classes="label" Text="Rolle / Beschreibung" />
<TextBlock Classes="label" Text="{Binding L[desktop.agents.roleDescription]}" />
<TextBox Text="{Binding Description}" AcceptsReturn="True" Height="54"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="System-Prompt (zusätzliche Anweisungen)" />
<TextBlock Classes="label" Text="{Binding L[desktop.agents.systemPrompt]}" />
<TextBox Text="{Binding SystemPrompt}" AcceptsReturn="True" Height="70"
TextWrapping="Wrap" />
</StackPanel>
@@ -173,38 +173,38 @@
<Border Classes="hr" />
<StackPanel Spacing="12">
<TextBlock Classes="kicker" Text="GRENZEN" />
<TextBlock Classes="h2" Text="Schutzgrenzen (LoopGuard &amp; Budget)" />
<TextBlock Classes="kicker" Text="{Binding L[desktop.agents.kickerLimits]}" />
<TextBlock Classes="h2" Text="{Binding L[desktop.agents.limitsTitle]}" />
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Max. Schritte pro Run" />
<TextBlock Classes="label" Text="{Binding L[desktop.agents.maxSteps]}" />
<TextBox Text="{Binding MaxSteps}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Timeout (Sekunden)" />
<TextBlock Classes="label" Text="{Binding L[desktop.agents.timeoutSeconds]}" />
<TextBox Text="{Binding TimeoutSeconds}" />
</StackPanel>
</Grid>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Max. Kontext-Tokens" />
<TextBlock Classes="label" Text="{Binding L[desktop.agents.maxContextTokens]}" />
<TextBox Text="{Binding MaxContextTokens}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Max. Tool-Ergebnis (Zeichen)" />
<TextBlock Classes="label" Text="{Binding L[desktop.agents.maxToolResultChars]}" />
<TextBox Text="{Binding MaxToolResultChars}" />
</StackPanel>
</Grid>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Tagesbudget ($ USD)" />
<TextBlock Classes="label" Text="{Binding L[desktop.agents.dailyCost]}" />
<TextBox Text="{Binding DailyCostUsd}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Tagesbudget (Tokens)" />
<TextBlock Classes="label" Text="{Binding L[desktop.agents.dailyTokens]}" />
<TextBox Text="{Binding DailyTokens}" />
</StackPanel>
</Grid>
@@ -225,13 +225,12 @@
<DockPanel Grid.Column="0" Margin="0,0,14,0">
<TextBlock DockPanel.Dock="Top" Classes="kicker" Margin="0,4,0,8"
Text="{Binding SelectedAgent.DisplayName,
StringFormat='ZUGELASSENE WERKZEUGE FÜR {0}'}" />
Text="{Binding AllowedToolsHeader}" />
<Border DockPanel.Dock="Top" Classes="thead">
<Grid ColumnDefinitions="36,1.4*,2.4*">
<TextBlock Grid.Column="1" Classes="th" Text="WERKZEUG" />
<TextBlock Grid.Column="2" Classes="th" Text="BESCHREIBUNG" />
<TextBlock Grid.Column="1" Classes="th" Text="{Binding L[desktop.agents.thTool]}" />
<TextBlock Grid.Column="2" Classes="th" Text="{Binding L[desktop.agents.thDescription]}" />
</Grid>
</Border>
@@ -258,7 +257,7 @@
<DockPanel Grid.Column="2" Margin="4,0,0,0">
<StackPanel DockPanel.Dock="Top" Spacing="2" Margin="0,4,0,10">
<TextBlock Classes="kicker" Text="WERKZEUG-EINSTELLUNGEN" />
<TextBlock Classes="kicker" Text="{Binding L[desktop.agents.toolSettingsKicker]}" />
<DockPanel>
<TextBlock Classes="h2" VerticalAlignment="Center"
Text="{Binding SelectedTool.Name}" />
@@ -267,7 +266,7 @@
IsEnabled="{Binding ToolSettings, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconSave}" Width="14" Height="14" />
<TextBlock Text="Übernehmen" />
<TextBlock Text="{Binding L[desktop.agents.apply]}" />
</StackPanel>
</Button>
</DockPanel>
@@ -18,41 +18,41 @@
<!-- ══════════════════ Manuell ══════════════════ -->
<StackPanel Spacing="12">
<TextBlock Classes="kicker" Text="MANUELL" />
<TextBlock Classes="h2" Text="Sicherung erstellen" />
<TextBlock Classes="kicker" Text="{Binding L[desktop.backup.kickerManual]}" />
<TextBlock Classes="h2" Text="{Binding L[desktop.backup.titleCreate]}" />
<Grid ColumnDefinitions="*,Auto" VerticalAlignment="Bottom">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Zielordner" />
<TextBlock Classes="label" Text="{Binding L[desktop.backup.targetFolder]}" />
<TextBox Text="{Binding TargetFolder}"
PlaceholderText="Zielordner für Sicherungen…" />
PlaceholderText="{Binding L[desktop.backup.targetFolderPlaceholder]}" />
</StackPanel>
<Button Grid.Column="1" Content="Durchsuchen…" Click="OnBrowseTargetFolder"
<Button Grid.Column="1" Content="{Binding L[desktop.backup.browse]}" Click="OnBrowseTargetFolder"
Margin="8,0,0,0" VerticalAlignment="Bottom" />
</Grid>
<StackPanel Spacing="6">
<TextBlock Classes="label" Text="Zugangsdaten" />
<TextBlock Classes="label" Text="{Binding L[desktop.backup.credentials]}" />
<StackPanel Orientation="Horizontal" Spacing="18">
<RadioButton GroupName="secrets" Content="nicht mitsichern"
<RadioButton GroupName="secrets" Content="{Binding L[desktop.backup.credentialsExclude]}"
IsChecked="{Binding SecretsExclude}" />
<RadioButton GroupName="secrets" Content="mit Passphrase schützen"
<RadioButton GroupName="secrets" Content="{Binding L[desktop.backup.credentialsProtect]}"
IsChecked="{Binding SecretsPassphrase}" />
</StackPanel>
</StackPanel>
<StackPanel Spacing="8" Margin="24,0,0,0" IsVisible="{Binding SecretsPassphrase}">
<TextBox Text="{Binding Passphrase}" PasswordChar="•" Width="320"
HorizontalAlignment="Left" PlaceholderText="Passphrase eingeben" />
HorizontalAlignment="Left" PlaceholderText="{Binding L[desktop.backup.passphrase]}" />
<TextBox Text="{Binding PassphraseRepeat}" PasswordChar="•" Width="320"
HorizontalAlignment="Left" PlaceholderText="Passphrase wiederholen" />
HorizontalAlignment="Left" PlaceholderText="{Binding L[desktop.backup.passphraseRepeat]}" />
</StackPanel>
<StackPanel Spacing="6">
<TextBlock Classes="label" Text="Umfang" />
<TextBlock Classes="label" Text="{Binding L[desktop.backup.scope]}" />
<StackPanel Orientation="Horizontal" Spacing="18">
<CheckBox Content="Chatverläufe" IsChecked="{Binding IncludeChatHistory}" />
<CheckBox Content="Protokolle" IsChecked="{Binding IncludeLogs}" />
<CheckBox Content="{Binding L[desktop.backup.scopeChat]}" IsChecked="{Binding IncludeChatHistory}" />
<CheckBox Content="{Binding L[desktop.backup.scopeLogs]}" IsChecked="{Binding IncludeLogs}" />
</StackPanel>
</StackPanel>
@@ -60,7 +60,7 @@
Command="{Binding CreateBackupCommand}" IsEnabled="{Binding !IsBusy}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPlay}" Width="12" Height="12" IsFilled="True" />
<TextBlock Text="Sicherung jetzt erstellen" />
<TextBlock Text="{Binding L[desktop.backup.createNow]}" />
</StackPanel>
</Button>
</StackPanel>
@@ -69,36 +69,36 @@
<!-- ══════════════════ Automatik ══════════════════ -->
<StackPanel Spacing="10">
<TextBlock Classes="kicker" Text="AUTOMATIK" />
<TextBlock Classes="h2" Text="Automatische Sicherung" />
<TextBlock Classes="kicker" Text="{Binding L[desktop.backup.kickerAuto]}" />
<TextBlock Classes="h2" Text="{Binding L[desktop.backup.titleAuto]}" />
<CheckBox Content="täglich sichern um" IsChecked="{Binding AutoBackupEnabled}" />
<CheckBox Content="{Binding L[desktop.backup.autoDaily]}" IsChecked="{Binding AutoBackupEnabled}" />
<StackPanel Orientation="Horizontal" Spacing="24" Margin="24,0,0,0"
IsEnabled="{Binding AutoBackupEnabled}">
<StackPanel Width="100">
<TextBlock Classes="label" Text="Uhrzeit" />
<TextBlock Classes="label" Text="{Binding L[desktop.backup.time]}" />
<TextBox Text="{Binding AutoBackupTime}" PlaceholderText="03:00" />
</StackPanel>
<StackPanel Width="160">
<TextBlock Classes="label" Text="Aufbewahren" />
<TextBlock Classes="label" Text="{Binding L[desktop.backup.keep]}" />
<TextBox Text="{Binding KeepCount}" />
</StackPanel>
</StackPanel>
<TextBlock Classes="caption" Margin="24,0,0,0" TextWrapping="Wrap"
Text="Die Automatik sichert ohne Zugangsdaten — eine gespeicherte Passphrase wäre wirkungslos." />
Text="{Binding L[desktop.backup.autoHint]}" />
</StackPanel>
<Border Classes="hr" />
<!-- ══════════════════ Bestand ══════════════════ -->
<StackPanel Spacing="10">
<TextBlock Classes="kicker" Text="BESTAND" />
<TextBlock Classes="kicker" Text="{Binding L[desktop.backup.kickerExisting]}" />
<DockPanel>
<TextBlock Classes="h2" Text="Vorhandene Sicherungen" VerticalAlignment="Center" />
<TextBlock Classes="h2" Text="{Binding L[desktop.backup.titleExisting]}" VerticalAlignment="Center" />
<!-- Mindestabstand zur Ueberschrift links. Vorher stand hier Margin="0" und
der Zwischenraum entstand nur, wenn das Fenster breit genug war; bei
@@ -106,15 +106,15 @@
beruehrte die Schaltflaeche "Aktualisieren". -->
<WrapPanel DockPanel.Dock="Right" HorizontalAlignment="Right" Classes="toolbar"
Margin="16,0,0,0">
<Button Classes="toolbar" Content="Aktualisieren"
<Button Classes="toolbar" Content="{Binding L[desktop.backup.refresh]}"
Command="{Binding RefreshBackupListCommand}" />
<Button Classes="toolbar" Content="Ordner öffnen"
<Button Classes="toolbar" Content="{Binding L[desktop.backup.openFolder]}"
Command="{Binding ShowSelectedInFolderCommand}"
IsEnabled="{Binding SelectedBackup, Converter={x:Static ObjectConverters.IsNotNull}}" />
<Button Classes="toolbar" Content="Löschen"
<Button Classes="toolbar" Content="{Binding L[desktop.backup.delete]}"
Command="{Binding DeleteSelectedCommand}"
IsEnabled="{Binding SelectedBackup, Converter={x:Static ObjectConverters.IsNotNull}}" />
<Button Classes="toolbar primary" Content="Wiederherstellen…"
<Button Classes="toolbar primary" Content="{Binding L[desktop.backup.restore]}"
Command="{Binding RestoreSelectedCommand}"
IsEnabled="{Binding SelectedBackup, Converter={x:Static ObjectConverters.IsNotNull}}" />
</WrapPanel>
@@ -122,11 +122,11 @@
<Border Classes="thead">
<Grid ColumnDefinitions="2.6*,1.5*,1.3*,0.9*,1.3*">
<TextBlock Grid.Column="0" Classes="th" Text="DATEINAME" />
<TextBlock Grid.Column="1" Classes="th" Text="ERSTELLT AM" />
<TextBlock Grid.Column="2" Classes="th" Text="INSTANZ" />
<TextBlock Grid.Column="3" Classes="th" Text="GRÖSSE" />
<TextBlock Grid.Column="4" Classes="th" Text="ZUGANGSDATEN" />
<TextBlock Grid.Column="0" Classes="th" Text="{Binding L[desktop.backup.thFilename]}" />
<TextBlock Grid.Column="1" Classes="th" Text="{Binding L[desktop.backup.thCreatedAt]}" />
<TextBlock Grid.Column="2" Classes="th" Text="{Binding L[desktop.backup.thInstance]}" />
<TextBlock Grid.Column="3" Classes="th" Text="{Binding L[desktop.backup.thSize]}" />
<TextBlock Grid.Column="4" Classes="th" Text="{Binding L[desktop.backup.thSecrets]}" />
</Grid>
</Border>
@@ -13,7 +13,7 @@
<!-- Agentenauswahl und Verlaufsbefehle -->
<StackPanel DockPanel.Dock="Top" Classes="toolbar" Margin="0,0,0,16">
<TextBlock Text="Agent:" FontWeight="SemiBold" VerticalAlignment="Center" />
<TextBlock Text="{Binding L[desktop.chat.agent]}" FontWeight="SemiBold" VerticalAlignment="Center" />
<ComboBox ItemsSource="{Binding AvailableAgents}" SelectedItem="{Binding SelectedAgent}"
Width="220">
<ComboBox.ItemTemplate>
@@ -28,14 +28,14 @@
<Button Classes="toolbar" Command="{Binding ClearHistoryCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconTrash}" Width="14" Height="14" />
<TextBlock Text="Verlauf leeren" />
<TextBlock Text="{Binding L[desktop.chat.clearHistory]}" />
</StackPanel>
</Button>
<Button Classes="toolbar" Command="{Binding AbortCommand}" IsEnabled="{Binding IsSending}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconStop}" Width="12" Height="12" IsFilled="True" />
<TextBlock Text="Abbrechen" />
<TextBlock Text="{Binding L[desktop.chat.abort]}" />
</StackPanel>
</Button>
</StackPanel>
@@ -47,11 +47,11 @@
<!-- Eingabe -->
<Grid DockPanel.Dock="Bottom" ColumnDefinitions="*,Auto" Margin="0,12,0,0">
<TextBox Grid.Column="0" Text="{Binding InputPrompt}"
PlaceholderText="Nachricht eingeben… (Strg+Eingabe zum Senden)"
PlaceholderText="{Binding L[desktop.chat.inputPlaceholder]}"
AcceptsReturn="True" Height="60" TextWrapping="Wrap"
KeyDown="OnInputKeyDown" />
<Button Grid.Column="1" Classes="primary" Content="Senden" Width="100"
<Button Grid.Column="1" Classes="primary" Content="{Binding L[desktop.chat.send]}" Width="100"
Command="{Binding SendMessageCommand}" IsEnabled="{Binding !IsSending}"
VerticalAlignment="Stretch" Margin="8,0,0,0" />
</Grid>
@@ -25,14 +25,14 @@
<!-- Anwendung -->
<StackPanel Spacing="10">
<TextBlock Classes="kicker" Text="ANWENDUNG" />
<TextBlock Classes="h2" Text="Build" />
<TextBlock Classes="kicker" Text="{Binding L[desktop.info.kickerApp]}" />
<TextBlock Classes="h2" Text="{Binding L[desktop.info.build]}" />
<Grid ColumnDefinitions="170,*" RowDefinitions="Auto,Auto" >
<TextBlock Grid.Row="0" Grid.Column="0" Classes="label" Text="Version" />
<TextBlock Grid.Row="0" Grid.Column="0" Classes="label" Text="{Binding L[desktop.info.version]}" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding AppVersion}" />
<TextBlock Grid.Row="1" Grid.Column="0" Classes="label" Text="Build-Zusammenfassung"
<TextBlock Grid.Row="1" Grid.Column="0" Classes="label" Text="{Binding L[desktop.info.buildSummary]}"
Margin="0,6,0,0" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding BuildSummary}"
TextWrapping="Wrap" Margin="0,6,0,0" />
@@ -43,36 +43,36 @@
<!-- Instanz -->
<StackPanel Spacing="10">
<TextBlock Classes="kicker" Text="INSTANZ" />
<TextBlock Classes="kicker" Text="{Binding L[desktop.info.kickerInstance]}" />
<DockPanel>
<TextBlock Classes="h2" Text="Aktuelle Instanz" VerticalAlignment="Center" />
<TextBlock Classes="h2" Text="{Binding L[desktop.info.currentInstance]}" VerticalAlignment="Center" />
<Button DockPanel.Dock="Right" HorizontalAlignment="Right" Classes="toolbar"
Command="{Binding OpenInstanceFolderCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconFolder}" Width="14" Height="14" />
<TextBlock Text="Instanz-Ordner öffnen" />
<TextBlock Text="{Binding L[desktop.info.openFolder]}" />
</StackPanel>
</Button>
</DockPanel>
<Grid ColumnDefinitions="170,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" Classes="label" Text="Name" />
<TextBlock Grid.Row="0" Grid.Column="0" Classes="label" Text="{Binding L[desktop.info.name]}" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding InstanceName}" />
<TextBlock Grid.Row="1" Grid.Column="0" Classes="label" Text="ID" Margin="0,6,0,0" />
<TextBlock Grid.Row="1" Grid.Column="0" Classes="label" Text="{Binding L[desktop.info.id]}" Margin="0,6,0,0" />
<TextBlock Grid.Row="1" Grid.Column="1" Classes="mono" Text="{Binding InstanceId}"
Margin="0,6,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Classes="label" Text="Pfad" Margin="0,6,0,0" />
<TextBlock Grid.Row="2" Grid.Column="0" Classes="label" Text="{Binding L[desktop.info.path]}" Margin="0,6,0,0" />
<SelectableTextBlock Grid.Row="2" Grid.Column="1" Text="{Binding InstancePath}"
TextWrapping="Wrap" Margin="0,6,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Classes="label" Text="Log-Ordner" Margin="0,6,0,0" />
<TextBlock Grid.Row="3" Grid.Column="0" Classes="label" Text="{Binding L[desktop.info.logDir]}" Margin="0,6,0,0" />
<SelectableTextBlock Grid.Row="3" Grid.Column="1" Text="{Binding LogDirectory}"
TextWrapping="Wrap" Margin="0,6,0,0" />
<TextBlock Grid.Row="4" Grid.Column="0" Classes="label" Text="OpenRouter" Margin="0,6,0,0" />
<TextBlock Grid.Row="4" Grid.Column="0" Classes="label" Text="{Binding L[desktop.info.openRouter]}" Margin="0,6,0,0" />
<TextBlock Grid.Row="4" Grid.Column="1" Text="{Binding OpenRouterStatus}" Margin="0,6,0,0" />
</Grid>
</StackPanel>
@@ -81,8 +81,8 @@
<!-- Baugruppen -->
<StackPanel Spacing="10">
<TextBlock Classes="kicker" Text="MODULE" />
<TextBlock Classes="h2" Text="Baugruppen" />
<TextBlock Classes="kicker" Text="{Binding L[desktop.info.kickerModules]}" />
<TextBlock Classes="h2" Text="{Binding L[desktop.info.assemblies]}" />
<!-- Feste Breiten statt Sternanteilen fuer die drei schmalen Spalten.
Sternbreiten teilen den Platz erst auf, NACHDEM die Mindestbreite jedes
@@ -99,10 +99,10 @@
Grenzfall nicht aneinanderstossen. -->
<Border Classes="thead">
<Grid ColumnDefinitions="170,60,150,*" ClipToBounds="True">
<TextBlock Grid.Column="0" Classes="th" Text="MODUL" />
<TextBlock Grid.Column="1" Classes="th" Text="BUILD" />
<TextBlock Grid.Column="2" Classes="th" Text="BUILD-DATUM" />
<TextBlock Grid.Column="3" Classes="th" Text="ÄNDERUNGEN" Margin="10,0,0,0" />
<TextBlock Grid.Column="0" Classes="th" Text="{Binding L[desktop.info.thModule]}" />
<TextBlock Grid.Column="1" Classes="th" Text="{Binding L[desktop.info.thBuild]}" />
<TextBlock Grid.Column="2" Classes="th" Text="{Binding L[desktop.info.thBuildDate]}" />
<TextBlock Grid.Column="3" Classes="th" Text="{Binding L[desktop.info.thChanges]}" Margin="10,0,0,0" />
</Grid>
</Border>
@@ -36,12 +36,12 @@
<Border Classes="hr" />
<Grid ColumnDefinitions="*,Auto">
<TextBox Grid.Column="0" Text="{Binding NewInstanceName}"
PlaceholderText="Name der neuen Instanz" />
PlaceholderText="{Binding L[desktop.instancePicker.newInstancePlaceholder]}" />
<Button Grid.Column="1" Classes="primary" Margin="8,0,0,0"
Command="{Binding CreateCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPlus}" Width="14" Height="14" />
<TextBlock Text="Anlegen" />
<TextBlock Text="{Binding L[desktop.instancePicker.create]}" />
</StackPanel>
</Button>
</Grid>
@@ -50,7 +50,7 @@
<!-- Abschluss -->
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="8"
HorizontalAlignment="Right" Margin="0,14,0,0">
<Button Content="Abbrechen" Command="{Binding CancelCommand}" IsCancel="True" />
<Button Content="{Binding L[common.cancel]}" Command="{Binding CancelCommand}" IsCancel="True" />
<Button Classes="primary" Content="{Binding ConfirmLabel}" IsDefault="True"
Command="{Binding StartCommand}" />
</StackPanel>
@@ -84,13 +84,13 @@
Stroke="{DynamicResource SurfaceBrush}" StrokeThickness="2"
Fill="{DynamicResource SuccessBrush}"
IsVisible="{Binding IsReady}"
ToolTip.Tip="API-Key hinterlegt" />
ToolTip.Tip="{Binding $parent[ItemsControl].((vm:InstancePickerViewModel)DataContext).L[desktop.instancePicker.tooltipConfigured]}" />
<Ellipse Width="10" Height="10" HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Stroke="{DynamicResource SurfaceBrush}" StrokeThickness="2"
Fill="{DynamicResource Neutral500Brush}"
IsVisible="{Binding !IsReady}"
ToolTip.Tip="Kein API-Key — Agenten bleiben deaktiviert" />
ToolTip.Tip="{Binding $parent[ItemsControl].((vm:InstancePickerViewModel)DataContext).L[desktop.instancePicker.tooltipMissing]}" />
</Panel>
<Button DockPanel.Dock="Right" Classes="toolbar" VerticalAlignment="Center"
@@ -100,7 +100,7 @@
<Border DockPanel.Dock="Right" Classes="tag ok" Margin="0,0,8,0"
IsVisible="{Binding IsCurrent}">
<TextBlock Text="Aktiv" />
<TextBlock Text="{Binding $parent[ItemsControl].((vm:InstancePickerViewModel)DataContext).L[desktop.instancePicker.active]}" />
</Border>
<StackPanel VerticalAlignment="Center">
@@ -7,14 +7,14 @@
x:Class="ClawdDotNet.Desktop.Views.LicenseWindow"
x:DataType="vm:LicenseViewModel"
Icon="/Assets/app.ico"
Title="ClawdDotNet – Lizenz"
Title="{Binding L[desktop.dialog.licenseTitle]}"
Width="520" SizeToContent="Height"
CanResize="False"
WindowStartupLocation="CenterScreen">
<StackPanel Margin="20" Spacing="12">
<TextBlock Classes="heading" Text="Lizenzschlüssel" />
<TextBlock Classes="heading" Text="{Binding L[desktop.dialog.licenseKey]}" />
<!-- Nur sichtbar, wenn es einen Grund gibt -->
<Border Classes="card"
@@ -28,15 +28,15 @@
FontFamily="{DynamicResource ContentControlThemeFontFamily}" />
<StackPanel Spacing="4">
<TextBlock Classes="caption" Text="Hardware-ID (für den Support):" />
<TextBlock Classes="caption" Text="{Binding L[desktop.dialog.hardwareId]}" />
<SelectableTextBlock Classes="caption"
Text="{Binding HardwareId}"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="8" HorizontalAlignment="Right">
<Button Content="Beenden" Command="{Binding CancelCommand}" />
<Button Content="Prüfen" Classes="accent" IsDefault="True"
<Button Content="{Binding L[common.exit]}" Command="{Binding CancelCommand}" />
<Button Content="{Binding L[desktop.dialog.verify]}" Classes="accent" IsDefault="True"
Command="{Binding ConfirmCommand}" />
</StackPanel>
@@ -15,11 +15,11 @@
<!-- Werkzeugleiste -->
<StackPanel DockPanel.Dock="Top" Classes="toolbar">
<TextBlock Text="Modul:" FontSize="12.5" VerticalAlignment="Center" />
<TextBlock Text="{Binding L[desktop.logs.module]}" FontSize="12.5" VerticalAlignment="Center" />
<ComboBox ItemsSource="{Binding Modules}" SelectedItem="{Binding SelectedModule}"
Width="180" MinHeight="30" Padding="8,3" />
<TextBlock Text="Stufe:" FontSize="12.5" VerticalAlignment="Center" Margin="6,0,0,0" />
<TextBlock Text="{Binding L[desktop.logs.level]}" FontSize="12.5" VerticalAlignment="Center" Margin="6,0,0,0" />
<ComboBox ItemsSource="{Binding Levels}" SelectedItem="{Binding SelectedLevel}"
Width="120" MinHeight="30" Padding="8,3" />
@@ -28,25 +28,25 @@
<Button Classes="toolbar" Command="{Binding RefreshModulesCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconRefresh}" Width="14" Height="14" />
<TextBlock Text="Aktualisieren" />
<TextBlock Text="{Binding L[desktop.logs.refresh]}" />
</StackPanel>
</Button>
<Button Classes="toolbar" Command="{Binding ClearCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconTrash}" Width="14" Height="14" />
<TextBlock Text="Leeren" />
<TextBlock Text="{Binding L[desktop.logs.clear]}" />
</StackPanel>
</Button>
<Button Classes="toolbar" Command="{Binding OpenFolderCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconFolder}" Width="14" Height="14" />
<TextBlock Text="Ordner öffnen" />
<TextBlock Text="{Binding L[desktop.logs.openFolder]}" />
</StackPanel>
</Button>
<CheckBox Content="Mitlaufen" IsChecked="{Binding FollowTail}"
<CheckBox Content="{Binding L[desktop.logs.follow]}" IsChecked="{Binding FollowTail}"
VerticalAlignment="Center" Margin="8,0,0,0" />
</StackPanel>
@@ -33,7 +33,7 @@
<Button DockPanel.Dock="Right" Classes="flat"
Command="{Binding ToggleSidebarCommand}"
ToolTip.Tip="Seitenleiste ein-/ausklappen"
ToolTip.Tip="{Binding L[desktop.mainWindow.sidebarTooltip]}"
Padding="4" VerticalAlignment="Center">
<ctrl:StrokeIcon Width="14" Height="14"
Data="{Binding IsSidebarCollapsed,
@@ -60,7 +60,7 @@
<Button DockPanel.Dock="Top" Classes="instance" Margin="12,12,12,0"
Command="{Binding OpenInstanceManagerCommand}"
IsEnabled="{Binding CanSwitchInstance}"
ToolTip.Tip="Instanz wechseln">
ToolTip.Tip="{Binding L[desktop.mainWindow.switchInstance]}">
<DockPanel>
<ctrl:StrokeIcon Data="{StaticResource IconLogo}" Width="15" Height="15"
DockPanel.Dock="Left" VerticalAlignment="Center" />
@@ -70,7 +70,7 @@
IsVisible="{Binding IsSidebarExpanded}" />
<StackPanel Margin="8,0,0,0" IsVisible="{Binding IsSidebarExpanded}">
<TextBlock Text="INSTANZ" FontSize="9" LetterSpacing="1"
<TextBlock Text="{Binding L[desktop.mainWindow.instance]}" FontSize="9" LetterSpacing="1"
Foreground="{DynamicResource AccentBrush}" />
<TextBlock Classes="h2" FontSize="13" Text="{Binding InstanceName}"
TextTrimming="CharacterEllipsis" />
@@ -142,7 +142,7 @@
HorizontalAlignment="Right" VerticalAlignment="Center">
<TextBlock Classes="caption" Text="{Binding ThemeLabel}" VerticalAlignment="Center" />
<Button Classes="icon" Command="{Binding ToggleThemeCommand}"
ToolTip.Tip="Dunkelmodus umschalten">
ToolTip.Tip="{Binding L[desktop.mainWindow.themeToggle]}">
<ctrl:StrokeIcon Width="16" Height="16"
Data="{Binding ThemeIconKey,
Converter={x:Static conv:IconConverter.Instance}}" />
@@ -172,7 +172,7 @@
Margin="0,0,10,0" />
<Button DockPanel.Dock="Right" Classes="flat" Padding="2"
Command="{Binding DismissCommand}" ToolTip.Tip="Ausblenden"
Command="{Binding DismissCommand}" ToolTip.Tip="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).L[desktop.mainWindow.dismiss]}"
VerticalAlignment="Center">
<ctrl:StrokeIcon Data="{StaticResource IconClose}" Width="13" Height="13" />
</Button>
@@ -190,7 +190,7 @@
<Border DockPanel.Dock="Bottom" Classes="statusbar">
<DockPanel>
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Classes="caption" Text="{Binding InstanceName, StringFormat='Instanz: {0}'}" />
<TextBlock Classes="caption" Text="{Binding InstanceStatusLabel}" />
<TextBlock Classes="caption" Text="·" />
<StackPanel Orientation="Horizontal" Spacing="5" VerticalAlignment="Center">
@@ -200,7 +200,7 @@
<Ellipse Width="7" Height="7" VerticalAlignment="Center"
Fill="{DynamicResource DangerBrush}"
IsVisible="{Binding !IsApiReachable}" />
<TextBlock Classes="caption" Text="{Binding ApiStatusText, StringFormat='API {0}'}" />
<TextBlock Classes="caption" Text="{Binding ApiStatusDisplay}" />
</StackPanel>
<TextBlock Classes="caption" Text="·"
@@ -8,7 +8,7 @@
Padding="16"
WindowStartupLocation="CenterOwner"
CanResize="False"
Title="Sicherung wiederherstellen"
Title="{Binding L[desktop.dialog.restoreTitle]}"
x:Class="ClawdDotNet.Desktop.Views.RestoreBackupWindow"
x:DataType="vm:RestoreBackupViewModel">
@@ -16,8 +16,8 @@
<!-- Buttons unten -->
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8" Margin="0,16,0,0">
<Button Content="Abbrechen" Command="{Binding CancelCommand}" />
<Button Content="Wiederherstellen" Command="{Binding ConfirmCommand}" Classes="accent" />
<Button Content="{Binding L[common.cancel]}" Command="{Binding CancelCommand}" />
<Button Content="{Binding L[desktop.backup.restore]}" Command="{Binding ConfirmCommand}" Classes="accent" />
</StackPanel>
<!-- Statusmeldung unten -->
@@ -29,34 +29,34 @@
<!-- Information -->
<Border Classes="card">
<StackPanel Spacing="4">
<TextBlock Text="Sicherungsinformationen" Classes="heading" />
<TextBlock Text="{Binding InstanceName, StringFormat='Instanz: {0}'}" />
<TextBlock Text="{Binding CreatedAt, StringFormat='Erstellt: {0:g}'}" />
<TextBlock Text="{Binding FileCount, StringFormat='Dateien: {0}'}" />
<TextBlock Text="{Binding SecretsDescription, StringFormat='Zugangsdaten: {0}'}" />
<TextBlock Text="{Binding L[desktop.dialog.backupInfo]}" Classes="heading" />
<TextBlock Text="{Binding InstanceInfoText}" />
<TextBlock Text="{Binding CreatedAtText}" />
<TextBlock Text="{Binding FileCountText}" />
<TextBlock Text="{Binding SecretsText}" />
</StackPanel>
</Border>
<!-- Zielordner -->
<StackPanel Spacing="4">
<TextBlock Text="Zielordner:" FontWeight="Bold" />
<TextBlock Text="{Binding L[desktop.dialog.targetDir]}" FontWeight="Bold" />
<Grid ColumnDefinitions="*,Auto">
<TextBox Text="{Binding TargetDirectory}" Grid.Column="0" />
<Button Content="Ordner wählen" Click="OnBrowseFolder" Grid.Column="1" Margin="8,0,0,0" />
<Button Content="{Binding L[desktop.dialog.chooseFolder]}" Click="OnBrowseFolder" Grid.Column="1" Margin="8,0,0,0" />
</Grid>
<TextBlock Text="Hinweis: Es wird empfohlen, in einen neuen Ordner wiederherzustellen, um Konflikte mit laufenden Prozessen zu vermeiden."
<TextBlock Text="{Binding L[desktop.dialog.restoreHint]}"
Classes="caption" TextWrapping="Wrap" />
</StackPanel>
<!-- Passphrase -->
<StackPanel Spacing="4" IsVisible="{Binding NeedsPassphrase}">
<TextBlock Text="Passphrase:" FontWeight="Bold" />
<TextBlock Text="{Binding L[desktop.dialog.passphrase]}" FontWeight="Bold" />
<TextBox Text="{Binding Passphrase}" PasswordChar="*" />
<TextBlock Text="Diese Sicherung enthält geschützte Zugangsdaten." Classes="caption" />
<TextBlock Text="{Binding L[desktop.dialog.passphraseProtected]}" Classes="caption" />
</StackPanel>
<!-- Überschreiben -->
<CheckBox Content="Vorhandene Dateien im Zielordner überschreiben" IsChecked="{Binding Overwrite}" Margin="0,8,0,0" />
<CheckBox Content="{Binding L[desktop.dialog.overwriteExisting]}" IsChecked="{Binding Overwrite}" Margin="0,8,0,0" />
</StackPanel>
</ScrollViewer>
@@ -15,11 +15,11 @@
<Border DockPanel.Dock="Bottom" BorderBrush="{DynamicResource DividerBrush}"
BorderThickness="0,1,0,0" Padding="0,12,0,0" Margin="0,16,0,0">
<StackPanel Orientation="Horizontal" Spacing="8" HorizontalAlignment="Right">
<Button Content="Verwerfen" Command="{Binding LoadFromHostCommand}" />
<Button Content="{Binding L[desktop.settings.discard]}" Command="{Binding LoadFromHostCommand}" />
<Button Classes="primary" Command="{Binding SaveSettingsCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconSave}" Width="14" Height="14" />
<TextBlock Text="Speichern" />
<TextBlock Text="{Binding L[desktop.settings.save]}" />
</StackPanel>
</Button>
</StackPanel>
@@ -30,39 +30,39 @@
<!-- ══════════════════ Instanz ══════════════════ -->
<StackPanel Spacing="14">
<TextBlock Classes="kicker" Text="INSTANZ" />
<TextBlock Classes="kicker" Text="{Binding L[desktop.settings.instance]}" />
<StackPanel>
<TextBlock Classes="label" Text="Instanz-Name" />
<TextBlock Classes="label" Text="{Binding L[desktop.settings.instanceName]}" />
<TextBox Text="{Binding InstanceName}" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="OpenRouter API-Key" />
<TextBlock Classes="label" Text="{Binding L[desktop.settings.apiKey]}" />
<TextBox Text="{Binding OpenRouterApiKey}" PasswordChar="•" />
</StackPanel>
<StackPanel>
<CheckBox IsChecked="{Binding AutostartByManager}"
Content="Autostart durch Manager" />
Content="{Binding L[desktop.settings.autostart]}" />
<TextBlock Classes="caption" TextWrapping="Wrap" Margin="0,4,0,0"
Text="Der Instanzmanager startet diese Instanz, wenn er hochkommt, und wieder, wenn sie abstürzt. Nach mehreren Neustarts in kurzer Folge setzt er aus und meldet es." />
Text="{Binding L[desktop.settings.autostartHint]}" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Protokoll-Verzeichnis" />
<TextBlock Classes="label" Text="{Binding L[desktop.settings.logDir]}" />
<TextBox Text="{Binding LogDirectory}" />
<TextBlock Classes="caption" TextWrapping="Wrap" Margin="0,4,0,0"
Text="Gilt nur für diese Instanz. Leer heißt: Unterordner Logs im Instanzverzeichnis." />
Text="{Binding L[desktop.settings.logDirHint]}" />
</StackPanel>
<Grid ColumnDefinitions="*,16,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Tagesbudget ($ USD)" />
<TextBlock Classes="label" Text="{Binding L[desktop.settings.dailyCost]}" />
<TextBox Text="{Binding InstanceDailyCostUsd}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Tagesbudget (Tokens)" />
<TextBlock Classes="label" Text="{Binding L[desktop.settings.dailyTokens]}" />
<TextBox Text="{Binding InstanceDailyTokens}" />
</StackPanel>
</Grid>
@@ -72,21 +72,33 @@
<!-- ══════════════════ Allgemein ══════════════════ -->
<StackPanel Spacing="14">
<TextBlock Classes="kicker" Text="ALLGEMEIN" />
<TextBlock Classes="kicker" Text="{Binding L[desktop.settings.general]}" />
<StackPanel>
<TextBlock Classes="label" Text="Instanzen-Verzeichnis" />
<TextBlock Classes="label" Text="{Binding L[desktop.settings.language]}" />
<ComboBox ItemsSource="{Binding AvailableLanguages}" SelectedItem="{Binding SelectedLanguageOption}"
HorizontalAlignment="Stretch">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="{Binding L[desktop.settings.instancesDir]}" />
<TextBox Text="{Binding InstancesDirectory}" />
</StackPanel>
<Grid ColumnDefinitions="*,16,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Minimaler Log-Level" />
<TextBlock Classes="label" Text="{Binding L[desktop.settings.minLogLevel]}" />
<ComboBox ItemsSource="{Binding LogLevels}" SelectedItem="{Binding MinimumLogLevel}"
HorizontalAlignment="Stretch" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="OpenRouter Base-URL" />
<TextBlock Classes="label" Text="{Binding L[desktop.settings.openRouterUrl]}" />
<TextBox Text="{Binding OpenRouterBaseUrl}" />
</StackPanel>
</Grid>
@@ -96,26 +108,26 @@
<!-- ══════════════════ Deploymentcenter ══════════════════ -->
<StackPanel Spacing="14">
<TextBlock Classes="kicker" Text="DEPLOYMENTCENTER &amp; LIZENZ" />
<TextBlock Classes="kicker" Text="{Binding L[desktop.settings.dcAndLicense]}" />
<StackPanel>
<TextBlock Classes="label" Text="Server-URL" />
<TextBlock Classes="label" Text="{Binding L[desktop.settings.dcUrl]}" />
<TextBox Text="{Binding DeploymentcenterUrl}" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Master-Token" />
<TextBlock Classes="label" Text="{Binding L[desktop.settings.dcToken]}" />
<TextBox Text="{Binding DeploymentcenterToken}" PasswordChar="•" />
</StackPanel>
<Grid ColumnDefinitions="*,16,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Update-Kanal" />
<TextBlock Classes="label" Text="{Binding L[desktop.settings.updateChannel]}" />
<ComboBox ItemsSource="{Binding UpdateChannels}" SelectedItem="{Binding UpdateChannel}"
HorizontalAlignment="Stretch" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Umgebung" />
<TextBlock Classes="label" Text="{Binding L[desktop.settings.dcEnv]}" />
<ComboBox ItemsSource="{Binding Environments}"
SelectedItem="{Binding DeploymentcenterEnvironment}"
HorizontalAlignment="Stretch" />
@@ -123,14 +135,14 @@
</Grid>
<StackPanel>
<TextBlock Classes="label" Text="Lizenzschlüssel" />
<TextBlock Classes="label" Text="{Binding L[desktop.settings.licenseKey]}" />
<TextBox Text="{Binding LicenseKey}" PasswordChar="•" />
</StackPanel>
<StackPanel Spacing="10" Margin="0,4,0,0">
<CheckBox Content="Fehler automatisch an Deploymentcenter melden"
<CheckBox Content="{Binding L[desktop.settings.errorReporting]}"
IsChecked="{Binding ErrorReportingEnabled}" />
<CheckBox Content="Beim Start automatisch auf Updates prüfen"
<CheckBox Content="{Binding L[desktop.settings.updateCheck]}"
IsChecked="{Binding UpdateCheckEnabled}" />
</StackPanel>
</StackPanel>
@@ -16,13 +16,13 @@
Bildpunkt, damit zwischen ihnen nur eine Linie steht und nicht zwei. -->
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal"
HorizontalAlignment="Left" Margin="0,0,0,14">
<ToggleButton Content="Geplante Jobs" Margin="0,0,-1,0"
<ToggleButton Content="{Binding L[desktop.tasks.tabJobs]}" Margin="0,0,-1,0"
IsChecked="{Binding IsJobsTab, Mode=OneWay}"
Command="{Binding SelectTabCommand}" CommandParameter="Jobs" />
<ToggleButton Content="Dienste" Margin="0,0,-1,0"
<ToggleButton Content="{Binding L[desktop.tasks.tabServices]}" Margin="0,0,-1,0"
IsChecked="{Binding IsServicesTab, Mode=OneWay}"
Command="{Binding SelectTabCommand}" CommandParameter="Services" />
<ToggleButton Content="Verlauf"
<ToggleButton Content="{Binding L[desktop.tasks.tabHistory]}"
IsChecked="{Binding IsHistoryTab, Mode=OneWay}"
Command="{Binding SelectTabCommand}" CommandParameter="History" />
</StackPanel>
@@ -40,7 +40,7 @@
<Button Classes="toolbar primary" Command="{Binding AddJobCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPlus}" Width="14" Height="14" />
<TextBlock Text="Job hinzufügen" />
<TextBlock Text="{Binding L[desktop.tasks.addJob]}" />
</StackPanel>
</Button>
@@ -48,7 +48,7 @@
IsEnabled="{Binding SelectedJob, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPencil}" Width="14" Height="14" />
<TextBlock Text="Bearbeiten" />
<TextBlock Text="{Binding L[desktop.tasks.editJob]}" />
</StackPanel>
</Button>
@@ -56,7 +56,7 @@
IsEnabled="{Binding SelectedJob, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconTrash}" Width="14" Height="14" />
<TextBlock Text="Entfernen" />
<TextBlock Text="{Binding L[desktop.tasks.removeJob]}" />
</StackPanel>
</Button>
@@ -64,7 +64,7 @@
IsEnabled="{Binding SelectedJob, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPower}" Width="14" Height="14" />
<TextBlock Text="Aktivieren/Deaktivieren" />
<TextBlock Text="{Binding L[desktop.tasks.toggleJob]}" />
</StackPanel>
</Button>
@@ -74,27 +74,27 @@
IsEnabled="{Binding SelectedJob, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPlay}" Width="12" Height="12" IsFilled="True" />
<TextBlock Text="Jetzt ausführen" />
<TextBlock Text="{Binding L[desktop.tasks.runJobNow]}" />
</StackPanel>
</Button>
<Button Classes="toolbar" Command="{Binding RefreshJobListCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconRefresh}" Width="14" Height="14" />
<TextBlock Text="Aktualisieren" />
<TextBlock Text="{Binding L[desktop.tasks.refresh]}" />
</StackPanel>
</Button>
</WrapPanel>
<Border DockPanel.Dock="Top" Classes="thead">
<Grid ColumnDefinitions="1.1*,1.3*,2.2*,1.1*,1.3*,1.3*,0.9*">
<TextBlock Grid.Column="0" Classes="th" Text="TYP" />
<TextBlock Grid.Column="1" Classes="th" Text="AGENT" />
<TextBlock Grid.Column="2" Classes="th" Text="TOOL / NACHRICHT" />
<TextBlock Grid.Column="3" Classes="th" Text="CRON" />
<TextBlock Grid.Column="4" Classes="th" Text="NÄCHSTER LAUF" />
<TextBlock Grid.Column="5" Classes="th" Text="LETZTER LAUF" />
<TextBlock Grid.Column="6" Classes="th" Text="STATUS" />
<TextBlock Grid.Column="0" Classes="th" Text="{Binding L[desktop.tasks.thType]}" />
<TextBlock Grid.Column="1" Classes="th" Text="{Binding L[desktop.tasks.thAgent]}" />
<TextBlock Grid.Column="2" Classes="th" Text="{Binding L[desktop.tasks.thTask]}" />
<TextBlock Grid.Column="3" Classes="th" Text="{Binding L[desktop.tasks.thCron]}" />
<TextBlock Grid.Column="4" Classes="th" Text="{Binding L[desktop.tasks.thNextRun]}" />
<TextBlock Grid.Column="5" Classes="th" Text="{Binding L[desktop.tasks.thLastRun]}" />
<TextBlock Grid.Column="6" Classes="th" Text="{Binding L[desktop.tasks.thStatus]}" />
</Grid>
</Border>
@@ -131,7 +131,7 @@
<Button Classes="toolbar primary" Command="{Binding AddServiceCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPlus}" Width="14" Height="14" />
<TextBlock Text="Dienst hinzufügen" />
<TextBlock Text="{Binding L[desktop.services.addService]}" />
</StackPanel>
</Button>
@@ -139,7 +139,7 @@
IsEnabled="{Binding SelectedService, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconTrash}" Width="14" Height="14" />
<TextBlock Text="Entfernen" />
<TextBlock Text="{Binding L[desktop.services.removeService]}" />
</StackPanel>
</Button>
@@ -152,25 +152,25 @@
IsEnabled="{Binding SelectedService, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconPower}" Width="14" Height="14" />
<TextBlock Text="Aktivieren/Deaktivieren" />
<TextBlock Text="{Binding L[desktop.services.toggleService]}" />
</StackPanel>
</Button>
<Button Classes="toolbar" Command="{Binding RefreshServiceListCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconRefresh}" Width="14" Height="14" />
<TextBlock Text="Aktualisieren" />
<TextBlock Text="{Binding L[desktop.tasks.refresh]}" />
</StackPanel>
</Button>
</WrapPanel>
<Border DockPanel.Dock="Top" Classes="thead">
<Grid ColumnDefinitions="1.8*,1*,0.7*,1*,3*">
<TextBlock Grid.Column="0" Classes="th" Text="DIENSTNAME" />
<TextBlock Grid.Column="1" Classes="th" Text="TYP" />
<TextBlock Grid.Column="2" Classes="th" Text="PORT" />
<TextBlock Grid.Column="3" Classes="th" Text="STATUS" />
<TextBlock Grid.Column="4" Classes="th" Text="BESCHREIBUNG" />
<TextBlock Grid.Column="0" Classes="th" Text="{Binding L[desktop.services.thName]}" />
<TextBlock Grid.Column="1" Classes="th" Text="{Binding L[desktop.services.thType]}" />
<TextBlock Grid.Column="2" Classes="th" Text="{Binding L[desktop.services.thPort]}" />
<TextBlock Grid.Column="3" Classes="th" Text="{Binding L[desktop.services.thStatus]}" />
<TextBlock Grid.Column="4" Classes="th" Text="{Binding L[desktop.services.thDescription]}" />
</Grid>
</Border>
@@ -206,19 +206,19 @@
<Button Classes="toolbar" Command="{Binding RefreshJobHistoryCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconRefresh}" Width="14" Height="14" />
<TextBlock Text="Verlauf aktualisieren" />
<TextBlock Text="{Binding L[desktop.history.refresh]}" />
</StackPanel>
</Button>
</StackPanel>
<Border DockPanel.Dock="Top" Classes="thead">
<Grid ColumnDefinitions="1.3*,1.6*,1.4*,2*,0.9*,2.4*">
<TextBlock Grid.Column="0" Classes="th" Text="ZEITPUNKT" />
<TextBlock Grid.Column="1" Classes="th" Text="JOB" />
<TextBlock Grid.Column="2" Classes="th" Text="AGENT" />
<TextBlock Grid.Column="3" Classes="th" Text="BESCHREIBUNG" />
<TextBlock Grid.Column="4" Classes="th" Text="STATUS" />
<TextBlock Grid.Column="5" Classes="th" Text="DETAILS" />
<TextBlock Grid.Column="0" Classes="th" Text="{Binding L[desktop.history.thTime]}" />
<TextBlock Grid.Column="1" Classes="th" Text="{Binding L[desktop.history.thJob]}" />
<TextBlock Grid.Column="2" Classes="th" Text="{Binding L[desktop.history.thAgent]}" />
<TextBlock Grid.Column="3" Classes="th" Text="{Binding L[desktop.history.thDescription]}" />
<TextBlock Grid.Column="4" Classes="th" Text="{Binding L[desktop.history.thStatus]}" />
<TextBlock Grid.Column="5" Classes="th" Text="{Binding L[desktop.history.thDetails]}" />
</Grid>
</Border>
@@ -17,12 +17,12 @@
<DockPanel>
<TextBlock x:Name="TitleText" DockPanel.Dock="Top" Classes="h1"
Text="Text bearbeiten" Margin="0,0,0,12" TextWrapping="Wrap" />
Text="Edit Text" Margin="0,0,0,12" TextWrapping="Wrap" />
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="8"
HorizontalAlignment="Right" Margin="0,14,0,0">
<Button Content="Abbrechen" IsCancel="True" Click="OnCancel" />
<Button Classes="primary" Content="Speichern" IsDefault="True" Click="OnSave" />
<Button x:Name="CancelButton" Content="Cancel" IsCancel="True" Click="OnCancel" />
<Button x:Name="SaveButton" Classes="primary" Content="Save" IsDefault="True" Click="OnSave" />
</StackPanel>
<TextBox x:Name="Editor" AcceptsReturn="True" TextWrapping="Wrap"
@@ -17,6 +17,9 @@ public partial class TextEditorWindow : Window
public TextEditorWindow()
{
InitializeComponent();
Title = ClawdDotNet.Desktop.Services.LocalizationManager.Instance["desktop.dialog.textEditorTitle"] ?? "Edit Text";
CancelButton.Content = ClawdDotNet.Desktop.Services.LocalizationManager.Instance["common.cancel"] ?? "Cancel";
SaveButton.Content = ClawdDotNet.Desktop.Services.LocalizationManager.Instance["common.save"] ?? "Save";
}
/// <summary>
@@ -15,19 +15,19 @@
<StackPanel Orientation="Horizontal" Spacing="12">
<Border Classes="card" MinWidth="160">
<StackPanel Spacing="6">
<TextBlock Classes="kicker" Text="TOKENS (7 TAGE)" />
<TextBlock Classes="kicker" Text="{Binding L[desktop.usage.kickerTokens7d]}" />
<TextBlock Classes="h2" Text="{Binding TotalTokens}" />
</StackPanel>
</Border>
<Border Classes="card" MinWidth="160">
<StackPanel Spacing="6">
<TextBlock Classes="kicker" Text="KOSTEN (7 TAGE)" />
<TextBlock Classes="kicker" Text="{Binding L[desktop.usage.kickerCost7d]}" />
<TextBlock Classes="h2" Text="{Binding TotalCost}" />
</StackPanel>
</Border>
<Border Classes="card" MinWidth="160">
<StackPanel Spacing="6">
<TextBlock Classes="kicker" Text="RUNS (7 TAGE)" />
<TextBlock Classes="kicker" Text="{Binding L[desktop.usage.kickerRuns7d]}" />
<TextBlock Classes="h2" Text="{Binding TotalRuns}" />
</StackPanel>
</Border>
@@ -35,7 +35,7 @@
<Button Classes="toolbar" Command="{Binding RefreshCommand}" VerticalAlignment="Bottom">
<StackPanel Orientation="Horizontal" Spacing="6">
<ctrl:StrokeIcon Data="{StaticResource IconRefresh}" Width="14" Height="14" />
<TextBlock Text="Aktualisieren" />
<TextBlock Text="{Binding L[desktop.usage.refresh]}" />
</StackPanel>
</Button>
</StackPanel>
@@ -46,7 +46,7 @@
<!-- Tagesbalken -->
<StackPanel Spacing="12" IsVisible="{Binding !IsEmpty}">
<TextBlock Classes="h2" Text="Tokens pro Tag" />
<TextBlock Classes="h2" Text="{Binding L[desktop.usage.tokensPerDay]}" />
<Border BorderBrush="{DynamicResource DividerBrush}" BorderThickness="0,0,0,1"
Padding="0,0,0,2">
@@ -78,15 +78,15 @@
<!-- Aufschlüsselung -->
<StackPanel Spacing="10" MaxWidth="860" HorizontalAlignment="Left"
IsVisible="{Binding !IsEmpty}">
<TextBlock Classes="h2" Text="Aufschlüsselung nach Agent &amp; Modell" />
<TextBlock Classes="h2" Text="{Binding L[desktop.usage.breakdownTitle]}" />
<Border Classes="thead">
<Grid ColumnDefinitions="2*,2*,1.2*,1.2*,0.8*">
<TextBlock Grid.Column="0" Classes="th" Text="AGENT" />
<TextBlock Grid.Column="1" Classes="th" Text="MODELL" />
<TextBlock Grid.Column="2" Classes="th" Text="TOKENS" />
<TextBlock Grid.Column="3" Classes="th" Text="KOSTEN" />
<TextBlock Grid.Column="4" Classes="th" Text="RUNS" />
<TextBlock Grid.Column="0" Classes="th" Text="{Binding L[desktop.usage.thAgent]}" />
<TextBlock Grid.Column="1" Classes="th" Text="{Binding L[desktop.usage.thModel]}" />
<TextBlock Grid.Column="2" Classes="th" Text="{Binding L[desktop.usage.thTokens]}" />
<TextBlock Grid.Column="3" Classes="th" Text="{Binding L[desktop.usage.thCost]}" />
<TextBlock Grid.Column="4" Classes="th" Text="{Binding L[desktop.usage.thRuns]}" />
</Grid>
</Border>
@@ -23,25 +23,25 @@
<StackPanel Spacing="12" IsVisible="{Binding ToolName,
Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=FTP}">
<StackPanel>
<TextBlock Classes="label" Text="Host" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.host]}" />
<TextBox Text="{Binding FtpHost}" PlaceholderText="ftp.example.com" />
</StackPanel>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Port" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.port]}" />
<TextBox Text="{Binding FtpPort}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Benutzername" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.username]}" />
<TextBox Text="{Binding FtpUsername}" />
</StackPanel>
</Grid>
<StackPanel>
<TextBlock Classes="label" Text="Passwort" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.password]}" />
<TextBox Text="{Binding FtpPassword}" PasswordChar="•" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Root-Pfad" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.rootPath]}" />
<TextBox Text="{Binding FtpRootPath}" />
</StackPanel>
</StackPanel>
@@ -51,36 +51,36 @@
Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=Mail}">
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Benutzername" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.username]}" />
<TextBox Text="{Binding MailUsername}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Passwort" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.password]}" />
<TextBox Text="{Binding MailPassword}" PasswordChar="•" />
</StackPanel>
</Grid>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="IMAP-Host" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.imapHost]}" />
<TextBox Text="{Binding MailImapHost}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="IMAP-Port" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.imapPort]}" />
<TextBox Text="{Binding MailImapPort}" />
</StackPanel>
</Grid>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="SMTP-Host" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.smtpHost]}" />
<TextBox Text="{Binding MailSmtpHost}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="SMTP-Port" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.smtpPort]}" />
<TextBox Text="{Binding MailSmtpPort}" />
</StackPanel>
</Grid>
<StackPanel>
<TextBlock Classes="label" Text="Erlaubte Empfänger" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.allowedRecipients]}" />
<TextBox Text="{Binding MailAllowedRecipients}"
PlaceholderText="adresse1@domain.de, adresse2@domain.de" />
</StackPanel>
@@ -90,21 +90,21 @@
<StackPanel Spacing="12" IsVisible="{Binding ToolName,
Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=Database}">
<StackPanel>
<TextBlock Classes="label" Text="Typ" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.type]}" />
<ComboBox ItemsSource="{Binding DbTypes}" SelectedItem="{Binding DbType}"
HorizontalAlignment="Stretch" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Connection-String" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.connectionString]}" />
<TextBox Text="{Binding DbConnectionString}" PasswordChar="•" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Zugriffsebene" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.accessLevel]}" />
<ComboBox ItemsSource="{Binding DbAccessLevels}" SelectedItem="{Binding DbAccessLevel}"
HorizontalAlignment="Stretch" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Erlaubte Tabellen" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.allowedTables]}" />
<TextBox Text="{Binding DbAllowedTables}" PlaceholderText="tabelle1, tabelle2" />
</StackPanel>
</StackPanel>
@@ -113,15 +113,15 @@
<StackPanel Spacing="12" IsVisible="{Binding ToolName,
Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=Telegram}">
<StackPanel>
<TextBlock Classes="label" Text="Bot-Token" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.botToken]}" />
<TextBox Text="{Binding TelegramBotToken}" PasswordChar="•" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Standard Chat-ID" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.defaultChatId]}" />
<TextBox Text="{Binding TelegramDefaultChatId}" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Erlaubte Chat-IDs" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.allowedChatIds]}" />
<TextBox Text="{Binding TelegramAllowedChatIds}" PlaceholderText="id1, id2" />
</StackPanel>
</StackPanel>
@@ -131,20 +131,20 @@
Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=DirectAPI}">
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Standard-Provider" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.defaultProvider]}" />
<TextBox Text="{Binding DirectApiDefaultProvider}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Cache TTL (Sekunden)" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.cacheTtl]}" />
<TextBox Text="{Binding DirectApiCacheTtlSeconds}" />
</StackPanel>
</Grid>
<StackPanel>
<TextBlock Classes="label" Text="Twelve Data Key" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.twelveDataKey]}" />
<TextBox Text="{Binding DirectApiTwelveDataKey}" PasswordChar="•" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Alpha Vantage Key" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.alphaVantageKey]}" />
<TextBox Text="{Binding DirectApiAlphaVantageKey}" PasswordChar="•" />
</StackPanel>
</StackPanel>
@@ -153,17 +153,17 @@
<StackPanel Spacing="12" IsVisible="{Binding ToolName,
Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=WebFetch}">
<StackPanel>
<TextBlock Classes="label" Text="Erlaubte Domains" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.allowedDomains]}" />
<TextBox Text="{Binding WebFetchAllowedDomains}"
PlaceholderText="reuters.com, coindesk.com" />
</StackPanel>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Max. Antwortgröße (KB)" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.maxResponseKb]}" />
<TextBox Text="{Binding WebFetchMaxResponseKb}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="User Agent" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.userAgent]}" />
<TextBox Text="{Binding WebFetchUserAgent}" />
</StackPanel>
</Grid>
@@ -173,34 +173,34 @@
<StackPanel Spacing="12" IsVisible="{Binding ToolName,
Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=SocialMediaManager}">
<StackPanel>
<TextBlock Classes="label" Text="X (Twitter) API-Key" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.xApiKey]}" />
<TextBox Text="{Binding SmmXApiKey}" PasswordChar="•" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="X Accounts" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.xAccounts]}" />
<TextBox Text="{Binding SmmXWatchAccounts}" PlaceholderText="account1, account2" />
</StackPanel>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="Subreddits" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.subreddits]}" />
<TextBox Text="{Binding SmmRedditWatchSubreddits}" PlaceholderText="stocks, investing" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="Reddit Post Limit" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.redditPostLimit]}" />
<TextBox Text="{Binding SmmRedditPostLimit}" />
</StackPanel>
</Grid>
<StackPanel>
<TextBlock Classes="label" Text="OpenRouter Key" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.openRouterKey]}" />
<TextBox Text="{Binding SmmOpenRouterApiKey}" PasswordChar="•" />
</StackPanel>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0">
<TextBlock Classes="label" Text="STT-Modell" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.sttModel]}" />
<TextBox Text="{Binding SmmSttModel}" />
</StackPanel>
<StackPanel Grid.Column="2">
<TextBlock Classes="label" Text="YouTube-Kanäle" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.youtubeChannels]}" />
<TextBox Text="{Binding SmmYoutubeChannels}" PlaceholderText="URL1, URL2" />
</StackPanel>
</Grid>
@@ -210,28 +210,28 @@
<StackPanel Spacing="12" IsVisible="{Binding ToolName,
Converter={x:Static conv:ToolMatchConverter.Instance}, ConverterParameter=FileRW}">
<StackPanel>
<TextBlock Classes="label" Text="Erlaubte Dateiendungen (persönlich)" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.fileRwPersonalExt]}" />
<TextBox Text="{Binding FileRwPersonalAllowedExtensions}" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Zugriffsebene (geteilt)" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.fileRwSharedAccess]}" />
<ComboBox ItemsSource="{Binding FileRwAccessLevels}"
SelectedItem="{Binding FileRwSharedAccessLevel}"
HorizontalAlignment="Stretch" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Erlaubte Dateiendungen (geteilt)" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.fileRwSharedExt]}" />
<TextBox Text="{Binding FileRwSharedAllowedExtensions}" />
</StackPanel>
<StackPanel>
<TextBlock Classes="label" Text="Geschützte Pfade" />
<TextBlock Classes="label" Text="{Binding L[desktop.toolSettings.fileRwProtectedPaths]}" />
<TextBox Text="{Binding FileRwProtectedPaths}" />
</StackPanel>
</StackPanel>
<!-- Werkzeuge ohne weitere Einstellungen -->
<TextBlock Classes="muted" FontStyle="Italic" TextWrapping="Wrap"
Text="Dieses Werkzeug benötigt keine weiteren Einstellungen."
Text="{Binding L[desktop.toolSettings.noSettings]}"
IsVisible="{Binding SettingsObject, Converter={x:Static ObjectConverters.IsNull}}" />
</StackPanel>
+339
View File
@@ -0,0 +1,339 @@
{
"desktop.mainWindow.title": "ClawdDotNet",
"desktop.mainWindow.sidebarTooltip": "Seitenleiste ein-/ausklappen",
"desktop.mainWindow.switchInstance": "Instanz wechseln",
"desktop.mainWindow.instance": "INSTANZ",
"desktop.mainWindow.themeLight": "Hellmodus",
"desktop.mainWindow.themeDark": "Dunkelmodus",
"desktop.mainWindow.themeToggle": "Farbschema umschalten",
"desktop.mainWindow.openInstanceFolder": "Instanz-Ordner öffnen",
"desktop.mainWindow.instanceManager": "Instanzmanager",
"desktop.mainWindow.exit": "Beenden",
"desktop.mainWindow.instanceLabel": "Instanz: {0}",
"desktop.mainWindow.apiStatus": "API {0}",
"desktop.mainWindow.credits": "Credits: ${0:F2} von ${1:F2}",
"desktop.mainWindow.creditsNone": "Credits: —",
"desktop.mainWindow.dismiss": "Ausblenden",
"desktop.nav.work": "Arbeiten",
"desktop.nav.analytics": "Analyse",
"desktop.nav.system": "System",
"desktop.status.unknown": "unbekannt",
"desktop.status.design": "Entwurf",
"desktop.status.noApiKey": "kein API-Key",
"desktop.status.noUsageData": "keine Nutzungsdaten",
"desktop.status.ready": "Bereit",
"desktop.status.reachable": "OK",
"desktop.status.unreachable": "nicht erreichbar",
"desktop.chat.agent": "Agent:",
"desktop.chat.clearHistory": "Verlauf leeren",
"desktop.chat.abort": "Abbrechen",
"desktop.chat.inputPlaceholder": "Nachricht eingeben… (Enter zum Senden, Shift+Enter für neue Zeile)",
"desktop.chat.send": "Senden",
"desktop.chat.statusReady": "Bereit",
"desktop.chat.statusSending": "Sende…",
"desktop.chat.statusDesign": "Entwurfsmodus",
"desktop.chat.noApiKey": "OpenRouter API-Key ist nicht konfiguriert. Bitte in den Einstellungen hinterlegen.",
"desktop.chat.noAgent": "Bitte einen Agenten auswählen.",
"desktop.chat.emptyMessage": "Bitte eine Nachricht eingeben.",
"desktop.agents.addAgent": "Agent",
"desktop.agents.removeAgent": "Agent",
"desktop.agents.runNow": "Jetzt ausf.",
"desktop.agents.identity": "Identity",
"desktop.agents.soul": "Soul",
"desktop.agents.folder": "Ordner",
"desktop.agents.save": "Speichern",
"desktop.agents.thDisplayName": "ANZEIGENAME",
"desktop.agents.thModel": "MODELL",
"desktop.agents.thSummary": "KURZINFO",
"desktop.agents.kickerGeneralModel": "ALLGEMEIN & MODELL",
"desktop.agents.agentId": "Agent-ID",
"desktop.agents.displayName": "Anzeigename",
"desktop.agents.model": "Modell",
"desktop.agents.promptCaching": "Prompt Caching",
"desktop.agents.roleDescription": "Rolle / Beschreibung",
"desktop.agents.systemPrompt": "System-Prompt (zusätzliche Anweisungen)",
"desktop.agents.kickerLimits": "GRENZEN",
"desktop.agents.limitsTitle": "Schutzgrenzen (LoopGuard & Budget)",
"desktop.agents.maxSteps": "Max. Schritte pro Run",
"desktop.agents.timeoutSeconds": "Timeout (Sekunden)",
"desktop.agents.maxContextTokens": "Max. Kontext-Tokens",
"desktop.agents.maxToolResultChars": "Max. Tool-Ergebnis (Zeichen)",
"desktop.agents.dailyCost": "Tagesbudget ($ USD)",
"desktop.agents.dailyTokens": "Tagesbudget (Tokens)",
"desktop.agents.allowedToolsFor": "ZUGELASSENE WERKZEUGE FÜR {0}",
"desktop.agents.thTool": "WERKZEUG",
"desktop.agents.thDescription": "BESCHREIBUNG",
"desktop.agents.toolSettingsKicker": "WERKZEUG-EINSTELLUNGEN",
"desktop.agents.apply": "Übernehmen",
"desktop.agents.saved": "Agenten erfolgreich gespeichert.",
"desktop.tasks.tabJobs": "Geplante Jobs",
"desktop.tasks.tabServices": "Dienste",
"desktop.tasks.tabHistory": "Verlauf",
"desktop.tasks.addJob": "Job hinzufügen",
"desktop.tasks.editJob": "Bearbeiten",
"desktop.tasks.removeJob": "Entfernen",
"desktop.tasks.toggleJob": "Aktivieren/Deaktivieren",
"desktop.tasks.runJobNow": "Jetzt ausführen",
"desktop.tasks.refresh": "Aktualisieren",
"desktop.tasks.thType": "TYP",
"desktop.tasks.thAgent": "AGENT",
"desktop.tasks.thTask": "TOOL / NACHRICHT",
"desktop.tasks.thCron": "CRON",
"desktop.tasks.thNextRun": "NÄCHSTER LAUF",
"desktop.tasks.thLastRun": "LETZTER LAUF",
"desktop.tasks.thStatus": "STATUS",
"desktop.services.addService": "Dienst hinzufügen",
"desktop.services.removeService": "Entfernen",
"desktop.services.toggleService": "Aktivieren/Deaktivieren",
"desktop.services.thName": "DIENSTNAME",
"desktop.services.thType": "TYP",
"desktop.services.thPort": "PORT",
"desktop.services.thStatus": "STATUS",
"desktop.services.thDescription": "BESCHREIBUNG",
"desktop.history.refresh": "Verlauf aktualisieren",
"desktop.history.thTime": "ZEITPUNKT",
"desktop.history.thJob": "JOB",
"desktop.history.thAgent": "AGENT",
"desktop.history.thDescription": "BESCHREIBUNG",
"desktop.history.thStatus": "STATUS",
"desktop.history.thDetails": "DETAILS",
"desktop.usage.kickerTokens7d": "TOKENS (7 TAGE)",
"desktop.usage.kickerCost7d": "KOSTEN (7 TAGE)",
"desktop.usage.kickerRuns7d": "RUNS (7 TAGE)",
"desktop.usage.refresh": "Aktualisieren",
"desktop.usage.tokensPerDay": "Tokens pro Tag",
"desktop.usage.breakdownTitle": "Aufschlüsselung nach Agent & Modell",
"desktop.usage.thAgent": "AGENT",
"desktop.usage.thModel": "MODELL",
"desktop.usage.thTokens": "TOKENS",
"desktop.usage.thCost": "KOSTEN",
"desktop.usage.thRuns": "RUNS",
"desktop.usage.empty": "Noch keine Tokennutzung aufgezeichnet.",
"desktop.agentChats.agentA": "Agent A:",
"desktop.agentChats.agentB": "Agent B:",
"desktop.agentChats.empty": "Noch keine Nachrichten zwischen diesen Agenten aufgezeichnet.",
"desktop.agentChats.needTwo": "Für einen Verlauf braucht es mindestens zwei Agenten in dieser Instanz.",
"desktop.backup.kickerManual": "MANUELL",
"desktop.backup.titleCreate": "Sicherung erstellen",
"desktop.backup.targetFolder": "Zielordner",
"desktop.backup.targetFolderPlaceholder": "Zielordner für Sicherungen…",
"desktop.backup.browse": "Durchsuchen…",
"desktop.backup.credentials": "Zugangsdaten",
"desktop.backup.credentialsExclude": "nicht mitsichern",
"desktop.backup.credentialsProtect": "mit Passphrase schützen",
"desktop.backup.passphrase": "Passphrase eingeben",
"desktop.backup.passphraseRepeat": "Passphrase wiederholen",
"desktop.backup.scope": "Umfang",
"desktop.backup.scopeChat": "Chatverläufe",
"desktop.backup.scopeLogs": "Protokolle",
"desktop.backup.createNow": "Sicherung jetzt erstellen",
"desktop.backup.kickerAuto": "AUTOMATIK",
"desktop.backup.titleAuto": "Automatische Sicherung",
"desktop.backup.autoDaily": "täglich sichern um",
"desktop.backup.time": "Uhrzeit",
"desktop.backup.keep": "Aufbewahren",
"desktop.backup.autoHint": "Die Automatik sichert ohne Zugangsdaten — eine gespeicherte Passphrase wäre wirkungslos.",
"desktop.backup.kickerExisting": "BESTAND",
"desktop.backup.titleExisting": "Vorhandene Sicherungen",
"desktop.backup.refresh": "Aktualisieren",
"desktop.backup.openFolder": "Ordner öffnen",
"desktop.backup.delete": "Löschen",
"desktop.backup.restore": "Wiederherstellen…",
"desktop.backup.thFilename": "DATEINAME",
"desktop.backup.thCreatedAt": "ERSTELLT AM",
"desktop.backup.thInstance": "INSTANZ",
"desktop.backup.thSize": "GRÖSSE",
"desktop.backup.thSecrets": "ZUGANGSDATEN",
"desktop.logs.module": "Modul:",
"desktop.logs.level": "Stufe:",
"desktop.logs.refresh": "Aktualisieren",
"desktop.logs.clear": "Leeren",
"desktop.logs.openFolder": "Ordner öffnen",
"desktop.logs.follow": "Mitlaufen",
"desktop.settings.discard": "Verwerfen",
"desktop.settings.save": "Speichern",
"desktop.settings.instance": "INSTANZ",
"desktop.settings.instanceName": "Instanz-Name",
"desktop.settings.apiKey": "OpenRouter API-Key",
"desktop.settings.autostart": "Autostart durch Manager",
"desktop.settings.autostartHint": "Der Instanzmanager startet diese Instanz, wenn er hochkommt, und wieder, wenn sie abstürzt. Nach mehreren Neustarts in kurzer Folge setzt er aus und meldet es.",
"desktop.settings.logDir": "Protokoll-Verzeichnis",
"desktop.settings.logDirHint": "Gilt nur für diese Instanz. Leer heißt: Unterordner Logs im Instanzverzeichnis.",
"desktop.settings.dailyCost": "Tagesbudget ($ USD)",
"desktop.settings.dailyTokens": "Tagesbudget (Tokens)",
"desktop.settings.general": "ALLGEMEIN",
"desktop.settings.instancesDir": "Instanzen-Verzeichnis",
"desktop.settings.minLogLevel": "Minimaler Log-Level",
"desktop.settings.openRouterUrl": "OpenRouter Base-URL",
"desktop.settings.language": "Sprache",
"desktop.settings.dcAndLicense": "DEPLOYMENTCENTER & LIZENZ",
"desktop.settings.dcUrl": "Server-URL",
"desktop.settings.dcToken": "Master-Token",
"desktop.settings.dcEnv": "Umgebung",
"desktop.settings.updateChannel": "Update-Kanal",
"desktop.settings.licenseKey": "Lizenzschlüssel",
"desktop.settings.errorReporting": "Fehler automatisch an Deploymentcenter melden",
"desktop.settings.updateCheck": "Beim Start automatisch auf Updates prüfen",
"desktop.settings.saved": "Einstellungen und Instanz-Konfiguration gespeichert.",
"desktop.settings.savedLogRestart": "Gespeichert. Das Protokoll-Verzeichnis gilt ab dem nächsten Start.",
"desktop.info.kickerApp": "ANWENDUNG",
"desktop.info.build": "Build",
"desktop.info.version": "Version",
"desktop.info.buildSummary": "Build-Zusammenfassung",
"desktop.info.kickerInstance": "INSTANZ",
"desktop.info.currentInstance": "Aktuelle Instanz",
"desktop.info.openFolder": "Instanz-Ordner öffnen",
"desktop.info.name": "Name",
"desktop.info.id": "ID",
"desktop.info.path": "Pfad",
"desktop.info.logDir": "Log-Ordner",
"desktop.info.openRouter": "OpenRouter",
"desktop.info.kickerModules": "MODULE",
"desktop.info.assemblies": "Baugruppen",
"desktop.info.thModule": "MODUL",
"desktop.info.thBuild": "BUILD",
"desktop.info.thBuildDate": "BUILD-DATUM",
"desktop.info.thChanges": "ÄNDERUNGEN",
"desktop.dialog.addAgent": "Neuen Agenten anlegen",
"desktop.dialog.agentId": "Agent-ID (eindeutiger Schlüssel):",
"desktop.dialog.agentDisplayName": "Anzeigename:",
"desktop.dialog.defaultModel": "Standard-Modell:",
"desktop.dialog.roleDescription": "Beschreibung / Rolle:",
"desktop.dialog.addJobTitle": "Job hinzufügen / bearbeiten",
"desktop.dialog.jobType": "Job-Typ:",
"desktop.dialog.agentWakeup": "Agent Wakeup (Periodische Ausführung)",
"desktop.dialog.toolJob": "Tool Job (Werkzeug-spezifischer Job)",
"desktop.dialog.jobTypeId": "Job-Typ-ID / Name:",
"desktop.dialog.taskMessage": "Aufgabenbeschreibung / Nachricht:",
"desktop.dialog.cronExpression": "Cron-Ausdruck:",
"desktop.dialog.cronHint": "Syntax: Min Std Tag Mon Wochentag (z.B. '*/15 * * * *' alle 15 Minuten)",
"desktop.dialog.runOnStart": "Beim Anwendungsstart einmalig sofort ausführen",
"desktop.dialog.addServiceTitle": "Dienst (Service) hinzufügen",
"desktop.dialog.serviceName": "Name des Dienstes:",
"desktop.dialog.serviceType": "Typ:",
"desktop.dialog.servicePort": "Port:",
"desktop.dialog.serviceDesc": "Beschreibung:",
"desktop.dialog.restoreTitle": "Sicherung wiederherstellen",
"desktop.dialog.backupInfo": "Sicherungsinformationen",
"desktop.dialog.backupInstance": "Instanz: {0}",
"desktop.dialog.backupCreated": "Erstellt: {0:g}",
"desktop.dialog.backupFiles": "Dateien: {0}",
"desktop.dialog.backupSecrets": "Zugangsdaten: {0}",
"desktop.dialog.targetDir": "Zielordner:",
"desktop.dialog.chooseFolder": "Ordner wählen",
"desktop.dialog.restoreHint": "Hinweis: Es wird empfohlen, in einen neuen Ordner wiederherzustellen, um Konflikte mit laufenden Prozessen zu vermeiden.",
"desktop.dialog.passphrase": "Passphrase:",
"desktop.dialog.passphraseProtected": "Diese Sicherung enthält geschützte Zugangsdaten.",
"desktop.dialog.overwriteExisting": "Vorhandene Dateien im Zielordner überschreiben",
"desktop.dialog.licenseTitle": "ClawdDotNet – Lizenz",
"desktop.dialog.licenseKey": "Lizenzschlüssel",
"desktop.dialog.hardwareId": "Hardware-ID (für den Support):",
"desktop.dialog.verify": "Prüfen",
"desktop.dialog.textEditorTitle": "Text bearbeiten",
"desktop.instancePicker.titleSwitch": "Instanz wechseln",
"desktop.instancePicker.titleSelect": "Instanz wählen",
"desktop.instancePicker.hintSwitch": "Jede Instanz läuft unabhängig mit eigenen Agenten und Daten. Wechseln startet ClawdDotNet mit der gewählten Instanz neu.",
"desktop.instancePicker.hintSelect": "Jede Instanz läuft unabhängig mit eigenen Agenten und Daten.",
"desktop.instancePicker.confirmSwitch": "Wechseln",
"desktop.instancePicker.confirmStart": "Starten",
"desktop.instancePicker.newInstancePlaceholder": "Name der neuen Instanz",
"desktop.instancePicker.create": "Anlegen",
"desktop.instancePicker.active": "Aktiv",
"desktop.instancePicker.dirLabel": "Verzeichnis: {0}",
"desktop.instancePicker.agentsCount": "{0} Agenten · API-Key {1}",
"desktop.instancePicker.apiKeyConfigured": "konfiguriert",
"desktop.instancePicker.apiKeyMissing": "fehlt",
"desktop.instancePicker.tooltipConfigured": "API-Key hinterlegt",
"desktop.instancePicker.tooltipMissing": "Kein API-Key — Agenten bleiben deaktiviert",
"desktop.instancePicker.nameRequired": "Bitte einen Instanznamen eingeben.",
"desktop.instancePicker.createFailed": "Instanz konnte nicht angelegt werden: {0}",
"desktop.startMode.title": "ClawdDotNet – Startmodus wählen",
"desktop.startMode.question": "Wie möchtest du ClawdDotNet starten?",
"desktop.startMode.managerBtn": "Instanzmanager starten (Empfohlen)",
"desktop.startMode.standaloneBtn": "Als Standalone-Instanz starten",
"desktop.startMode.explanation": "• Instanzmanager (Empfohlen):\n Startet den zentralen Hintergrund-Manager und öffnet die Verwaltung im Browser. Ermöglicht den stabilen Dauerbetrieb mehrerer Instanzen parallel ohne Absturzrisiko.\n\n• Standalone-Modus:\n Startet genau eine Instanz direkt als einzelnes Desktop-Fenster.",
"desktop.standaloneSwitch.title": "ClawdDotNet – Instanzwechsel (Standalone)",
"desktop.standaloneSwitch.warning": "Achtung: Standalone-Modus",
"desktop.standaloneSwitch.text": "Diese Instanz läuft eigenständig ohne Instanzmanager.\n\nWenn du die Instanz hier wechselst, wird diese Instanz vollständig beendet und alle laufenden Agenten und Hintergrunddienste werden gestoppt.\n\nEmpfehlung: Starte stattdessen den Instanzmanager, um mehrere Instanzen gleichzeitig und unabhängig voneinander zu betreiben.",
"desktop.standaloneSwitch.switchAnyway": "Instanz trotzdem wechseln",
"desktop.toolSettings.noSettings": "Dieses Werkzeug benötigt keine weiteren Einstellungen.",
"desktop.toolSettings.host": "Host",
"desktop.toolSettings.port": "Port",
"desktop.toolSettings.username": "Benutzername",
"desktop.toolSettings.password": "Passwort",
"desktop.toolSettings.rootPath": "Root-Pfad",
"desktop.toolSettings.imapHost": "IMAP-Host",
"desktop.toolSettings.imapPort": "IMAP-Port",
"desktop.toolSettings.smtpHost": "SMTP-Host",
"desktop.toolSettings.smtpPort": "SMTP-Port",
"desktop.toolSettings.allowedRecipients": "Erlaubte Empfänger",
"desktop.toolSettings.type": "Typ",
"desktop.toolSettings.connectionString": "Connection-String",
"desktop.toolSettings.accessLevel": "Zugriffsebene",
"desktop.toolSettings.allowedTables": "Erlaubte Tabellen",
"desktop.toolSettings.botToken": "Bot-Token",
"desktop.toolSettings.defaultChatId": "Standard Chat-ID",
"desktop.toolSettings.allowedChatIds": "Erlaubte Chat-IDs",
"desktop.toolSettings.defaultProvider": "Standard-Provider",
"desktop.toolSettings.cacheTtl": "Cache TTL (Sekunden)",
"desktop.toolSettings.twelveDataKey": "Twelve Data Key",
"desktop.toolSettings.alphaVantageKey": "Alpha Vantage Key",
"desktop.toolSettings.allowedDomains": "Erlaubte Domains",
"desktop.toolSettings.maxResponseKb": "Max. Antwortgröße (KB)",
"desktop.toolSettings.userAgent": "User Agent",
"desktop.toolSettings.xApiKey": "X (Twitter) API-Key",
"desktop.toolSettings.xAccounts": "X Accounts",
"desktop.toolSettings.subreddits": "Subreddits",
"desktop.toolSettings.redditPostLimit": "Reddit Post Limit",
"desktop.toolSettings.openRouterKey": "OpenRouter Key",
"desktop.toolSettings.sttModel": "STT-Modell",
"desktop.toolSettings.youtubeChannels": "YouTube-Kanäle",
"desktop.toolSettings.fileRwPersonalExt": "Erlaubte Dateiendungen (persönlich)",
"desktop.toolSettings.fileRwSharedAccess": "Zugriffsebene (geteilt)",
"desktop.toolSettings.fileRwSharedExt": "Erlaubte Dateiendungen (geteilt)",
"desktop.toolSettings.fileRwProtectedPaths": "Geschützte Pfade",
"nav.chat": "Chat",
"nav.agents": "Agenten",
"nav.tasks": "Aufgaben",
"nav.usage": "Tokennutzung",
"nav.agentChats": "Agenten-Chats",
"nav.backup": "Sicherung",
"nav.logs": "Protokoll",
"nav.settings": "Einstellungen",
"nav.info": "Info",
"common.all": "Alle",
"common.save": "Speichern",
"common.cancel": "Abbrechen",
"common.delete": "Löschen",
"common.edit": "Bearbeiten",
"common.refresh": "Aktualisieren",
"common.close": "Schließen",
"common.discard": "Verwerfen",
"common.ready": "Bereit",
"common.loading": "Lade…",
"common.create": "Anlegen",
"common.add": "Hinzufügen",
"common.apply": "Übernehmen",
"common.ok": "OK",
"common.exit": "Beenden"
}
+339
View File
@@ -0,0 +1,339 @@
{
"desktop.mainWindow.title": "ClawdDotNet",
"desktop.mainWindow.sidebarTooltip": "Toggle sidebar",
"desktop.mainWindow.switchInstance": "Switch instance",
"desktop.mainWindow.instance": "INSTANCE",
"desktop.mainWindow.themeLight": "Light Mode",
"desktop.mainWindow.themeDark": "Dark Mode",
"desktop.mainWindow.themeToggle": "Toggle theme",
"desktop.mainWindow.openInstanceFolder": "Open Instance Folder",
"desktop.mainWindow.instanceManager": "Instance Manager",
"desktop.mainWindow.exit": "Exit",
"desktop.mainWindow.instanceLabel": "Instance: {0}",
"desktop.mainWindow.apiStatus": "API {0}",
"desktop.mainWindow.credits": "Credits: ${0:F2} of ${1:F2}",
"desktop.mainWindow.creditsNone": "Credits: —",
"desktop.mainWindow.dismiss": "Dismiss",
"desktop.nav.work": "Work",
"desktop.nav.analytics": "Analytics",
"desktop.nav.system": "System",
"desktop.status.unknown": "unknown",
"desktop.status.design": "Design",
"desktop.status.noApiKey": "no API key",
"desktop.status.noUsageData": "no usage data",
"desktop.status.ready": "Ready",
"desktop.status.reachable": "OK",
"desktop.status.unreachable": "unreachable",
"desktop.chat.agent": "Agent:",
"desktop.chat.clearHistory": "Clear History",
"desktop.chat.abort": "Abort",
"desktop.chat.inputPlaceholder": "Type message… (Enter to send, Shift+Enter for new line)",
"desktop.chat.send": "Send",
"desktop.chat.statusReady": "Ready",
"desktop.chat.statusSending": "Sending…",
"desktop.chat.statusDesign": "Design Mode",
"desktop.chat.noApiKey": "OpenRouter API key is not configured. Please enter the key in Settings.",
"desktop.chat.noAgent": "Please select an agent.",
"desktop.chat.emptyMessage": "Please enter a message.",
"desktop.agents.addAgent": "Agent",
"desktop.agents.removeAgent": "Agent",
"desktop.agents.runNow": "Run Now",
"desktop.agents.identity": "Identity",
"desktop.agents.soul": "Soul",
"desktop.agents.folder": "Folder",
"desktop.agents.save": "Save",
"desktop.agents.thDisplayName": "DISPLAY NAME",
"desktop.agents.thModel": "MODEL",
"desktop.agents.thSummary": "SUMMARY",
"desktop.agents.kickerGeneralModel": "GENERAL & MODEL",
"desktop.agents.agentId": "Agent ID",
"desktop.agents.displayName": "Display Name",
"desktop.agents.model": "Model",
"desktop.agents.promptCaching": "Prompt Caching",
"desktop.agents.roleDescription": "Role / Description",
"desktop.agents.systemPrompt": "System Prompt (additional instructions)",
"desktop.agents.kickerLimits": "LIMITS",
"desktop.agents.limitsTitle": "Guardrails (LoopGuard & Budget)",
"desktop.agents.maxSteps": "Max Steps per Run",
"desktop.agents.timeoutSeconds": "Timeout (seconds)",
"desktop.agents.maxContextTokens": "Max Context Tokens",
"desktop.agents.maxToolResultChars": "Max Tool Result (chars)",
"desktop.agents.dailyCost": "Daily Budget ($ USD)",
"desktop.agents.dailyTokens": "Daily Budget (Tokens)",
"desktop.agents.allowedToolsFor": "ALLOWED TOOLS FOR {0}",
"desktop.agents.thTool": "TOOL",
"desktop.agents.thDescription": "DESCRIPTION",
"desktop.agents.toolSettingsKicker": "TOOL SETTINGS",
"desktop.agents.apply": "Apply",
"desktop.agents.saved": "Agents saved successfully.",
"desktop.tasks.tabJobs": "Scheduled Jobs",
"desktop.tasks.tabServices": "Services",
"desktop.tasks.tabHistory": "History",
"desktop.tasks.addJob": "Add Job",
"desktop.tasks.editJob": "Edit",
"desktop.tasks.removeJob": "Remove",
"desktop.tasks.toggleJob": "Enable/Disable",
"desktop.tasks.runJobNow": "Run Now",
"desktop.tasks.refresh": "Refresh",
"desktop.tasks.thType": "TYPE",
"desktop.tasks.thAgent": "AGENT",
"desktop.tasks.thTask": "TOOL / MESSAGE",
"desktop.tasks.thCron": "CRON",
"desktop.tasks.thNextRun": "NEXT RUN",
"desktop.tasks.thLastRun": "LAST RUN",
"desktop.tasks.thStatus": "STATUS",
"desktop.services.addService": "Add Service",
"desktop.services.removeService": "Remove",
"desktop.services.toggleService": "Enable/Disable",
"desktop.services.thName": "SERVICE NAME",
"desktop.services.thType": "TYPE",
"desktop.services.thPort": "PORT",
"desktop.services.thStatus": "STATUS",
"desktop.services.thDescription": "DESCRIPTION",
"desktop.history.refresh": "Refresh History",
"desktop.history.thTime": "TIMESTAMP",
"desktop.history.thJob": "JOB",
"desktop.history.thAgent": "AGENT",
"desktop.history.thDescription": "DESCRIPTION",
"desktop.history.thStatus": "STATUS",
"desktop.history.thDetails": "DETAILS",
"desktop.usage.kickerTokens7d": "TOKENS (7 DAYS)",
"desktop.usage.kickerCost7d": "COST (7 DAYS)",
"desktop.usage.kickerRuns7d": "RUNS (7 DAYS)",
"desktop.usage.refresh": "Refresh",
"desktop.usage.tokensPerDay": "Tokens per Day",
"desktop.usage.breakdownTitle": "Breakdown by Agent & Model",
"desktop.usage.thAgent": "AGENT",
"desktop.usage.thModel": "MODEL",
"desktop.usage.thTokens": "TOKENS",
"desktop.usage.thCost": "COST",
"desktop.usage.thRuns": "RUNS",
"desktop.usage.empty": "No token usage recorded yet.",
"desktop.agentChats.agentA": "Agent A:",
"desktop.agentChats.agentB": "Agent B:",
"desktop.agentChats.empty": "No messages between these agents yet.",
"desktop.agentChats.needTwo": "At least two agents are required in this instance to view a conversation history.",
"desktop.backup.kickerManual": "MANUAL",
"desktop.backup.titleCreate": "Create Backup",
"desktop.backup.targetFolder": "Target Folder",
"desktop.backup.targetFolderPlaceholder": "Target folder for backups…",
"desktop.backup.browse": "Browse…",
"desktop.backup.credentials": "Credentials",
"desktop.backup.credentialsExclude": "do not include",
"desktop.backup.credentialsProtect": "protect with passphrase",
"desktop.backup.passphrase": "Enter passphrase",
"desktop.backup.passphraseRepeat": "Repeat passphrase",
"desktop.backup.scope": "Scope",
"desktop.backup.scopeChat": "Chat history",
"desktop.backup.scopeLogs": "Logs",
"desktop.backup.createNow": "Create Backup Now",
"desktop.backup.kickerAuto": "AUTOMATIC",
"desktop.backup.titleAuto": "Automatic Backup",
"desktop.backup.autoDaily": "daily backup at",
"desktop.backup.time": "Time",
"desktop.backup.keep": "Keep Count",
"desktop.backup.autoHint": "Automatic backup excludes credentials — a saved passphrase would be ineffective.",
"desktop.backup.kickerExisting": "EXISTING",
"desktop.backup.titleExisting": "Existing Backups",
"desktop.backup.refresh": "Refresh",
"desktop.backup.openFolder": "Open Folder",
"desktop.backup.delete": "Delete",
"desktop.backup.restore": "Restore…",
"desktop.backup.thFilename": "FILENAME",
"desktop.backup.thCreatedAt": "CREATED AT",
"desktop.backup.thInstance": "INSTANCE",
"desktop.backup.thSize": "SIZE",
"desktop.backup.thSecrets": "CREDENTIALS",
"desktop.logs.module": "Module:",
"desktop.logs.level": "Level:",
"desktop.logs.refresh": "Refresh",
"desktop.logs.clear": "Clear",
"desktop.logs.openFolder": "Open Folder",
"desktop.logs.follow": "Follow Tail",
"desktop.settings.discard": "Discard",
"desktop.settings.save": "Save",
"desktop.settings.instance": "INSTANCE",
"desktop.settings.instanceName": "Instance Name",
"desktop.settings.apiKey": "OpenRouter API Key",
"desktop.settings.autostart": "Autostart by Manager",
"desktop.settings.autostartHint": "The instance manager starts this instance on launch and restarts it if it crashes.",
"desktop.settings.logDir": "Log Directory",
"desktop.settings.logDirHint": "Applies only to this instance. Empty means: Logs subfolder in instance directory.",
"desktop.settings.dailyCost": "Daily Budget ($ USD)",
"desktop.settings.dailyTokens": "Daily Budget (Tokens)",
"desktop.settings.general": "GENERAL",
"desktop.settings.instancesDir": "Instances Directory",
"desktop.settings.minLogLevel": "Minimum Log Level",
"desktop.settings.openRouterUrl": "OpenRouter Base URL",
"desktop.settings.language": "Language",
"desktop.settings.dcAndLicense": "DEPLOYMENTCENTER & LICENSE",
"desktop.settings.dcUrl": "Server URL",
"desktop.settings.dcToken": "Master Token",
"desktop.settings.dcEnv": "Environment",
"desktop.settings.updateChannel": "Update Channel",
"desktop.settings.licenseKey": "License Key",
"desktop.settings.errorReporting": "Automatically report errors to Deploymentcenter",
"desktop.settings.updateCheck": "Check for updates automatically on startup",
"desktop.settings.saved": "Settings and instance configuration saved successfully.",
"desktop.settings.savedLogRestart": "Saved. The log directory will take effect upon next restart.",
"desktop.info.kickerApp": "APPLICATION",
"desktop.info.build": "Build",
"desktop.info.version": "Version",
"desktop.info.buildSummary": "Build Summary",
"desktop.info.kickerInstance": "INSTANCE",
"desktop.info.currentInstance": "Current Instance",
"desktop.info.openFolder": "Open Instance Folder",
"desktop.info.name": "Name",
"desktop.info.id": "ID",
"desktop.info.path": "Path",
"desktop.info.logDir": "Log Directory",
"desktop.info.openRouter": "OpenRouter",
"desktop.info.kickerModules": "MODULES",
"desktop.info.assemblies": "Assemblies",
"desktop.info.thModule": "MODULE",
"desktop.info.thBuild": "BUILD",
"desktop.info.thBuildDate": "BUILD DATE",
"desktop.info.thChanges": "CHANGES",
"desktop.dialog.addAgent": "Create New Agent",
"desktop.dialog.agentId": "Agent ID (unique key):",
"desktop.dialog.agentDisplayName": "Display Name:",
"desktop.dialog.defaultModel": "Default Model:",
"desktop.dialog.roleDescription": "Description / Role:",
"desktop.dialog.addJobTitle": "Add / Edit Job",
"desktop.dialog.jobType": "Job Type:",
"desktop.dialog.agentWakeup": "Agent Wakeup (Periodic Execution)",
"desktop.dialog.toolJob": "Tool Job (Tool-specific Job)",
"desktop.dialog.jobTypeId": "Job Type ID / Name:",
"desktop.dialog.taskMessage": "Task Description / Message:",
"desktop.dialog.cronExpression": "Cron Expression:",
"desktop.dialog.cronHint": "Syntax: Min Hour Day Month Weekday (e.g. '*/15 * * * *' every 15 minutes)",
"desktop.dialog.runOnStart": "Run once immediately on application startup",
"desktop.dialog.addServiceTitle": "Add Service",
"desktop.dialog.serviceName": "Service Name:",
"desktop.dialog.serviceType": "Type:",
"desktop.dialog.servicePort": "Port:",
"desktop.dialog.serviceDesc": "Description:",
"desktop.dialog.restoreTitle": "Restore Backup",
"desktop.dialog.backupInfo": "Backup Information",
"desktop.dialog.backupInstance": "Instance: {0}",
"desktop.dialog.backupCreated": "Created: {0:g}",
"desktop.dialog.backupFiles": "Files: {0}",
"desktop.dialog.backupSecrets": "Credentials: {0}",
"desktop.dialog.targetDir": "Target Directory:",
"desktop.dialog.chooseFolder": "Choose Folder",
"desktop.dialog.restoreHint": "Note: It is recommended to restore into a new folder to avoid conflicts with running processes.",
"desktop.dialog.passphrase": "Passphrase:",
"desktop.dialog.passphraseProtected": "This backup contains protected credentials.",
"desktop.dialog.overwriteExisting": "Overwrite existing files in target folder",
"desktop.dialog.licenseTitle": "ClawdDotNet – License",
"desktop.dialog.licenseKey": "License Key",
"desktop.dialog.hardwareId": "Hardware ID (for support):",
"desktop.dialog.verify": "Verify",
"desktop.dialog.textEditorTitle": "Edit Text",
"desktop.instancePicker.titleSwitch": "Switch Instance",
"desktop.instancePicker.titleSelect": "Choose Instance",
"desktop.instancePicker.hintSwitch": "Each instance runs independently with its own agents and data. Switching restarts ClawdDotNet with the selected instance.",
"desktop.instancePicker.hintSelect": "Each instance runs independently with its own agents and data.",
"desktop.instancePicker.confirmSwitch": "Switch",
"desktop.instancePicker.confirmStart": "Start",
"desktop.instancePicker.newInstancePlaceholder": "Name of new instance",
"desktop.instancePicker.create": "Create",
"desktop.instancePicker.active": "Active",
"desktop.instancePicker.dirLabel": "Directory: {0}",
"desktop.instancePicker.agentsCount": "{0} Agents · API Key {1}",
"desktop.instancePicker.apiKeyConfigured": "configured",
"desktop.instancePicker.apiKeyMissing": "missing",
"desktop.instancePicker.tooltipConfigured": "API key configured",
"desktop.instancePicker.tooltipMissing": "No API key — agents remain inactive",
"desktop.instancePicker.nameRequired": "Please enter an instance name.",
"desktop.instancePicker.createFailed": "Instance could not be created: {0}",
"desktop.startMode.title": "ClawdDotNet – Select Start Mode",
"desktop.startMode.question": "How would you like to start ClawdDotNet?",
"desktop.startMode.managerBtn": "Launch Instance Manager (Recommended)",
"desktop.startMode.standaloneBtn": "Start as Standalone Instance",
"desktop.startMode.explanation": "• Instance Manager (Recommended):\n Starts the central background manager and opens management in your browser. Enables stable parallel operation of multiple instances without crash risks.\n\n• Standalone Mode:\n Starts exactly one instance directly as a single desktop window.",
"desktop.standaloneSwitch.title": "ClawdDotNet – Instance Switch (Standalone)",
"desktop.standaloneSwitch.warning": "Attention: Standalone Mode",
"desktop.standaloneSwitch.text": "This instance runs standalone without the Instance Manager.\n\nIf you switch instances here, this instance will be fully stopped and all running agents and background services will end.\n\nRecommendation: Launch the Instance Manager instead to run multiple instances concurrently and independently.",
"desktop.standaloneSwitch.switchAnyway": "Switch Instance Anyway",
"desktop.toolSettings.noSettings": "This tool does not require additional settings.",
"desktop.toolSettings.host": "Host",
"desktop.toolSettings.port": "Port",
"desktop.toolSettings.username": "Username",
"desktop.toolSettings.password": "Password",
"desktop.toolSettings.rootPath": "Root Path",
"desktop.toolSettings.imapHost": "IMAP Host",
"desktop.toolSettings.imapPort": "IMAP Port",
"desktop.toolSettings.smtpHost": "SMTP Host",
"desktop.toolSettings.smtpPort": "SMTP Port",
"desktop.toolSettings.allowedRecipients": "Allowed Recipients",
"desktop.toolSettings.type": "Type",
"desktop.toolSettings.connectionString": "Connection String",
"desktop.toolSettings.accessLevel": "Access Level",
"desktop.toolSettings.allowedTables": "Allowed Tables",
"desktop.toolSettings.botToken": "Bot Token",
"desktop.toolSettings.defaultChatId": "Default Chat ID",
"desktop.toolSettings.allowedChatIds": "Allowed Chat IDs",
"desktop.toolSettings.defaultProvider": "Default Provider",
"desktop.toolSettings.cacheTtl": "Cache TTL (seconds)",
"desktop.toolSettings.twelveDataKey": "Twelve Data Key",
"desktop.toolSettings.alphaVantageKey": "Alpha Vantage Key",
"desktop.toolSettings.allowedDomains": "Allowed Domains",
"desktop.toolSettings.maxResponseKb": "Max Response Size (KB)",
"desktop.toolSettings.userAgent": "User Agent",
"desktop.toolSettings.xApiKey": "X (Twitter) API Key",
"desktop.toolSettings.xAccounts": "X Accounts",
"desktop.toolSettings.subreddits": "Subreddits",
"desktop.toolSettings.redditPostLimit": "Reddit Post Limit",
"desktop.toolSettings.openRouterKey": "OpenRouter Key",
"desktop.toolSettings.sttModel": "STT Model",
"desktop.toolSettings.youtubeChannels": "YouTube Channels",
"desktop.toolSettings.fileRwPersonalExt": "Allowed File Extensions (personal)",
"desktop.toolSettings.fileRwSharedAccess": "Access Level (shared)",
"desktop.toolSettings.fileRwSharedExt": "Allowed File Extensions (shared)",
"desktop.toolSettings.fileRwProtectedPaths": "Protected Paths",
"nav.chat": "Chat",
"nav.agents": "Agents",
"nav.tasks": "Tasks",
"nav.usage": "Token Usage",
"nav.agentChats": "Agent Chats",
"nav.backup": "Backup",
"nav.logs": "Logs",
"nav.settings": "Settings",
"nav.info": "Info",
"common.all": "All",
"common.save": "Save",
"common.cancel": "Cancel",
"common.delete": "Delete",
"common.edit": "Edit",
"common.refresh": "Refresh",
"common.close": "Close",
"common.discard": "Discard",
"common.ready": "Ready",
"common.loading": "Loading…",
"common.create": "Create",
"common.add": "Add",
"common.apply": "Apply",
"common.ok": "OK",
"common.exit": "Exit"
}