diff --git a/apps/plumeimpactor/src/screen/mod.rs b/apps/plumeimpactor/src/screen/mod.rs index ba45f804..0b5cfd27 100644 --- a/apps/plumeimpactor/src/screen/mod.rs +++ b/apps/plumeimpactor/src/screen/mod.rs @@ -93,6 +93,7 @@ pub struct Impactor { login_windows: std::collections::HashMap, pending_installation: bool, certificate_reset_queue: VecDeque, + selected_locale: Option, } #[derive(Debug, Clone, PartialEq)] @@ -139,6 +140,7 @@ impl Impactor { login_windows: std::collections::HashMap::new(), pending_installation: false, certificate_reset_queue: VecDeque::new(), + selected_locale: None, }, open_task, ) @@ -572,6 +574,14 @@ impl Impactor { } screen.update(msg).map(Message::SettingsScreen) } + settings::Message::SelectLocale(choice) => { + self.selected_locale = choice.clone(); + let effective = choice + .or_else(|| current_locale::current_locale().ok()) + .unwrap_or_else(|| "en".to_string()); + rust_i18n::set_locale(&effective); + Task::none() + } _ => screen.update(msg).map(Message::SettingsScreen), } } else { @@ -835,7 +845,7 @@ impl Impactor { ImpactorScreen::Main(screen) => screen.view().map(Message::MainScreen), ImpactorScreen::Utilities(screen) => screen.view().map(Message::UtilitiesScreen), ImpactorScreen::Settings(screen) => screen - .view(&self.account_store) + .view(&self.account_store, &self.selected_locale) .map(Message::SettingsScreen), ImpactorScreen::Installer(screen) => { screen.view(has_device).map(Message::InstallerScreen) diff --git a/apps/plumeimpactor/src/screen/settings.rs b/apps/plumeimpactor/src/screen/settings.rs index d9777acb..cbf70c66 100644 --- a/apps/plumeimpactor/src/screen/settings.rs +++ b/apps/plumeimpactor/src/screen/settings.rs @@ -29,6 +29,38 @@ pub enum Message { FetchTeams(String), TeamsLoaded(String, Vec), ToggleAutoStart(bool), + SelectLocale(Option), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LocaleChoice { + code: Option, +} + +impl LocaleChoice { + fn system() -> Self { + Self { code: None } + } + + fn explicit(code: String) -> Self { + Self { code: Some(code) } + } + + fn code(&self) -> Option<&str> { + self.code.as_deref() + } +} + +impl std::fmt::Display for LocaleChoice { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.code { + None => write!(f, "{}", rust_i18n::t!("settings_system_language")), + Some(code) => { + let name = rust_i18n::t!("_language_name", locale = code); + write!(f, "{}", name) + } + } + } } #[derive(Debug)] @@ -58,11 +90,16 @@ impl SettingsScreen { } Message::ToggleAutoStart(_) => Task::none(), Message::SelectTeam(_, _) => Task::none(), + Message::SelectLocale(_) => Task::none(), _ => Task::none(), } } - pub fn view<'a>(&'a self, account_store: &'a Option) -> Element<'a, Message> { + pub fn view<'a>( + &'a self, + account_store: &'a Option, + selected_locale: &'a Option, + ) -> Element<'a, Message> { let Some(store) = account_store else { return column![text(t!("settings_loading_accounts"))] .spacing(appearance::THEME_PADDING) @@ -156,6 +193,7 @@ impl SettingsScreen { let auto_start_enabled = crate::startup::auto_start_enabled(); content = content.push(self.view_auto_start_toggle(auto_start_enabled)); + content = content.push(self.view_language_picker(selected_locale)); content = content.push(self.view_account_buttons(selected_index)); content.into() @@ -168,6 +206,35 @@ impl SettingsScreen { .into() } + fn view_language_picker<'a>( + &'a self, + selected_locale: &'a Option, + ) -> Element<'a, Message> { + let mut codes: Vec = rust_i18n::available_locales!() + .into_iter() + .map(|s| s.to_string()) + .collect(); + codes.sort(); + + let mut choices: Vec = Vec::with_capacity(codes.len() + 1); + choices.push(LocaleChoice::system()); + choices.extend(codes.into_iter().map(LocaleChoice::explicit)); + + let current = match selected_locale { + None => LocaleChoice::system(), + Some(code) => LocaleChoice::explicit(code.clone()), + }; + + let picker = pick_list(choices, Some(current), |choice: LocaleChoice| { + Message::SelectLocale(choice.code().map(|s| s.to_string())) + }) + .style(appearance::s_pick_list); + + column![text(t!("settings_language")), picker] + .spacing(appearance::THEME_PADDING) + .into() + } + fn view_account_buttons(&self, selected_index: Option) -> Element<'_, Message> { let mut buttons = row![ button(appearance::icon_text( diff --git a/locales/ar.toml b/locales/ar.toml index 3f809c50..0ff424a6 100644 --- a/locales/ar.toml +++ b/locales/ar.toml @@ -47,6 +47,8 @@ settings_launch_on_startup = "التشغيل عند بدء النظام" settings_export_p12 = "تصدير P12" settings_select_teams = "اختر فريقًا..." settings_loading_teams = "جارٍ تحميل الفِرَق..." +settings_language = "اللغة:" +settings_system_language = "النظام" utilities_loading = "جارٍ التحميل..." utilities_refresh_installed_apps = "تحديث التطبيقات المثبتة" diff --git a/locales/en.toml b/locales/en.toml index 1324c608..cbda1593 100644 --- a/locales/en.toml +++ b/locales/en.toml @@ -47,6 +47,8 @@ settings_launch_on_startup = "Launch on Startup" settings_export_p12 = "Export P12" settings_select_teams = "Select team..." settings_loading_teams = "Loading teams..." +settings_language = "Language:" +settings_system_language = "System" utilities_loading = "Loading..." utilities_refresh_installed_apps = "Refresh Installed Apps"