156 lines
3.6 KiB
Go
156 lines
3.6 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"reasonix/internal/config"
|
|
"reasonix/internal/i18n"
|
|
)
|
|
|
|
func (m *chatTUI) runLanguageSubcommand(input string) {
|
|
args := tokenizeArgs(input)
|
|
if len(args) < 2 {
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
m.notice("language: " + err.Error())
|
|
return
|
|
}
|
|
saved := languageDisplay(cfg.Language)
|
|
resolved := i18n.DetectLanguage(cfg.Language)
|
|
m.notice(i18n.M.LanguageHeader + "\n" + describeLanguages(saved, resolved) + "\n" + i18n.M.LanguageHint)
|
|
return
|
|
}
|
|
if len(args) > 2 {
|
|
m.notice(i18n.M.LanguageHint)
|
|
return
|
|
}
|
|
|
|
lang, err := normalizeLanguageArg(args[1])
|
|
if err != nil {
|
|
m.notice(err.Error())
|
|
return
|
|
}
|
|
path := config.SourcePath()
|
|
if path == "" {
|
|
path = config.UserConfigPath()
|
|
}
|
|
if path == "" {
|
|
m.notice("language: cannot resolve config path")
|
|
return
|
|
}
|
|
// Lock the whole load-modify-save cycle, including the user-scope override
|
|
// cleanup — its own read-modify-write on the user config. path may be a
|
|
// project config here; holding the user-config lock for that case is
|
|
// harmless. The controller update below runs off-lock.
|
|
if err := func() error {
|
|
unlock := config.LockUserConfigEdits()
|
|
defer unlock()
|
|
edit := config.LoadForEdit(path)
|
|
if err := edit.SetLanguage(lang); err != nil {
|
|
return err
|
|
}
|
|
if err := edit.SaveTo(path); err != nil {
|
|
return err
|
|
}
|
|
if lang == "" {
|
|
return clearUserLanguageOverride(path)
|
|
}
|
|
return nil
|
|
}(); err != nil {
|
|
m.notice("language: " + err.Error())
|
|
return
|
|
}
|
|
|
|
resolved := i18n.DetectLanguage(lang)
|
|
if m.ctrl != nil {
|
|
m.ctrl.SetResponseLanguage(lang)
|
|
}
|
|
m.notice(fmt.Sprintf(i18n.M.LanguageChangedFmt, languageDisplay(lang), resolved))
|
|
}
|
|
|
|
// clearUserLanguageOverride drops a stale user-level language override after a
|
|
// project-level write. Callers must hold LockUserConfigEdits: this is its own
|
|
// load-modify-save on the user config and must not take the lock itself.
|
|
func clearUserLanguageOverride(primaryPath string) error {
|
|
userPath := config.UserConfigPath()
|
|
if userPath == "" || sameConfigPath(primaryPath, userPath) {
|
|
return nil
|
|
}
|
|
if _, err := os.Stat(userPath); err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
edit := config.LoadForEdit(userPath)
|
|
if strings.TrimSpace(edit.Language) == "" {
|
|
return nil
|
|
}
|
|
if err := edit.SetLanguage(""); err != nil {
|
|
return err
|
|
}
|
|
return edit.SaveTo(userPath)
|
|
}
|
|
|
|
func sameConfigPath(a, b string) bool {
|
|
aa, errA := filepath.Abs(a)
|
|
bb, errB := filepath.Abs(b)
|
|
if errA == nil {
|
|
a = aa
|
|
}
|
|
if errB == nil {
|
|
b = bb
|
|
}
|
|
return filepath.Clean(a) == filepath.Clean(b)
|
|
}
|
|
|
|
func normalizeLanguageArg(s string) (string, error) {
|
|
switch strings.ToLower(strings.TrimSpace(s)) {
|
|
case "", "auto", "detect", "default":
|
|
return "", nil
|
|
case "en", "english":
|
|
return "en", nil
|
|
case "zh", "cn", "chinese", "中文":
|
|
return "zh", nil
|
|
default:
|
|
return "", fmt.Errorf("usage: /language auto|en|zh")
|
|
}
|
|
}
|
|
|
|
func languageDisplay(lang string) string {
|
|
if strings.TrimSpace(lang) == "" {
|
|
return "auto"
|
|
}
|
|
return lang
|
|
}
|
|
|
|
func describeLanguages(current, resolved string) string {
|
|
items := []struct {
|
|
tag string
|
|
hint string
|
|
}{
|
|
{"auto", i18n.M.ArgLanguageAuto},
|
|
{"en", i18n.M.ArgLanguageEn},
|
|
{"zh", i18n.M.ArgLanguageZh},
|
|
}
|
|
var b strings.Builder
|
|
for _, it := range items {
|
|
marker := " "
|
|
if it.tag == current {
|
|
marker = "• "
|
|
}
|
|
hint := it.hint
|
|
if it.tag == current {
|
|
hint += " · " + i18n.M.ArgThemeCurrent
|
|
}
|
|
if it.tag == "auto" && current == "auto" {
|
|
hint += " · " + resolved
|
|
}
|
|
fmt.Fprintf(&b, "%s%-6s %s\n", marker, it.tag, hint)
|
|
}
|
|
return strings.TrimRight(b.String(), "\n")
|
|
}
|