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 -14
View File
@@ -1,3 +1,7 @@
// Package langdetect guesses whether an announcement is German or English.
// It is a pure function over two small word lists, deliberately without any
// ML: announcements are one or two sentences, and the only question is which
// voice should read them.
package langdetect
import (
@@ -5,14 +9,12 @@ import (
"unicode"
)
// Detect erkennt die Sprache eines Ansage-Texts und liefert "de" oder "en".
// Default (auch bei leerem oder unentscheidbarem Text) ist "de".
//
// Heuristik: Common-Word-Listen deutscher und englischer Wörter plus
// Umlaute/ß als starke Deutsch-Signale. Englisch gewinnt nur bei strikt
// mehr englischen Signalen als deutschen; bei Gleichstand entscheidet der
// Default "de". Die Funktion ist rein (kein globaler Zustand, keine IO).
func Detect(text string) string {
// Detect returns "de" or "en" for text. Umlauts and ß count as strong German
// signals; otherwise common-word hits decide. English wins only with strictly
// more English than German hits; ties and undecidable text (empty, numbers,
// punctuation) return fallback, which callers take from the configured
// default language.
func Detect(text, fallback string) string {
var de, en int
for _, r := range text {
switch r {
@@ -33,14 +35,18 @@ func Detect(text string) string {
}
}
if en > de {
switch {
case en > de:
return "en"
case de > en:
return "de"
default:
return fallback
}
return "de"
}
// germanWords sind häufige deutsche Wörter, die im Englischen nicht
// vorkommen (Mehrdeutigkeiten wie „was" und „in" wurden weggelassen).
// germanWords are common German words that do not occur in English
// (ambiguous ones such as "was" and "in" are left out on purpose).
var germanWords = map[string]struct{}{
"der": {}, "die": {}, "das": {}, "und": {}, "nicht": {}, "ist": {},
"ein": {}, "eine": {}, "einen": {}, "einem": {}, "einer": {},
@@ -58,8 +64,7 @@ var germanWords = map[string]struct{}{
"wurden": {}, "haben": {}, "geht": {}, "machen": {},
}
// englishWords sind häufige englische Wörter, die im Deutschen nicht
// vorkommen.
// englishWords are common English words that do not occur in German.
var englishWords = map[string]struct{}{
"the": {}, "is": {}, "not": {}, "and": {}, "with": {}, "for": {},
"on": {}, "of": {}, "to": {}, "from": {}, "by": {}, "at": {},
+26 -24
View File
@@ -2,36 +2,38 @@ package langdetect
import "testing"
// TestDetect ist tischgesteuert: Eingabetext → erwartete Sprache.
// Der Default "de" gilt für leeren, unentscheidbaren und gemischten
// Text ohne englische Mehrheit.
// TestDetect is table-driven: input text, expected language. The fallback
// applies to empty, undecidable and tied text; it is passed explicitly so
// the test pins both directions.
func TestDetect(t *testing.T) {
cases := []struct {
name string
text string
want string
name string
text string
fallback string
want string
}{
{"leer", "", "de"},
{"nur whitespace", " \n\t ", "de"},
{"nur interpunktion", "!!! ??? ...", "de"},
{"deutsch satz", "Der Build wurde erfolgreich abgeschlossen und das Deployment ist fertig.", "de"},
{"deutsch frage mit was", "Was macht er?", "de"},
{"deutsch umlaute", "Größe früh Ärzteübung — schön daß es klappt", "de"},
{"ß alleine", "weiß", "de"},
{"englisch satz", "The build has finished and the deployment is done.", "en"},
{"englisch imperativ", "Please wait until the tests are green.", "en"},
{"englisch kurz", "done", "en"},
{"gemischt englisch dominiert", "the build is done and ready for you", "en"},
{"gemischt deutsch dominiert", "Der build ist done und fertig für dich", "de"},
{"gemischt gleichstand", "der the", "de"},
{"unklare sprache", "Build 2026-10-18 14:22 UTC", "de"},
{"agenten-typisch en", "Task completed. Please review the changes and approve.", "en"},
{"agenten-typisch de", "Aufgabe abgeschlossen. Bitte prüfen und freigeben.", "de"},
{"empty → fallback de", "", "de", "de"},
{"empty → fallback en", "", "en", "en"},
{"whitespace only", " \n\t ", "en", "en"},
{"punctuation only", "!!! ??? ...", "de", "de"},
{"german sentence", "Der Build wurde erfolgreich abgeschlossen und das Deployment ist fertig.", "en", "de"},
{"undecidable german question → fallback", "Was macht er?", "en", "en"},
{"german umlauts", "Größe früh Ärzteübung — schön daß es klappt", "en", "de"},
{"ß alone", "weiß", "en", "de"},
{"english sentence", "The build has finished and the deployment is done.", "de", "en"},
{"english imperative", "Please wait until the tests are green.", "de", "en"},
{"english single word", "done", "de", "en"},
{"mixed, english dominates", "the build is done and ready for you", "de", "en"},
{"mixed, german dominates", "Der build ist done und fertig für dich", "en", "de"},
{"tie → fallback", "der the", "en", "en"},
{"undecidable → fallback", "Build 2026-10-18 14:22 UTC", "de", "de"},
{"agent-style en", "Task completed. Please review the changes and approve.", "de", "en"},
{"agent-style de", "Aufgabe abgeschlossen. Bitte prüfen und freigeben.", "en", "de"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := Detect(tc.text); got != tc.want {
t.Errorf("Detect(%q) = %q, want %q", tc.text, got, tc.want)
if got := Detect(tc.text, tc.fallback); got != tc.want {
t.Errorf("Detect(%q, %q) = %q, want %q", tc.text, tc.fallback, got, tc.want)
}
})
}
-4
View File
@@ -1,4 +0,0 @@
// Package langdetect erkennt die Sprache eines Ansage-Texts als reine
// Funktion Detect(text) → "de" | "en" (Default "de"; Heuristik über
// Common-Word-Listen und Umlaute/ß).
package langdetect