fix: change the language dynamically (#843)

Drew Smirnoff created

Change summary

main.go                 | 24 ++++++++++++++++++++++++
tui/messages.go         |  2 ++
tui/settings.go         | 32 ++++++++++++++++++++++++++++++++
tui/settings_general.go |  6 +++++-
4 files changed, 63 insertions(+), 1 deletion(-)

Detailed changes

main.go 🔗

@@ -870,6 +870,30 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		m.current, _ = m.current.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height})
 		return m, m.current.Init()
 
+	case tui.LanguageChangedMsg:
+		// Rebuild all models with new translations
+		// Keep current view type but recreate with fresh i18n
+		switch curr := m.current.(type) {
+		case *tui.Settings:
+			// Preserve settings state when rebuilding
+			newSettings := tui.NewSettings(m.config)
+			newSettings.RestoreState(curr.GetState())
+			m.current = newSettings
+		case *tui.Composer:
+			// Preserve composer state if possible, for now just refresh
+			m.current = tui.NewChoice()
+		case *tui.Inbox:
+			m.current = tui.NewChoice()
+		case *tui.FolderInbox:
+			// Just rebuild settings view, folder inbox will be recreated on next navigation
+			m.current = tui.NewSettings(m.config)
+		default:
+			// For other views, return to choice menu
+			m.current = tui.NewChoice()
+		}
+		m.current, _ = m.current.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height})
+		return m, m.current.Init()
+
 	case tui.GoToSettingsMsg:
 		m.current = tui.NewSettings(m.config)
 		m.current, _ = m.current.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height})

tui/messages.go 🔗

@@ -526,6 +526,8 @@ type SendRSVPMsg struct {
 }
 
 // RSVPResultMsg signals that RSVP was sent (or failed)
+type LanguageChangedMsg struct{}
+
 type RSVPResultMsg struct {
 	Err       error
 	Response  string // "ACCEPTED", "DECLINED", "TENTATIVE"

tui/settings.go 🔗

@@ -75,6 +75,16 @@ type Settings struct {
 	confirmingDisable bool
 }
 
+type SettingsState struct {
+	ActivePane     SettingsPane
+	ActiveCategory SettingsCategory
+	MenuCursor     int
+	GeneralCursor  int
+	AccountsCursor int
+	ThemeCursor    int
+	ListsCursor    int
+}
+
 func NewSettings(cfg *config.Config) *Settings {
 	if cfg == nil {
 		cfg = &config.Config{}
@@ -110,6 +120,28 @@ func NewSettings(cfg *config.Config) *Settings {
 	}
 }
 
+func (m *Settings) GetState() SettingsState {
+	return SettingsState{
+		ActivePane:     m.activePane,
+		ActiveCategory: m.activeCategory,
+		MenuCursor:     m.menuCursor,
+		GeneralCursor:  m.generalCursor,
+		AccountsCursor: m.accountsCursor,
+		ThemeCursor:    m.themeCursor,
+		ListsCursor:    m.listsCursor,
+	}
+}
+
+func (m *Settings) RestoreState(state SettingsState) {
+	m.activePane = state.ActivePane
+	m.activeCategory = state.ActiveCategory
+	m.menuCursor = state.MenuCursor
+	m.generalCursor = state.GeneralCursor
+	m.accountsCursor = state.AccountsCursor
+	m.themeCursor = state.ThemeCursor
+	m.listsCursor = state.ListsCursor
+}
+
 func (m *Settings) Init() tea.Cmd {
 	return textinput.Blink
 }

tui/settings_general.go 🔗

@@ -54,6 +54,10 @@ func (m *Settings) updateGeneral(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
 			nextIdx := (currentIdx + 1) % len(langs)
 			m.cfg.Language = langs[nextIdx]
 			_ = config.SaveConfig(m.cfg)
+			// Apply language change immediately
+			i18n.GetManager().SetLanguage(m.cfg.Language)
+			// Trigger full UI rebuild
+			return m, func() tea.Msg { return LanguageChangedMsg{} }
 		case 5: // Edit Signature
 			if msg.String() == "enter" || msg.String() == "right" || msg.String() == "l" {
 				return m, func() tea.Msg { return GoToSignatureEditorMsg{} }
@@ -77,7 +81,7 @@ func (m *Settings) viewGeneral() string {
 		{"settings_general.hide_tips", onOff(m.cfg.HideTips), "Hide helpful hints displayed at the bottom of the screen."},
 		{"settings_general.disable_notifications", onOff(m.cfg.DisableNotifications), "Turn off desktop notifications for new mail."},
 		{"settings_general.date_format", getDateFormatLabel(m.cfg.DateFormat), "Change how dates and times are displayed."},
-		{"settings_general.language", getLanguageLabel(m.cfg.GetLanguage()), "Change the interface language. Restart required."},
+		{"settings_general.language", getLanguageLabel(m.cfg.GetLanguage()), "Change the interface language. Changes apply instantly."},
 		{"settings_general.signature", getSignatureStatus(), "Configure the signature appended to your outgoing emails."},
 	}