Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding localisation logic #49

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion crate/multiworld-gui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ use {
Kind as Frontend,
},
github::Repo,
localisation::{
Locale,
Message::*,
},
ws::{
ServerError,
latest::{
Expand Down Expand Up @@ -380,6 +384,7 @@ enum Message {
SetExistingRoomSelection(RoomFormatter),
SetFrontend(Frontend),
SetLobbyView(LobbyView),
SetLocale(Locale),
SetNewRoomName(String),
SetPassword(String),
SetRoomView(RoomView),
Expand Down Expand Up @@ -438,6 +443,7 @@ struct State {
update_state: UpdateState,
send_all_path: String,
send_all_world: String,
locale: Locale,
}

impl State {
Expand Down Expand Up @@ -686,6 +692,7 @@ impl Application for State {
update_state: UpdateState::Pending,
send_all_path: String::default(),
send_all_world: String::default(),
locale: config.locale.unwrap_or_default(),
frontend, config_error, persistent_state_error, persistent_state,
}, cmd(future::ok(Message::CheckForUpdates)))
}
Expand Down Expand Up @@ -1418,6 +1425,15 @@ impl Application for State {
Message::SetCreateNewRoom(new_val) => if let SessionState::Lobby { ref mut create_new_room, .. } = self.server_connection { *create_new_room = new_val },
Message::SetExistingRoomSelection(name) => if let SessionState::Lobby { ref mut existing_room_selection, .. } = self.server_connection { *existing_room_selection = Some(name) },
Message::SetFrontend(new_frontend) => self.frontend.kind = new_frontend,
Message::SetLocale(new_locale) => {
self.locale = new_locale;
return cmd(async move {
let mut config = Config::load().await?;
config.locale = Some(new_locale);
config.save().await?;
Ok(Message::Nop)
})
},
Message::SetNewRoomName(name) => if let SessionState::Lobby { ref mut new_room_name, .. } = self.server_connection { *new_room_name = name },
Message::SetPassword(new_password) => if let SessionState::Lobby { ref mut password, .. } = self.server_connection { *password = new_password },
Message::SetSendAllPath(new_path) => self.send_all_path = new_path,
Expand Down Expand Up @@ -1535,7 +1551,7 @@ impl Application for State {
col = col.push(
Row::new()
.push("1. ")
.push(Button::new("Open Project64").on_press(Message::LaunchProject64))
.push(Button::new(self.locale.message(OpenPj64Button)).on_press(Message::LaunchProject64))
.align_items(iced::Alignment::Center));
col = col.push("2. In Project64's Debugger menu, select Scripts\n3. In the Scripts window, select ootrmw.js and click Run\n4. Wait until the Output area says “Connected to multiworld app”. (This should take less than 5 seconds.) You can then close the Scripts window.")
} else {
Expand Down Expand Up @@ -1647,6 +1663,7 @@ impl Application for State {
.push(Button::new("Sign in with racetime.gg").on_press(Message::SetLobbyView(LobbyView::Login { provider: login::Provider::RaceTime, no_midos_house_account: false })))
.push(Button::new("Sign in with Discord").on_press(Message::SetLobbyView(LobbyView::Login { provider: login::Provider::Discord, no_midos_house_account: false })));
}
col = col.push(PickList::new(Locale::ALL, Some(self.locale), Message::SetLocale)).into();
col.spacing(8)
}
SessionState::Lobby { view: LobbyView::Login { provider, no_midos_house_account: true }, wrong_password: false, .. } => Column::new()
Expand Down
66 changes: 54 additions & 12 deletions crate/multiworld-installer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ use {
config::Config,
frontend::Kind as Emulator, //TODO rename to Frontend?
github::Repo,
localisation::{
Locale,
Message::*,
},
},
};
#[cfg(target_os = "linux")] use {
Expand Down Expand Up @@ -192,6 +196,7 @@ enum Message {
SetEmulator(Emulator),
SetInstallEmulator(bool),
SetOpenEmulator(bool),
SetLocale(Locale),
}

fn cmd(future: impl Future<Output = Result<Message, Error>> + Send + 'static) -> Command<Message> {
Expand Down Expand Up @@ -232,6 +237,12 @@ struct Pj64ConfigDebugger {
enum Page {
Error(Arc<Error>, bool),
Elevated,
SelectLocale {
emulator: Option<Emulator>,
install_emulator: Option<bool>,
emulator_path: Option<String>,
multiworld_path: Option<String>,
},
SelectEmulator {
emulator: Option<Emulator>,
install_emulator: Option<bool>,
Expand Down Expand Up @@ -291,6 +302,7 @@ struct State {
create_emulator_desktop_shortcut: bool,
// Page::AskLaunch
open_emulator: bool,
locale: Locale,
}

impl Application for State {
Expand All @@ -299,25 +311,25 @@ impl Application for State {
type Theme = Theme;
type Flags = Args;

fn new(Args { mut emulator }: Args) -> (Self, Command<Message>) {
fn new(Args { mut emulator , locale}: Args) -> (Self, Command<Message>) {
if let Ok(only_emulator) = all().filter(Emulator::is_supported).exactly_one() {
emulator.get_or_insert(only_emulator);
}
let page = match emulator {
Some(_) => Page::SelectEmulator { emulator, install_emulator: None, emulator_path: None, multiworld_path: None },
None => Page::SelectLocale { emulator, install_emulator: None, emulator_path: None, multiworld_path: None }
};
(Self {
http_client: reqwest::Client::builder()
.user_agent(concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")))
.use_rustls_tls()
.https_only(true)
.build().expect("failed to build HTTP client"),
page: Page::SelectEmulator {
install_emulator: None,
emulator_path: None,
multiworld_path: None,
emulator,
},
page,
create_multiworld_desktop_shortcut: true,
create_emulator_desktop_shortcut: true,
open_emulator: true,
locale: locale.unwrap_or_default(),
}, if emulator.is_some() {
cmd(future::ok(Message::Continue))
} else {
Expand Down Expand Up @@ -348,7 +360,8 @@ impl Application for State {
fn update(&mut self, msg: Message) -> Command<Message> {
match msg {
Message::Back => self.page = match self.page {
Page::Error(_, _) | Page::Elevated | Page::SelectEmulator { .. } => unreachable!(),
Page::Error(_, _) | Page::Elevated | Page::SelectLocale { .. } => unreachable!(),
Page::SelectEmulator { emulator, install_emulator, ref emulator_path, ref multiworld_path } => Page::SelectLocale { emulator, install_emulator, emulator_path: emulator_path.clone(), multiworld_path: multiworld_path.clone() },
Page::EmulatorWarning { emulator, install_emulator, ref emulator_path, ref multiworld_path } => Page::SelectEmulator { emulator: Some(emulator), install_emulator, emulator_path: emulator_path.clone(), multiworld_path: multiworld_path.clone() },
Page::LocateEmulator { emulator, install_emulator, ref emulator_path, ref multiworld_path } => Page::SelectEmulator { emulator: Some(emulator), install_emulator: Some(install_emulator), emulator_path: Some(emulator_path.clone()), multiworld_path: multiworld_path.clone() },
Page::AskBizHawkUpdate { ref emulator_path, ref multiworld_path } => Page::LocateEmulator { emulator: Emulator::BizHawk, install_emulator: false, emulator_path: emulator_path.clone(), multiworld_path: multiworld_path.clone() },
Expand Down Expand Up @@ -405,6 +418,9 @@ impl Application for State {
Message::ConfigWriteFailed => if let Page::InstallMultiworld { ref mut config_write_failed, .. } = self.page { *config_write_failed = true },
Message::Continue => match self.page {
Page::Error(_, _) | Page::Elevated | Page::Project64EmError { .. } => unreachable!(),
Page::SelectLocale { emulator, install_emulator, ref emulator_path, ref multiworld_path } => {
self.page = Page::SelectEmulator { emulator, install_emulator, emulator_path: emulator_path.clone(), multiworld_path: multiworld_path.clone() }
}
Page::SelectEmulator { emulator, install_emulator, ref emulator_path, ref multiworld_path } => {
let emulator = emulator.expect("emulator must be selected to continue here");
match emulator {
Expand All @@ -416,13 +432,18 @@ impl Application for State {
#[cfg(target_os = "windows")] Emulator::Pj64V3 | Emulator::Pj64V4 if !is_elevated() => {
// Project64 installation and plugin installation both require admin permissions (UAC)
self.page = Page::Elevated;
let locale = self.locale;
return cmd(async move {
let arg = match emulator {
let emulator_arg = match emulator {
Emulator::Pj64V3 => "--emulator=pj64v3",
Emulator::Pj64V4 => "--emulator=pj64v4",
_ => unreachable!(),
};
tokio::task::spawn_blocking(move || Ok::<_, Error>(runas::Command::new(env::current_exe()?).arg(arg).gui(true).status().at_command("runas")?.check("runas")?)).await??;
let locale_arg = match locale {
Locale::EN => "--locale=en",
Locale::FR => "--locale=fr",
};
tokio::task::spawn_blocking(move || Ok::<_, Error>(runas::Command::new(env::current_exe()?).arg(emulator_arg).arg(locale_arg).gui(true).status().at_command("runas")?.check("runas")?)).await??;
Ok(Message::Exit)
})
}
Expand Down Expand Up @@ -764,13 +785,15 @@ impl Application for State {
_ => unreachable!(),
};
self.page = Page::InstallMultiworld { emulator, emulator_path: emulator_path.clone(), multiworld_path: multiworld_path.clone(), config_write_failed: false };
let locale = self.locale;
match emulator {
Emulator::Dummy => unreachable!(),
Emulator::EverDrive => {
let create_desktop_shortcut = self.create_multiworld_desktop_shortcut;
return cmd(async move {
let mut new_mw_config = Config::load().await?;
new_mw_config.default_frontend = Some(Emulator::EverDrive);
new_mw_config.locale = Some(locale);
new_mw_config.save().await?;
let multiworld_path = PathBuf::from(multiworld_path.expect("multiworld app path must be set for Project64"));
fs::create_dir_all(multiworld_path.parent().ok_or(Error::Root)?).await?;
Expand All @@ -794,6 +817,7 @@ impl Application for State {
Emulator::BizHawk => return cmd(async move {
let mut new_mw_config = Config::load().await?;
new_mw_config.default_frontend = Some(Emulator::BizHawk);
new_mw_config.locale = Some(locale);
new_mw_config.save().await?;
let emulator_dir = PathBuf::from(emulator_path.expect("emulator path must be set for BizHawk"));
let external_tools_dir = emulator_dir.join("ExternalTools");
Expand Down Expand Up @@ -849,6 +873,7 @@ impl Application for State {
let mut new_mw_config = Config::load().await?;
new_mw_config.default_frontend = Some(Emulator::Pj64V3);
new_mw_config.pj64_script_path = Some(script_path);
new_mw_config.locale = Some(locale);
new_mw_config.save().await?;
let config_path = emulator_dir.join("Config");
fs::create_dir(&config_path).await.exist_ok()?;
Expand Down Expand Up @@ -938,6 +963,7 @@ impl Application for State {
Message::SetCreateEmulatorDesktopShortcut(create_desktop_shortcut) => self.create_emulator_desktop_shortcut = create_desktop_shortcut,
Message::SetCreateMultiworldDesktopShortcut(create_desktop_shortcut) => self.create_multiworld_desktop_shortcut = create_desktop_shortcut,
Message::SetEmulator(new_emulator) => if let Page::SelectEmulator { ref mut emulator, .. } = self.page { *emulator = Some(new_emulator) },
Message::SetLocale(new_locale) => self.locale = new_locale,
Message::SetInstallEmulator(new_install_emulator) => if let Page::LocateEmulator { ref mut install_emulator, .. } = self.page { *install_emulator = new_install_emulator },
Message::SetOpenEmulator(open_emulator) => self.open_emulator = open_emulator,
}
Expand Down Expand Up @@ -975,10 +1001,24 @@ impl Application for State {
None,
),
Page::Elevated => (
Text::new("The installer has been reopened with admin permissions. Please continue there.").into(),
Text::new(self.locale.message(InstallerReopenUAC)).into(),
false,
None,
),
Page::SelectLocale { .. } => (
{
let mut col = Column::new();
col = col.push("Select language you wish to proceed with");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This text should probably be kept as simple as possible, so someone with minimal knowledge of English can understand it:

Suggested change
col = col.push("Select language you wish to proceed with");
col = col.push("Select language");

col = col.push(PickList::new(Locale::ALL, Some(self.locale), Message::SetLocale));
col.spacing(8).into()
},
false,
Some({
let mut row = Row::new();
row = row.push(Text::new("Continue"));
(Into::<Element<'_, Message>>::into(row.spacing(8)), true)
})
),
Page::SelectEmulator { emulator, .. } => (
{
let mut col = Column::new();
Expand All @@ -991,7 +1031,7 @@ impl Application for State {
col = col.push(Button::new(Text::new("See platform support status")).on_press(Message::PlatformSupport));
col.spacing(8).into()
},
false,
true,
Some({
let mut row = Row::new();
#[cfg(target_os = "windows")] if matches!(emulator, Some(Emulator::Pj64V3 | Emulator::Pj64V4)) && !is_elevated() {
Expand Down Expand Up @@ -1170,6 +1210,8 @@ impl Application for State {
struct Args {
#[clap(long, value_enum)]
emulator: Option<Emulator>,
#[clap(long, value_enum)]
locale: Option<Locale>,
}

#[derive(Debug, thiserror::Error)]
Expand Down
7 changes: 6 additions & 1 deletion crate/multiworld/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ use {
Serialize,
},
url::Url,
crate::frontend::Kind as Frontend,
crate::{
frontend::Kind as Frontend,
localisation::Locale,
},
};
#[cfg(unix)] use xdg::BaseDirectories;
#[cfg(windows)] use directories::ProjectDirs;
Expand All @@ -27,6 +30,7 @@ pub struct Config {
#[serde(default)]
pub refresh_tokens: BTreeMap<crate::IdentityProvider, String>,
pub pj64_script_path: Option<PathBuf>,
pub locale: Option<Locale>,
#[serde(default = "default_websocket_hostname")]
pub websocket_hostname: String,
}
Expand Down Expand Up @@ -112,6 +116,7 @@ impl Default for Config {
refresh_tokens: BTreeMap::default(),
pj64_script_path: None,
websocket_hostname: default_websocket_hostname(),
locale: None,
}
}
}
1 change: 1 addition & 0 deletions crate/multiworld/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ use {
#[cfg(feature = "sqlx")] use sqlx::PgPool;

pub mod config;
pub mod localisation;
pub mod frontend;
pub mod github;
pub mod ws;
Expand Down
62 changes: 62 additions & 0 deletions crate/multiworld/src/localisation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use {
serde::{
Deserialize,
Serialize,
}, std::fmt
};

#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, clap::ValueEnum)]
#[clap(rename_all = "lower")]
pub enum Locale {
#[default]
EN,
FR,
}

impl Locale {
pub const ALL: [Locale; 2] = [
Locale::EN,
Locale::FR,
];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of hardcoding this list, I recommend deriving enum-iterator::Sequence on Locale. Usages of Locale::ALL can then be replaced with enum_iterator::all() (or all::<Locale>() if Rust can't infer the type).


pub fn message(&self, message: Message) -> &str {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To support formatted strings, a good tradeoff between convenience and performance is the Cow type:

Suggested change
pub fn message(&self, message: Message) -> &str {
pub fn message(&self, message: Message) -> Cow<'static, str> {

You can then use Cow::Borrowed("...") for string literals and Cow::Owned(format!("...")) for formatted strings.

match message {
Message::InstallerReopenUAC => { //used in installer
match self {
Locale::EN => "The installer has been reopened with admin permissions. Please continue there.",
Locale::FR => "L'installateur a été ré-ouvert avec les permissions administrateur. Veuillez continuer dans la nouvelle fenêtre.",
}
},
Message::OpenPj64Button => { // used in gui
match self {
Locale::EN => "Open Project64",
Locale::FR => "Ouvrir Project64"
}
},
// TODO find a way to translate formatted text somehow
//Message::AMessageWithParameter(my_int, my_string) => {
// match self {
// Locale::EN => format!("My integer is: {} and my string is: {}",my_int,my_string).as_str(),
// Locale::FR => format!("My integer is: {} and my string is: {}",my_int,my_string).as_str(),
// }
//}
}
}
}

impl fmt::Display for Locale {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
// add local formating for display
Self::EN => write!(f, "English"),
Self::FR => write!(f, "Français"),
}
}
}

pub enum Message {
InstallerReopenUAC,
OpenPj64Button,
//AMessageWithParameter(i32,String),
}