Stufe 4c (#764): Kommentare in C#, WebUI-Skripten, CSS und HTML auf Englisch

Rund 2.600 Kommentarblöcke in 310 C#-Dateien übersetzt, Begründungen vollständig
erhalten: blockweise gleiche Anzahl wie vorher, kein Block unter 60 % seiner
Länge, der Code außerhalb der Kommentare per Tokenizer byte-identisch geprüft.
Zitate deutscher Oberflächentexte und Testdaten bleiben deutsch.

WebUI: Kommentare in JS/CSS/HTML übersetzt, JSDoc-@param an die in 4b
umbenannten Parameter angeglichen.

Dabei behoben:
- 4b hatte "tag" (HTML-Tag) als deutsches "Tag" gelesen und zu "dayValue"
  gemacht (makeElement, svg, Tagesübersicht) - zurück auf tag/day.
- Aufgaben "InProgress" bekamen die Plakettenart "laufend", für die es nie eine
  CSS-Klasse gab - jetzt "success" wie laufende Instanzen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-10-04 12:45:03 +02:00
co-authored by Claude Opus 5.5
parent da4ceac7d5
commit 837a634a22
311 changed files with 7238 additions and 7252 deletions
@@ -7,13 +7,13 @@ using Shouldly;
namespace ClawdDotNet.Core.Tests.Security;
/// <summary>
/// S7 aus der Bestandsaufnahme: OpenRouter-Schlüssel, Datenbank-Verbindungszeichenfolgen
/// samt Passwort, Mail-Zugangsdaten und das Telegram-2FA-Passwort lagen im Klartext in
/// den JSON-Dateien. Wer die Dateien lesen konnte, hatte alle Zugänge.
/// S7 from the inventory: OpenRouter keys, database connection strings
/// including password, mail credentials and the Telegram 2FA password were in plain text in
/// the JSON files. Whoever could read the files had all access.
///
/// Mit der Linux-Portierung kam <c>enc:v2</c> dazu (AES-GCM statt DPAPI). Die Tests
/// laufen deshalb gegen einen eigenen Schlüssel in einem Wegwerfverzeichnis — sonst
/// würden sie den echten Benutzerschlüssel anlegen oder lesen.
/// With the Linux port <c>enc:v2</c> was added (AES-GCM instead of DPAPI). The tests
/// therefore run against a key of their own in a throwaway directory — otherwise
/// they would create or read the real user key.
/// </summary>
[Collection(SecretKeyCollection.Name)]
public sealed class SecretProtectorTests : IDisposable
@@ -30,7 +30,7 @@ public sealed class SecretProtectorTests : IDisposable
{
SecretKeyStore.UseDirectory(null);
try { Directory.Delete(_keyDir, recursive: true); }
catch { /* Aufräumen ist Nebensache */ }
catch { /* Cleaning up is secondary */ }
}
[Fact]
@@ -64,8 +64,8 @@ public sealed class SecretProtectorTests : IDisposable
[Fact]
public void Mehrfaches_Verschluesseln_veraendert_nichts()
{
// Wichtig, weil beim Speichern nicht bekannt ist, ob ein Wert schon
// verschlüsselt war.
// Important, because when saving it is not known whether a value was already
// encrypted.
var once = SecretProtector.Protect("geheim");
var twice = SecretProtector.Protect(once);
@@ -76,14 +76,14 @@ public sealed class SecretProtectorTests : IDisposable
[Fact]
public void Klartext_aus_alten_Konfigurationen_wird_unveraendert_gelesen()
{
// Zwei Gründe, nicht nur einer:
// Two reasons, not just one:
//
// 1. Bestehende Installationen müssen ohne Zutun weiterlaufen.
// 2. Die Erstinstallation über den update-agent schreibt Lizenzschlüssel und
// Deploymentcenter-Token als Klartext in die Settings.json — der Installer
// kennt unsere Verschlüsselung nicht. Beim ersten Speichern werden sie
// übernommen und verschlüsselt. Wer diesen Zweig entfernt, weil er nach
// Altlast aussieht, sperrt jede frisch installierte Instanz aus.
// 1. Existing installations must keep running without intervention.
// 2. The initial installation via the update-agent writes license key and
// Deploymentcenter token as plain text into Settings.json — the installer
// does not know our encryption. On the first save they are
// taken over and encrypted. Whoever removes this branch because it looks like
// legacy locks out every freshly installed instance.
SecretProtector.Unprotect("sk-or-alter-klartext").ShouldBe("sk-or-alter-klartext");
}
@@ -99,8 +99,8 @@ public sealed class SecretProtectorTests : IDisposable
[Fact]
public void Ein_beschaedigter_Wert_wird_gemeldet_statt_stillschweigend_durchgereicht()
{
// Nach Benutzer- oder Rechnerwechsel ist der Wert hier nicht lesbar. Ihn als
// Klartext auszugeben würde einen unbrauchbaren Schlüssel an die API schicken.
// After a change of user or machine the value is not readable here. Outputting it as
// plain text would send an unusable key to the API.
Should.Throw<SecretProtectionException>(
() => SecretProtector.Unprotect("enc:v1:das-ist-kein-gueltiger-block"));
@@ -117,14 +117,14 @@ public sealed class SecretProtectorTests : IDisposable
}
// ═══════════════════════════════════════════════════════════
// Plattformübergreifendes Format (enc:v2)
// Cross-platform format (enc:v2)
// ═══════════════════════════════════════════════════════════
[Fact]
public void Neue_Werte_werden_im_plattformuebergreifenden_Format_geschrieben()
{
// Der Kern der Portierung: Unter Linux gab die vorige Fassung hier den Klartext
// zurück. Auf einem Server, der gesichert wird, war das schlechter als nichts.
// The core of the port: under Linux the previous version returned the plain text
// here. On a server that gets backed up, that was worse than nothing.
var value = SecretProtector.Protect("sk-or-v1-geheim");
value.ShouldStartWith("enc:v2:");
@@ -133,9 +133,9 @@ public sealed class SecretProtectorTests : IDisposable
[Fact]
public void Dieselbe_Eingabe_ergibt_zweimal_verschiedene_Ausgaben()
{
// AES-GCM mit zufälligem Nonce: Aus gleichen Werten dürfen keine gleichen
// Blöcke werden, sonst verrät die Konfigurationsdatei, wo dasselbe Passwort
// mehrfach benutzt wird.
// AES-GCM with a random nonce: identical values must not become identical
// blocks, otherwise the configuration file reveals where the same password
// is used several times.
var a = SecretProtector.Protect("dasselbe-passwort");
var b = SecretProtector.Protect("dasselbe-passwort");
@@ -147,8 +147,8 @@ public sealed class SecretProtectorTests : IDisposable
[Fact]
public void Ein_veraenderter_Block_wird_erkannt_und_nicht_entschluesselt()
{
// Der Zweck der Authentifizierung in AES-GCM: Ein manipulierter Block darf
// keinen halb geratenen Klartext ergeben.
// The purpose of the authentication in AES-GCM: a tampered block must
// not yield half-guessed plain text.
var value = SecretProtector.Protect("sk-or-v1-geheim")!;
var payload = Convert.FromBase64String(value["enc:v2:".Length..]);
payload[^1] ^= 0xFF;
@@ -160,8 +160,8 @@ public sealed class SecretProtectorTests : IDisposable
[Fact]
public void Ein_fremder_Schluessel_kann_den_Wert_nicht_lesen()
{
// Genau die Eigenschaft, die den Schutz ausmacht: Die Konfigurationsdatei allein
// nützt auf einem anderen Rechner nichts, weil der Schlüssel nicht mitreist.
// Exactly the property that makes up the protection: the configuration file alone
// is of no use on another machine, because the key does not travel along.
var value = SecretProtector.Protect("sk-or-v1-geheim");
var fremd = Path.Combine(Path.GetTempPath(), "clawd-tests", Guid.NewGuid().ToString("N"));
@@ -191,10 +191,10 @@ public sealed class SecretProtectorTests : IDisposable
}
/// <summary>
/// <c>[LinuxFact]</c> hält den Test unter Windows an, der Plattform-Analysator sieht
/// das aber nicht — er kennt nur Attribute wie <c>SupportedOSPlatform</c>. Ohne die
/// Angabe blieb CA1416 als einzige Warnung im Build stehen, und eine einzelne
/// Warnung, die immer da ist, liest bald niemand mehr.
/// <c>[LinuxFact]</c> stops the test under Windows, but the platform analyzer does not see
/// that — it only knows attributes like <c>SupportedOSPlatform</c>. Without the
/// annotation CA1416 remained as the only warning in the build, and a single
/// warning that is always there soon nobody reads any more.
/// </summary>
[LinuxFact]
[System.Runtime.Versioning.SupportedOSPlatform("linux")]
@@ -209,8 +209,8 @@ public sealed class SecretProtectorTests : IDisposable
[LinuxFact]
public void Ein_DPAPI_Wert_aus_einer_Windows_Instanz_wird_mit_Begruendung_abgewiesen()
{
// Der Umzugsfall. Ihn als Klartext durchzureichen würde einen unbrauchbaren
// Schlüssel an die API schicken — die Meldung muss sagen, was zu tun ist.
// The moving case. Passing it on as plain text would send an unusable
// key to the API — the message must say what to do.
var ex = Should.Throw<SecretProtectionException>(
() => SecretProtector.Unprotect("enc:v1:" + Convert.ToBase64String([1, 2, 3, 4])));
@@ -233,7 +233,7 @@ public sealed class ConfigSecretsTests : IDisposable
{
SecretKeyStore.UseDirectory(null);
try { Directory.Delete(_keyDir, recursive: true); }
catch { /* Aufräumen ist Nebensache */ }
catch { /* Cleaning up is secondary */ }
}
[Theory]
@@ -326,8 +326,8 @@ public sealed class ConfigSecretsTests : IDisposable
[Fact]
public void Werte_aus_deserialisiertem_JSON_werden_ebenfalls_erfasst()
{
// Nach dem Deserialisieren stecken die Werte als JsonElement im Wörterbuch,
// nicht als String.
// After deserialization the values sit in the dictionary as JsonElement,
// not as string.
var json = """
{ "agentId": "test",
"tools": { "Mail": { "smtpHost": "smtp.example.com", "password": "geheim123" } } }