From 0ffbf1935c9d091cce22a5583275ac0902f7a693 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 02 Jul 2026 03:04:51 +0800
Subject: [PATCH] Add Cantonese language model support

---
 privatevoice.src/services/engine_service_test.go |  113 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 112 insertions(+), 1 deletions(-)

diff --git a/privatevoice.src/services/engine_service_test.go b/privatevoice.src/services/engine_service_test.go
index 95ebd23..9aab675 100644
--- a/privatevoice.src/services/engine_service_test.go
+++ b/privatevoice.src/services/engine_service_test.go
@@ -1,6 +1,11 @@
 package services
 
-import "testing"
+import (
+	"testing"
+
+	"voicesnap/internal/config"
+	"voicesnap/internal/model"
+)
 
 func TestEngineServiceDownloadStatusTracksCancelAndFinish(t *testing.T) {
 	service := NewEngineService(nil)
@@ -53,3 +58,109 @@
 		t.Fatalf("modelID after finish = %v, want empty", got)
 	}
 }
+
+func TestListModelOptionsForCantoneseOnlyOffersSenseVoiceYue(t *testing.T) {
+	cfg := config.Default()
+	cfg.LanguageMode = config.LanguageModeManual
+	cfg.LanguageID = model.CantoneseLanguageID
+	cfg.ModelSelectionMode = config.ModelSelectionModeAuto
+
+	service := NewEngineService(cfg)
+	options := service.ListModelOptions()
+	if len(options) != 1 {
+		t.Fatalf("options len = %d, want 1: %#v", len(options), options)
+	}
+	if got := options[0]["modelID"]; got != model.SenseVoiceYueModelID {
+		t.Fatalf("modelID = %v, want %s", got, model.SenseVoiceYueModelID)
+	}
+	if got := options[0]["isDefault"]; got != true {
+		t.Fatalf("isDefault = %v, want true", got)
+	}
+}
+
+func TestListModelOptionsForLanguagePreviewsCantoneseWithoutPersisting(t *testing.T) {
+	cfg := config.Default()
+	cfg.LanguageMode = config.LanguageModeManual
+	cfg.LanguageID = model.DefaultLanguageID
+	service := NewEngineService(cfg)
+
+	options := service.ListModelOptionsForLanguage(model.CantoneseLanguageID)
+	if len(options) != 1 {
+		t.Fatalf("options len = %d, want 1: %#v", len(options), options)
+	}
+	if got := options[0]["modelID"]; got != model.SenseVoiceYueModelID {
+		t.Fatalf("modelID = %v, want %s", got, model.SenseVoiceYueModelID)
+	}
+	if got := options[0]["languageID"]; got != model.CantoneseLanguageID {
+		t.Fatalf("languageID = %v, want %s", got, model.CantoneseLanguageID)
+	}
+	if cfg.LanguageID != model.DefaultLanguageID {
+		t.Fatalf("cfg.LanguageID = %q, want unchanged %q", cfg.LanguageID, model.DefaultLanguageID)
+	}
+}
+
+func TestListModelOptionsForLanguageDoesNotMarkSharedCurrentModelForPendingLanguage(t *testing.T) {
+	cfg := config.Default()
+	cfg.LanguageMode = config.LanguageModeManual
+	cfg.LanguageID = model.DefaultLanguageID
+	cfg.ModelSelectionMode = config.ModelSelectionModeManual
+	cfg.SelectedModelID = model.XASRZhEn960ModelID
+	service := NewEngineService(cfg)
+
+	options := service.ListModelOptionsForLanguage(model.EnglishLanguageID)
+	var found bool
+	for _, option := range options {
+		if option["modelID"] != model.XASRZhEn960ModelID {
+			continue
+		}
+		found = true
+		if got := option["isCurrent"]; got != false {
+			t.Fatalf("isCurrent = %v, want false for pending language shared model", got)
+		}
+	}
+	if !found {
+		t.Fatalf("expected %s in English options: %#v", model.XASRZhEn960ModelID, options)
+	}
+}
+
+func TestAllowedModelProfileForLanguageAcceptsCantoneseWithoutPersisting(t *testing.T) {
+	cfg := config.Default()
+	cfg.LanguageMode = config.LanguageModeManual
+	cfg.LanguageID = model.DefaultLanguageID
+	service := NewEngineService(cfg)
+
+	profile, languageProfile, err := service.allowedModelProfileForLanguage(model.CantoneseLanguageID, model.SenseVoiceYueModelID)
+	if err != nil {
+		t.Fatalf("allowedModelProfileForLanguage returned error: %v", err)
+	}
+	if profile.ID != model.SenseVoiceYueModelID {
+		t.Fatalf("profile id = %q, want %q", profile.ID, model.SenseVoiceYueModelID)
+	}
+	if languageProfile.ID != model.CantoneseLanguageID {
+		t.Fatalf("language profile id = %q, want %q", languageProfile.ID, model.CantoneseLanguageID)
+	}
+	if _, err := service.allowedModelProfile(model.SenseVoiceYueModelID); err == nil {
+		t.Fatal("expected current Chinese language to reject SenseVoice Yue")
+	}
+}
+
+func TestAllowedModelProfileForLanguageAcceptsTargetLanguageUpgradeWithoutPersisting(t *testing.T) {
+	cfg := config.Default()
+	cfg.LanguageMode = config.LanguageModeManual
+	cfg.LanguageID = model.DefaultLanguageID
+	service := NewEngineService(cfg)
+
+	profile, languageProfile, err := service.allowedModelProfileForLanguage(model.EnglishLanguageID, model.ParakeetModelID)
+	if err != nil {
+		t.Fatalf("allowedModelProfileForLanguage returned error: %v", err)
+	}
+	if profile.ID != model.ParakeetModelID {
+		t.Fatalf("profile id = %q, want %q", profile.ID, model.ParakeetModelID)
+	}
+	if languageProfile.ID != model.EnglishLanguageID {
+		t.Fatalf("language profile id = %q, want %q", languageProfile.ID, model.EnglishLanguageID)
+	}
+	if _, err := service.allowedModelProfile(model.ParakeetModelID); err == nil {
+		t.Fatal("expected current Chinese language to reject Parakeet")
+	}
+}

--
Gitblit v1.9.3