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

+19 -20
View File
@@ -11,19 +11,19 @@ import (
"time"
)
// Client ist der HTTP-Client der CLI für den Daemon auf 127.0.0.1 —
// dieselbe Vertragsschnittstelle, die server.go bedient.
// Client is the CLI's HTTP client for the daemon on 127.0.0.1; the same
// contract that server.go serves.
type Client struct {
// BaseURL ist die Daemon-Adresse (http://127.0.0.1:<port>).
// BaseURL is the daemon address (http://127.0.0.1:<port>).
BaseURL string
// HTTP ist der verwendete Client; nil wird von NewClient gesetzt.
// HTTP is the client in use; NewClient sets it when nil.
HTTP *http.Client
}
// NewClient baut den Client für einen Daemon-Port. Zeit-Budgets steuert der
// Aufrufer pro Aufruf über den Context (Daemon-Erkennung kurz, /speak
// großzügiger); das Client-Timeout ist nur die harte Decke.
// NewClient builds the client for a daemon port. The caller controls time
// budgets per call via the context (daemon detection short, /speak more
// generous); the client timeout is only the hard ceiling.
func NewClient(port int) *Client {
return &Client{
BaseURL: fmt.Sprintf("http://127.0.0.1:%d", port),
@@ -31,10 +31,10 @@ func NewClient(port int) *Client {
}
}
// HTTPError ist eine Daemon-Antwort mit unerwartetem Status-Code (z. B.
// 400 Validierung). Netz-Fehler (Daemon tot) sind dagegen reguläre
// *url.Error-Werte — die CLI unterscheidet danach: HTTP-Fehler anzeigen,
// Netz-Fehler → Degraded Mode.
// HTTPError is a daemon response with an unexpected status code (e.g. 400
// validation). Network errors (daemon dead) are regular *url.Error values
// instead; the CLI distinguishes by that: show HTTP errors, fall into
// degraded mode on network errors.
type HTTPError struct {
Status int
Body string
@@ -44,7 +44,7 @@ func (e *HTTPError) Error() string {
return fmt.Sprintf("HTTP %d: %s", e.Status, e.Body)
}
// Status ruft GET /status (Daemon-Erkennung, --wait-Polling).
// Status calls GET /status (daemon detection, --wait polling).
func (c *Client) Status(ctx context.Context) (*StatusResponse, error) {
var st StatusResponse
if err := c.do(ctx, http.MethodGet, "/status", nil, http.StatusOK, &st); err != nil {
@@ -53,7 +53,7 @@ func (c *Client) Status(ctx context.Context) (*StatusResponse, error) {
return &st, nil
}
// Speak ruft POST /speak (enqueue).
// Speak calls POST /speak (enqueue).
func (c *Client) Speak(ctx context.Context, req SpeakRequest) (*SpeakResponse, error) {
var resp SpeakResponse
if err := c.do(ctx, http.MethodPost, "/speak", req, http.StatusAccepted, &resp); err != nil {
@@ -62,8 +62,7 @@ func (c *Client) Speak(ctx context.Context, req SpeakRequest) (*SpeakResponse, e
return &resp, nil
}
// Cancel ruft POST /cancel; Rückgabe ist die Zahl der abgebrochenen
// Ansagen.
// Cancel calls POST /cancel; returns the number of cancelled announcements.
func (c *Client) Cancel(ctx context.Context) (int, error) {
var resp CancelResponse
if err := c.do(ctx, http.MethodPost, "/cancel", nil, http.StatusOK, &resp); err != nil {
@@ -72,7 +71,7 @@ func (c *Client) Cancel(ctx context.Context) (int, error) {
return resp.Cancelled, nil
}
// SetMute ruft POST /mute (muted=true) bzw. POST /unmute (muted=false).
// SetMute calls POST /mute (muted=true) or POST /unmute (muted=false).
func (c *Client) SetMute(ctx context.Context, muted bool) error {
path := "/mute"
if !muted {
@@ -81,14 +80,14 @@ func (c *Client) SetMute(ctx context.Context, muted bool) error {
return c.do(ctx, http.MethodPost, path, nil, http.StatusOK, nil)
}
// Shutdown ruft POST /shutdown (`talk daemon stop`).
// Shutdown calls POST /shutdown (`talk daemon stop`).
func (c *Client) Shutdown(ctx context.Context) error {
return c.do(ctx, http.MethodPost, "/shutdown", nil, http.StatusOK, nil)
}
// do ist der gemeinsame Kern: Request bauen, senden, Status prüfen,
// dekodieren. Netz-Fehler kommen ungefiltert zurück (Aufrufer unterscheidet
// über errors.As(&HTTPError)).
// do is the shared core: build the request, send it, check the status,
// decode. Network errors are returned unfiltered (the caller distinguishes
// via errors.As(&HTTPError)).
func (c *Client) do(ctx context.Context, method, path string, body any, wantStatus int, out any) error {
var rd io.Reader
if body != nil {