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,
}
impl<T: ItemDesc> From<&T> for ItemKey {
impl<T: ItemDesc + ?Sized> From<&T> for ItemKey {
fn from(item_desc: &T) -> Self {
let item_definition_id = item_desc.item_definition_id();

View File

@ -14,13 +14,15 @@ use crate::{
recipe::RecipeInput,
terrain::Block,
};
use common_i18n::Content;
use core::{
convert::TryFrom,
mem,
num::{NonZeroU32, NonZeroU64},
};
use crossbeam_utils::atomic::AtomicCell;
use hashbrown::Equivalent;
use hashbrown::{Equivalent, HashMap};
use item_key::ItemKey;
use serde::{de, Deserialize, Serialize, Serializer};
use specs::{Component, DenseVecStorage, DerefFlaggedStorage};
use std::{borrow::Cow, collections::hash_map::DefaultHasher, fmt, sync::Arc};
@ -342,6 +344,7 @@ pub enum ItemKind {
},
Ingredient {
/// Used to generate names for modular items composed of this ingredient
#[deprecated]
descriptor: String,
},
TagExamples {
@ -383,6 +386,7 @@ impl ItemKind {
},
ItemKind::Throwable { kind } => format!("Throwable: {:?}", kind),
ItemKind::Utility { kind } => format!("Utility: {:?}", kind),
#[allow(deprecated)]
ItemKind::Ingredient { descriptor } => format!("Ingredient: {}", descriptor),
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)]
pub enum ItemName {
Direct(String),
Modular,
Component(String),
// TODO: probably make a Resource if used outside of voxygen
// TODO: add hot-reloading similar to how ItemImgs does it?
// TODO: make it work with plugins (via Concatenate?)
/// 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)]
@ -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]
pub fn name(&self) -> Cow<str> {
match &self.item_base {
@ -1400,6 +1411,7 @@ pub fn flatten_counted_items<'a>(
pub trait ItemDesc {
#[deprecated]
fn description(&self) -> &str;
#[deprecated]
fn name(&self) -> Cow<str>;
fn kind(&self) -> Cow<ItemKind>;
fn amount(&self) -> NonZeroU32;
@ -1420,6 +1432,18 @@ pub trait ItemDesc {
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 {

View File

@ -3,7 +3,7 @@ use super::{
img_ids::{Imgs, ImgsRot},
item_imgs::ItemImgs,
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::{
game_input::GameInput,
@ -21,7 +21,7 @@ use common::{
combat::{combat_rating, perception_dist_multiplier_from_stealth, Damage},
comp::{
inventory::InventorySortOrder,
item::{ItemDef, ItemDesc, MaterialStatManifest, Quality},
item::{ItemDef, ItemDesc, ItemL10n, MaterialStatManifest, Quality},
Body, Energy, Health, Inventory, Poise, SkillSet, Stats,
},
};
@ -81,6 +81,7 @@ pub struct InventoryScroller<'a> {
slot_manager: &'a mut SlotManager,
pulse: f32,
localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
show_stats: bool,
show_bag_inv: bool,
on_right: bool,
@ -105,6 +106,7 @@ impl<'a> InventoryScroller<'a> {
slot_manager: &'a mut SlotManager,
pulse: f32,
localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
show_stats: bool,
show_bag_inv: bool,
on_right: bool,
@ -127,6 +129,7 @@ impl<'a> InventoryScroller<'a> {
slot_manager,
pulse,
localized_strings,
item_l10n,
show_stats,
show_bag_inv,
on_right,
@ -365,8 +368,18 @@ impl<'a> InventoryScroller<'a> {
items.sort_by_cached_key(|(_, item)| {
(
item.is_none(),
item.as_ref()
.map(|i| (std::cmp::Reverse(i.quality()), i.name(), i.amount())),
item.as_ref().map(|i| {
(
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);
}
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(
state.ids.inv_alignment,
0.0 + y as f64 * slot_size,
@ -638,6 +652,7 @@ pub struct Bag<'a> {
slot_manager: &'a mut SlotManager,
pulse: f32,
localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
stats: &'a Stats,
skill_set: &'a SkillSet,
health: &'a Health,
@ -663,6 +678,7 @@ impl<'a> Bag<'a> {
slot_manager: &'a mut SlotManager,
pulse: f32,
localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
stats: &'a Stats,
skill_set: &'a SkillSet,
health: &'a Health,
@ -686,6 +702,7 @@ impl<'a> Bag<'a> {
slot_manager,
pulse,
localized_strings,
item_l10n,
stats,
skill_set,
energy,
@ -805,6 +822,7 @@ impl<'a> Widget for Bag<'a> {
self.pulse,
self.msm,
self.localized_strings,
self.item_l10n,
)
.title_font_size(self.fonts.cyri.scale(20))
.parent(ui.window)
@ -821,6 +839,7 @@ impl<'a> Widget for Bag<'a> {
self.slot_manager,
self.pulse,
self.localized_strings,
self.item_l10n,
self.show.stats,
self.show.bag_inv,
true,

View File

@ -3,7 +3,7 @@ use super::{
img_ids::{Imgs, ImgsRot},
item_imgs::{animate_by_pulse, ItemImgs},
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::{
fonts::Fonts,
@ -19,8 +19,8 @@ use common::{
item_key::ItemKey,
modular::{self, ModularComponent},
tool::{AbilityMap, ToolKind},
Item, ItemBase, ItemDef, ItemDesc, ItemKind, ItemTag, MaterialStatManifest, Quality,
TagExampleInfo,
Item, ItemBase, ItemDef, ItemDesc, ItemKind, ItemL10n, ItemTag, MaterialStatManifest,
Quality, TagExampleInfo,
},
slot::{InvSlotId, Slot},
Inventory,
@ -151,6 +151,7 @@ pub struct Crafting<'a> {
imgs: &'a Imgs,
fonts: &'a Fonts,
localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
pulse: f32,
rot_imgs: &'a ImgsRot,
item_tooltip_manager: &'a mut ItemTooltipManager,
@ -171,6 +172,7 @@ impl<'a> Crafting<'a> {
imgs: &'a Imgs,
fonts: &'a Fonts,
localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
pulse: f32,
rot_imgs: &'a ImgsRot,
item_tooltip_manager: &'a mut ItemTooltipManager,
@ -187,6 +189,7 @@ impl<'a> Crafting<'a> {
imgs,
fonts,
localized_strings,
item_l10n,
pulse,
rot_imgs,
item_tooltip_manager,
@ -355,6 +358,7 @@ impl<'a> Widget for Crafting<'a> {
self.pulse,
self.msm,
self.localized_strings,
self.item_l10n,
)
.title_font_size(self.fonts.cyri.scale(20))
.parent(ui.window)
@ -594,6 +598,7 @@ impl<'a> Widget for Crafting<'a> {
.iter()
.filter(|(_, recipe)| match search_filter {
SearchFilter::None => {
#[allow(deprecated)]
let output_name = recipe.output.0.name().to_lowercase();
search_keys
.iter()
@ -608,9 +613,11 @@ impl<'a> Widget for Crafting<'a> {
};
match input {
#[allow(deprecated)]
RecipeInput::Item(def) => search(&def.name()),
RecipeInput::Tag(tag) => search(tag.name()),
RecipeInput::TagSameItem(tag) => search(tag.name()),
#[allow(deprecated)]
RecipeInput::ListSameItem(defs) => {
defs.iter().any(|def| search(&def.name()))
},
@ -667,6 +674,7 @@ impl<'a> Widget for Crafting<'a> {
!is_craftable,
!has_materials,
recipe.output.0.quality(),
#[allow(deprecated)]
recipe.output.0.name(),
)
});
@ -1214,6 +1222,8 @@ impl<'a> Widget for Crafting<'a> {
};
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(
&self
.item_imgs
@ -1221,7 +1231,7 @@ impl<'a> Widget for Crafting<'a> {
self.pulse,
))
.w_h(55.0, 55.0)
.label(&output_item.name())
.label(&name)
.label_color(TEXT_COLOR)
.label_font_size(self.fonts.cyri.scale(14))
.label_font_id(self.fonts.cyri.conrod_id)
@ -1926,6 +1936,7 @@ impl<'a> Widget for Crafting<'a> {
.was_clicked()
{
events.push(Event::ChangeCraftingTab(CraftingTab::All));
#[allow(deprecated)]
events.push(Event::SearchRecipe(Some(item_def.name().to_string())));
}
// Item image
@ -1963,7 +1974,13 @@ impl<'a> Widget for Crafting<'a> {
.font_size(self.fonts.cyri.scale(14))
.color(TEXT_COLOR)
.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)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14))
@ -1972,14 +1989,35 @@ impl<'a> Widget for Crafting<'a> {
} else {
// Ingredients
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) => {
// TODO: Localize!
format!("Any {} item", tag.name())
},
RecipeInput::ListSameItem(item_defs) => {
// TODO: Localize!
format!(
"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,
img_ids::{Imgs, ImgsRot},
item_imgs::ItemImgs,
HudInfo, Show, Windows, TEXT_COLOR,
util, HudInfo, Show, Windows, TEXT_COLOR,
};
use crate::ui::{fonts::Fonts, ImageFrame, ItemTooltip, ItemTooltipManager, ItemTooltipable};
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::{
color,
position::Dimension,
@ -58,6 +58,7 @@ pub struct LootScroller<'a> {
rot_imgs: &'a ImgsRot,
fonts: &'a Fonts,
localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
msm: &'a MaterialStatManifest,
item_tooltip_manager: &'a mut ItemTooltipManager,
pulse: f32,
@ -76,6 +77,7 @@ impl<'a> LootScroller<'a> {
rot_imgs: &'a ImgsRot,
fonts: &'a Fonts,
localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
msm: &'a MaterialStatManifest,
item_tooltip_manager: &'a mut ItemTooltipManager,
pulse: f32,
@ -90,6 +92,7 @@ impl<'a> LootScroller<'a> {
rot_imgs,
fonts,
localized_strings,
item_l10n,
msm,
item_tooltip_manager,
pulse,
@ -153,6 +156,7 @@ impl<'a> Widget for LootScroller<'a> {
self.pulse,
self.msm,
self.localized_strings,
self.item_l10n,
)
.title_font_size(self.fonts.cyri.scale(20))
.parent(ui.window)
@ -351,7 +355,11 @@ impl<'a> Widget for LootScroller<'a> {
&i18n::fluent_args! {
"actor" => taken_by,
"amount" => amount,
"item" => item.name(),
"item" => {
let (name, _) =
util::item_text(&item, self.localized_strings, self.item_l10n);
name
},
},
);
let label_font_size = 20;

View File

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

View File

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

View File

@ -10,7 +10,7 @@ use vek::*;
use client::Client;
use common::{
comp::{
inventory::item::{ItemDesc, MaterialStatManifest, Quality},
inventory::item::{ItemDesc, ItemL10n, MaterialStatManifest, Quality},
Inventory, Stats,
},
trade::{PendingTrade, SitePrices, TradeAction, TradePhase},
@ -35,7 +35,8 @@ use super::{
img_ids::{Imgs, ImgsRot},
item_imgs::ItemImgs,
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;
@ -98,6 +99,7 @@ pub struct Trade<'a> {
common: widget::CommonBuilder,
slot_manager: &'a mut SlotManager,
localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
msm: &'a MaterialStatManifest,
pulse: f32,
show: &'a mut Show,
@ -116,6 +118,7 @@ impl<'a> Trade<'a> {
item_tooltip_manager: &'a mut ItemTooltipManager,
slot_manager: &'a mut SlotManager,
localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
msm: &'a MaterialStatManifest,
pulse: f32,
show: &'a mut Show,
@ -132,6 +135,7 @@ impl<'a> Trade<'a> {
common: widget::CommonBuilder::default(),
slot_manager,
localized_strings,
item_l10n,
msm,
pulse,
show,
@ -345,6 +349,7 @@ impl<'a> Trade<'a> {
self.pulse,
self.msm,
self.localized_strings,
self.item_l10n,
)
.title_font_size(self.fonts.cyri.scale(20))
.parent(ui.window)
@ -362,6 +367,7 @@ impl<'a> Trade<'a> {
self.slot_manager,
self.pulse,
self.localized_strings,
self.item_l10n,
false,
true,
false,
@ -530,7 +536,11 @@ impl<'a> Trade<'a> {
let itemname = slot
.invslot
.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(""));
let is_present = slot.quantity > 0 && slot.invslot.is_some();
Text::new(&format!("{} x {}", slot.quantity, itemname))

View File

@ -5,7 +5,7 @@ use common::{
item::{
armor::{Armor, ArmorKind, Protection},
tool::{Hands, Tool, ToolKind},
Effects, Item, ItemDefinitionId, ItemDesc, ItemKind, MaterialKind,
Effects, Item, ItemDefinitionId, ItemDesc, ItemKind, ItemL10n, MaterialKind,
MaterialStatManifest,
},
BuffKind,
@ -15,7 +15,7 @@ use common::{
};
use conrod_core::image;
use i18n::{fluent_args, Localization};
use std::{borrow::Cow, fmt::Write};
use std::{borrow::Cow, fmt::Write, num::NonZeroU32};
pub fn price_desc<'a>(
prices: &Option<SitePrices>,
@ -61,6 +61,31 @@ pub fn price_desc<'a>(
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> {
match kind {
ItemKind::Armor(armor) => armor_kind(armor, i18n),

View File

@ -10,7 +10,7 @@ use common::{
comp::{
item::{
armor::Protection, item_key::ItemKey, modular::ModularComponent, Item, ItemDesc,
ItemKind, ItemTag, MaterialStatManifest, Quality,
ItemKind, ItemL10n, ItemTag, MaterialStatManifest, Quality,
},
Energy,
},
@ -296,6 +296,7 @@ pub struct ItemTooltip<'a> {
item_imgs: &'a ItemImgs,
pulse: f32,
localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
}
#[derive(Clone, Debug, Default, PartialEq, WidgetStyle)]
@ -360,6 +361,7 @@ impl<'a> ItemTooltip<'a> {
pulse: f32,
msm: &'a MaterialStatManifest,
localized_strings: &'a Localization,
item_l10n: &'a ItemL10n,
) -> Self {
ItemTooltip {
common: widget::CommonBuilder::default(),
@ -377,6 +379,7 @@ impl<'a> ItemTooltip<'a> {
item_imgs,
pulse,
localized_strings,
item_l10n,
}
}
@ -450,6 +453,7 @@ impl<'a> Widget for ItemTooltip<'a> {
} = args;
let i18n = &self.localized_strings;
let item_l10n = &self.item_l10n;
let inventories = self.client.inventories();
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 (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();
@ -1266,7 +1270,8 @@ impl<'a> Widget for ItemTooltip<'a> {
fn default_y_dimension(&self, ui: &Ui) -> Dimension {
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);