refactor: switch the whole project to English
talk is going to be published. Code comments, CLI output, error messages,
daemon log lines, MCP descriptions and results, test names and assertions,
README, spec, skill, build scripts and flake are now English. Only the
spoken announcement texts stay bilingual: the wrap result and the test
sentence are German or English depending on the detected language.
Language fallback is now configurable: `lang = "en" | "de"` (env
TALK_LANG, default en) is used when detection cannot tell (numbers, single
words, ties) and for talk's own announcements. langdetect.Detect takes the
fallback as an argument instead of hard-coding German. `talk setup` asks for
it; `talk doctor` and `talk status` show it.
Vocabulary pinned across packages so the process-boundary tests observe
stable strings: chain log lines ("chain: tier X skipped (not available)",
"falling back to the next tier", "spoken via X"), queue lines ("discarded:
quiet hours ... active", "replaced by toast", "max_age"), CLI notes
("speaking directly", "daemon started at", "no daemon", "nothing to cancel"),
status block with aligned English labels, MCP messages, origin guard 403.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
4d30bd95ad
commit
85fa64cfe6
95 files changed
+4707
-4854
No files matched your search
+30
-30
@@ -8,15 +8,15 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestMutePersistenzUeberRestart (Abnahmekriterium): `talk mute` → Daemon-
|
||||
// Restart → weiter stumm. Mute verwirft Ansagen dokumentiert mit
|
||||
// 202 {id:"", muted:true} und ohne jeden Provider-Kontakt.
|
||||
func TestMutePersistenzUeberRestart(t *testing.T) {
|
||||
// TestMutePersistenceAcrossRestart (acceptance criterion): `talk mute` →
|
||||
// daemon restart → still muted. Mute discards announcements as documented
|
||||
// with 202 {id:"", muted:true} and without any provider contact.
|
||||
func TestMutePersistenceAcrossRestart(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
fake := newFakeProvider(t, 0)
|
||||
d := startDaemon(t, root, fake.srv.URL, nil)
|
||||
|
||||
// Über die HTTP-API stummschalten: Datei mute liegt im Config-Dir.
|
||||
// Mute via the HTTP API: the mute file is in the config dir.
|
||||
code, m := postPath(t, d, "/mute")
|
||||
if code != 200 || m["muted"] != true {
|
||||
t.Fatalf("POST /mute: %d %v", code, m)
|
||||
@@ -24,72 +24,72 @@ func TestMutePersistenzUeberRestart(t *testing.T) {
|
||||
muteFile := filepath.Join(configDir(root), "mute")
|
||||
b, err := os.ReadFile(muteFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Mute-Datei fehlt im Config-Dir: %v", err)
|
||||
t.Fatalf("mute file missing in the config dir: %v", err)
|
||||
}
|
||||
if strings.TrimSpace(string(b)) != "true" {
|
||||
t.Errorf("Mute-Datei = %q, want true", b)
|
||||
t.Errorf("mute file = %q, want true", b)
|
||||
}
|
||||
|
||||
// Ansagen werden verworfen: 202, muted:true, leere ID, kein Treffer.
|
||||
code, m = speak(t, d, `{"text":"stille"}`)
|
||||
// Announcements are discarded: 202, muted:true, empty ID, no hit.
|
||||
code, m = speak(t, d, `{"text":"silence"}`)
|
||||
if code != 202 || m["muted"] != true || m["id"] != "" {
|
||||
t.Fatalf("speak bei Mute: %d %v, want 202 {id:, muted:true}", code, m)
|
||||
t.Fatalf("speak while muted: %d %v, want 202 {id:, muted:true}", code, m)
|
||||
}
|
||||
fake.assertNoHits(t, 300*time.Millisecond)
|
||||
if st := getStatus(t, d); !st.Muted {
|
||||
t.Fatal("/status meldet muted=false")
|
||||
t.Fatal("/status reports muted=false")
|
||||
}
|
||||
|
||||
// Daemon-Restart mit demselben Config-Dir: weiter stumm.
|
||||
// Daemon restart with the same config dir: still muted.
|
||||
d.stop(t)
|
||||
d2 := startDaemon(t, root, fake.srv.URL, nil)
|
||||
|
||||
if st := getStatus(t, d2); !st.Muted {
|
||||
t.Fatalf("nach Restart muted = false, want true (persistent)")
|
||||
t.Fatalf("after restart muted = false, want true (persistent)")
|
||||
}
|
||||
code, m = speak(t, d2, `{"text":"immer noch stumm"}`)
|
||||
code, m = speak(t, d2, `{"text":"still silent"}`)
|
||||
if code != 202 || m["muted"] != true {
|
||||
t.Fatalf("speak nach Restart bei Mute: %d %v", code, m)
|
||||
t.Fatalf("speak after restart while muted: %d %v", code, m)
|
||||
}
|
||||
fake.assertNoHits(t, 300*time.Millisecond)
|
||||
|
||||
// Unmute: Ansagen laufen wieder.
|
||||
// Unmute: announcements play again.
|
||||
code, m = postPath(t, d2, "/unmute")
|
||||
if code != 200 || m["muted"] != false {
|
||||
t.Fatalf("POST /unmute: %d %v", code, m)
|
||||
}
|
||||
code, m = speak(t, d2, `{"text":"wieder da"}`)
|
||||
code, m = speak(t, d2, `{"text":"back again"}`)
|
||||
if code != 202 || m["id"] == "" {
|
||||
t.Fatalf("speak nach unmute: %d %v, want 202 mit id", code, m)
|
||||
t.Fatalf("speak after unmute: %d %v, want 202 with id", code, m)
|
||||
}
|
||||
hits := fake.waitHits(t, 1)
|
||||
if hits[0].Text != "wieder da" {
|
||||
t.Errorf("Treffer = %q, want wieder da", hits[0].Text)
|
||||
if hits[0].Text != "back again" {
|
||||
t.Errorf("hit = %q, want back again", hits[0].Text)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOfflineMuteDateiWirdVonDaemonGelesen: eine offline (CLI, ohne
|
||||
// Daemon) geschriebene Mute-Datei wirkt sofort im Daemon — beide lesen
|
||||
// dieselbe Quelle.
|
||||
func TestOfflineMuteDateiWirdVonDaemonGelesen(t *testing.T) {
|
||||
// TestOfflineMuteFileIsReadByDaemon: a mute file written offline (CLI,
|
||||
// without daemon) takes effect in the daemon immediately — both read the
|
||||
// same source.
|
||||
func TestOfflineMuteFileIsReadByDaemon(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
fake := newFakeProvider(t, 0)
|
||||
d := startDaemon(t, root, fake.srv.URL, nil)
|
||||
deadEnv := daemonEnv(root, 1, fake.srv.URL, nil)
|
||||
|
||||
// Offline stummschalten (talk mute ohne Daemon → Datei direkt).
|
||||
// Mute offline (talk mute without daemon → file directly).
|
||||
res := runTalk(t, deadEnv, "mute")
|
||||
if res.code != 0 || res.stdout != "muted\n" {
|
||||
t.Fatalf("talk mute (offline): %d %q — stderr: %s", res.code, res.stdout, res.stderr)
|
||||
}
|
||||
|
||||
// Der laufende Daemon sieht den Zustand bei der nächsten Ansage.
|
||||
// The running daemon sees the state on the next announcement.
|
||||
if st := getStatus(t, d); !st.Muted {
|
||||
t.Fatal("Daemon sieht offline geschriebene Mute-Datei nicht")
|
||||
t.Fatal("daemon does not see the mute file written offline")
|
||||
}
|
||||
code, m := speak(t, d, `{"text":"soll verworfen werden"}`)
|
||||
code, m := speak(t, d, `{"text":"should be discarded"}`)
|
||||
if code != 202 || m["muted"] != true {
|
||||
t.Fatalf("speak: %d %v, want verworfen", code, m)
|
||||
t.Fatalf("speak: %d %v, want discarded", code, m)
|
||||
}
|
||||
fake.assertNoHits(t, 300*time.Millisecond)
|
||||
|
||||
@@ -98,6 +98,6 @@ func TestOfflineMuteDateiWirdVonDaemonGelesen(t *testing.T) {
|
||||
t.Fatalf("talk unmute (offline): %d — stderr: %s", res.code, res.stderr)
|
||||
}
|
||||
if st := getStatus(t, d); st.Muted {
|
||||
t.Fatal("Daemon sieht offline geschriebene Unmute-Datei nicht")
|
||||
t.Fatal("daemon does not see the unmute file written offline")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user