diff --git a/Cargo.lock b/Cargo.lock index e7390a75..22b96934 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -376,6 +376,15 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arc-swap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -800,6 +809,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" +[[package]] +name = "base62" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd637ac531c60eb7fbc4684dc061c2d7d90d73d758181aa02eeff0464b9eee4b" + [[package]] name = "base64" version = "0.21.7" @@ -953,6 +968,16 @@ dependencies = [ "piper", ] +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.19.0" @@ -2394,7 +2419,7 @@ dependencies = [ "atomic", "pear", "serde", - "toml 0.8.2", + "toml 0.8.23", "uncased", "version_check", ] @@ -2864,7 +2889,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" dependencies = [ "heck 0.4.1", - "proc-macro-crate 2.0.2", + "proc-macro-crate 2.0.0", "proc-macro-error", "proc-macro2", "quote", @@ -2887,6 +2912,30 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags 1.3.2", + "ignore", + "walkdir", +] + [[package]] name = "glow" version = "0.16.0" @@ -3784,6 +3833,22 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "image" version = "0.25.9" @@ -3907,6 +3972,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -4572,6 +4646,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "normpath" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf23ab2b905654b4cb177e30b629937b3868311d4e1cba859f899c041046e69b" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "notify-rust" version = "4.11.7" @@ -5693,6 +5776,7 @@ dependencies = [ "plume_store", "plume_utils", "rfd", + "rust-i18n", "rustls 0.23.32", "single-instance", "thiserror 2.0.17", @@ -5869,11 +5953,10 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "2.0.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" dependencies = [ - "toml_datetime 0.6.3", "toml_edit 0.20.2", ] @@ -6161,7 +6244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96b0d374c7e4e985e6bc97ca7e7ad1d9642a8415db2017777d6e383002edaab2" dependencies = [ "either", - "itertools", + "itertools 0.13.0", "proc-macro2", "quote", "rayon", @@ -6519,6 +6602,57 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rust-i18n" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21031bf5e6f2c0ae745d831791c403608e99a8bd3776c7e5e5535acd70c3b7ba" +dependencies = [ + "globwalk", + "regex", + "rust-i18n-macro", + "rust-i18n-support", + "smallvec", +] + +[[package]] +name = "rust-i18n-macro" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51fe5295763b358606f7ca26a564e20f4469775a57ec1f09431249a33849ff52" +dependencies = [ + "glob", + "proc-macro2", + "quote", + "rust-i18n-support", + "serde", + "serde_json", + "serde_yaml", + "syn 2.0.106", +] + +[[package]] +name = "rust-i18n-support" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69bcc115c8eea2803aa3d85362e339776f4988a0349f2f475af572e497443f6f" +dependencies = [ + "arc-swap", + "base62", + "globwalk", + "itertools 0.11.0", + "lazy_static", + "normpath", + "proc-macro2", + "regex", + "serde", + "serde_json", + "serde_yaml", + "siphasher", + "toml 0.8.23", + "triomphe", +] + [[package]] name = "rustc-demangle" version = "0.1.26" @@ -6679,7 +6813,7 @@ dependencies = [ "security-framework 3.5.1", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -7094,6 +7228,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + [[package]] name = "skrifa" version = "0.37.0" @@ -7479,7 +7619,7 @@ dependencies = [ "cfg-expr", "heck 0.5.0", "pkg-config", - "toml 0.8.2", + "toml 0.8.23", "version-compare", ] @@ -7792,14 +7932,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.2" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "toml_edit 0.20.2", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", ] [[package]] @@ -7819,9 +7959,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] @@ -7842,7 +7982,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap", - "toml_datetime 0.6.3", + "toml_datetime 0.6.11", "winnow 0.5.40", ] @@ -7851,12 +7991,24 @@ name = "toml_edit" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "winnow 0.5.40", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.13", ] [[package]] @@ -7880,6 +8032,12 @@ dependencies = [ "winnow 0.7.13", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "toml_writer" version = "1.0.6+spec-1.1.0" @@ -7989,6 +8147,17 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "triomphe" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" +dependencies = [ + "arc-swap", + "serde", + "stable_deref_trait", +] + [[package]] name = "try-lock" version = "0.2.5" diff --git a/README.md b/README.md index 88deb90f..9347e5ba 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,14 @@ Supported apps for pairing file: You can retrieve this file by either sideloading the supported app of your choice, or going to the `Utilities` page when a device is connected and press install for the supported app. Head over to the [downloads](https://github.com/khcrysalis/PlumeImpactor/releases). +## Translating + +Impactor now has translation support, all localizations are located in `locales/.toml`. + +To add a new language, copy `en.toml` to `.toml` so all the strings are copied appropriately, and start translating! + +To properly contribute and test potentially test localizations, you can head over to the [contributing guide](./CONTRIBUTING.md). + ## Sponsors | Thanks to all my [sponsors](https://github.com/sponsors/khcrysalis)!! | diff --git a/apps/plumeimpactor/Cargo.toml b/apps/plumeimpactor/Cargo.toml index cfbad21d..cea86b33 100644 --- a/apps/plumeimpactor/Cargo.toml +++ b/apps/plumeimpactor/Cargo.toml @@ -31,6 +31,7 @@ open = "5.3" # notify-rust = "4.11" # Notifications single-instance = "0.3" # Ensuring single instance of the app on Linux and Windows auto-launcher = "0.6" # Auto-launching on startup +rust-i18n = "4.0" [target.'cfg(target_os = "macos")'.dependencies] plume_gestalt = { path = "../../crates/plume_gestalt" } diff --git a/apps/plumeimpactor/src/appearance/fonts.rs b/apps/plumeimpactor/src/appearance/fonts.rs index d19eea3c..c81dd46c 100644 --- a/apps/plumeimpactor/src/appearance/fonts.rs +++ b/apps/plumeimpactor/src/appearance/fonts.rs @@ -23,7 +23,7 @@ pub(crate) const FILE: &str = "\u{f15b}"; pub(crate) fn icon_text( icon: &'static str, - label: &'static str, + label: std::borrow::Cow<'_, str>, color: Option, ) -> Element<'static, M> { let icon_font = Font { @@ -42,8 +42,9 @@ pub(crate) fn icon_text( icon_text_widget = icon_text_widget.color(c); } row = row.push(icon_text_widget); + let str = label.to_string(); - let mut label_widget = Text::new(label); + let mut label_widget = Text::new(str); if let Some(c) = color { label_widget = label_widget.color(c); } diff --git a/apps/plumeimpactor/src/main.rs b/apps/plumeimpactor/src/main.rs index 5b14e699..0baa1828 100644 --- a/apps/plumeimpactor/src/main.rs +++ b/apps/plumeimpactor/src/main.rs @@ -1,5 +1,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +rust_i18n::i18n!("../../locales", fallback = "en"); + use crate::refresh::spawn_refresh_daemon; #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] diff --git a/apps/plumeimpactor/src/screen/general.rs b/apps/plumeimpactor/src/screen/general.rs index 337e0984..0c041301 100644 --- a/apps/plumeimpactor/src/screen/general.rs +++ b/apps/plumeimpactor/src/screen/general.rs @@ -1,6 +1,7 @@ use iced::widget::{button, column, container, image, row, text}; use iced::{Center, Color, Element, Fill, Task}; use plume_utils::Package; +use rust_i18n::t; use crate::appearance; use std::sync::OnceLock; @@ -37,8 +38,8 @@ impl GeneralScreen { return Task::perform( async { rfd::AsyncFileDialog::new() - .add_filter("iOS App Package", &["ipa", "tipa"]) - .set_title("Select IPA/TIPA file") + .add_filter(t!("ipa"), &["ipa", "tipa"]) + .set_title(t!("select_ipa")) .pick_file() .await .map(|file| file.path().to_path_buf()) @@ -86,7 +87,7 @@ impl GeneralScreen { let screen_content = column![ container(text("")).height(appearance::THEME_PADDING * 2.0), image(image_handle.clone()).height(INSTALL_IMAGE_HEIGHT), - text("Drag & drop an IPA here") + text(t!("drag_and_drop")) .size(appearance::THEME_FONT_SIZE + 7.0) .color(Color::from_rgba(1.0, 1.0, 1.0, 0.3)) ] @@ -96,14 +97,14 @@ impl GeneralScreen { let footer_links = row![ button(appearance::icon_text( appearance::STAR, - "Star us on GitHub!", + t!("star_us"), Some(Color::from_rgb(1.0, 0.75, 0.8)), )) .on_press(Message::OpenGitHub) .style(iced::widget::button::text), button(appearance::icon_text( appearance::STAR, - "Donate!", + t!("donate"), Some(Color::from_rgb(1.0, 0.75, 0.8)), )) .on_press(Message::OpenDonate) @@ -121,13 +122,17 @@ impl GeneralScreen { fn view_buttons(&self) -> Element<'_, Message> { container( row![ - button(appearance::icon_text(appearance::WRENCH, "Utilities", None)) - .on_press(Message::NavigateToUtilities) - .width(Fill) - .style(appearance::s_button), + button(appearance::icon_text( + appearance::WRENCH, + t!("utilities"), + None + )) + .on_press(Message::NavigateToUtilities) + .width(Fill) + .style(appearance::s_button), button(appearance::icon_text( appearance::DOWNLOAD, - "Import .ipa / .tipa", + t!("import_ipa"), None )) .on_press(Message::OpenFileDialog) diff --git a/apps/plumeimpactor/src/screen/package.rs b/apps/plumeimpactor/src/screen/package.rs index 52f96564..a3e9ec5b 100644 --- a/apps/plumeimpactor/src/screen/package.rs +++ b/apps/plumeimpactor/src/screen/package.rs @@ -3,6 +3,7 @@ use iced::widget::{ }; use iced::{Alignment, Center, Element, Fill, Task}; use plume_utils::{Package, PlistInfoTrait, SignerInstallMode, SignerMode, SignerOptions}; +use rust_i18n::t; use std::path::PathBuf; use tiny_skia::{FillRule, Mask, Path, PathBuilder, Transform}; @@ -52,9 +53,7 @@ impl PackageScreen { .and_then(|data| icon_handle_from_bytes(data)); let custom_icon_path = options.custom_icon.clone(); - let custom_icon_handle = custom_icon_path - .as_ref() - .and_then(icon_handle_from_path); + let custom_icon_handle = custom_icon_path.as_ref().and_then(icon_handle_from_path); Self { selected_package: package, @@ -257,8 +256,8 @@ impl PackageScreen { fn view_no_package(&self) -> Element<'_, Message> { column![ - text("No package selected").size(32), - text("Go back and select a file").size(16), + text(t!("options_no_package")).size(32), + text(t!("options_go_back_and_get_package")).size(16), ] .spacing(appearance::THEME_PADDING) .align_x(Center) @@ -274,9 +273,9 @@ impl PackageScreen { row![ self.view_custom_icon(), column![ - text("Name:").size(12), + text(t!("options_name")).size(12), text_input( - "App name", + "example", self.options.custom_name.as_ref().unwrap_or(&pkg_name) ) .on_input(Message::UpdateCustomName) @@ -285,32 +284,40 @@ impl PackageScreen { .spacing(8), ] .spacing(8), - text("Identifier:").size(12), + text(t!("options_identifier")).size(12), text_input( - "Bundle identifier", + "com.example.app", self.options.custom_identifier.as_ref().unwrap_or(&pkg_id) ) .on_input(Message::UpdateCustomIdentifier) .padding(8), - text("Version:").size(12), + text(t!("options_version")).size(12), text_input( - "Version", + "1.0.0", self.options.custom_version.as_ref().unwrap_or(&pkg_ver) ) .on_input(Message::UpdateCustomVersion) .padding(8), - text("Entitlements:").size(12), + text(t!("options_entitlements")).size(12), self.view_custom_entitlements(), - text("Only available if \"Only Register Main Bundle\" is enabled.").size(11), - text("Tweaks:").size(12), + text(t!("options_entitlements_warn")).size(11), + text(t!("options_tweaks")).size(12), self.view_tweaks(), row![ - button(appearance::icon_text(appearance::PLUS, "Add Tweak", None)) - .on_press(Message::AddTweak) - .style(appearance::s_button), - button(appearance::icon_text(appearance::PLUS, "Add Bundle", None)) - .on_press(Message::AddBundle) - .style(appearance::s_button), + button(appearance::icon_text( + appearance::PLUS, + t!("options_add_tweak"), + None + )) + .on_press(Message::AddTweak) + .style(appearance::s_button), + button(appearance::icon_text( + appearance::PLUS, + t!("options_add_bundle"), + None + )) + .on_press(Message::AddBundle) + .style(appearance::s_button), ] .spacing(8), ] @@ -321,51 +328,51 @@ impl PackageScreen { fn view_options_column(&self) -> Element<'_, Message> { column![ - text("General:").size(12), + text(t!("options_general")).size(12), checkbox(self.options.features.support_minimum_os_version) - .label("Support older versions (7+)") + .label(t!("options_support_versions")) .on_toggle(Message::ToggleMinimumOsVersion), checkbox(self.options.features.support_file_sharing) - .label("Force File Sharing") + .label(t!("options_file_sharing")) .on_toggle(Message::ToggleFileSharing), checkbox(self.options.features.support_ipad_fullscreen) - .label("Force iPad Fullscreen") + .label(t!("options_ipad_fullscreen")) .on_toggle(Message::ToggleIpadFullscreen), checkbox(self.options.features.support_game_mode) - .label("Force Game Mode") + .label(t!("options_game_mode")) .on_toggle(Message::ToggleGameMode), checkbox(self.options.features.support_pro_motion) - .label("Force Pro Motion") + .label(t!("options_pro_motion")) .on_toggle(Message::ToggleProMotion), - text("Advanced:").size(12), + text(t!("options_advanced")).size(12), checkbox(self.options.embedding.single_profile) - .label("Only Register Main Bundle") + .label(t!("options_register_main_bundle")) .on_toggle(Message::ToggleSingleProfile), checkbox(self.options.features.support_liquid_glass) - .label("Force Liquid Glass (26+)") + .label(t!("options_liquid_glass")) .on_toggle(Message::ToggleLiquidGlass), checkbox(self.options.features.support_ellekit) - .label("Replace Substrate with ElleKit") + .label(t!("options_ellekit")) .on_toggle(Message::ToggleElleKit), checkbox(self.options.refresh) - .label("Auto Refresh [BETA]") + .label(t!("options_auto_refresh")) .on_toggle(Message::ToggleRefresh), - text("Mode:").size(12), + text(t!("options_mode")).size(12), pick_list( &[SignerInstallMode::Install, SignerInstallMode::Export][..], Some(self.options.install_mode), Message::UpdateInstallMode ) .style(appearance::s_pick_list) - .placeholder("Select mode"), - text("Signing:").size(12), + .placeholder(t!("options_mode_desc")), + text(t!("options_signing")).size(12), pick_list( &[SignerMode::Pem, SignerMode::Adhoc, SignerMode::None][..], Some(self.options.mode), Message::UpdateSignerMode ) .style(appearance::s_pick_list) - .placeholder("Select signing method"), + .placeholder(t!("options_signing_desc")), ] .spacing(8) .width(Fill) @@ -374,15 +381,15 @@ impl PackageScreen { fn view_buttons(&self, has_device: bool) -> Element<'_, Message> { let (button_enabled, button_label) = match self.options.install_mode { - SignerInstallMode::Install => (has_device, "Install"), - SignerInstallMode::Export => (true, "Export"), + SignerInstallMode::Install => (has_device, t!("install")), + SignerInstallMode::Export => (true, t!("export")), }; container( row![ button(appearance::icon_text( appearance::CHEVRON_BACK, - "Back", + t!("back"), None )) .on_press(Message::Back) diff --git a/apps/plumeimpactor/src/screen/progress.rs b/apps/plumeimpactor/src/screen/progress.rs index 0c6222e4..d79ca5f8 100644 --- a/apps/plumeimpactor/src/screen/progress.rs +++ b/apps/plumeimpactor/src/screen/progress.rs @@ -4,6 +4,7 @@ use iced::Element; use iced::Length::Fill; use iced::Task; use iced::widget::{button, column, container, row, text}; +use rust_i18n::t; use crate::appearance; @@ -56,7 +57,7 @@ impl ProgressScreen { let error_msg = status.clone(); std::thread::spawn(move || { rfd::MessageDialog::new() - .set_title("Installation Failed") + .set_title(t!("progress_failed")) .set_description(&error_msg) .set_buttons(rfd::MessageButtons::Ok) .show(); @@ -72,13 +73,13 @@ impl ProgressScreen { } Message::InstallationError(error) => { self.progress = -1; - self.status = format!("Error: {}", error); + self.status = format!("ERR: {}", error); self.progress_rx = None; self.is_installing = false; std::thread::spawn(move || { rfd::MessageDialog::new() - .set_title("Installation Failed") + .set_title(t!("progress_failed")) .set_description(&error) .set_buttons(rfd::MessageButtons::Ok) .show(); @@ -88,7 +89,7 @@ impl ProgressScreen { } Message::InstallationFinished => { self.progress = 100; - self.status = "Finished!".to_string(); + self.status = t!("progress_finished").to_string(); self.progress_rx = None; self.is_installing = false; @@ -102,7 +103,7 @@ impl ProgressScreen { let progress_bar = iced::widget::progress_bar(0.0..=100.0, self.progress as f32); let screen_content = column![ - text("Installing application, this will take a moment. Do not disconnect the device until finished.").size(14), + text(t!("progress_installing_application")).size(14), text(format!("{}% – {}", self.progress, self.status)).size(14), progress_bar, container(text("")).height(Fill), @@ -120,7 +121,7 @@ impl ProgressScreen { container(row![ button(appearance::icon_text( appearance::CHEVRON_BACK, - "Back", + t!("back"), None )) .on_press_maybe((!self.is_installing).then_some(Message::Back)) diff --git a/apps/plumeimpactor/src/screen/settings.rs b/apps/plumeimpactor/src/screen/settings.rs index 6fa6bc2c..d9777acb 100644 --- a/apps/plumeimpactor/src/screen/settings.rs +++ b/apps/plumeimpactor/src/screen/settings.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use iced::widget::{button, checkbox, column, container, pick_list, row, scrollable, text}; use iced::{Alignment, Element, Fill, Task}; use plume_store::AccountStore; +use rust_i18n::t; use crate::appearance; @@ -63,7 +64,7 @@ impl SettingsScreen { pub fn view<'a>(&'a self, account_store: &'a Option) -> Element<'a, Message> { let Some(store) = account_store else { - return column![text("Loading accounts...")] + return column![text(t!("settings_loading_accounts"))] .spacing(appearance::THEME_PADDING) .padding(appearance::THEME_PADDING) .into(); @@ -116,11 +117,11 @@ impl SettingsScreen { }; let placeholder = if is_loading { - "Loading teams...".to_string() + t!("settings_select_teams").to_string() } else if !team_id.is_empty() { team_id.to_string() } else { - "Select team...".to_string() + t!("settings_loading_teams").to_string() }; let email_owned = email.to_string(); @@ -150,7 +151,7 @@ impl SettingsScreen { }, )); } else { - content = content.push(text("No accounts added yet")); + content = content.push(text(t!("settings_no_accounts_yet"))); } let auto_start_enabled = crate::startup::auto_start_enabled(); @@ -162,16 +163,20 @@ impl SettingsScreen { fn view_auto_start_toggle(&self, auto_start_enabled: bool) -> Element<'_, Message> { checkbox(auto_start_enabled) - .label("Launch on Startup") + .label(t!("settings_launch_on_startup")) .on_toggle(Message::ToggleAutoStart) .into() } fn view_account_buttons(&self, selected_index: Option) -> Element<'_, Message> { let mut buttons = row![ - button(appearance::icon_text(appearance::PLUS, "Add Account", None)) - .on_press(Message::ShowLogin) - .style(appearance::s_button) + button(appearance::icon_text( + appearance::PLUS, + t!("settings_add_account"), + None + )) + .on_press(Message::ShowLogin) + .style(appearance::s_button) ] .spacing(appearance::THEME_PADDING); @@ -180,16 +185,20 @@ impl SettingsScreen { .push( button(appearance::icon_text( appearance::MINUS, - "Remove Account", + t!("settings_remove_account"), None, )) .on_press(Message::RemoveAccount(index)) .style(appearance::s_button), ) .push( - button(appearance::icon_text(appearance::SHARE, "Export P12", None)) - .on_press(Message::ExportP12) - .style(appearance::s_button), + button(appearance::icon_text( + appearance::SHARE, + t!("settings_export_p12"), + None, + )) + .on_press(Message::ExportP12) + .style(appearance::s_button), ); } diff --git a/apps/plumeimpactor/src/screen/utilties.rs b/apps/plumeimpactor/src/screen/utilties.rs index 40cf61ac..7936d221 100644 --- a/apps/plumeimpactor/src/screen/utilties.rs +++ b/apps/plumeimpactor/src/screen/utilties.rs @@ -1,5 +1,6 @@ use iced::widget::{button, column, container, row, rule, scrollable, text, toggler}; use iced::{Center, Color, Element, Task}; +use rust_i18n::t; use crate::appearance; use crate::defaults::get_data_path; @@ -72,7 +73,9 @@ impl UtilitiesScreen { }; if screen.device.as_ref().map(|d| d.is_mac).unwrap_or(false) { - screen.status_message = Some(StatusMessage::error("macOS devices are not supported")); + screen.status_message = Some(StatusMessage::error(t!( + "utilities_mac_devices_not_supported" + ))); } screen @@ -219,7 +222,7 @@ impl UtilitiesScreen { match result { Ok(_) => { self.status_message = - Some(StatusMessage::success("Device paired successfully!")); + Some(StatusMessage::success(t!("utilities_paired_success"))); } Err(e) => { self.status_message = Some(StatusMessage::error(e)); @@ -229,7 +232,7 @@ impl UtilitiesScreen { } Message::InstallPairingResult(app_key, result) => { let status = match result { - Ok(_) => StatusMessage::success("Pairing file installed successfully!"), + Ok(_) => StatusMessage::success(t!("utilities_device_paired_success")), Err(e) => StatusMessage::error(e), }; self.app_statuses.insert(app_key, status); @@ -254,8 +257,9 @@ impl UtilitiesScreen { .spacing(4), ); } else { - content = - content.push(text("No device connected").color(Color::from_rgb(0.7, 0.7, 0.7))); + content = content.push( + text(t!("utilities_no_device_connected")).color(Color::from_rgb(0.7, 0.7, 0.7)), + ); } if let Some(ref status) = self.status_message { @@ -264,15 +268,15 @@ impl UtilitiesScreen { if self.device.is_some() && !self.device.as_ref().unwrap().is_mac { let refresh_button_text = if self.loading { - "Loading..." + t!("utilities_loading") } else { - "Refresh Installed Apps" + t!("utilities_refresh_installed_apps") }; let trust_button_text = if self.trust_loading { - "Pairing..." + t!("utilities_pairing") } else { - "Trust Device" + t!("utilities_trust_device") }; content = content.push( @@ -299,7 +303,7 @@ impl UtilitiesScreen { } let toggle = toggler(self.rppairing_enabled) - .label("Use Remote Pairing (17.4+)") + .label(t!("utilities_use_remote_pairing")) .on_toggle(Message::ToggleRPPairing); content = content.push(toggle); @@ -321,7 +325,7 @@ impl UtilitiesScreen { )) .size(14) .width(iced::Length::Fill), - button(text("Install Pairing").align_x(Center)) + button(text(t!("utilities_install_pairing")).align_x(Center)) .on_press(Message::InstallPairingFile(app.clone())) .style(appearance::s_button) ] diff --git a/apps/plumeimpactor/src/screen/windows/login_window.rs b/apps/plumeimpactor/src/screen/windows/login_window.rs index fbc70f20..c81c4b80 100644 --- a/apps/plumeimpactor/src/screen/windows/login_window.rs +++ b/apps/plumeimpactor/src/screen/windows/login_window.rs @@ -3,6 +3,7 @@ use iced::widget::{button, column, container, row, text, text_input}; use iced::{Alignment, Element, Fill, Task, window}; use plume_core::{AnisetteConfiguration, auth::Account}; use plume_store::{AccountStore, GsaAccount}; +use rust_i18n::t; use std::sync::mpsc as std_mpsc; use crate::appearance; @@ -178,12 +179,12 @@ impl LoginWindow { } fn view_login(&self) -> Element<'_, Message> { - let email_input = text_input("Email", &self.email) + let email_input = text_input("example@riseup.net", &self.email) .on_input(Message::EmailChanged) .padding(8) .width(Fill); - let mut password_input = text_input("Password", &self.password) + let mut password_input = text_input("1234", &self.password) .on_input(Message::PasswordChanged) .secure(true) .padding(8) @@ -193,11 +194,10 @@ impl LoginWindow { } let mut content = column![ - text("Your Apple ID is used to sign and install apps. Credentials sent only to Apple.") - .size(14), - text("Email:").size(14), + text(t!("login_only_set_to_fruit")).size(14), + text(t!("login_email")).size(14), email_input, - text("Password:").size(14), + text(t!("login_password")).size(14), password_input, ] .spacing(appearance::THEME_PADDING) @@ -211,14 +211,14 @@ impl LoginWindow { let buttons = row![ container(text("")).width(Fill), - button("Cancel") + button(text(t!("cancel"))) .on_press(Message::LoginCancel) .style(appearance::s_button), - button(if self.is_logging_in { - "Logging In..." + button(text(if self.is_logging_in { + t!("login_loading") } else { - "Next" - }) + t!("next") + })) .on_press_maybe(if self.is_logging_in { None } else { @@ -244,8 +244,8 @@ impl LoginWindow { } let mut content = column![ - text("Two-Factor Authentication").size(20), - text("Enter the verification code sent to your device:").size(14), + text(t!("login_two_fa")).size(20), + text(t!("login_two_fa_desc")).size(14), code_input, ] .spacing(appearance::THEME_PADDING) @@ -259,15 +259,15 @@ impl LoginWindow { } let buttons = row![ - button("Cancel") + button(text(t!("cancel"))) .on_press(Message::TwoFactorCancel) .style(appearance::s_button) .padding(8), - button(if self.is_logging_in { - "Verifying..." + button(text(if self.is_logging_in { + t!("login_verifying") } else { - "Verify" - }) + t!("login_verify") + })) .on_press_maybe(if self.is_logging_in { None } else { diff --git a/locales/en.toml b/locales/en.toml new file mode 100644 index 00000000..1324c608 --- /dev/null +++ b/locales/en.toml @@ -0,0 +1,73 @@ +_language_name = "English" + +install = "Install" +export = "Export" +drag_and_drop = "Drag & drop an IPA here" +select_ipa = "Select IPA/TIPA file" +ipa = "iOS App Package" +utilities = "Utilities" +import_ipa = "Import .ipa / .tipa" +donate = "Donate!" +star_us = "Star us on GitHub!" +back = "Back" +cancel = "Cancel" +next = "Next" + +options_no_package = "No package selected" +options_go_back_and_get_package = "Go back and select a file" +options_name = "Name:" +options_identifier = "Identifier:" +options_version = "Version:" +options_entitlements = "Entitlements:" +options_entitlements_warn = "Only available if \"Only Register Main Bundle\" is enabled." +options_tweaks = "Tweaks:" +options_add_tweak = "Add Tweak" +options_add_bundle = "Add Bundle" +options_general = "General:" +options_support_versions = "Support older versions (7+)" +options_file_sharing = "Force File Sharing" +options_ipad_fullscreen = "Force iPad Fullscreen" +options_game_mode = "Force Game Mode" +options_pro_motion = "Force Pro Motion" +options_advanced = "Advanced:" +options_register_main_bundle = "Only Register Main Bundle" +options_liquid_glass = "Force Liquid Glass (26+)" +options_ellekit = "Replace Substrate with ElleKit" +options_auto_refresh = "Auto Refresh [BETA]" +options_mode = "Mode:" +options_mode_desc = "Select mode" +options_signing = "Signing:" +options_signing_desc = "Select signing method" + +settings_loading_accounts = "Loading accounts..." +settings_add_account = "Add Account" +settings_remove_account = "Remove Account" +settings_no_accounts_yet = "No accounts added yet" +settings_launch_on_startup = "Launch on Startup" +settings_export_p12 = "Export P12" +settings_select_teams = "Select team..." +settings_loading_teams = "Loading teams..." + +utilities_loading = "Loading..." +utilities_refresh_installed_apps = "Refresh Installed Apps" +utilities_pairing = "Pairing..." +utilities_trust_device = "Trust Device" +utilities_install_pairing = "Install Pairing" +utilities_use_remote_pairing = "Use Remote Pairing (17.4+)" +utilities_no_device_connected = "No Device Connected" +utilities_device_paired_success = "Pairing file installed successfully!" +utilities_paired_success = "Device paired successfully!" +utilities_mac_devices_not_supported = "macOS devices are not supported" + +progress_installing_application = "Installing application, this will take a moment. Do not disconnect the device until finished." +progress_finished = "Finished!" +progress_failed = "Installation Failed" + +login_only_set_to_fruit = "Your Apple ID is used to sign and install apps. Credentials sent only to Apple." +login_email = "Email:" +login_password = "Password:" +login_loading = "Logging in..." +login_two_fa = "Two-Factor Authentication" +login_two_fa_desc = "Enter the verification code sent to your device:" +login_verify = "Verify" +login_verifying = "Verifying..."