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:
co-authored by
Claude Opus 5.5
parent
69718122f3
commit
ad71682c2d
@@ -15,7 +15,7 @@ public sealed class MailTool : IAgentTool, IToolJobProvider
|
||||
{
|
||||
public string Name => "Mail";
|
||||
|
||||
public string Description => "Erlaubt das Senden und Empfangen von E-Mails über SMTP und IMAP.";
|
||||
public string Description => "Allows sending and receiving emails via SMTP and IMAP.";
|
||||
|
||||
public JsonElement InputSchema { get; } = JsonDocument.Parse("""
|
||||
{
|
||||
@@ -24,27 +24,27 @@ public sealed class MailTool : IAgentTool, IToolJobProvider
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": ["send", "read_inbox", "read_message", "mark_read"],
|
||||
"description": "Die auszuführende Aktion"
|
||||
"description": "The action to perform"
|
||||
},
|
||||
"to": {
|
||||
"type": "string",
|
||||
"description": "Empfänger-E-Mail (nur für send)"
|
||||
"description": "Recipient email (only for send)"
|
||||
},
|
||||
"subject": {
|
||||
"type": "string",
|
||||
"description": "Betreff (nur für send)"
|
||||
"description": "Subject (only for send)"
|
||||
},
|
||||
"body": {
|
||||
"type": "string",
|
||||
"description": "Inhalt der E-Mail (nur für send)"
|
||||
"description": "Body of the email (only for send)"
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Maximale Anzahl an E-Mails zum Abrufen (nur für read_inbox)"
|
||||
"description": "Maximum number of emails to retrieve (only for read_inbox)"
|
||||
},
|
||||
"messageId": {
|
||||
"type": "string",
|
||||
"description": "Die ID der Nachricht (für read_message oder mark_read)"
|
||||
"description": "The message ID (for read_message or mark_read)"
|
||||
}
|
||||
},
|
||||
"required": ["action"]
|
||||
@@ -67,28 +67,28 @@ public sealed class MailTool : IAgentTool, IToolJobProvider
|
||||
"read_inbox" => await HandleReadInboxAsync(input, context, ct),
|
||||
"read_message" => await HandleReadMessageAsync(input, context, ct),
|
||||
"mark_read" => await HandleMarkReadAsync(input, context, ct),
|
||||
_ => ToolResult.Fail($"Unbekannte Aktion: {action}")
|
||||
_ => ToolResult.Fail($"Unknown action: {action}")
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Logger.LogError(ex, "Fehler bei Mail Aktion {Action}", action);
|
||||
return ToolResult.Fail($"Fehler: {ex.Message}");
|
||||
context.Logger.LogError(ex, "Error during Mail action {Action}", action);
|
||||
return ToolResult.Fail($"Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ToolResult> HandleSendAsync(JsonElement input, AgentToolContext context, CancellationToken ct)
|
||||
{
|
||||
var to = input.TryGetProperty("to", out var t) ? t.GetString() : null;
|
||||
var subject = (input.TryGetProperty("subject", out var s) ? s.GetString() : null) ?? "(Kein Betreff)";
|
||||
var subject = (input.TryGetProperty("subject", out var s) ? s.GetString() : null) ?? "(No Subject)";
|
||||
var body = (input.TryGetProperty("body", out var b) ? b.GetString() : null) ?? "";
|
||||
|
||||
if (string.IsNullOrWhiteSpace(to)) return ToolResult.Fail("'to' ist erforderlich.");
|
||||
if (string.IsNullOrWhiteSpace(to)) return ToolResult.Fail("'to' is required.");
|
||||
|
||||
// Sicherheitsprüfung: Erlaubte Empfänger
|
||||
if (!IsRecipientAllowed(to, context))
|
||||
{
|
||||
return ToolResult.Fail($"Sicherheitsfehler: Senden an '{to}' ist nicht erlaubt.");
|
||||
return ToolResult.Fail($"Security error: Sending to '{to}' is not allowed.");
|
||||
}
|
||||
|
||||
if (!TryReadAccess(context, "smtpHost", "smtpPort", 587, out var access, out var fehlt))
|
||||
@@ -106,7 +106,7 @@ public sealed class MailTool : IAgentTool, IToolJobProvider
|
||||
await client.SendAsync(message, ct);
|
||||
await client.DisconnectAsync(true, ct);
|
||||
|
||||
return ToolResult.Ok($"E-Mail erfolgreich an {to} gesendet.");
|
||||
return ToolResult.Ok($"Email successfully sent to {to}.");
|
||||
}
|
||||
|
||||
private async Task<ToolResult> HandleReadInboxAsync(JsonElement input, AgentToolContext context, CancellationToken ct)
|
||||
@@ -143,7 +143,7 @@ public sealed class MailTool : IAgentTool, IToolJobProvider
|
||||
{
|
||||
if (!input.TryGetProperty("messageId", out var mi) || !int.TryParse(mi.GetString(), out var index))
|
||||
{
|
||||
return ToolResult.Fail("'messageId' (Index) ist erforderlich.");
|
||||
return ToolResult.Fail("'messageId' (index) is required.");
|
||||
}
|
||||
|
||||
if (!TryReadAccess(context, "imapHost", "imapPort", 993, out var access, out var fehlt))
|
||||
@@ -173,7 +173,7 @@ public sealed class MailTool : IAgentTool, IToolJobProvider
|
||||
{
|
||||
if (!input.TryGetProperty("messageId", out var mi) || !int.TryParse(mi.GetString(), out var index))
|
||||
{
|
||||
return ToolResult.Fail("'messageId' (Index) ist erforderlich.");
|
||||
return ToolResult.Fail("'messageId' (index) is required.");
|
||||
}
|
||||
|
||||
if (!TryReadAccess(context, "imapHost", "imapPort", 993, out var access, out var fehlt))
|
||||
@@ -187,7 +187,7 @@ public sealed class MailTool : IAgentTool, IToolJobProvider
|
||||
await client.Inbox!.AddFlagsAsync(index, MessageFlags.Seen, true, ct);
|
||||
|
||||
await client.DisconnectAsync(true, ct);
|
||||
return ToolResult.Ok($"Nachricht {index} als gelesen markiert.");
|
||||
return ToolResult.Ok($"Message {index} marked as read.");
|
||||
}
|
||||
|
||||
/// <summary>Ein Zugang: Server, Port und Anmeldedaten — vollständig oder gar nicht.</summary>
|
||||
@@ -224,8 +224,8 @@ public sealed class MailTool : IAgentTool, IToolJobProvider
|
||||
|
||||
if (missing.Count > 0)
|
||||
{
|
||||
error = "Das Mail-Werkzeug ist nicht vollständig eingerichtet. " +
|
||||
$"Es fehlt in der Werkzeug-Konfiguration: {string.Join(", ", missing)}.";
|
||||
error = "The mail tool is not fully configured. " +
|
||||
$"Missing in tool configuration: {string.Join(", ", missing)}.";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ public sealed class MailTool : IAgentTool, IToolJobProvider
|
||||
|
||||
public IReadOnlyList<ToolJobDefinition> GetJobDefinitions() =>
|
||||
[
|
||||
new("mail_check_unread", "Mail Check", "Prüft regelmäßig auf neue ungelesene E-Mails")
|
||||
new("mail_check_unread", "Mail Check", "Periodically checks for new unread emails")
|
||||
];
|
||||
|
||||
public async Task<ToolJobResult> ExecuteJobAsync(
|
||||
@@ -270,7 +270,7 @@ public sealed class MailTool : IAgentTool, IToolJobProvider
|
||||
string? workspacePath = null)
|
||||
{
|
||||
if (jobTypeId != "mail_check_unread")
|
||||
return ToolJobResult.NoAction($"Unbekannter Job: {jobTypeId}");
|
||||
return ToolJobResult.NoAction($"Unknown job: {jobTypeId}");
|
||||
|
||||
try
|
||||
{
|
||||
@@ -281,7 +281,7 @@ public sealed class MailTool : IAgentTool, IToolJobProvider
|
||||
var password = toolConfig.TryGetValue("password", out var pw) ? pw?.ToString() : null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(imapHost) || string.IsNullOrWhiteSpace(username))
|
||||
return ToolJobResult.NoAction("Mail-Konfiguration unvollständig");
|
||||
return ToolJobResult.NoAction("Mail configuration incomplete");
|
||||
|
||||
await client.ConnectAsync(imapHost, imapPort, true, ct);
|
||||
await client.AuthenticateAsync(username ?? "", password ?? "", ct);
|
||||
@@ -294,7 +294,7 @@ public sealed class MailTool : IAgentTool, IToolJobProvider
|
||||
if (uids.Count == 0)
|
||||
{
|
||||
await client.DisconnectAsync(true, ct);
|
||||
return ToolJobResult.NoAction("Keine neuen ungelesenen Mails");
|
||||
return ToolJobResult.NoAction("No new unread emails");
|
||||
}
|
||||
|
||||
// Um zu vermeiden, dass wir bei jedem Tick für die gleichen Mails wecken,
|
||||
@@ -307,7 +307,7 @@ public sealed class MailTool : IAgentTool, IToolJobProvider
|
||||
if (newUids.Count == 0)
|
||||
{
|
||||
await client.DisconnectAsync(true, ct);
|
||||
return ToolJobResult.NoAction("Keine neuen Mails seit der letzten Prüfung");
|
||||
return ToolJobResult.NoAction("No new emails since last check");
|
||||
}
|
||||
|
||||
// Die neuesten Nachrichten abrufen für die Zusammenfassung
|
||||
@@ -315,10 +315,10 @@ public sealed class MailTool : IAgentTool, IToolJobProvider
|
||||
foreach (var uid in newUids.Take(5))
|
||||
{
|
||||
var msg = await client.Inbox!.GetMessageAsync(uid, ct);
|
||||
summary.Add($"- Von: {msg.From}, Betreff: {msg.Subject}");
|
||||
summary.Add($"- From: {msg.From}, Subject: {msg.Subject}");
|
||||
}
|
||||
|
||||
if (newUids.Count > 5) summary.Add($"- ... und {newUids.Count - 5} weitere");
|
||||
if (newUids.Count > 5) summary.Add($"- ... and {newUids.Count - 5} more");
|
||||
|
||||
// Höchste UID speichern
|
||||
var maxUid = newUids.Max(u => u.Id);
|
||||
@@ -327,14 +327,14 @@ public sealed class MailTool : IAgentTool, IToolJobProvider
|
||||
await client.DisconnectAsync(true, ct);
|
||||
|
||||
return ToolJobResult.Wake(
|
||||
$"Du hast {newUids.Count} neue ungelesene E-Mails erhalten:\n" + string.Join("\n", summary),
|
||||
logSummary: $"{newUids.Count} neue Mails gefunden"
|
||||
$"You have received {newUids.Count} new unread email(s):\n" + string.Join("\n", summary),
|
||||
logSummary: $"{newUids.Count} new email(s) found"
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Fehler im Mail Background Job");
|
||||
return ToolJobResult.NoAction($"Fehler: {ex.Message}");
|
||||
logger.LogError(ex, "Error in Mail background job");
|
||||
return ToolJobResult.NoAction($"Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user