Skip to content

Commit 82c6554

Browse files
authored
feat(indicator): embed cue assets and app-owned i18n copy (#2)
1 parent f4dfda4 commit 82c6554

File tree

17 files changed

+182
-261
lines changed

17 files changed

+182
-261
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ Local-first speech-to-text CLI.
2626
- indicator backends:
2727
- `hypr` notifications
2828
- `desktop` (freedesktop notifications, e.g. mako)
29-
- optional WAV cue files for start/stop/complete/cancel
29+
- embedded cue WAV assets for start/stop/complete/cancel (not user-configurable)
30+
- built-in indicator localization scaffolding (English catalog currently shipped)
3031
- built-in environment diagnostics via `sotto doctor`
3132

3233
## Platform scope (current)

apps/sotto/internal/config/defaults.go

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,12 @@ func Default() Config {
2020
},
2121
Transcript: TranscriptConfig{TrailingSpace: true},
2222
Indicator: IndicatorConfig{
23-
Enable: true,
24-
Backend: "hypr",
25-
DesktopAppName: "sotto-indicator",
26-
SoundEnable: true,
27-
SoundStartFile: "",
28-
SoundStopFile: "",
29-
SoundCompleteFile: "",
30-
SoundCancelFile: "",
31-
Height: 28,
32-
TextRecording: "Recording…",
33-
TextProcessing: "Transcribing…",
34-
TextError: "Speech recognition error",
35-
ErrorTimeoutMS: 1600,
23+
Enable: true,
24+
Backend: "hypr",
25+
DesktopAppName: "sotto-indicator",
26+
SoundEnable: true,
27+
Height: 28,
28+
ErrorTimeoutMS: 1600,
3629
},
3730
Clipboard: CommandConfig{Raw: clipboard, Argv: mustParseArgv(clipboard)},
3831
Vocab: VocabConfig{

apps/sotto/internal/config/parser_jsonc.go

Lines changed: 6 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,12 @@ type jsoncTranscript struct {
4949
}
5050

5151
type jsoncIndicator struct {
52-
Enable *bool `json:"enable"`
53-
Backend *string `json:"backend"`
54-
DesktopAppName *string `json:"desktop_app_name"`
55-
SoundEnable *bool `json:"sound_enable"`
56-
SoundStartFile *string `json:"sound_start_file"`
57-
SoundStopFile *string `json:"sound_stop_file"`
58-
SoundCompleteFile *string `json:"sound_complete_file"`
59-
SoundCancelFile *string `json:"sound_cancel_file"`
60-
Height *int `json:"height"`
61-
TextRecording *string `json:"text_recording"`
62-
TextProcessing *string `json:"text_processing"`
63-
TextTranscribing *string `json:"text_transcribing"`
64-
TextError *string `json:"text_error"`
65-
ErrorTimeoutMS *int `json:"error_timeout_ms"`
52+
Enable *bool `json:"enable"`
53+
Backend *string `json:"backend"`
54+
DesktopAppName *string `json:"desktop_app_name"`
55+
SoundEnable *bool `json:"sound_enable"`
56+
Height *int `json:"height"`
57+
ErrorTimeoutMS *int `json:"error_timeout_ms"`
6658
}
6759

6860
type jsoncVocab struct {
@@ -201,34 +193,9 @@ func (payload jsoncConfig) applyTo(cfg *Config) ([]Warning, error) {
201193
if payload.Indicator.SoundEnable != nil {
202194
cfg.Indicator.SoundEnable = *payload.Indicator.SoundEnable
203195
}
204-
if payload.Indicator.SoundStartFile != nil {
205-
cfg.Indicator.SoundStartFile = *payload.Indicator.SoundStartFile
206-
}
207-
if payload.Indicator.SoundStopFile != nil {
208-
cfg.Indicator.SoundStopFile = *payload.Indicator.SoundStopFile
209-
}
210-
if payload.Indicator.SoundCompleteFile != nil {
211-
cfg.Indicator.SoundCompleteFile = *payload.Indicator.SoundCompleteFile
212-
}
213-
if payload.Indicator.SoundCancelFile != nil {
214-
cfg.Indicator.SoundCancelFile = *payload.Indicator.SoundCancelFile
215-
}
216196
if payload.Indicator.Height != nil {
217197
cfg.Indicator.Height = *payload.Indicator.Height
218198
}
219-
if payload.Indicator.TextRecording != nil {
220-
cfg.Indicator.TextRecording = *payload.Indicator.TextRecording
221-
}
222-
if payload.Indicator.TextTranscribing != nil {
223-
cfg.Indicator.TextProcessing = *payload.Indicator.TextTranscribing
224-
warnings = append(warnings, Warning{Message: "indicator.text_transcribing is deprecated; use indicator.text_processing"})
225-
}
226-
if payload.Indicator.TextProcessing != nil {
227-
cfg.Indicator.TextProcessing = *payload.Indicator.TextProcessing
228-
}
229-
if payload.Indicator.TextError != nil {
230-
cfg.Indicator.TextError = *payload.Indicator.TextError
231-
}
232199
if payload.Indicator.ErrorTimeoutMS != nil {
233200
cfg.Indicator.ErrorTimeoutMS = *payload.Indicator.ErrorTimeoutMS
234201
}

apps/sotto/internal/config/parser_legacy.go

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -242,54 +242,12 @@ func applyRootKey(cfg *Config, key, value string) error {
242242
return fmt.Errorf("invalid bool for indicator.sound_enable: %w", err)
243243
}
244244
cfg.Indicator.SoundEnable = b
245-
case "indicator.sound_start_file":
246-
v, err := parseStringValue(value)
247-
if err != nil {
248-
return err
249-
}
250-
cfg.Indicator.SoundStartFile = v
251-
case "indicator.sound_stop_file":
252-
v, err := parseStringValue(value)
253-
if err != nil {
254-
return err
255-
}
256-
cfg.Indicator.SoundStopFile = v
257-
case "indicator.sound_complete_file":
258-
v, err := parseStringValue(value)
259-
if err != nil {
260-
return err
261-
}
262-
cfg.Indicator.SoundCompleteFile = v
263-
case "indicator.sound_cancel_file":
264-
v, err := parseStringValue(value)
265-
if err != nil {
266-
return err
267-
}
268-
cfg.Indicator.SoundCancelFile = v
269245
case "indicator.height":
270246
n, err := strconv.Atoi(value)
271247
if err != nil {
272248
return fmt.Errorf("invalid int for indicator.height: %w", err)
273249
}
274250
cfg.Indicator.Height = n
275-
case "indicator.text_recording":
276-
v, err := parseStringValue(value)
277-
if err != nil {
278-
return err
279-
}
280-
cfg.Indicator.TextRecording = v
281-
case "indicator.text_processing", "indicator.text_transcribing":
282-
v, err := parseStringValue(value)
283-
if err != nil {
284-
return err
285-
}
286-
cfg.Indicator.TextProcessing = v
287-
case "indicator.text_error":
288-
v, err := parseStringValue(value)
289-
if err != nil {
290-
return err
291-
}
292-
cfg.Indicator.TextError = v
293251
case "indicator.error_timeout_ms":
294252
n, err := strconv.Atoi(value)
295253
if err != nil {

apps/sotto/internal/config/parser_test.go

Lines changed: 10 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package config
33
import (
44
"strings"
55
"testing"
6+
7+
"github.com/stretchr/testify/require"
68
)
79

810
func TestParseValidJSONCConfig(t *testing.T) {
@@ -193,27 +195,6 @@ func TestParseIndicatorBackend(t *testing.T) {
193195
}
194196
}
195197

196-
func TestParseIndicatorTextTranscribingAliasWarning(t *testing.T) {
197-
cfg, warnings, err := Parse(`{"indicator":{"text_transcribing":"Working..."}}`, Default())
198-
if err != nil {
199-
t.Fatalf("Parse() error = %v", err)
200-
}
201-
if cfg.Indicator.TextProcessing != "Working..." {
202-
t.Fatalf("unexpected text processing value: %q", cfg.Indicator.TextProcessing)
203-
}
204-
205-
found := false
206-
for _, w := range warnings {
207-
if strings.Contains(w.Message, "text_transcribing") {
208-
found = true
209-
break
210-
}
211-
}
212-
if !found {
213-
t.Fatalf("expected alias warning, warnings=%+v", warnings)
214-
}
215-
}
216-
217198
func TestParseIndicatorSoundEnable(t *testing.T) {
218199
cfg, _, err := Parse(`{"indicator":{"sound_enable":false}}`, Default())
219200
if err != nil {
@@ -224,33 +205,16 @@ func TestParseIndicatorSoundEnable(t *testing.T) {
224205
}
225206
}
226207

227-
func TestParseIndicatorSoundFiles(t *testing.T) {
228-
cfg, _, err := Parse(`
229-
{
230-
"indicator": {
231-
"sound_start_file": "/tmp/start.wav",
232-
"sound_stop_file": "/tmp/stop.wav",
233-
"sound_complete_file": "/tmp/complete.wav",
234-
"sound_cancel_file": "/tmp/cancel.wav"
235-
}
208+
func TestParseIndicatorTextKeysRejected(t *testing.T) {
209+
_, _, err := Parse(`{"indicator":{"text_recording":"Recording"}}`, Default())
210+
require.Error(t, err)
211+
require.Contains(t, err.Error(), "unknown field")
236212
}
237-
`, Default())
238-
if err != nil {
239-
t.Fatalf("Parse() error = %v", err)
240-
}
241213

242-
if cfg.Indicator.SoundStartFile != "/tmp/start.wav" {
243-
t.Fatalf("unexpected start file: %q", cfg.Indicator.SoundStartFile)
244-
}
245-
if cfg.Indicator.SoundStopFile != "/tmp/stop.wav" {
246-
t.Fatalf("unexpected stop file: %q", cfg.Indicator.SoundStopFile)
247-
}
248-
if cfg.Indicator.SoundCompleteFile != "/tmp/complete.wav" {
249-
t.Fatalf("unexpected complete file: %q", cfg.Indicator.SoundCompleteFile)
250-
}
251-
if cfg.Indicator.SoundCancelFile != "/tmp/cancel.wav" {
252-
t.Fatalf("unexpected cancel file: %q", cfg.Indicator.SoundCancelFile)
253-
}
214+
func TestParseIndicatorSoundFileKeysRejected(t *testing.T) {
215+
_, _, err := Parse(`{"indicator":{"sound_start_file":"/tmp/start.wav"}}`, Default())
216+
require.Error(t, err)
217+
require.Contains(t, err.Error(), "unknown field")
254218
}
255219

256220
func TestParseInitializesNilVocabMap(t *testing.T) {

apps/sotto/internal/config/types.go

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,12 @@ type TranscriptConfig struct {
4343

4444
// IndicatorConfig controls visual indicator and audio cue behavior.
4545
type IndicatorConfig struct {
46-
Enable bool
47-
Backend string
48-
DesktopAppName string
49-
SoundEnable bool
50-
SoundStartFile string
51-
SoundStopFile string
52-
SoundCompleteFile string
53-
SoundCancelFile string
54-
Height int
55-
TextRecording string
56-
TextProcessing string
57-
TextError string
58-
ErrorTimeoutMS int
46+
Enable bool
47+
Backend string
48+
DesktopAppName string
49+
SoundEnable bool
50+
Height int
51+
ErrorTimeoutMS int
5952
}
6053

6154
// CommandConfig stores a raw command string and its parsed argv form.
15 KB
Binary file not shown.
40.4 KB
Binary file not shown.
25.4 KB
Binary file not shown.
25.4 KB
Binary file not shown.

0 commit comments

Comments
 (0)