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
@@ -8,69 +8,68 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DefaultName ist der Provider, den New("") liefert (Einzelkonstruktion,
|
||||
// z. B. für doctor). Die Default-Ansage-Kette ab Task 03 steht in
|
||||
// DefaultOrder (chain.go): elevenlabs → openai → edge → osbuiltin.
|
||||
// DefaultName is the provider New("") returns (single construction, e.g.
|
||||
// for doctor). The default announcement chain lives in DefaultOrder
|
||||
// (chain.go): elevenlabs → openai → edge → osbuiltin.
|
||||
const DefaultName = "osbuiltin"
|
||||
|
||||
// ErrNoByteOutput meldet, dass die Plattform keine über Synthesize
|
||||
// beziehbaren Audio-Bytes liefern kann — konkret Linux mit speech-dispatcher
|
||||
// (spd-say) ohne espeak: spd-say kann Audio nur selbst über den Daemon
|
||||
// abspielen, nicht in einen Prozess schreiben. Aufrufer sollen auf
|
||||
// DirectSpeaker ausweichen (siehe dort).
|
||||
// ErrNoByteOutput reports that the platform cannot provide audio bytes via
|
||||
// Synthesize; concretely Linux with speech-dispatcher (spd-say) but without
|
||||
// espeak: spd-say can only play audio itself through the daemon, it cannot
|
||||
// write it to a process. Callers should fall back to DirectSpeaker (see
|
||||
// there).
|
||||
var ErrNoByteOutput = errors.New(
|
||||
"diese Plattform kann keine Audio-Bytes liefern (Linux ohne espeak; spd-say spielt nur direkt)")
|
||||
"this platform cannot provide audio bytes (Linux without espeak; spd-say only speaks directly)")
|
||||
|
||||
// Provider ist der Vertrag aller TTS-Backends von talk.
|
||||
// Provider is the contract of all talk TTS backends.
|
||||
//
|
||||
// Synthesize liefert dekodierbare Audio-Bytes; das Format ist von der
|
||||
// Implementierung dokumentiert: osbuiltin liefert RIFF/WAVE (PCM), die
|
||||
// Cloud-Provider elevenlabs, openai und edge liefern MP3 — beides wird vom
|
||||
// audio-Paket (oto-Sink) verstanden.
|
||||
// Synthesize returns decodable audio bytes; the format is documented by the
|
||||
// implementation: osbuiltin returns RIFF/WAVE (PCM), the cloud providers
|
||||
// elevenlabs, openai and edge return MP3. Both are understood by the audio
|
||||
// package (oto sink).
|
||||
type Provider interface {
|
||||
// Name liefert den eindeutigen Providernamen (CLI --provider, Config).
|
||||
// Name returns the unique provider name (CLI --provider, config).
|
||||
Name() string
|
||||
|
||||
// Available meldet, ob der Provider auf diesem System überhaupt nutzbar
|
||||
// ist (z. B. Binary oder Key vorhanden). Muss billig und ohne Neben-
|
||||
// wirkungen sein; false heißt: Kette soll überspringen werden.
|
||||
// Available reports whether the provider is usable on this system at all
|
||||
// (e.g. binary or key present). Must be cheap and side-effect free;
|
||||
// false means: the chain should skip this tier.
|
||||
Available(ctx context.Context) bool
|
||||
|
||||
// Synthesize erzeugt Audio-Bytes für text in der Sprache lang.
|
||||
// Synthesize produces audio bytes for text in language lang.
|
||||
Synthesize(ctx context.Context, text, lang string) ([]byte, error)
|
||||
}
|
||||
|
||||
// DirectSpeaker ist eine dokumentierte optionale Erweiterung über das
|
||||
// Provider-Interface hinaus (kein Bestandteil des Basisvertrags): Provider,
|
||||
// die Text ohne Umweg über Synthesize-Bytes selbst hörbar ausgeben können.
|
||||
// Notwendig auf Linux-Systemen, deren OS-TTS (speech-dispatcher) keine
|
||||
// Audio-Bytes an Prozesse liefern kann — dort spielt osbuiltin.SpeakDirect
|
||||
// über spd-say selbst ab (blockierend bis zum Ende der Ansage).
|
||||
// DirectSpeaker is a documented optional extension beyond the Provider
|
||||
// interface (not part of the base contract): providers that can make text
|
||||
// audible themselves without going through Synthesize bytes. Required on
|
||||
// Linux systems whose OS TTS (speech-dispatcher) cannot hand audio bytes to
|
||||
// processes; there osbuiltin.SpeakDirect plays via spd-say itself (blocking
|
||||
// until the announcement has finished).
|
||||
//
|
||||
// In der Kette (chain.go) ist SpeakDirect die letzte Stufe, wenn kein
|
||||
// Provider Bytes liefern kann; Chain implementiert DirectSpeaker selbst und
|
||||
// reicht an den Stufen-Provider durch.
|
||||
// In the chain (chain.go) SpeakDirect is the last tier when no provider can
|
||||
// deliver bytes; Chain implements DirectSpeaker itself and forwards to the
|
||||
// tier provider.
|
||||
type DirectSpeaker interface {
|
||||
// SpeakDirect gibt text in der Sprache lang direkt über das OS-TTS aus
|
||||
// und blockiert bis zum Ende der Wiedergabe (oder bis ctx abgebrochen
|
||||
// wird).
|
||||
// SpeakDirect speaks text in language lang directly through the OS TTS
|
||||
// and blocks until playback has finished (or ctx is cancelled).
|
||||
SpeakDirect(ctx context.Context, text, lang string) error
|
||||
}
|
||||
|
||||
// VoicePinner ist eine dokumentierte optionale Erweiterung über das
|
||||
// Provider-Interface hinaus: Provider, die eine explizite Voice-ID pinnen
|
||||
// können (das CLI-Flag --voice). Die gepinnte Stimme schlägt das
|
||||
// voice.<provider>.<lang>-Mapping der config.toml und die eingebauten
|
||||
// neutralen Defaults. Die Kette ruft PinVoice beim Bau auf alle Stufen auf,
|
||||
// die die Erweiterung implementieren (Stufen ohne Voice-Begriff, z. B.
|
||||
// osbuiltin, loggen einen Hinweis statt still zu ignorieren).
|
||||
// VoicePinner is a documented optional extension beyond the Provider
|
||||
// interface: providers that can pin an explicit voice ID (the CLI flag
|
||||
// --voice). The pinned voice beats the voice.<provider>.<lang> mapping of
|
||||
// config.toml and the built-in neutral defaults. The chain calls PinVoice
|
||||
// at construction on all tiers that implement the extension (tiers without
|
||||
// a notion of voice, e.g. osbuiltin, log a note instead of silently
|
||||
// ignoring it).
|
||||
type VoicePinner interface {
|
||||
PinVoice(id string)
|
||||
}
|
||||
|
||||
// registry bildet Providernamen auf Konstruktoren; NewChain und New bauen
|
||||
// darüber. Die Konstruktoren erhalten die ChainConfig (Endpunkte, Voices,
|
||||
// Keys via Getenv), damit Kette und Daemon (Task 04) dieselbe Quelle haben.
|
||||
// registry maps provider names to constructors; NewChain and New build
|
||||
// through it. The constructors receive the ChainConfig (endpoints, voices,
|
||||
// keys via Getenv) so that the chain and the daemon share one source.
|
||||
var registry = map[string]func(ChainConfig) Provider{
|
||||
"elevenlabs": func(cfg ChainConfig) Provider { return NewElevenLabs(cfg) },
|
||||
"openai": func(cfg ChainConfig) Provider { return NewOpenAI(cfg) },
|
||||
@@ -78,7 +77,7 @@ var registry = map[string]func(ChainConfig) Provider{
|
||||
"osbuiltin": func(ChainConfig) Provider { return OSBuiltin{} },
|
||||
}
|
||||
|
||||
// Names liefert die registrierten Providernamen (sortiert).
|
||||
// Names returns the registered provider names (sorted).
|
||||
func Names() []string {
|
||||
names := make([]string, 0, len(registry))
|
||||
for name := range registry {
|
||||
@@ -88,16 +87,16 @@ func Names() []string {
|
||||
return names
|
||||
}
|
||||
|
||||
// New liefert den Provider mit dem angegebenen Namen (Einzelkonstruktion);
|
||||
// "" wählt DefaultName. Für die Ansage-Kette (Fallback) NewChain verwenden.
|
||||
// Unbekannte Namen sind ein klarer Fehler.
|
||||
// New returns the provider with the given name (single construction); ""
|
||||
// selects DefaultName. For the announcement chain (fallback) use NewChain.
|
||||
// Unknown names are a clear error.
|
||||
func New(name string, cfg ChainConfig) (Provider, error) {
|
||||
if name == "" {
|
||||
name = DefaultName
|
||||
}
|
||||
ctor, ok := registry[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unbekannter oder noch nicht implementierter Provider %q (verfügbar: %s)",
|
||||
return nil, fmt.Errorf("unknown provider %q (available: %s)",
|
||||
name, strings.Join(Names(), ", "))
|
||||
}
|
||||
if cfg.Getenv == nil {
|
||||
|
||||
Reference in new issue
Block a user