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
@@ -12,14 +12,14 @@ public sealed class FTPTool : IAgentTool
|
||||
public string Name => "FTP";
|
||||
|
||||
public string Description => """
|
||||
Erlaubt den Dateitransfer zwischen dem lokalen Workspace und einem Remote-FTP/FTPS-Server.
|
||||
Allows file transfer between the local workspace and a remote FTP/FTPS server.
|
||||
|
||||
Für localPath werden die gleichen Workspace-Prefixe wie beim FileRW-Tool unterstützt:
|
||||
- "personal:website/index.html" → Datei im persönlichen Agent-Workspace
|
||||
- "shared:website/index.html" → Datei im geteilten SharedWorkspace
|
||||
- Ohne Prefix wird der persönliche Workspace verwendet.
|
||||
For localPath, the same workspace prefixes as in the FileRW tool are supported:
|
||||
- "personal:website/index.html" → file in the agent's personal workspace
|
||||
- "shared:website/index.html" → file in the shared SharedWorkspace
|
||||
- Without prefix, the personal workspace is used.
|
||||
|
||||
Beispiel Upload: { "action": "upload", "localPath": "shared:website/index.html", "remotePath": "/index.html" }
|
||||
Example upload: { "action": "upload", "localPath": "shared:website/index.html", "remotePath": "/index.html" }
|
||||
""";
|
||||
|
||||
public JsonElement InputSchema { get; } = JsonDocument.Parse("""
|
||||
@@ -29,15 +29,15 @@ public sealed class FTPTool : IAgentTool
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": ["upload", "download", "list", "delete"],
|
||||
"description": "Die auszuführende Aktion"
|
||||
"description": "The action to perform"
|
||||
},
|
||||
"localPath": {
|
||||
"type": "string",
|
||||
"description": "Pfad zur lokalen Datei mit optionalem Workspace-Prefix: 'personal:datei.txt' (Standard) oder 'shared:datei.txt' für den SharedWorkspace. Ohne Prefix wird der persönliche Workspace verwendet."
|
||||
"description": "Path to local file with optional workspace prefix: 'personal:file.txt' (default) or 'shared:file.txt' for the SharedWorkspace. Without prefix, personal workspace is used."
|
||||
},
|
||||
"remotePath": {
|
||||
"type": "string",
|
||||
"description": "Pfad auf dem FTP-Server"
|
||||
"description": "Path on the FTP server"
|
||||
}
|
||||
},
|
||||
"required": ["action"]
|
||||
@@ -62,7 +62,7 @@ public sealed class FTPTool : IAgentTool
|
||||
|
||||
if (string.IsNullOrWhiteSpace(host))
|
||||
{
|
||||
return ToolResult.Fail("Konfigurationsfehler: 'host' ist erforderlich.");
|
||||
return ToolResult.Fail("Configuration error: 'host' is required.");
|
||||
}
|
||||
|
||||
// SSL/TLS-Modus aus Config lesen (Standard: true)
|
||||
@@ -91,13 +91,13 @@ public sealed class FTPTool : IAgentTool
|
||||
"download" => await HandleDownloadAsync(ftpClient, input, context, ct),
|
||||
"list" => await HandleListAsync(ftpClient, input, context, ct),
|
||||
"delete" => await HandleDeleteAsync(ftpClient, input, context, ct),
|
||||
_ => ToolResult.Fail($"Unbekannte Aktion: {action}")
|
||||
_ => ToolResult.Fail($"Unknown action: {action}")
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Logger.LogError(ex, "Fehler bei FTP Aktion {Action} auf {Host}", action, host);
|
||||
return ToolResult.Fail($"Fehler: {ex.Message}");
|
||||
context.Logger.LogError(ex, "Error during FTP action {Action} on {Host}", action, host);
|
||||
return ToolResult.Fail($"Error: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -119,24 +119,24 @@ public sealed class FTPTool : IAgentTool
|
||||
|
||||
if (localPath.StartsWith("shared:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
relativePath = localPath[7..]; // nach "shared:"
|
||||
relativePath = localPath[7..]; // after "shared:"
|
||||
basePath = context.SharedWorkspacePath
|
||||
?? throw new ArgumentException("Kein SharedWorkspace konfiguriert.");
|
||||
?? throw new ArgumentException("No SharedWorkspace configured.");
|
||||
label = "shared";
|
||||
}
|
||||
else if (localPath.StartsWith("personal:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
relativePath = localPath[9..]; // nach "personal:"
|
||||
relativePath = localPath[9..]; // after "personal:"
|
||||
basePath = context.WorkspacePath
|
||||
?? throw new ArgumentException("Kein persönlicher Workspace konfiguriert.");
|
||||
?? throw new ArgumentException("No personal workspace configured.");
|
||||
label = "personal";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Kein Prefix → persönlicher Workspace als Standard
|
||||
// No prefix → personal workspace as default
|
||||
relativePath = localPath;
|
||||
basePath = context.WorkspacePath
|
||||
?? throw new ArgumentException("Kein persönlicher Workspace konfiguriert.");
|
||||
?? throw new ArgumentException("No personal workspace configured.");
|
||||
label = "personal";
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ public sealed class FTPTool : IAgentTool
|
||||
if (!PathBoundary.IsInside(fullPath, basePath))
|
||||
{
|
||||
throw new UnauthorizedAccessException(
|
||||
$"Zugriff außerhalb des {label} Workspace-Verzeichnisses verweigert.");
|
||||
$"Access outside the {label} workspace directory denied.");
|
||||
}
|
||||
|
||||
return (fullPath, label);
|
||||
@@ -174,7 +174,7 @@ public sealed class FTPTool : IAgentTool
|
||||
|
||||
if (string.IsNullOrWhiteSpace(localRel) || string.IsNullOrWhiteSpace(remote))
|
||||
{
|
||||
return ToolResult.Fail("'localPath' und 'remotePath' sind für upload erforderlich.");
|
||||
return ToolResult.Fail("'localPath' and 'remotePath' are required for upload.");
|
||||
}
|
||||
|
||||
try
|
||||
@@ -182,11 +182,11 @@ public sealed class FTPTool : IAgentTool
|
||||
var (localFull, wsLabel) = ResolveLocalPath(localRel, context);
|
||||
|
||||
if (!File.Exists(localFull))
|
||||
return ToolResult.Fail($"Lokale Datei nicht gefunden: {localRel} (aufgelöst: {localFull})");
|
||||
return ToolResult.Fail($"Local file not found: {localRel} (resolved: {localFull})");
|
||||
|
||||
var status = await client.UploadFile(localFull, remote, FtpRemoteExists.Overwrite, true, FtpVerify.None, null, ct);
|
||||
if (status != FtpStatus.Success)
|
||||
return ToolResult.Fail($"Upload fehlgeschlagen: {status}");
|
||||
return ToolResult.Fail($"Upload failed: {status}");
|
||||
|
||||
// Bei HTML/CSS/JS-Uploads: .htaccess für UTF-8 sicherstellen
|
||||
var extra = "";
|
||||
@@ -194,10 +194,10 @@ public sealed class FTPTool : IAgentTool
|
||||
{
|
||||
var htaccessDeployed = await EnsureHtaccessAsync(client, remote, context.Logger, ct);
|
||||
if (htaccessDeployed)
|
||||
extra = " (.htaccess für UTF-8-Encoding wurde automatisch erstellt)";
|
||||
extra = " (.htaccess for UTF-8 encoding was automatically created)";
|
||||
}
|
||||
|
||||
return ToolResult.Ok($"[{wsLabel} Workspace] Datei erfolgreich hochgeladen: {localRel} → {remote}{extra}");
|
||||
return ToolResult.Ok($"[{wsLabel} Workspace] File successfully uploaded: {localRel} → {remote}{extra}");
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
@@ -265,7 +265,7 @@ public sealed class FTPTool : IAgentTool
|
||||
|
||||
if (status == FtpStatus.Success)
|
||||
{
|
||||
logger.LogInformation("UTF-8 Charset-Block an bestehende .htaccess angehängt: {Path}", htaccessPath);
|
||||
logger.LogInformation("UTF-8 charset block appended to existing .htaccess: {Path}", htaccessPath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -279,14 +279,14 @@ public sealed class FTPTool : IAgentTool
|
||||
|
||||
if (status == FtpStatus.Success)
|
||||
{
|
||||
logger.LogInformation("Neue UTF-8 .htaccess erstellt: {Path}", htaccessPath);
|
||||
logger.LogInformation("New UTF-8 .htaccess created: {Path}", htaccessPath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Konnte .htaccess nicht automatisch anpassen (nicht kritisch)");
|
||||
logger.LogWarning(ex, "Could not automatically adjust .htaccess (non-critical)");
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -299,7 +299,7 @@ public sealed class FTPTool : IAgentTool
|
||||
|
||||
if (string.IsNullOrWhiteSpace(localRel) || string.IsNullOrWhiteSpace(remote))
|
||||
{
|
||||
return ToolResult.Fail("'localPath' und 'remotePath' sind für download erforderlich.");
|
||||
return ToolResult.Fail("'localPath' and 'remotePath' are required for download.");
|
||||
}
|
||||
|
||||
try
|
||||
@@ -311,8 +311,8 @@ public sealed class FTPTool : IAgentTool
|
||||
|
||||
var status = await client.DownloadFile(localFull, remote, FtpLocalExists.Overwrite, FtpVerify.None, null, ct);
|
||||
return status == FtpStatus.Success
|
||||
? ToolResult.Ok($"[{wsLabel} Workspace] Datei erfolgreich heruntergeladen: {remote} → {localRel}")
|
||||
: ToolResult.Fail($"Download fehlgeschlagen: {status}");
|
||||
? ToolResult.Ok($"[{wsLabel} Workspace] File successfully downloaded: {remote} → {localRel}")
|
||||
: ToolResult.Fail($"Download failed: {status}");
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
@@ -344,9 +344,9 @@ public sealed class FTPTool : IAgentTool
|
||||
private async Task<ToolResult> HandleDeleteAsync(IAsyncFtpClient client, JsonElement input, AgentToolContext context, CancellationToken ct)
|
||||
{
|
||||
var remote = input.TryGetProperty("remotePath", out var rp) ? rp.GetString() : null;
|
||||
if (string.IsNullOrWhiteSpace(remote)) return ToolResult.Fail("'remotePath' ist für delete erforderlich.");
|
||||
if (string.IsNullOrWhiteSpace(remote)) return ToolResult.Fail("'remotePath' is required for delete.");
|
||||
|
||||
await client.DeleteFile(remote, ct);
|
||||
return ToolResult.Ok($"Datei/Verzeichnis gelöscht: {remote}");
|
||||
return ToolResult.Ok($"File/directory deleted: {remote}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user