feat: persist desktop language preference

This commit is contained in:
2026-07-11 02:00:10 +08:00
parent e412cfc45a
commit 15f1f413d0
4 changed files with 134 additions and 2 deletions
+41
View File
@@ -21,6 +21,7 @@ type Config struct {
CurrentVaultPath string `json:"currentVaultPath"`
RecentVaults []string `json:"recentVaults"`
Theme string `json:"theme"`
Language string `json:"language"`
DevMode bool `json:"devMode"`
UserPluginsDir string `json:"userPluginsDir"`
Workbench WorkbenchPreferences `json:"workbench,omitempty"`
@@ -30,6 +31,21 @@ type Config struct {
LastOpenedAt string `json:"lastOpenedAt"`
}
const (
LanguageSystem = "system"
LanguageEnglish = "en"
LanguageRussian = "ru"
)
func validLanguage(language string) bool {
switch language {
case LanguageSystem, LanguageEnglish, LanguageRussian:
return true
default:
return false
}
}
type WorkbenchPreferences struct {
DefaultTextEditorProvider string `json:"defaultTextEditorProvider,omitempty"`
DefaultMarkdownEditorProvider string `json:"defaultMarkdownEditorProvider,omitempty"`
@@ -121,6 +137,9 @@ func (m *Manager) Load() error {
if cfg.Theme == "" {
cfg.Theme = "dark"
}
if !validLanguage(cfg.Language) {
cfg.Language = LanguageSystem
}
if cfg.RecentVaults == nil {
cfg.RecentVaults = []string{}
}
@@ -179,6 +198,9 @@ func (m *Manager) Update(patch *Config) error {
if patch.Theme != "" {
m.config.Theme = patch.Theme
}
if patch.Language != "" && validLanguage(patch.Language) {
m.config.Language = patch.Language
}
if patch.UserPluginsDir != "" {
m.config.UserPluginsDir = patch.UserPluginsDir
}
@@ -200,6 +222,23 @@ func (m *Manager) Update(patch *Config) error {
return m.saveLocked()
}
// UpdateLanguage validates and persists the installation-local UI language.
func (m *Manager) UpdateLanguage(language string) error {
language = strings.TrimSpace(language)
if !validLanguage(language) {
return fmt.Errorf("unsupported language %q", language)
}
m.mu.Lock()
defer m.mu.Unlock()
if m.config == nil {
m.config = defaultConfig()
}
m.config.Language = language
m.config.LastOpenedAt = time.Now().UTC().Format(time.RFC3339)
return m.saveLocked()
}
// UpdateSync replaces sync settings without changing unrelated app settings.
func (m *Manager) UpdateSync(syncSettings SyncSettings) error {
m.mu.Lock()
@@ -287,6 +326,7 @@ func defaultConfig() *Config {
CurrentVaultPath: "",
RecentVaults: []string{},
Theme: "dark",
Language: LanguageSystem,
DevMode: false,
UserPluginsDir: filepath.Join(os.Getenv("HOME"), ".config", "verstak", "plugins"),
Workbench: WorkbenchPreferences{},
@@ -303,6 +343,7 @@ func copyConfig(c *Config) *Config {
CurrentVaultPath: c.CurrentVaultPath,
RecentVaults: recent,
Theme: c.Theme,
Language: c.Language,
DevMode: c.DevMode,
UserPluginsDir: c.UserPluginsDir,
Workbench: c.Workbench,
+47
View File
@@ -25,6 +25,53 @@ func TestLoad_DefaultCreation(t *testing.T) {
if cfg.CurrentVaultPath != "" {
t.Errorf("CurrentVaultPath: got %q, want empty", cfg.CurrentVaultPath)
}
if cfg.Language != "system" {
t.Errorf("Language: got %q, want %q", cfg.Language, "system")
}
}
func TestLoad_MissingLanguageDefaultsToSystem(t *testing.T) {
path := filepath.Join(t.TempDir(), "config.json")
if err := os.WriteFile(path, []byte(`{"schemaVersion":1,"theme":"dark","devMode":true}`), 0o600); err != nil {
t.Fatal(err)
}
m := NewManager(path)
if err := m.Load(); err != nil {
t.Fatalf("Load: %v", err)
}
if got := m.Get().Language; got != "system" {
t.Fatalf("Language = %q, want system", got)
}
}
func TestUpdateLanguagePersistsAndPreservesUnrelatedSettings(t *testing.T) {
path := filepath.Join(t.TempDir(), "config.json")
m := NewManager(path)
if err := m.Load(); err != nil {
t.Fatal(err)
}
if err := m.Update(&Config{Theme: "light", DevMode: true}); err != nil {
t.Fatal(err)
}
if err := m.UpdateLanguage("ru"); err != nil {
t.Fatalf("UpdateLanguage: %v", err)
}
reloaded := NewManager(path)
if err := reloaded.Load(); err != nil {
t.Fatal(err)
}
cfg := reloaded.Get()
if cfg.Language != "ru" || cfg.Theme != "light" || !cfg.DevMode {
t.Fatalf("settings after language update = %+v", cfg)
}
if err := reloaded.UpdateLanguage("de"); err == nil {
t.Fatal("UpdateLanguage(de) succeeded, want validation error")
}
if got := reloaded.Get().Language; got != "ru" {
t.Fatalf("Language after rejected update = %q, want ru", got)
}
}
func TestLoad_CorruptConfig(t *testing.T) {