Add ItemDesc::l10n method

- Add ItemL10n struct that is similar to ItemImgs except it holds i18n
  description and not items. ItemDesc::l10n uses this struct to provide
  common_i18n::Content for both names and descriptions.
  So far it only used in voxygen, but it can be used by rtsim in
  dialogues.
- Introduced new deprecation, ItemKind::Ingredient, because it uses
  item.name().
  It's not deleted, because it's used in inventory sorting, and our
  inventory sorting is, for some reason, server-side.
- Crafting UI also still uses deprecated item.name(), because again,
  sorting. It's probably will be easier to handle, because it's UI
  sorting and we can use localized names here, but still, it's a thing
  to discuss.
- Moved Item::describe() to voxygen/hud/util.

The most important thing to note is we don't want to completely delete
deprecated .name() and .description() along with corresponding fields
in ItemDef because ItemDef is now "public" API, exposed in plugins and I
don't want to break plugins before we actually implement i18n for them.
Otherwise, it would be basically impossible to use items in plugins.

What's left is actually fully implementing ItemDesc::l10n, create
item_l10n.ron and add fallback on current .name() and .description()
implementation.
This commit is contained in:
juliancoffee 2024-01-11 18:28:25 +02:00
parent 8263154a7e
commit 18e507315f
10 changed files with 205 additions and 54 deletions

View File

@ -14,7 +14,7 @@ pub enum ItemKey {
Empty, Empty,
} }
impl<T: ItemDesc> From<&T> for ItemKey { impl<T: ItemDesc + ?Sized> From<&T> for ItemKey {
fn from(item_desc: &T) -> Self { fn from(item_desc: &T) -> Self {
let item_definition_id = item_desc.item_definition_id(); let item_definition_id = item_desc.item_definition_id();

View File

@ -14,13 +14,15 @@ use crate::{
recipe::RecipeInput, recipe::RecipeInput,
terrain::Block, terrain::Block,
}; };
use common_i18n::Content;
use core::{ use core::{
convert::TryFrom, convert::TryFrom,
mem, mem,
num::{NonZeroU32, NonZeroU64}, num::{NonZeroU32, NonZeroU64},
}; };
use crossbeam_utils::atomic::AtomicCell; use crossbeam_utils::atomic::AtomicCell;
use hashbrown::Equivalent; use hashbrown::{Equivalent, HashMap};
use item_key::ItemKey;
use serde::{de, Deserialize, Serialize, Serializer}; use serde::{de, Deserialize, Serialize, Serializer};
use specs::{Component, DenseVecStorage, DerefFlaggedStorage}; use specs::{Component, DenseVecStorage, DerefFlaggedStorage};
use std::{borrow::Cow, collections::hash_map::DefaultHasher, fmt, sync::Arc}; use std::{borrow::Cow, collections::hash_map::DefaultHasher, fmt, sync::Arc};
@ -342,6 +344,7 @@ pub enum ItemKind {
}, },
Ingredient { Ingredient {
/// Used to generate names for modular items composed of this ingredient /// Used to generate names for modular items composed of this ingredient
#[deprecated]
descriptor: String, descriptor: String,
}, },
TagExamples { TagExamples {
@ -383,6 +386,7 @@ impl ItemKind {
}, },
ItemKind::Throwable { kind } => format!("Throwable: {:?}", kind), ItemKind::Throwable { kind } => format!("Throwable: {:?}", kind),
ItemKind::Utility { kind } => format!("Utility: {:?}", kind), ItemKind::Utility { kind } => format!("Utility: {:?}", kind),
#[allow(deprecated)]
ItemKind::Ingredient { descriptor } => format!("Ingredient: {}", descriptor), ItemKind::Ingredient { descriptor } => format!("Ingredient: {}", descriptor),
ItemKind::TagExamples { item_ids } => format!("TagExamples: {:?}", item_ids), ItemKind::TagExamples { item_ids } => format!("TagExamples: {:?}", item_ids),
} }
@ -466,11 +470,28 @@ impl Hash for Item {
} }
} }
// at the time of writing, we use Fluent, which supports attributes
// and we can get both name and description using them
type I18nId = String;
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ItemName { // TODO: probably make a Resource if used outside of voxygen
Direct(String), // TODO: add hot-reloading similar to how ItemImgs does it?
Modular, // TODO: make it work with plugins (via Concatenate?)
Component(String), /// To be used with ItemDesc::l10n
pub struct ItemL10n {
/// maps ItemKey to i18n identifier
map: HashMap<ItemKey, I18nId>,
}
impl assets::Asset for ItemL10n {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
impl ItemL10n {
pub fn new_expect() -> Self { ItemL10n::load_expect("common.item_l10n").read().clone() }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -1147,16 +1168,6 @@ impl Item {
}) })
} }
/// Generate a human-readable description of the item and amount.
#[deprecated]
pub fn describe(&self) -> String {
if self.amount() > 1 {
format!("{} x {}", self.amount(), self.name())
} else {
self.name().to_string()
}
}
#[deprecated] #[deprecated]
pub fn name(&self) -> Cow<str> { pub fn name(&self) -> Cow<str> {
match &self.item_base { match &self.item_base {
@ -1400,6 +1411,7 @@ pub fn flatten_counted_items<'a>(
pub trait ItemDesc { pub trait ItemDesc {
#[deprecated] #[deprecated]
fn description(&self) -> &str; fn description(&self) -> &str;
#[deprecated]
fn name(&self) -> Cow<str>; fn name(&self) -> Cow<str>;
fn kind(&self) -> Cow<ItemKind>; fn kind(&self) -> Cow<ItemKind>;
fn amount(&self) -> NonZeroU32; fn amount(&self) -> NonZeroU32;
@ -1420,6 +1432,18 @@ pub trait ItemDesc {
None None
} }
} }
/// Return name's and description's localization descriptors
fn l10n(&self, l10n: &ItemL10n) -> (Content, Content) {
let item_key: ItemKey = self.into();
let _key = l10n.map.get(&item_key);
(
// construct smth like Content::Attr
todo!(),
todo!(),
)
}
} }
impl ItemDesc for Item { impl ItemDesc for Item {

View File

@ -3,7 +3,7 @@ use super::{
img_ids::{Imgs, ImgsRot}, img_ids::{Imgs, ImgsRot},
item_imgs::ItemImgs, item_imgs::ItemImgs,
slots::{ArmorSlot, EquipSlot, InventorySlot, SlotManager}, slots::{ArmorSlot, EquipSlot, InventorySlot, SlotManager},
HudInfo, Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, util, HudInfo, Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
}; };
use crate::{ use crate::{
game_input::GameInput, game_input::GameInput,
@ -21,7 +21,7 @@ use common::{
combat::{combat_rating, perception_dist_multiplier_from_stealth, Damage}, combat::{combat_rating, perception_dist_multiplier_from_stealth, Damage},
comp::{ comp::{
inventory::InventorySortOrder, inventory::InventorySortOrder,
item::{ItemDef, ItemDesc, MaterialStatManifest, Quality}, item::{ItemDef, ItemDesc, ItemL10n, MaterialStatManifest, Quality},
Body, Energy, Health, Inventory, Poise, SkillSet, Stats, Body, Energy, Health, Inventory, Poise, SkillSet, Stats,
}, },
}; };
@ -81,6 +81,7 @@ pub struct InventoryScroller<'a> {
slot_manager: &'a mut SlotManager, slot_manager: &'a mut SlotManager,
pulse: f32, pulse: f32,
localized_strings: &'a Localization, localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
show_stats: bool, show_stats: bool,
show_bag_inv: bool, show_bag_inv: bool,
on_right: bool, on_right: bool,
@ -105,6 +106,7 @@ impl<'a> InventoryScroller<'a> {
slot_manager: &'a mut SlotManager, slot_manager: &'a mut SlotManager,
pulse: f32, pulse: f32,
localized_strings: &'a Localization, localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
show_stats: bool, show_stats: bool,
show_bag_inv: bool, show_bag_inv: bool,
on_right: bool, on_right: bool,
@ -127,6 +129,7 @@ impl<'a> InventoryScroller<'a> {
slot_manager, slot_manager,
pulse, pulse,
localized_strings, localized_strings,
item_l10n,
show_stats, show_stats,
show_bag_inv, show_bag_inv,
on_right, on_right,
@ -365,8 +368,18 @@ impl<'a> InventoryScroller<'a> {
items.sort_by_cached_key(|(_, item)| { items.sort_by_cached_key(|(_, item)| {
( (
item.is_none(), item.is_none(),
item.as_ref() item.as_ref().map(|i| {
.map(|i| (std::cmp::Reverse(i.quality()), i.name(), i.amount())), (
std::cmp::Reverse(i.quality()),
{
// TODO: we do double the work here, optimize?
let (name, _) =
util::item_text(i, self.localized_strings, self.item_l10n);
name
},
i.amount(),
)
}),
) )
}); });
} }
@ -457,7 +470,8 @@ impl<'a> InventoryScroller<'a> {
.set(state.ids.inv_slots[i], ui); .set(state.ids.inv_slots[i], ui);
} }
if self.details_mode { if self.details_mode {
Text::new(&item.name()) let (name, _) = util::item_text(item, self.localized_strings, self.item_l10n);
Text::new(&name)
.top_left_with_margins_on( .top_left_with_margins_on(
state.ids.inv_alignment, state.ids.inv_alignment,
0.0 + y as f64 * slot_size, 0.0 + y as f64 * slot_size,
@ -638,6 +652,7 @@ pub struct Bag<'a> {
slot_manager: &'a mut SlotManager, slot_manager: &'a mut SlotManager,
pulse: f32, pulse: f32,
localized_strings: &'a Localization, localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
stats: &'a Stats, stats: &'a Stats,
skill_set: &'a SkillSet, skill_set: &'a SkillSet,
health: &'a Health, health: &'a Health,
@ -663,6 +678,7 @@ impl<'a> Bag<'a> {
slot_manager: &'a mut SlotManager, slot_manager: &'a mut SlotManager,
pulse: f32, pulse: f32,
localized_strings: &'a Localization, localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
stats: &'a Stats, stats: &'a Stats,
skill_set: &'a SkillSet, skill_set: &'a SkillSet,
health: &'a Health, health: &'a Health,
@ -686,6 +702,7 @@ impl<'a> Bag<'a> {
slot_manager, slot_manager,
pulse, pulse,
localized_strings, localized_strings,
item_l10n,
stats, stats,
skill_set, skill_set,
energy, energy,
@ -805,6 +822,7 @@ impl<'a> Widget for Bag<'a> {
self.pulse, self.pulse,
self.msm, self.msm,
self.localized_strings, self.localized_strings,
self.item_l10n,
) )
.title_font_size(self.fonts.cyri.scale(20)) .title_font_size(self.fonts.cyri.scale(20))
.parent(ui.window) .parent(ui.window)
@ -821,6 +839,7 @@ impl<'a> Widget for Bag<'a> {
self.slot_manager, self.slot_manager,
self.pulse, self.pulse,
self.localized_strings, self.localized_strings,
self.item_l10n,
self.show.stats, self.show.stats,
self.show.bag_inv, self.show.bag_inv,
true, true,

View File

@ -3,7 +3,7 @@ use super::{
img_ids::{Imgs, ImgsRot}, img_ids::{Imgs, ImgsRot},
item_imgs::{animate_by_pulse, ItemImgs}, item_imgs::{animate_by_pulse, ItemImgs},
slots::{CraftSlot, CraftSlotInfo, SlotManager}, slots::{CraftSlot, CraftSlotInfo, SlotManager},
HudInfo, Show, TEXT_COLOR, TEXT_DULL_RED_COLOR, TEXT_GRAY_COLOR, UI_HIGHLIGHT_0, UI_MAIN, util, HudInfo, Show, TEXT_COLOR, TEXT_DULL_RED_COLOR, TEXT_GRAY_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
}; };
use crate::ui::{ use crate::ui::{
fonts::Fonts, fonts::Fonts,
@ -19,8 +19,8 @@ use common::{
item_key::ItemKey, item_key::ItemKey,
modular::{self, ModularComponent}, modular::{self, ModularComponent},
tool::{AbilityMap, ToolKind}, tool::{AbilityMap, ToolKind},
Item, ItemBase, ItemDef, ItemDesc, ItemKind, ItemTag, MaterialStatManifest, Quality, Item, ItemBase, ItemDef, ItemDesc, ItemKind, ItemL10n, ItemTag, MaterialStatManifest,
TagExampleInfo, Quality, TagExampleInfo,
}, },
slot::{InvSlotId, Slot}, slot::{InvSlotId, Slot},
Inventory, Inventory,
@ -151,6 +151,7 @@ pub struct Crafting<'a> {
imgs: &'a Imgs, imgs: &'a Imgs,
fonts: &'a Fonts, fonts: &'a Fonts,
localized_strings: &'a Localization, localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
pulse: f32, pulse: f32,
rot_imgs: &'a ImgsRot, rot_imgs: &'a ImgsRot,
item_tooltip_manager: &'a mut ItemTooltipManager, item_tooltip_manager: &'a mut ItemTooltipManager,
@ -171,6 +172,7 @@ impl<'a> Crafting<'a> {
imgs: &'a Imgs, imgs: &'a Imgs,
fonts: &'a Fonts, fonts: &'a Fonts,
localized_strings: &'a Localization, localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
pulse: f32, pulse: f32,
rot_imgs: &'a ImgsRot, rot_imgs: &'a ImgsRot,
item_tooltip_manager: &'a mut ItemTooltipManager, item_tooltip_manager: &'a mut ItemTooltipManager,
@ -187,6 +189,7 @@ impl<'a> Crafting<'a> {
imgs, imgs,
fonts, fonts,
localized_strings, localized_strings,
item_l10n,
pulse, pulse,
rot_imgs, rot_imgs,
item_tooltip_manager, item_tooltip_manager,
@ -355,6 +358,7 @@ impl<'a> Widget for Crafting<'a> {
self.pulse, self.pulse,
self.msm, self.msm,
self.localized_strings, self.localized_strings,
self.item_l10n,
) )
.title_font_size(self.fonts.cyri.scale(20)) .title_font_size(self.fonts.cyri.scale(20))
.parent(ui.window) .parent(ui.window)
@ -594,6 +598,7 @@ impl<'a> Widget for Crafting<'a> {
.iter() .iter()
.filter(|(_, recipe)| match search_filter { .filter(|(_, recipe)| match search_filter {
SearchFilter::None => { SearchFilter::None => {
#[allow(deprecated)]
let output_name = recipe.output.0.name().to_lowercase(); let output_name = recipe.output.0.name().to_lowercase();
search_keys search_keys
.iter() .iter()
@ -608,9 +613,11 @@ impl<'a> Widget for Crafting<'a> {
}; };
match input { match input {
#[allow(deprecated)]
RecipeInput::Item(def) => search(&def.name()), RecipeInput::Item(def) => search(&def.name()),
RecipeInput::Tag(tag) => search(tag.name()), RecipeInput::Tag(tag) => search(tag.name()),
RecipeInput::TagSameItem(tag) => search(tag.name()), RecipeInput::TagSameItem(tag) => search(tag.name()),
#[allow(deprecated)]
RecipeInput::ListSameItem(defs) => { RecipeInput::ListSameItem(defs) => {
defs.iter().any(|def| search(&def.name())) defs.iter().any(|def| search(&def.name()))
}, },
@ -667,6 +674,7 @@ impl<'a> Widget for Crafting<'a> {
!is_craftable, !is_craftable,
!has_materials, !has_materials,
recipe.output.0.quality(), recipe.output.0.quality(),
#[allow(deprecated)]
recipe.output.0.name(), recipe.output.0.name(),
) )
}); });
@ -1214,6 +1222,8 @@ impl<'a> Widget for Crafting<'a> {
}; };
if let Some(output_item) = output_item { if let Some(output_item) = output_item {
let (name, _) =
util::item_text(&output_item, self.localized_strings, self.item_l10n);
Button::image(animate_by_pulse( Button::image(animate_by_pulse(
&self &self
.item_imgs .item_imgs
@ -1221,7 +1231,7 @@ impl<'a> Widget for Crafting<'a> {
self.pulse, self.pulse,
)) ))
.w_h(55.0, 55.0) .w_h(55.0, 55.0)
.label(&output_item.name()) .label(&name)
.label_color(TEXT_COLOR) .label_color(TEXT_COLOR)
.label_font_size(self.fonts.cyri.scale(14)) .label_font_size(self.fonts.cyri.scale(14))
.label_font_id(self.fonts.cyri.conrod_id) .label_font_id(self.fonts.cyri.conrod_id)
@ -1926,6 +1936,7 @@ impl<'a> Widget for Crafting<'a> {
.was_clicked() .was_clicked()
{ {
events.push(Event::ChangeCraftingTab(CraftingTab::All)); events.push(Event::ChangeCraftingTab(CraftingTab::All));
#[allow(deprecated)]
events.push(Event::SearchRecipe(Some(item_def.name().to_string()))); events.push(Event::SearchRecipe(Some(item_def.name().to_string())));
} }
// Item image // Item image
@ -1963,7 +1974,13 @@ impl<'a> Widget for Crafting<'a> {
.font_size(self.fonts.cyri.scale(14)) .font_size(self.fonts.cyri.scale(14))
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.ids.req_text[i], ui); .set(state.ids.req_text[i], ui);
Text::new(&item_def.name())
let (name, _) = util::item_text(
item_def.as_ref(),
self.localized_strings,
self.item_l10n,
);
Text::new(&name)
.right_from(state.ids.ingredient_frame[i], 10.0) .right_from(state.ids.ingredient_frame[i], 10.0)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14)) .font_size(self.fonts.cyri.scale(14))
@ -1972,14 +1989,35 @@ impl<'a> Widget for Crafting<'a> {
} else { } else {
// Ingredients // Ingredients
let name = match recipe_input { let name = match recipe_input {
RecipeInput::Item(_) => item_def.name().to_string(), RecipeInput::Item(_) => {
let (name, _) = util::item_text(
item_def.as_ref(),
self.localized_strings,
self.item_l10n,
);
name
},
RecipeInput::Tag(tag) | RecipeInput::TagSameItem(tag) => { RecipeInput::Tag(tag) | RecipeInput::TagSameItem(tag) => {
// TODO: Localize!
format!("Any {} item", tag.name()) format!("Any {} item", tag.name())
}, },
RecipeInput::ListSameItem(item_defs) => { RecipeInput::ListSameItem(item_defs) => {
// TODO: Localize!
format!( format!(
"Any of {}", "Any of {}",
item_defs.iter().map(|def| def.name()).collect::<String>() item_defs
.iter()
.map(|def| {
let (name, _) = util::item_text(
def.as_ref(),
self.localized_strings,
self.item_l10n,
);
name
})
.collect::<String>()
) )
}, },
}; };

View File

@ -2,11 +2,11 @@ use super::{
animate_by_pulse, get_quality_col, animate_by_pulse, get_quality_col,
img_ids::{Imgs, ImgsRot}, img_ids::{Imgs, ImgsRot},
item_imgs::ItemImgs, item_imgs::ItemImgs,
HudInfo, Show, Windows, TEXT_COLOR, util, HudInfo, Show, Windows, TEXT_COLOR,
}; };
use crate::ui::{fonts::Fonts, ImageFrame, ItemTooltip, ItemTooltipManager, ItemTooltipable}; use crate::ui::{fonts::Fonts, ImageFrame, ItemTooltip, ItemTooltipManager, ItemTooltipable};
use client::Client; use client::Client;
use common::comp::inventory::item::{Item, ItemDesc, MaterialStatManifest, Quality}; use common::comp::inventory::item::{Item, ItemDesc, ItemL10n, MaterialStatManifest, Quality};
use conrod_core::{ use conrod_core::{
color, color,
position::Dimension, position::Dimension,
@ -58,6 +58,7 @@ pub struct LootScroller<'a> {
rot_imgs: &'a ImgsRot, rot_imgs: &'a ImgsRot,
fonts: &'a Fonts, fonts: &'a Fonts,
localized_strings: &'a Localization, localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
msm: &'a MaterialStatManifest, msm: &'a MaterialStatManifest,
item_tooltip_manager: &'a mut ItemTooltipManager, item_tooltip_manager: &'a mut ItemTooltipManager,
pulse: f32, pulse: f32,
@ -76,6 +77,7 @@ impl<'a> LootScroller<'a> {
rot_imgs: &'a ImgsRot, rot_imgs: &'a ImgsRot,
fonts: &'a Fonts, fonts: &'a Fonts,
localized_strings: &'a Localization, localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
msm: &'a MaterialStatManifest, msm: &'a MaterialStatManifest,
item_tooltip_manager: &'a mut ItemTooltipManager, item_tooltip_manager: &'a mut ItemTooltipManager,
pulse: f32, pulse: f32,
@ -90,6 +92,7 @@ impl<'a> LootScroller<'a> {
rot_imgs, rot_imgs,
fonts, fonts,
localized_strings, localized_strings,
item_l10n,
msm, msm,
item_tooltip_manager, item_tooltip_manager,
pulse, pulse,
@ -153,6 +156,7 @@ impl<'a> Widget for LootScroller<'a> {
self.pulse, self.pulse,
self.msm, self.msm,
self.localized_strings, self.localized_strings,
self.item_l10n,
) )
.title_font_size(self.fonts.cyri.scale(20)) .title_font_size(self.fonts.cyri.scale(20))
.parent(ui.window) .parent(ui.window)
@ -351,7 +355,11 @@ impl<'a> Widget for LootScroller<'a> {
&i18n::fluent_args! { &i18n::fluent_args! {
"actor" => taken_by, "actor" => taken_by,
"amount" => amount, "amount" => amount,
"item" => item.name(), "item" => {
let (name, _) =
util::item_text(&item, self.localized_strings, self.item_l10n);
name
},
}, },
); );
let label_font_size = 20; let label_font_size = 20;

View File

@ -98,7 +98,7 @@ use common::{
}, },
item::{ item::{
tool::{AbilityContext, ToolKind}, tool::{AbilityContext, ToolKind},
ItemDesc, MaterialStatManifest, Quality, ItemDefinitionIdOwned, ItemDesc, ItemL10n, MaterialStatManifest, Quality,
}, },
loot_owner::LootOwnerKind, loot_owner::LootOwnerKind,
pet::is_mountable, pet::is_mountable,
@ -1286,6 +1286,7 @@ pub struct Hud {
world_map: (/* Id */ Vec<Rotations>, Vec2<u32>), world_map: (/* Id */ Vec<Rotations>, Vec2<u32>),
imgs: Imgs, imgs: Imgs,
item_imgs: ItemImgs, item_imgs: ItemImgs,
item_l10n: ItemL10n,
fonts: Fonts, fonts: Fonts,
rot_imgs: ImgsRot, rot_imgs: ImgsRot,
failed_block_pickups: HashMap<VolumePos, CollectFailedData>, failed_block_pickups: HashMap<VolumePos, CollectFailedData>,
@ -1341,6 +1342,8 @@ impl Hud {
let rot_imgs = ImgsRot::load(&mut ui).expect("Failed to load rot images!"); let rot_imgs = ImgsRot::load(&mut ui).expect("Failed to load rot images!");
// Load item images. // Load item images.
let item_imgs = ItemImgs::new(&mut ui, imgs.not_found); let item_imgs = ItemImgs::new(&mut ui, imgs.not_found);
// Load item text ("reference" to name and description)
let item_l10n = ItemL10n::new_expect();
// Load fonts. // Load fonts.
let fonts = Fonts::load(global_state.i18n.read().fonts(), &mut ui) let fonts = Fonts::load(global_state.i18n.read().fonts(), &mut ui)
.expect("Impossible to load fonts!"); .expect("Impossible to load fonts!");
@ -1380,6 +1383,7 @@ impl Hud {
world_map, world_map,
rot_imgs, rot_imgs,
item_imgs, item_imgs,
item_l10n,
fonts, fonts,
ids, ids,
failed_block_pickups: HashMap::default(), failed_block_pickups: HashMap::default(),
@ -2008,7 +2012,7 @@ impl Hud {
// Item // Item
overitem::Overitem::new( overitem::Overitem::new(
item.describe().into(), util::describe(item, i18n, &self.item_l10n).into(),
quality, quality,
distance, distance,
fonts, fonts,
@ -2091,20 +2095,27 @@ impl Hud {
)] )]
}, },
BlockInteraction::Unlock(kind) => { BlockInteraction::Unlock(kind) => {
let item_name = |item_id: &ItemDefinitionIdOwned| {
item_id
.as_ref()
.itemdef_id()
.map(|id| {
let item = Item::new_from_asset_expect(id);
util::describe(&item, i18n, &self.item_l10n)
})
.unwrap_or_else(|| "modular item".to_string())
};
vec![(Some(GameInput::Interact), match kind { vec![(Some(GameInput::Interact), match kind {
UnlockKind::Free => i18n.get_msg("hud-open").to_string(), UnlockKind::Free => i18n.get_msg("hud-open").to_string(),
UnlockKind::Requires(item) => i18n UnlockKind::Requires(item_id) => i18n
.get_msg_ctx("hud-unlock-requires", &i18n::fluent_args! { .get_msg_ctx("hud-unlock-requires", &i18n::fluent_args! {
"item" => item.as_ref().itemdef_id() "item" => item_name(item_id),
.map(|id| Item::new_from_asset_expect(id).describe())
.unwrap_or_else(|| "modular item".to_string()),
}) })
.to_string(), .to_string(),
UnlockKind::Consumes(item) => i18n UnlockKind::Consumes(item_id) => i18n
.get_msg_ctx("hud-unlock-requires", &i18n::fluent_args! { .get_msg_ctx("hud-unlock-requires", &i18n::fluent_args! {
"item" => item.as_ref().itemdef_id() "item" => item_name(item_id),
.map(|id| Item::new_from_asset_expect(id).describe())
.unwrap_or_else(|| "modular item".to_string()),
}) })
.to_string(), .to_string(),
})] })]
@ -3166,6 +3177,7 @@ impl Hud {
item_tooltip_manager, item_tooltip_manager,
&mut self.slot_manager, &mut self.slot_manager,
i18n, i18n,
&self.item_l10n,
&msm, &msm,
self.floaters.combo_floater, self.floaters.combo_floater,
&context, &context,
@ -3213,6 +3225,7 @@ impl Hud {
&mut self.slot_manager, &mut self.slot_manager,
self.pulse, self.pulse,
i18n, i18n,
&self.item_l10n,
player_stats, player_stats,
skill_set, skill_set,
health, health,
@ -3257,6 +3270,7 @@ impl Hud {
item_tooltip_manager, item_tooltip_manager,
&mut self.slot_manager, &mut self.slot_manager,
i18n, i18n,
&self.item_l10n,
&msm, &msm,
self.pulse, self.pulse,
&mut self.show, &mut self.show,
@ -3337,6 +3351,7 @@ impl Hud {
&self.imgs, &self.imgs,
&self.fonts, &self.fonts,
i18n, i18n,
&self.item_l10n,
self.pulse, self.pulse,
&self.rot_imgs, &self.rot_imgs,
item_tooltip_manager, item_tooltip_manager,
@ -3503,6 +3518,7 @@ impl Hud {
&self.rot_imgs, &self.rot_imgs,
&self.fonts, &self.fonts,
i18n, i18n,
&self.item_l10n,
&msm, &msm,
item_tooltip_manager, item_tooltip_manager,
self.pulse, self.pulse,

View File

@ -19,7 +19,6 @@ use crate::{
GlobalState, GlobalState,
}; };
use i18n::Localization; use i18n::Localization;
use std::borrow::Cow;
use client::{self, Client}; use client::{self, Client};
use common::comp::{ use common::comp::{
@ -27,7 +26,7 @@ use common::comp::{
ability::{AbilityInput, Stance}, ability::{AbilityInput, Stance},
item::{ item::{
tool::{AbilityContext, ToolKind}, tool::{AbilityContext, ToolKind},
ItemDesc, MaterialStatManifest, ItemDesc, ItemL10n, MaterialStatManifest,
}, },
skillset::SkillGroupKind, skillset::SkillGroupKind,
Ability, ActiveAbilities, Body, CharacterState, Combo, Energy, Health, Inventory, Poise, Ability, ActiveAbilities, Body, CharacterState, Combo, Energy, Health, Inventory, Poise,
@ -308,6 +307,7 @@ pub struct Skillbar<'a> {
item_tooltip_manager: &'a mut ItemTooltipManager, item_tooltip_manager: &'a mut ItemTooltipManager,
slot_manager: &'a mut slots::SlotManager, slot_manager: &'a mut slots::SlotManager,
localized_strings: &'a Localization, localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
pulse: f32, pulse: f32,
#[conrod(common_builder)] #[conrod(common_builder)]
common: widget::CommonBuilder, common: widget::CommonBuilder,
@ -344,6 +344,7 @@ impl<'a> Skillbar<'a> {
item_tooltip_manager: &'a mut ItemTooltipManager, item_tooltip_manager: &'a mut ItemTooltipManager,
slot_manager: &'a mut slots::SlotManager, slot_manager: &'a mut slots::SlotManager,
localized_strings: &'a Localization, localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
msm: &'a MaterialStatManifest, msm: &'a MaterialStatManifest,
combo_floater: Option<ComboFloater>, combo_floater: Option<ComboFloater>,
context: &'a AbilityContext, context: &'a AbilityContext,
@ -375,6 +376,7 @@ impl<'a> Skillbar<'a> {
item_tooltip_manager, item_tooltip_manager,
slot_manager, slot_manager,
localized_strings, localized_strings,
item_l10n,
msm, msm,
combo_floater, combo_floater,
context, context,
@ -1008,6 +1010,7 @@ impl<'a> Skillbar<'a> {
self.pulse, self.pulse,
self.msm, self.msm,
self.localized_strings, self.localized_strings,
self.item_l10n,
) )
.title_font_size(self.fonts.cyri.scale(20)) .title_font_size(self.fonts.cyri.scale(20))
.parent(ui.window) .parent(ui.window)
@ -1028,9 +1031,12 @@ impl<'a> Skillbar<'a> {
let (hotbar, inventory, _, skill_set, active_abilities, _, contexts, _, _, _) = let (hotbar, inventory, _, skill_set, active_abilities, _, contexts, _, _, _) =
content_source; content_source;
hotbar.get(slot).and_then(|content| match content { hotbar.get(slot).and_then(|content| match content {
hotbar::SlotContents::Inventory(i, _) => inventory hotbar::SlotContents::Inventory(i, _) => inventory.get_by_hash(i).map(|item| {
.get_by_hash(i) let (title, desc) =
.map(|item| (item.name(), Cow::Borrowed(item.description()))), util::item_text(item, self.localized_strings, self.item_l10n);
(title.into(), desc.into())
}),
hotbar::SlotContents::Ability(i) => active_abilities hotbar::SlotContents::Ability(i) => active_abilities
.and_then(|a| { .and_then(|a| {
a.auxiliary_set(Some(inventory), Some(skill_set)) a.auxiliary_set(Some(inventory), Some(skill_set))

View File

@ -10,7 +10,7 @@ use vek::*;
use client::Client; use client::Client;
use common::{ use common::{
comp::{ comp::{
inventory::item::{ItemDesc, MaterialStatManifest, Quality}, inventory::item::{ItemDesc, ItemL10n, MaterialStatManifest, Quality},
Inventory, Stats, Inventory, Stats,
}, },
trade::{PendingTrade, SitePrices, TradeAction, TradePhase}, trade::{PendingTrade, SitePrices, TradeAction, TradePhase},
@ -35,7 +35,8 @@ use super::{
img_ids::{Imgs, ImgsRot}, img_ids::{Imgs, ImgsRot},
item_imgs::ItemImgs, item_imgs::ItemImgs,
slots::{SlotKind, SlotManager, TradeSlot}, slots::{SlotKind, SlotManager, TradeSlot},
Hud, HudInfo, Show, TradeAmountInput, TEXT_COLOR, TEXT_GRAY_COLOR, UI_HIGHLIGHT_0, UI_MAIN, util, Hud, HudInfo, Show, TradeAmountInput, TEXT_COLOR, TEXT_GRAY_COLOR, UI_HIGHLIGHT_0,
UI_MAIN,
}; };
use std::borrow::Cow; use std::borrow::Cow;
@ -98,6 +99,7 @@ pub struct Trade<'a> {
common: widget::CommonBuilder, common: widget::CommonBuilder,
slot_manager: &'a mut SlotManager, slot_manager: &'a mut SlotManager,
localized_strings: &'a Localization, localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
msm: &'a MaterialStatManifest, msm: &'a MaterialStatManifest,
pulse: f32, pulse: f32,
show: &'a mut Show, show: &'a mut Show,
@ -116,6 +118,7 @@ impl<'a> Trade<'a> {
item_tooltip_manager: &'a mut ItemTooltipManager, item_tooltip_manager: &'a mut ItemTooltipManager,
slot_manager: &'a mut SlotManager, slot_manager: &'a mut SlotManager,
localized_strings: &'a Localization, localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
msm: &'a MaterialStatManifest, msm: &'a MaterialStatManifest,
pulse: f32, pulse: f32,
show: &'a mut Show, show: &'a mut Show,
@ -132,6 +135,7 @@ impl<'a> Trade<'a> {
common: widget::CommonBuilder::default(), common: widget::CommonBuilder::default(),
slot_manager, slot_manager,
localized_strings, localized_strings,
item_l10n,
msm, msm,
pulse, pulse,
show, show,
@ -345,6 +349,7 @@ impl<'a> Trade<'a> {
self.pulse, self.pulse,
self.msm, self.msm,
self.localized_strings, self.localized_strings,
self.item_l10n,
) )
.title_font_size(self.fonts.cyri.scale(20)) .title_font_size(self.fonts.cyri.scale(20))
.parent(ui.window) .parent(ui.window)
@ -362,6 +367,7 @@ impl<'a> Trade<'a> {
self.slot_manager, self.slot_manager,
self.pulse, self.pulse,
self.localized_strings, self.localized_strings,
self.item_l10n,
false, false,
true, true,
false, false,
@ -530,7 +536,11 @@ impl<'a> Trade<'a> {
let itemname = slot let itemname = slot
.invslot .invslot
.and_then(|i| inventory.get(i)) .and_then(|i| inventory.get(i))
.map(|i| i.name()) .map(|i| {
let (name, _) = util::item_text(&i, self.localized_strings, self.item_l10n);
Cow::Owned(name)
})
.unwrap_or(Cow::Borrowed("")); .unwrap_or(Cow::Borrowed(""));
let is_present = slot.quantity > 0 && slot.invslot.is_some(); let is_present = slot.quantity > 0 && slot.invslot.is_some();
Text::new(&format!("{} x {}", slot.quantity, itemname)) Text::new(&format!("{} x {}", slot.quantity, itemname))

View File

@ -5,7 +5,7 @@ use common::{
item::{ item::{
armor::{Armor, ArmorKind, Protection}, armor::{Armor, ArmorKind, Protection},
tool::{Hands, Tool, ToolKind}, tool::{Hands, Tool, ToolKind},
Effects, Item, ItemDefinitionId, ItemDesc, ItemKind, MaterialKind, Effects, Item, ItemDefinitionId, ItemDesc, ItemKind, ItemL10n, MaterialKind,
MaterialStatManifest, MaterialStatManifest,
}, },
BuffKind, BuffKind,
@ -15,7 +15,7 @@ use common::{
}; };
use conrod_core::image; use conrod_core::image;
use i18n::{fluent_args, Localization}; use i18n::{fluent_args, Localization};
use std::{borrow::Cow, fmt::Write}; use std::{borrow::Cow, fmt::Write, num::NonZeroU32};
pub fn price_desc<'a>( pub fn price_desc<'a>(
prices: &Option<SitePrices>, prices: &Option<SitePrices>,
@ -61,6 +61,31 @@ pub fn price_desc<'a>(
Some((buy_string, sell_string, deal_goodness)) Some((buy_string, sell_string, deal_goodness))
} }
pub fn item_text<'a, I: ItemDesc + ?Sized>(
item: &I,
i18n: &'a Localization,
l10n_spec: &'a ItemL10n,
) -> (String, String) {
let (title, desc) = item.l10n(l10n_spec);
(i18n.get_content(&title), i18n.get_content(&desc))
}
pub fn describe<'a, I: ItemDesc + ?Sized>(
item: &I,
i18n: &'a Localization,
l10n_spec: &'a ItemL10n,
) -> String {
let (title, _) = item_text(item, i18n, l10n_spec);
let amount = item.amount();
if amount > NonZeroU32::new(1).unwrap() {
format!("{amount} x {title}")
} else {
title
}
}
pub fn kind_text<'a>(kind: &ItemKind, i18n: &'a Localization) -> Cow<'a, str> { pub fn kind_text<'a>(kind: &ItemKind, i18n: &'a Localization) -> Cow<'a, str> {
match kind { match kind {
ItemKind::Armor(armor) => armor_kind(armor, i18n), ItemKind::Armor(armor) => armor_kind(armor, i18n),

View File

@ -10,7 +10,7 @@ use common::{
comp::{ comp::{
item::{ item::{
armor::Protection, item_key::ItemKey, modular::ModularComponent, Item, ItemDesc, armor::Protection, item_key::ItemKey, modular::ModularComponent, Item, ItemDesc,
ItemKind, ItemTag, MaterialStatManifest, Quality, ItemKind, ItemL10n, ItemTag, MaterialStatManifest, Quality,
}, },
Energy, Energy,
}, },
@ -296,6 +296,7 @@ pub struct ItemTooltip<'a> {
item_imgs: &'a ItemImgs, item_imgs: &'a ItemImgs,
pulse: f32, pulse: f32,
localized_strings: &'a Localization, localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
} }
#[derive(Clone, Debug, Default, PartialEq, WidgetStyle)] #[derive(Clone, Debug, Default, PartialEq, WidgetStyle)]
@ -360,6 +361,7 @@ impl<'a> ItemTooltip<'a> {
pulse: f32, pulse: f32,
msm: &'a MaterialStatManifest, msm: &'a MaterialStatManifest,
localized_strings: &'a Localization, localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
) -> Self { ) -> Self {
ItemTooltip { ItemTooltip {
common: widget::CommonBuilder::default(), common: widget::CommonBuilder::default(),
@ -377,6 +379,7 @@ impl<'a> ItemTooltip<'a> {
item_imgs, item_imgs,
pulse, pulse,
localized_strings, localized_strings,
item_l10n,
} }
} }
@ -450,6 +453,7 @@ impl<'a> Widget for ItemTooltip<'a> {
} = args; } = args;
let i18n = &self.localized_strings; let i18n = &self.localized_strings;
let item_l10n = &self.item_l10n;
let inventories = self.client.inventories(); let inventories = self.client.inventories();
let inventory = match inventories.get(self.info.viewpoint_entity) { let inventory = match inventories.get(self.info.viewpoint_entity) {
@ -465,7 +469,7 @@ impl<'a> Widget for ItemTooltip<'a> {
let equipped_item = inventory.equipped_items_replaceable_by(item_kind).next(); let equipped_item = inventory.equipped_items_replaceable_by(item_kind).next();
let (title, desc) = (item.name().to_string(), item.description().to_string()); let (title, desc) = util::item_text(item, i18n, item_l10n);
let item_kind = util::kind_text(item_kind, i18n).to_string(); let item_kind = util::kind_text(item_kind, i18n).to_string();
@ -1266,7 +1270,8 @@ impl<'a> Widget for ItemTooltip<'a> {
fn default_y_dimension(&self, ui: &Ui) -> Dimension { fn default_y_dimension(&self, ui: &Ui) -> Dimension {
let item = &self.item; let item = &self.item;
let desc = item.description().to_string(); // TODO: we do double work here, does it need optimization?
let (_, desc) = util::item_text(item, self.localized_strings, self.item_l10n);
let (text_w, _image_w) = self.text_image_width(260.0); let (text_w, _image_w) = self.text_image_width(260.0);