From 8c837da5616c2bad7e70c53723848341a709bd7d Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Sat, 25 Jun 2022 14:23:06 +0300 Subject: [PATCH] Add i18n keys to abilities * New hud/ability.ron file for ability localizations * i18n keys are created by adding .name and .desc to ability id. Because of how i18n worked (returning key if string wasn't found), it was impossible to do because it leads to UB in case string is missed. To solve this we've added get_opt method that returns None * New Localization::get_or method for convinient fallback key usage --- assets/voxygen/i18n/en/hud/ability.ron | 35 ++++++++++++++++ voxygen/i18n/src/lib.rs | 32 ++++++++------ voxygen/src/hud/diary.rs | 4 +- voxygen/src/hud/skillbar.rs | 2 +- voxygen/src/hud/util.rs | 58 +++++++------------------- 5 files changed, 73 insertions(+), 58 deletions(-) create mode 100644 assets/voxygen/i18n/en/hud/ability.ron diff --git a/assets/voxygen/i18n/en/hud/ability.ron b/assets/voxygen/i18n/en/hud/ability.ron new file mode 100644 index 0000000000..e9c64c1039 --- /dev/null +++ b/assets/voxygen/i18n/en/hud/ability.ron @@ -0,0 +1,35 @@ +/// WARNING: Localization files shall be saved in UTF-8 format without BOM + +/// Localization for "global" English +( + string_map: { + // Debug stick + "common.abilities.debug.possess.name": "Possessing Arrow", + "common.abilities.debug.possess.desc": "Shoots a poisonous arrow. Lets you control your target.", + // Sword + "common.abilities.sword.spin.name": "Whirlwind", + "common.abilities.sword.spin.desc": "Move forward while spinning with your sword.", + // Axe + "common.abilities.axe.leap.name": "Axe Jump", + "common.abilities.axe.leap.desc": "A jump with the slashing leap to position of cursor.", + // Hammer + "common.abilities.hammer.leap.name": "Smash of Doom", + "common.abilities.hammer.leap.desc": "An AOE attack with knockback. Leaps to position of cursor.", + // Bow + "common.abilities.bow.shotgun.name": "Burst", + "common.abilities.bow.shotgun.desc": "Launches a burst of arrows", + // Staff + "common.abilities.staff.fireshockwave.name": "Ring of Fire", + "common.abilities.staff.fireshockwave.desc": "Ignites the ground with fiery shockwave.", + // Sceptre + "common.abilities.sceptre.wardingaura.name": "Warding Aura", + "common.abilities.sceptre.wardingaura.desc": "Wards your allies against enemy attacks.", + // Unknown + "common.abilities.unknown.name": "Ability has no title", + "common.abilities.unknown.desc": "Ability has no description", + }, + + + vector_map: { + } +) diff --git a/voxygen/i18n/src/lib.rs b/voxygen/i18n/src/lib.rs index 4c954d7f61..d5dcf0408f 100644 --- a/voxygen/i18n/src/lib.rs +++ b/voxygen/i18n/src/lib.rs @@ -80,17 +80,14 @@ struct Language { impl Language { /// Get a localized text from the given key - pub fn get<'a>(&'a self, key: &'a str) -> Option<&str> { + pub fn get<'a>(&'a self, key: &str) -> Option<&str> { self.string_map.get(key).map(String::as_str) } /// Get a variation of localized text from the given key /// /// `index` should be a random number from `0` to `u16::max()` - /// - /// If the key is not present in the localization object - /// then the key is returned. - pub fn get_variation<'a>(&'a self, key: &'a str, index: u16) -> Option<&str> { + pub fn get_variation<'a>(&'a self, key: &str, index: u16) -> Option<&str> { self.vector_map.get(key).and_then(|v| { if v.is_empty() { None @@ -163,19 +160,30 @@ pub struct LocalizationGuard { pub type Localization = LocalizationGuard; impl LocalizationGuard { + /// Get a localized text from the given key + /// + /// First lookup is done in the active language, second in + /// the fallback (if present). + pub fn get_opt<'a>(&'a self, key: &str) -> Option<&'a str> { + self.active + .get(key) + .or_else(|| self.fallback.as_ref().and_then(|f| f.get(key))) + } + /// Get a localized text from the given key /// /// First lookup is done in the active language, second in /// the fallback (if present). /// If the key is not present in the localization object /// then the key is returned. - pub fn get<'a>(&'a self, key: &'a str) -> &str { - self.active.get(key).unwrap_or_else(|| { - self.fallback - .as_ref() - .and_then(|f| f.get(key)) - .unwrap_or(key) - }) + pub fn get<'a>(&'a self, key: &'a str) -> &str { self.get_opt(key).unwrap_or(key) } + + /// Get a localized text from the given key + /// + /// First lookup is done in the active language, second in + /// the fallback (if present). + pub fn get_or<'a>(&'a self, key: &str, fallback_key: &str) -> Option<&'a str> { + self.get_opt(key).or_else(|| self.get_opt(fallback_key)) } /// Get a variation of localized text from the given key diff --git a/voxygen/src/hud/diary.rs b/voxygen/src/hud/diary.rs index 1977cfb479..b7eeff1f87 100644 --- a/voxygen/src/hud/diary.rs +++ b/voxygen/src/hud/diary.rs @@ -835,7 +835,7 @@ impl<'a> Widget for Diary<'a> { ) .ability_id(Some(self.inventory)); let (ability_title, ability_desc) = if let Some(ability_id) = ability_id { - util::ability_description(ability_id) + util::ability_description(ability_id, self.localized_strings) } else { (Cow::Borrowed("Drag an ability here to use it."), "") }; @@ -1033,7 +1033,7 @@ impl<'a> Widget for Diary<'a> { .enumerate() { let (ability_title, ability_desc) = - util::ability_description(ability_id.unwrap_or("")); + util::ability_description(ability_id.unwrap_or(""), self.localized_strings); let (align_state, image_offsets) = if id_index < 6 { (state.ids.sb_page_left_align, 120.0 * id_index as f64) diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index 731a5fd829..3c178504e5 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -612,7 +612,7 @@ impl<'a> Skillbar<'a> { .get(i) .and_then(|a| Ability::from(*a).ability_id(Some(inventory))) }) - .map(util::ability_description), + .map(|id| util::ability_description(id, self.localized_strings)), }) }; diff --git a/voxygen/src/hud/util.rs b/voxygen/src/hud/util.rs index d397836b34..f934aea939 100644 --- a/voxygen/src/hud/util.rs +++ b/voxygen/src/hud/util.rs @@ -356,48 +356,20 @@ pub fn ability_image(imgs: &img_ids::Imgs, ability_id: &str) -> image::Id { } } -#[rustfmt::skip] -pub fn ability_description(ability_id: &str) -> (Cow, &str) { - let (name, desc) = match ability_id { - // Debug stick - "common.abilities.debug.possess" => ( - "Possessing Arrow", - "Shoots a poisonous arrow. Lets you control your target.", +pub fn ability_description<'a>( + ability_id: &'a str, + loc: &'a i18n::Localization, +) -> (Cow<'a, str>, &'a str) { + let (name, desc) = ( + format!("{}.name", ability_id), + format!("{}.desc", ability_id), + ); + ( + Cow::Borrowed( + loc.get_or(&name, "common.abilities.unknown.name") + .unwrap_or(ability_id), ), - // Sword - "common.abilities.sword.spin" => ( - "Whirlwind", - "Move forward while spinning with your sword.", - ), - // Axe - "common.abilities.axe.leap" => ( - "Axe Jump", - "A jump with the slashing leap to position of cursor.", - ), - // Hammer - "common.abilities.hammer.leap" => ( - "Smash of Doom", - "An AOE attack with knockback. Leaps to position of cursor.", - ), - // Bow - "common.abilities.bow.shotgun" => ( - "Burst", - "Launches a burst of arrows", - ), - // Staff - "common.abilities.staff.fireshockwave" => ( - "Ring of Fire", - "Ignites the ground with fiery shockwave.", - ), - // Sceptre - "common.abilities.sceptre.wardingaura" => ( - "Thorn Bulwark", - "Protects you and your group with thorns for a short amount of time.", - ), - _ => ( - "Ability has no title", - "Ability has no description." - ), - }; - (Cow::Borrowed(name), desc) + loc.get_or(&desc, "common.abilities.unknown.desc") + .unwrap_or(ability_id), + ) }