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:
m3ta-chironandClaude Fable 5.1 committed 2026-10-24 11:15:35 +02:00
1 parent 4d30bd95ad
commit 85fa64cfe6
95 files changed
+4707 -4854

No files matched your search

+56 -55
View File
@@ -1,53 +1,53 @@
package api
// Request/Response-Typen des Daemon-HTTP-Vertrags (Task-04-Brief, bindende
// Felder). Die CLI und spätere Konsumenten (MCP, Task 06) benutzen dieselben
// Typen — eine Quelle, kein Contract-Drift zwischen Daemon und Client.
// Request/response types of the daemon HTTP contract (binding fields). The
// CLI and other consumers (MCP) use the same types: one source, no contract
// drift between daemon and client.
// SpeakRequest ist der Body von POST /speak.
// SpeakRequest is the body of POST /speak.
type SpeakRequest struct {
// Text ist der Ansage-Text (Pflicht).
// Text is the announcement text (required).
Text string `json:"text"`
// Priority: low | normal | high (Default normal, wenn leer).
// Priority: low | normal | high (default normal when empty).
Priority string `json:"priority,omitempty"`
// Provider pinnt die Provider-Kette (--provider).
// Provider pins the provider chain (--provider).
Provider string `json:"provider,omitempty"`
// Voice pinnt eine Voice-ID (--voice).
// Voice pins a voice ID (--voice).
Voice string `json:"voice,omitempty"`
// Lang pinnt die Sprache de | en (leer = automatische Erkennung).
// Lang pins the language de | en (empty = automatic detection).
Lang string `json:"lang,omitempty"`
// Now lässt die Ansage vor allen wartenden springen (--now).
// Now lets the announcement jump ahead of all waiting ones (--now).
Now bool `json:"now,omitempty"`
}
// SpeakResponse ist die Antwort von POST /speak: 202 {"id":"…"} — bei
// aktivem Mute 202 {"id":"","muted":true} (Ansage verworfen; dokumentiertes
// Verhalten, kein Fehler). Seit Task 05 meldet verworfenen Spam ebenso
// dokumentiert: 202 {"id":"","discarded":"rate_limit"|"quiet_hours"} —
// plus "toast":true, wenn die Ruhezeit-Ansage durch eine Benachrichtigung
// ersetzt wurde (quiet_mode = "toast").
// SpeakResponse is the response of POST /speak: 202 {"id":"..."}; with mute
// active 202 {"id":"","muted":true} (announcement discarded; documented
// behaviour, not an error). Discarded spam is reported just as
// documented: 202 {"id":"","discarded":"rate_limit"|"quiet_hours"}, plus
// "toast":true when the quiet-hours announcement was replaced by a
// notification (quiet_mode = "toast").
type SpeakResponse struct {
ID string `json:"id"`
Muted bool `json:"muted,omitempty"`
// Discarded ist der Verwurfs-Grund ("rate_limit" | "quiet_hours");
// leer, wenn die Ansage aufgenommen wurde.
// Discarded is the discard reason ("rate_limit" | "quiet_hours"); empty
// when the announcement was admitted.
Discarded string `json:"discarded,omitempty"`
// Toast meldet die Ersetzung durch eine Benachrichtigung.
// Toast reports the replacement by a notification.
Toast bool `json:"toast,omitempty"`
// Truncated meldet, dass der Text auf queue.max_text gekürzt und so
// aufgenommen wurde (Gesetz gegen vorgelesene Langantworten).
// Truncated reports that the text was cut to queue.max_text and
// admitted that way (the law against read-aloud long answers).
Truncated bool `json:"truncated,omitempty"`
}
// CurrentItem beschreibt das laufende Item in GET /status.
// CurrentItem describes the running item in GET /status.
type CurrentItem struct {
ID string `json:"id"`
Text string `json:"text"`
@@ -55,12 +55,12 @@ type CurrentItem struct {
Now bool `json:"now"`
}
// StatusResponse ist die Antwort von GET /status: Queue-Länge, aktuelles
// Item (null im Leerlauf), Mute-State, aktiver Provider, Daemon-Version.
// QueueIDs ist die Ergänzung der wartenden IDs in Abarbeitungs-Reihenfolge
// — die CLI braucht sie für ihr --wait-Polling. Task 05 ergänzt additiv
// den Gesetz-State (rate_limit, quiet_hours); Task 06 additiv mcp/cors
// (Compat-Felder: MCP-Endpoint vorhanden, konfigurierte CORS-Origins).
// StatusResponse is the response of GET /status: queue length, current item
// (null when idle), mute state, active provider, daemon version. QueueIDs
// lists the waiting IDs in processing order; the CLI needs them for its
// --wait polling. The law state (rate_limit, quiet_hours) and mcp/cors
// (compat fields: MCP endpoint present, configured CORS origins) are
// additive.
type StatusResponse struct {
Version string `json:"version"`
Queue int `json:"queue"`
@@ -72,59 +72,60 @@ type StatusResponse struct {
QuietHours *QuietHoursState `json:"quiet_hours,omitempty"`
Limits *LimitsState `json:"limits,omitempty"`
// MCP meldet, dass der Daemon den MCP-Endpoint anbietet (immer true;
// Feld existiert für additive Compat-Sicht von Clients/Browsern).
// MCP reports that the daemon offers the MCP endpoint (always true; the
// field exists for an additive compat view of clients/browsers).
MCP bool `json:"mcp"`
// Cors ist die konfigurierte CORS-Erlaubnisliste von POST /speak
// (leeres [] = Tür zu). Immer vorhanden (auch leer), nicht omitempty.
// Cors is the configured CORS allow list of POST /speak (empty [] =
// door closed). Always present (even when empty), not omitempty.
Cors []string `json:"cors"`
}
// RateLimitState ist der Rate-Limit-State in GET /status (Task 05).
// RateLimitState is the rate-limit state in GET /status.
type RateLimitState struct {
// Interval ist der konfigurierte Mindestabstand als Dauer-String
// ("30s"; "0s" = unbegrenzt).
// Interval is the configured minimum spacing as a duration string
// ("30s"; "0s" = unlimited).
Interval string `json:"interval"`
// AllowHighBurst meldet, ob priority=high verstoßen darf.
// AllowHighBurst reports whether priority=high may violate it.
AllowHighBurst bool `json:"allow_high_burst"`
// NextSlot ist der nächste freie Ansage-Slot als RFC3339-Zeitstempel;
// null, wenn sofort gespielt werden dürfte (oder unbegrenzt).
// NextSlot is the next free announcement slot as an RFC3339 timestamp;
// null when playback would be allowed immediately (or unlimited).
NextSlot *string `json:"next_slot"`
}
// LimitsState sind die Längen- und Alters-Gesetze in GET /status.
// LimitsState holds the length and age laws in GET /status.
type LimitsState struct {
// MaxText ist die Obergrenze der Ansage-Länge in Zeichen (0 = unbegrenzt).
// MaxText is the upper bound of the announcement length in characters
// (0 = unlimited).
MaxText int `json:"max_text"`
// MaxAge ist die längste Wartezeit in der Queue als Dauer-String
// ("2m0s"; "0s" = unbegrenzt).
// MaxAge is the longest wait in the queue as a duration string ("2m0s";
// "0s" = unlimited).
MaxAge string `json:"max_age"`
}
// QuietHoursState ist der Ruhezeit-State in GET /status (Task 05).
// QuietHoursState is the quiet-hours state in GET /status.
type QuietHoursState struct {
// Window ist das konfigurierte Fenster ("18:00-08:00"; "" = deaktiviert).
// Window is the configured window ("18:00-08:00"; "" = disabled).
Window string `json:"window"`
// Active meldet, ob die lokale Zeit gerade im Fenster liegt.
// Active reports whether local time is currently inside the window.
Active bool `json:"active"`
// Mode ist der konfigurierte Ruhezeit-Modus ("discard" | "toast").
// Mode is the configured quiet-hours mode ("discard" | "toast").
Mode string `json:"mode"`
// Until ist das Fenster-Ende als RFC3339-Zeitstempel, wenn aktiv;
// sonst null.
// Until is the window end as an RFC3339 timestamp when active;
// otherwise null.
Until *string `json:"until"`
}
// PendingOrCurrent meldet, ob die Ansage mit der ID noch wartet oder
// gerade läuft — Grundlage des --wait-Pollings der CLI. Ist die Ansage
// weder wartend noch laufend, ist sie fertig (oder abgebrochen) — in
// beiden Fällen hat der Aufrufer nichts mehr zu warten.
// PendingOrCurrent reports whether the announcement with the ID is still
// waiting or currently playing; the basis of the CLI's --wait polling. If
// it is neither waiting nor playing, it is done (or cancelled); in both
// cases the caller has nothing left to wait for.
func (s StatusResponse) PendingOrCurrent(id string) bool {
if s.Current != nil && s.Current.ID == id {
return true
@@ -137,17 +138,17 @@ func (s StatusResponse) PendingOrCurrent(id string) bool {
return false
}
// CancelResponse ist die Antwort von POST /cancel.
// CancelResponse is the response of POST /cancel.
type CancelResponse struct {
Cancelled int `json:"cancelled"`
}
// MuteResponse ist die Antwort von POST /mute und POST /unmute.
// MuteResponse is the response of POST /mute and POST /unmute.
type MuteResponse struct {
Muted bool `json:"muted"`
}
// ShutdownResponse ist die Antwort von POST /shutdown.
// ShutdownResponse is the response of POST /shutdown.
type ShutdownResponse struct {
OK bool `json:"ok"`
}