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
+47
-43
@@ -16,9 +16,9 @@ import (
|
||||
|
||||
func readFile(path string) ([]byte, error) { return os.ReadFile(path) }
|
||||
|
||||
// setupEnv stellt den Wizard auf „interaktiv“ und pinnt die Kette auf einen
|
||||
// Fake-ElevenLabs-Endpunkt (sink null) — Probehören und Testsatz laufen
|
||||
// gegen den Fake. Rückgabe: Zähler der Synthese-Aufrufe.
|
||||
// setupEnv makes the wizard "interactive" and pins the chain to a fake
|
||||
// ElevenLabs endpoint (sink null): preview and test sentence hit the fake.
|
||||
// Returns the counter of synthesis calls.
|
||||
func setupEnv(t *testing.T, root string, extra map[string]string) *atomic.Int32 {
|
||||
t.Helper()
|
||||
var calls atomic.Int32
|
||||
@@ -44,52 +44,56 @@ func setupEnv(t *testing.T, root string, extra map[string]string) *atomic.Int32
|
||||
return &calls
|
||||
}
|
||||
|
||||
// TestSetupSchreibtConfig: Antworten über stdin — Stimme 2 für Deutsch
|
||||
// (probehören, übernehmen), Englisch Default behalten, Ruhezeit setzen —
|
||||
// ergeben eine config.toml mit genau diesen Werten; Probehören und
|
||||
// Testsatz treffen den Fake.
|
||||
func TestSetupSchreibtConfig(t *testing.T) {
|
||||
// TestSetupWritesConfig: answers via stdin (language en; voice 2 for
|
||||
// English, preview, accept; German keeps the default; quiet hours) yield a
|
||||
// config.toml with exactly those values; preview and test sentence hit the
|
||||
// fake.
|
||||
func TestSetupWritesConfig(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
calls := setupEnv(t, root, nil)
|
||||
stdin := strings.NewReader("2\nj\nj\n\nn\nj\n22:00-07:00\n")
|
||||
stdin := strings.NewReader("en\n2\ny\ny\n\nn\ny\n22:00-07:00\n")
|
||||
|
||||
var out, errBuf bytes.Buffer
|
||||
code := run([]string{"setup"}, stdin, &out, &errBuf)
|
||||
if code != exitOK {
|
||||
t.Fatalf("talk setup: Exit %d\nstdout: %s\nstderr: %s", code, out.String(), errBuf.String())
|
||||
t.Fatalf("talk setup: exit %d\nstdout: %s\nstderr: %s", code, out.String(), errBuf.String())
|
||||
}
|
||||
path := filepath.Join(root, "talk", "config.toml")
|
||||
var got struct {
|
||||
Lang string `toml:"lang"`
|
||||
QuietHours string `toml:"quiet_hours"`
|
||||
Voice map[string]map[string]string `toml:"voice"`
|
||||
}
|
||||
if _, err := toml.DecodeFile(path, &got); err != nil {
|
||||
t.Fatalf("config lesen: %v", err)
|
||||
t.Fatalf("reading config: %v", err)
|
||||
}
|
||||
if got.Lang != "en" {
|
||||
t.Errorf("lang = %q, want en", got.Lang)
|
||||
}
|
||||
if got.QuietHours != "22:00-07:00" {
|
||||
t.Errorf("quiet_hours = %q", got.QuietHours)
|
||||
}
|
||||
if got.Voice["elevenlabs"]["de"] != "pNInz6obpgDQGcFmaJgB" { // Nr. 2 = Adam
|
||||
t.Errorf("voice.elevenlabs.de = %q, want Adam", got.Voice["elevenlabs"]["de"])
|
||||
if got.Voice["elevenlabs"]["en"] != "pNInz6obpgDQGcFmaJgB" { // no. 2 = Adam
|
||||
t.Errorf("voice.elevenlabs.en = %q, want Adam", got.Voice["elevenlabs"]["en"])
|
||||
}
|
||||
if _, ok := got.Voice["elevenlabs"]["en"]; ok {
|
||||
t.Errorf("voice.elevenlabs.en gesetzt, obwohl Default behalten wurde: %v", got.Voice)
|
||||
if _, ok := got.Voice["elevenlabs"]["de"]; ok {
|
||||
t.Errorf("voice.elevenlabs.de set although the default was kept: %v", got.Voice)
|
||||
}
|
||||
if n := calls.Load(); n != 2 { // 1× Probehören de + 1× Testsatz
|
||||
t.Errorf("Fake-Aufrufe = %d, want 2 (Probehören + Testsatz)", n)
|
||||
if n := calls.Load(); n != 2 { // 1× preview en + 1× test sentence
|
||||
t.Errorf("fake calls = %d, want 2 (preview + test sentence)", n)
|
||||
}
|
||||
if _, err := os.Stat(path + ".bak"); !os.IsNotExist(err) {
|
||||
t.Errorf("Sicherung ohne Vorgänger-Datei angelegt")
|
||||
t.Errorf("backup created without a previous file")
|
||||
}
|
||||
if !strings.Contains(out.String(), "Gespeichert") || !strings.Contains(out.String(), "Gesprochen über elevenlabs") {
|
||||
if !strings.Contains(out.String(), "Saved") || !strings.Contains(out.String(), "Spoken via elevenlabs") {
|
||||
t.Errorf("stdout = %q", out.String())
|
||||
}
|
||||
}
|
||||
|
||||
// TestSetupMergtBestehendeConfig: Vorhandene Werte (anderer Port, andere
|
||||
// Provider-Stimme) bleiben erhalten, es gibt eine .bak, und „keine“ löscht
|
||||
// eine vorhandene Ruhezeit.
|
||||
func TestSetupMergtBestehendeConfig(t *testing.T) {
|
||||
// TestSetupMergesExistingConfig: existing values (other port, another
|
||||
// provider's voice) survive, there is a .bak, and "none" removes an existing
|
||||
// quiet window.
|
||||
func TestSetupMergesExistingConfig(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
setupEnv(t, root, nil)
|
||||
dir := filepath.Join(root, "talk")
|
||||
@@ -101,63 +105,63 @@ func TestSetupMergtBestehendeConfig(t *testing.T) {
|
||||
if err := os.WriteFile(path, []byte(orig), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Das Setup selbst muss auf den Default-Port 7800 zeigen, nicht 7900 aus
|
||||
// der Datei — sonst würde die Daemon-Erkennung einen fremden Port
|
||||
// anfassen. TALK_PORT=1 aus setGetenv schlägt die Datei.
|
||||
stdin := strings.NewReader("1\nn\nj\n1\nn\nj\nkeine\n")
|
||||
// The setup itself must not touch port 7900 from the file: TALK_PORT=1
|
||||
// from setGetenv beats the file.
|
||||
stdin := strings.NewReader("\n1\nn\ny\n1\nn\ny\nnone\n")
|
||||
var out, errBuf bytes.Buffer
|
||||
if code := run([]string{"setup"}, stdin, &out, &errBuf); code != exitOK {
|
||||
t.Fatalf("talk setup: Exit %d\n%s\n%s", code, out.String(), errBuf.String())
|
||||
t.Fatalf("talk setup: exit %d\n%s\n%s", code, out.String(), errBuf.String())
|
||||
}
|
||||
bak, err := os.ReadFile(path + ".bak")
|
||||
if err != nil || string(bak) != orig {
|
||||
t.Errorf(".bak = %q (%v), want Original", bak, err)
|
||||
t.Errorf(".bak = %q (%v), want the original", bak, err)
|
||||
}
|
||||
var got map[string]any
|
||||
if _, err := toml.DecodeFile(path, &got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := got["quiet_hours"]; ok {
|
||||
t.Errorf("quiet_hours nicht entfernt: %v", got)
|
||||
t.Errorf("quiet_hours not removed: %v", got)
|
||||
}
|
||||
if srv, _ := got["server"].(map[string]any); srv["port"] != int64(7900) {
|
||||
t.Errorf("server.port verloren: %v", got["server"])
|
||||
t.Errorf("server.port lost: %v", got["server"])
|
||||
}
|
||||
voice, _ := got["voice"].(map[string]any)
|
||||
if edge, _ := voice["edge"].(map[string]any); edge["de"] != "de-DE-ConradNeural" {
|
||||
t.Errorf("voice.edge.de verloren: %v", voice)
|
||||
t.Errorf("voice.edge.de lost: %v", voice)
|
||||
}
|
||||
// Default behalten (Nr. 1 = Rachel = Repo-Default) → kein elevenlabs-Eintrag.
|
||||
// Default kept (no. 1 = Rachel = built-in default) → no elevenlabs entry.
|
||||
if _, ok := voice["elevenlabs"]; ok {
|
||||
t.Errorf("voice.elevenlabs geschrieben, obwohl Default behalten: %v", voice)
|
||||
t.Errorf("voice.elevenlabs written although the default was kept: %v", voice)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSetupAbbruchSchreibtNichts: „q“ bricht ab, keine Datei entsteht.
|
||||
func TestSetupAbbruchSchreibtNichts(t *testing.T) {
|
||||
// TestSetupAbortWritesNothing: "q" aborts, no file is created.
|
||||
func TestSetupAbortWritesNothing(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
setupEnv(t, root, nil)
|
||||
var out, errBuf bytes.Buffer
|
||||
if code := run([]string{"setup"}, strings.NewReader("q\n"), &out, &errBuf); code != exitOK {
|
||||
t.Fatalf("Exit %d, stderr %q", code, errBuf.String())
|
||||
t.Fatalf("exit %d, stderr %q", code, errBuf.String())
|
||||
}
|
||||
if !strings.Contains(out.String(), "Abgebrochen") {
|
||||
if !strings.Contains(out.String(), "Aborted") {
|
||||
t.Errorf("stdout = %q", out.String())
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(root, "talk", "config.toml")); !os.IsNotExist(err) {
|
||||
t.Error("config.toml trotz Abbruch angelegt")
|
||||
t.Error("config.toml created despite abort")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSetupOhneTerminal: Pipe → keine Fragen, wirksame Konfiguration, Exit 0.
|
||||
func TestSetupOhneTerminal(t *testing.T) {
|
||||
// TestSetupWithoutTerminal: a pipe → no questions, effective configuration,
|
||||
// exit 0.
|
||||
func TestSetupWithoutTerminal(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
setGetenv(t, map[string]string{"XDG_CONFIG_HOME": root, "TALK_AUDIO_SINK": "null"})
|
||||
var out, errBuf bytes.Buffer
|
||||
if code := runCLI([]string{"setup"}, &out, &errBuf); code != exitOK {
|
||||
t.Fatalf("Exit %d, stderr %q", code, errBuf.String())
|
||||
t.Fatalf("exit %d, stderr %q", code, errBuf.String())
|
||||
}
|
||||
if !strings.Contains(out.String(), "Wirksame Konfiguration") || !strings.Contains(out.String(), "ruhezeit: keine") {
|
||||
if !strings.Contains(out.String(), "Effective configuration") || !strings.Contains(out.String(), "quiet hours: none") {
|
||||
t.Errorf("stdout = %q", out.String())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user