From 0f700f935f7e2daaf83f523cd5a59b7417c80761 Mon Sep 17 00:00:00 2001 From: leonardotrapani Date: Sun, 1 Feb 2026 13:36:49 +0100 Subject: [PATCH] validate general.language in config and use effective language for model compatibility --- internal/config/config_test.go | 97 ++++++++++++++++++++++++++++++++++ internal/config/validate.go | 12 +++-- progress.txt | 13 +++++ tasks/prd.jsonc | 2 +- 4 files changed, 119 insertions(+), 5 deletions(-) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 253ac23..e73712a 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -2039,3 +2039,100 @@ func TestConfig_EffectiveLanguage(t *testing.T) { } }) } + +func TestConfig_Validate_GeneralLanguage(t *testing.T) { + baseConfig := func() *Config { + return &Config{ + Recording: RecordingConfig{ + SampleRate: 16000, + Channels: 1, + Format: "s16", + BufferSize: 8192, + ChannelBufferSize: 30, + Timeout: time.Minute, + }, + Transcription: TranscriptionConfig{ + Provider: "openai", + APIKey: "test-key", + Model: "whisper-1", + }, + Injection: InjectionConfig{ + Backends: []string{"clipboard"}, + YdotoolTimeout: 5 * time.Second, + WtypeTimeout: 5 * time.Second, + ClipboardTimeout: 3 * time.Second, + }, + Notifications: NotificationsConfig{Type: "log"}, + } + } + + t.Run("valid general.language passes validation", func(t *testing.T) { + config := baseConfig() + config.General.Language = "es" + + err := config.Validate() + if err != nil { + t.Errorf("Validate() should pass with valid general.language: %v", err) + } + }) + + t.Run("general.language validated against model", func(t *testing.T) { + config := baseConfig() + config.General.Language = "es" + config.Transcription.Provider = "groq-transcription" + config.Transcription.Model = "distil-whisper-large-v3-en" // english-only model + config.Transcription.APIKey = "gsk-test-key" + + err := config.Validate() + if err == nil { + t.Error("Validate() should fail when general.language incompatible with model") + } + if err != nil && !strings.Contains(err.Error(), "does not support Spanish") { + t.Errorf("error should mention Spanish, got: %v", err) + } + }) + + t.Run("transcription.language override validated against model", func(t *testing.T) { + config := baseConfig() + config.General.Language = "en" // compatible + config.Transcription.Language = "es" // override with incompatible + config.Transcription.Provider = "groq-transcription" + config.Transcription.Model = "distil-whisper-large-v3-en" // english-only model + config.Transcription.APIKey = "gsk-test-key" + + err := config.Validate() + if err == nil { + t.Error("Validate() should fail when transcription.language override is incompatible") + } + if err != nil && !strings.Contains(err.Error(), "does not support Spanish") { + t.Errorf("error should mention Spanish, got: %v", err) + } + }) + + t.Run("valid override with compatible language", func(t *testing.T) { + config := baseConfig() + config.General.Language = "es" // would be incompatible + config.Transcription.Language = "en" // override with compatible + config.Transcription.Provider = "groq-transcription" + config.Transcription.Model = "distil-whisper-large-v3-en" // english-only model + config.Transcription.APIKey = "gsk-test-key" + + err := config.Validate() + if err != nil { + t.Errorf("Validate() should pass when transcription.language override is compatible: %v", err) + } + }) + + t.Run("auto language always passes", func(t *testing.T) { + config := baseConfig() + config.General.Language = "" // auto + config.Transcription.Provider = "groq-transcription" + config.Transcription.Model = "distil-whisper-large-v3-en" // english-only model + config.Transcription.APIKey = "gsk-test-key" + + err := config.Validate() + if err != nil { + t.Errorf("Validate() should pass with auto language: %v", err) + } + }) +} diff --git a/internal/config/validate.go b/internal/config/validate.go index 6805b3b..cbe2482 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -85,9 +85,12 @@ func (c *Config) Validate() error { } } - // validate language code - warn if not recognized but don't error + // validate language codes - warn if not recognized but don't error + if c.General.Language != "" && !language.IsValidCode(c.General.Language) { + log.Printf("warning: unrecognized language code '%s' in general.language, will be passed as-is to provider", c.General.Language) + } if c.Transcription.Language != "" && !language.IsValidCode(c.Transcription.Language) { - log.Printf("warning: unrecognized language code '%s', will be passed as-is to provider", c.Transcription.Language) + log.Printf("warning: unrecognized language code '%s' in transcription.language, will be passed as-is to provider", c.Transcription.Language) } // validate model exists @@ -111,8 +114,9 @@ func (c *Config) Validate() error { return fmt.Errorf("invalid model for %s: %s (available: %s)", c.Transcription.Provider, c.Transcription.Model, strings.Join(modelIDs, ", ")) } - // validate language-model compatibility - if err := ValidateModelLanguageCompatibility(registryName, c.Transcription.Model, c.Transcription.Language); err != nil { + // validate language-model compatibility using effective language (transcription overrides general) + effectiveLanguage := c.resolveEffectiveLanguage() + if err := ValidateModelLanguageCompatibility(registryName, c.Transcription.Model, effectiveLanguage); err != nil { return err } diff --git a/progress.txt b/progress.txt index 53925d6..a11c454 100644 --- a/progress.txt +++ b/progress.txt @@ -595,4 +595,17 @@ Started: Sun Feb 1 12:22:47 AM CET 2026 - TUI already displays err.Error() so improvements propagate automatically - Added TestValidateModelLanguage_ErrorFormat test verifying error includes model name, docs URL, and language - Updated test expectations in config_test.go for new error format +- All tests passing, typecheck passes + +### Task 11: Update config validation for general language +- Updated `internal/config/validate.go` to validate `general.language` if set +- Added warning for unrecognized `general.language` code (warns but doesn't error) +- Changed language-model compatibility check to use effective language (`resolveEffectiveLanguage()`) +- Effective language = transcription.language override, or general.language if no override +- Added comprehensive tests in config_test.go: + - `TestConfig_Validate_GeneralLanguage/valid_general.language_passes_validation` + - `TestConfig_Validate_GeneralLanguage/general.language_validated_against_model` + - `TestConfig_Validate_GeneralLanguage/transcription.language_override_validated_against_model` + - `TestConfig_Validate_GeneralLanguage/valid_override_with_compatible_language` + - `TestConfig_Validate_GeneralLanguage/auto_language_always_passes` - All tests passing, typecheck passes \ No newline at end of file diff --git a/tasks/prd.jsonc b/tasks/prd.jsonc index 3ffdbee..9225297 100644 --- a/tasks/prd.jsonc +++ b/tasks/prd.jsonc @@ -184,7 +184,7 @@ "Config with general.language='invalid' warns but doesn't hard fail", "Typecheck passes" ], - "passes": false + "passes": true }, { "title": "Update README and docs for general language setting",