From eddfeb59b49c89e56d6dfe0e28412a170b99ecd2 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Thu, 24 Sep 2026 12:41:33 +0800 Subject: [PATCH] fix(i18n): refresh docked panel titles on language switch PanelHandle snapshots DockPanel::title at registration and the tab strip renders that cache, so switching the UI language at runtime (the menu's language items or the preferences combo) left every docked tab in the previous language: the English UI with Chinese tabs from the report. Add dock panel title/content refresh to gpui (oak-gpui 45871acf1a): DockArea::refresh_panel_titles re-reads every held title through a type-erased provider captured from the concrete panel entity, marks each panel view dirty so its localized content re-renders, and repaints the chrome. Call it from both shell language-switch paths; the preferences dialog repaints itself too. The app test pins the refresh end to end: after LanguageChanged the project tab's cached title follows the new language. --- crates/oak-app/src/app.rs | 41 +++++++++++++++++++++++++++++++++++++-- gpui | 2 +- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/crates/oak-app/src/app.rs b/crates/oak-app/src/app.rs index 03b2748ff..d57a74348 100644 --- a/crates/oak-app/src/app.rs +++ b/crates/oak-app/src/app.rs @@ -1663,9 +1663,12 @@ impl OakApp { /// Switches the UI language live: updates the [`i18n`] global, rebuilds /// the menu bar (so the menu labels and the language checkmark move - /// immediately), and repaints the whole shell. + /// immediately), refreshes the docked panels' cached titles (the tab + /// strip snapshots them at registration), and repaints the whole shell. fn switch_language(&mut self, code: &str, cx: &mut Context) { crate::i18n::set_language_code(code); + self.dock + .update(cx, |dock, cx| dock.refresh_panel_titles(cx)); self.rebuild_menu_bar(cx); cx.notify(); } @@ -2229,7 +2232,11 @@ impl OakApp { this.apply_dark(dark, cx); } crate::dialogs::PreferencesEvent::LanguageChanged => { + this.dock + .update(cx, |dock, cx| dock.refresh_panel_titles(cx)); this.rebuild_menu_bar(cx); + // The dialog's own labels are localized: repaint it too. + _content.update(cx, |_dialog, cx| cx.notify()); cx.notify(); } crate::dialogs::PreferencesEvent::ShortcutsChanged => { @@ -7602,6 +7609,9 @@ mod tests { .lock() .unwrap_or_else(|e| e.into_inner()); let _theme = ConfigGuard::pin(crate::oakui::real::CONFIG_KEY_THEME); + // Deterministic language for the tab-title refresh assertion below + // (the panels snapshot their localized titles at registration). + crate::i18n::set_language_code("en-US"); let (_window, root) = mock_shell(cx); crate::actions::reset_all_custom_shortcuts(); @@ -7628,8 +7638,27 @@ mod tests { cx.run_until_parked(); assert!(cx.read(|app| root.read(app).dark)); - // LanguageChanged rebuilds the menu bar. + // LanguageChanged rebuilds the menu bar and refreshes the docked + // panels' cached (localized) titles: the tab strip renders the + // snapshot taken at registration, not the live `DockPanel::title`. let menu_before = cx.read(|app| root.read(app).menu_bar.entity_id()); + let project_title = |cx: &mut TestAppContext| { + cx.read(|app| { + root.read(app) + .dock + .read(app) + .panel(crate::panels::ids::PROJECT) + .expect("the project panel is docked") + .title() + .clone() + }) + }; + assert_eq!( + project_title(cx).as_ref(), + crate::i18n::tr("panel.project"), + "the tab starts in the shell's registration language" + ); + crate::i18n::set_language_code("zh-CN"); cx.update(|app| { content.update(app, |_content, cx| { cx.emit(crate::dialogs::PreferencesEvent::LanguageChanged) @@ -7640,6 +7669,14 @@ mod tests { cx.read(|app| root.read(app).menu_bar.entity_id()), menu_before ); + let refreshed = project_title(cx); + assert_eq!( + refreshed.as_ref(), + crate::i18n::tr("panel.project"), + "the cached tab title follows the new language" + ); + assert_ne!(refreshed.as_ref(), "Project", "the stale English label is gone"); + crate::i18n::set_language_code("en-US"); // ShortcutsChanged re-binds the key map and rebuilds the menu bar. crate::actions::set_custom_shortcut("snapping", vec!["f5".to_string()]); diff --git a/gpui b/gpui index 56be9f758..45871acf1 160000 --- a/gpui +++ b/gpui @@ -1 +1 @@ -Subproject commit 56be9f7582c1ab0269a575a176b86f3ff9ae8564 +Subproject commit 45871acf1abdf22e693e6b12d43dcaa15ca54fa9