diff --git a/crates/benilla-app/src/char_create/refresh.rs b/crates/benilla-app/src/char_create/refresh.rs index 212404b38..8b3cca7b9 100644 --- a/crates/benilla-app/src/char_create/refresh.rs +++ b/crates/benilla-app/src/char_create/refresh.rs @@ -4,7 +4,6 @@ use bevy::input::mouse::{AccumulatedMouseMotion, MouseScrollUnit, MouseWheel}; use bevy::prelude::*; -use crate::char_select::{class_name, race_name}; use crate::entities::CharCreate; use crate::glue_strings::GlueStrings; @@ -123,8 +122,8 @@ pub(super) fn refresh_dynamic( if alliance { "Alliance" } else { "Horde" }, ) .to_string(), - DynText::InfoTitle(InfoKind::Race) => race_name(sel.race).to_string(), - DynText::InfoTitle(InfoKind::Class) => class_name(sel.class).to_string(), + DynText::InfoTitle(InfoKind::Race) => strings.race_name(sel.race).to_string(), + DynText::InfoTitle(InfoKind::Class) => strings.class_name(sel.class).to_string(), // Never trimmed: each `FACTION_INFO_*`, `RACE_INFO_*` and `CLASS_*` string opens with // eight spaces, which is the reference's first-line clearance past the header icon. DynText::InfoBody(InfoKind::Faction) => strings @@ -155,7 +154,7 @@ pub(super) fn refresh_dynamic( .to_string(), DynText::ClassSlotLabel(i) => classes .get(*i as usize) - .map(|&c| class_name(c).to_string()) + .map(|&c| strings.class_name(c).to_string()) .unwrap_or_default(), }; if t.0 != new { @@ -402,3 +401,61 @@ pub(super) fn scroll_visuals( }; } } + +#[cfg(test)] +mod localization_tests { + use super::*; + + #[test] + fn russian_creation_headers_and_class_labels_follow_selection() { + let data = benilla_formats::wow_data_or_skip!(); + if benilla_formats::ClientLocale::RuRu + .pack_folder(&data) + .is_none() + { + benilla_formats::skipped_optional("no ruRU language pack", &[data.join("ruRU")]); + return; + } + let mut chain = + benilla_formats::Chain::open_localized(&data, benilla_formats::ClientLocale::RuRu) + .unwrap(); + let mut app = App::new(); + app.init_resource::() + .init_resource::() + .insert_resource(crate::glue_strings::table_from_chain(&mut chain)) + .insert_resource(CharCreate( + benilla_formats::CharCreateCatalog::load(&mut chain).unwrap(), + )) + .add_systems(Update, refresh_dynamic); + app.world_mut().resource_mut::().race = 1; + app.world_mut().resource_mut::().class = 1; + let race = app + .world_mut() + .spawn((DynText::InfoTitle(InfoKind::Race), Text::default())) + .id(); + let class = app + .world_mut() + .spawn((DynText::InfoTitle(InfoKind::Class), Text::default())) + .id(); + let label = app + .world_mut() + .spawn((DynText::ClassSlotLabel(0), Text::default())) + .id(); + let second_label = app + .world_mut() + .spawn((DynText::ClassSlotLabel(1), Text::default())) + .id(); + app.update(); + assert_eq!(app.world().get::(race).unwrap().0, "Человек"); + assert_eq!(app.world().get::(class).unwrap().0, "Воин"); + assert_eq!(app.world().get::(label).unwrap().0, "Воин"); + assert_eq!(app.world().get::(second_label).unwrap().0, "Паладин"); + app.world_mut().resource_mut::().race = 6; + app.world_mut().resource_mut::().class = 11; + app.update(); + assert_eq!(app.world().get::(race).unwrap().0, "Таурен"); + assert_eq!(app.world().get::(class).unwrap().0, "Друид"); + assert_eq!(app.world().get::(label).unwrap().0, "Воин"); + assert_eq!(app.world().get::(second_label).unwrap().0, "Охотник"); + } +} diff --git a/crates/benilla-app/src/char_create/screen.rs b/crates/benilla-app/src/char_create/screen.rs index c7323048f..9a98d9705 100644 --- a/crates/benilla-app/src/char_create/screen.rs +++ b/crates/benilla-app/src/char_create/screen.rs @@ -6,7 +6,7 @@ use bevy::prelude::*; use bevy::ui_render::ui_material::MaterialNode; use bevy::window::PrimaryWindow; -use crate::char_select::{race_name, wow_font}; +use crate::char_select::wow_font; use crate::entities::CharCreate; use crate::glue_strings::GlueStrings; use crate::portrait::{GlueLook, GluePreview, PortraitImages, PortraitSource, GLUE_SLOT}; @@ -280,7 +280,7 @@ fn left_tower( Some(DynIcon::Race(race)), None, None::, - race_name(race), + strings.race_name(race), art, s, ); diff --git a/crates/benilla-app/src/char_select/dialog.rs b/crates/benilla-app/src/char_select/dialog.rs index ce4d150cf..ac059da92 100644 --- a/crates/benilla-app/src/char_select/dialog.rs +++ b/crates/benilla-app/src/char_select/dialog.rs @@ -25,7 +25,7 @@ use super::wow_font; pub(super) struct DeleteDialog { pub(super) open: bool, /// The snapshotted target: guid, name, level, class name. - target: Option<(u64, String, u8, &'static str)>, + target: Option<(u64, String, u8, String)>, /// The confirm box's text; the reference's `letters="32"` cap is its `max_letters`. pub(super) typed: EditBoxState, /// The spawned dialog root, while up. @@ -35,9 +35,9 @@ pub(super) struct DeleteDialog { } impl DeleteDialog { - pub(super) fn open_for(&mut self, guid: u64, name: String, level: u8, class: &'static str) { + pub(super) fn open_for(&mut self, guid: u64, name: String, level: u8, class: &str) { self.open = true; - self.target = Some((guid, name, level, class)); + self.target = Some((guid, name, level, class.to_string())); self.typed.set_text(""); } @@ -204,17 +204,10 @@ fn spawn_dialog( let (name, level, class) = dialog .target .as_ref() - .map(|(_, n, l, c)| (n.clone(), *l, *c)) + .map(|(_, n, l, c)| (n.clone(), *l, c.as_str())) .unwrap_or_default(); // `CONFIRM_CHAR_DELETE`'s `|cffffffff` span splits it: gold lead above, white identity below. - let raw = strings.text( - "CONFIRM_CHAR_DELETE", - "Do you want to delete\n|cffffffff%s Level %d %s|r?", - ); - let filled = raw - .replacen("%s", &name, 1) - .replacen("%d", &level.to_string(), 1) - .replacen("%s", class, 1); + let filled = delete_prompt(strings, &name, level, class); let (lead, identity) = match filled.split_once("|cffffffff") { Some((a, b)) => (a.trim_end_matches('\n').to_string(), b.replace("|r", "")), None => (filled.clone(), String::new()), @@ -452,3 +445,41 @@ fn spawn_dialog( }) .id() } + +fn delete_prompt(strings: &GlueStrings, name: &str, level: u8, class: &str) -> String { + let template = strings.text( + "CONFIRM_CHAR_DELETE", + "Do you want to delete\n|cffffffff%s Level %d %s|r?", + ); + benilla_ui::strings::fill(template, &[name.into(), level.into(), class.into()]) +} + +#[cfg(test)] +mod localization_tests { + use super::*; + + #[test] + fn russian_delete_prompt_reorders_name_class_and_level_without_deleting() { + let data = benilla_formats::wow_data_or_skip!(); + if benilla_formats::ClientLocale::RuRu + .pack_folder(&data) + .is_none() + { + benilla_formats::skipped_optional("no ruRU language pack", &[data.join("ruRU")]); + return; + } + let mut chain = + benilla_formats::Chain::open_localized(&data, benilla_formats::ClientLocale::RuRu) + .unwrap(); + let strings = crate::glue_strings::table_from_chain(&mut chain); + let prompt = delete_prompt(&strings, "Qera", 34, strings.class_name(11)); + assert_eq!( + prompt.split_once("|cffffffff").unwrap().1, + "Qera, Друид 34-го уровня|r" + ); + assert_eq!( + delete_prompt(&GlueStrings::default(), "Agvin", 12, "Warrior"), + "Do you want to delete\n|cffffffffAgvin Level 12 Warrior|r?" + ); + } +} diff --git a/crates/benilla-app/src/char_select/input.rs b/crates/benilla-app/src/char_select/input.rs index c58f30081..4d0c13c04 100644 --- a/crates/benilla-app/src/char_select/input.rs +++ b/crates/benilla-app/src/char_select/input.rs @@ -12,7 +12,7 @@ use crate::sound::GlueSound; use super::dialog::DeleteDialog; use super::screen::SelectAction; -use super::{class_name, send_pick, ClientState, Roster}; +use super::{send_pick, ClientState, Roster}; use crate::glue::{drag_yaw, ROTATE_RATE}; @@ -30,6 +30,7 @@ pub(super) fn select_input( mut realms: ResMut, pick: Res, mut dialog: ResMut, + strings: Option>, mut panel: ResMut, choice: Res, mut next: ResMut>, @@ -71,7 +72,9 @@ pub(super) fn select_input( // With no selection the click only plays the sound (`selectedIndex > 0` gate). sounds.write(GlueSound("gsCharacterSelectionDelCharacter")); if let Some(c) = roster.selected_char() { - dialog.open_for(c.guid, c.name.clone(), c.level, class_name(c.class)); + let empty = crate::glue_strings::GlueStrings::default(); + let strings = strings.as_deref().unwrap_or(&empty); + dialog.open_for(c.guid, c.name.clone(), c.level, strings.class_name(c.class)); } } SelectAction::CreateChar => { diff --git a/crates/benilla-app/src/char_select/mod.rs b/crates/benilla-app/src/char_select/mod.rs index 529349067..ed076f42e 100644 --- a/crates/benilla-app/src/char_select/mod.rs +++ b/crates/benilla-app/src/char_select/mod.rs @@ -758,6 +758,7 @@ fn clear_select_entry(mut commands: Commands) { /// is up, with `` pre-typed, for a shot. fn debug_select_dialog( roster: Res, + strings: Option>, mut dialog: ResMut, time: Res