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
+24
-24
@@ -7,29 +7,29 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Erlaubte Werte für quiet_mode (öffentlicher Config-Wert).
|
||||
// Allowed values for quiet_mode (public config value).
|
||||
const (
|
||||
QuietModeDiscard = "discard"
|
||||
QuietModeToast = "toast"
|
||||
)
|
||||
|
||||
// clockTime ist eine Tageszeit in Minuten-Auflösung (lokale Zeit, 24 h).
|
||||
// clockTime is a time of day at minute resolution (local time, 24 h).
|
||||
type clockTime struct{ hour, minute int }
|
||||
|
||||
// minutes liefert Minuten seit Mitternacht.
|
||||
// minutes returns minutes since midnight.
|
||||
func (c clockTime) minutes() int { return c.hour*60 + c.minute }
|
||||
|
||||
// String rendert die Uhrzeit als "HH:MM".
|
||||
// String renders the time as "HH:MM".
|
||||
func (c clockTime) String() string { return fmt.Sprintf("%02d:%02d", c.hour, c.minute) }
|
||||
|
||||
// QuietHours ist ein Ruhezeit-Fenster in lokaler Zeit, z. B. "18:00-08:00"
|
||||
// (überspannt Mitternacht). Der Nullwert (Start == End) ist deaktiviert —
|
||||
// rund-um-die-Uhr-Stumm ist mute, nicht quiet_hours.
|
||||
// QuietHours is a quiet window in local time, for example "18:00-08:00"
|
||||
// (spanning midnight). The zero value (Start == End) is disabled; silence
|
||||
// around the clock is mute, not quiet hours.
|
||||
type QuietHours struct {
|
||||
Start, End clockTime
|
||||
}
|
||||
|
||||
// String rendert das Fenster als "HH:MM-HH:MM" (Nullwert: "").
|
||||
// String renders the window as "HH:MM-HH:MM" (zero value: "").
|
||||
func (q QuietHours) String() string {
|
||||
if q.Start == q.End {
|
||||
return ""
|
||||
@@ -37,17 +37,17 @@ func (q QuietHours) String() string {
|
||||
return q.Start.String() + "-" + q.End.String()
|
||||
}
|
||||
|
||||
// ParseQuietHours parst ein Ruhezeit-Fenster "HH:MM-HH:MM" (lokale Zeit,
|
||||
// 24 h; Stunden ein- oder zweistellig). Fenster über Mitternacht
|
||||
// (Start > End) sind erlaubt; Start == End deaktiviert das Fenster. Der
|
||||
// Leerstring ist das deaktivierte Fenster (kein Fehler).
|
||||
// ParseQuietHours parses a quiet window "HH:MM-HH:MM" (local time, 24 h;
|
||||
// hours with one or two digits). Windows across midnight (Start > End) are
|
||||
// allowed; Start == End disables the window. The empty string is the
|
||||
// disabled window (no error).
|
||||
func ParseQuietHours(s string) (QuietHours, error) {
|
||||
if s == "" {
|
||||
return QuietHours{}, nil
|
||||
}
|
||||
startS, endS, ok := strings.Cut(s, "-")
|
||||
if !ok {
|
||||
return QuietHours{}, fmt.Errorf("quiet_hours: %q ist nicht HH:MM-HH:MM", s)
|
||||
return QuietHours{}, fmt.Errorf("quiet_hours: %q is not HH:MM-HH:MM", s)
|
||||
}
|
||||
start, err := parseClock(startS)
|
||||
if err != nil {
|
||||
@@ -60,29 +60,29 @@ func ParseQuietHours(s string) (QuietHours, error) {
|
||||
return QuietHours{Start: start, End: end}, nil
|
||||
}
|
||||
|
||||
// parseClock parst "H[H]:MM" in Uhrzeit-Bestandteile.
|
||||
// parseClock parses "H[H]:MM" into its parts.
|
||||
func parseClock(s string) (clockTime, error) {
|
||||
hourS, minuteS, ok := strings.Cut(s, ":")
|
||||
if !ok || len(hourS) < 1 || len(hourS) > 2 || len(minuteS) != 2 {
|
||||
return clockTime{}, fmt.Errorf("%q ist nicht HH:MM", s)
|
||||
return clockTime{}, fmt.Errorf("%q is not HH:MM", s)
|
||||
}
|
||||
hour, err := strconv.Atoi(hourS)
|
||||
if err != nil {
|
||||
return clockTime{}, fmt.Errorf("%q ist nicht HH:MM", s)
|
||||
return clockTime{}, fmt.Errorf("%q is not HH:MM", s)
|
||||
}
|
||||
minute, err := strconv.Atoi(minuteS)
|
||||
if err != nil {
|
||||
return clockTime{}, fmt.Errorf("%q ist nicht HH:MM", s)
|
||||
return clockTime{}, fmt.Errorf("%q is not HH:MM", s)
|
||||
}
|
||||
if hour < 0 || hour > 23 || minute < 0 || minute > 59 {
|
||||
return clockTime{}, fmt.Errorf("%q ist keine gültige Uhrzeit (24 h)", s)
|
||||
return clockTime{}, fmt.Errorf("%q is not a valid time of day (24 h)", s)
|
||||
}
|
||||
return clockTime{hour: hour, minute: minute}, nil
|
||||
}
|
||||
|
||||
// Active meldet, ob der lokale Zeitpunkt t im Ruhefenster liegt.
|
||||
// Tagfenster (Start < End) gelten ab Start bis ausschließlich End;
|
||||
// Nachtfenster (Start > End) überspannen Mitternacht.
|
||||
// Active reports whether the local time t lies inside the window. Day
|
||||
// windows (Start < End) run from Start up to but excluding End; night
|
||||
// windows (Start > End) span midnight.
|
||||
func (q QuietHours) Active(t time.Time) bool {
|
||||
if q.Start == q.End {
|
||||
return false
|
||||
@@ -95,15 +95,15 @@ func (q QuietHours) Active(t time.Time) bool {
|
||||
return m >= s || m < e
|
||||
}
|
||||
|
||||
// Until liefert das lokale Ende des gerade aktiven Fensters — Nullzeit,
|
||||
// wenn gerade kein Ruhefenster aktiv ist. Basis für GET /status.
|
||||
// Until returns the local end of the currently active window; the zero time
|
||||
// when no window is active. Basis for GET /status.
|
||||
func (q QuietHours) Until(t time.Time) time.Time {
|
||||
if !q.Active(t) {
|
||||
return time.Time{}
|
||||
}
|
||||
end := time.Date(t.Year(), t.Month(), t.Day(), q.End.hour, q.End.minute, 0, 0, t.Location())
|
||||
if q.Start.minutes() > q.End.minutes() && t.Hour()*60+t.Minute() >= q.Start.minutes() {
|
||||
end = end.AddDate(0, 0, 1) // Nachtfenster: das Ende ist am Folgetag
|
||||
end = end.AddDate(0, 0, 1) // night window: the end is tomorrow
|
||||
}
|
||||
return end
|
||||
}
|
||||
Reference in new issue
Block a user