mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Implement item localization
- Add Content::Key as proxy to Language::try_msg - Add Content::Attr as proxy to Language::try_attr - Extend ItemKey::TagExamples so it includes base asset id - Implement ItemDesc::l10n using new Content variants - Add all_items_expect() function to grab all items, because try_all_item_defs() covers only items in asset folder. Required assets will go in next commit
This commit is contained in:
parent
18e507315f
commit
aba8ec7558
@ -9,8 +9,11 @@ common-abilities-staff-fireshockwave = Ring of Fire
|
|||||||
common-abilities-sceptre-wardingaura = Warding Aura
|
common-abilities-sceptre-wardingaura = Warding Aura
|
||||||
.desc = Wards your allies against enemy attacks.
|
.desc = Wards your allies against enemy attacks.
|
||||||
|
|
||||||
# internally translations, currently only used in zh-Hans
|
# internal terms, currently only used in zh-Hans
|
||||||
# If we remove them here, they also get auto-removed in zh-Hans, so please keep them, even when not used in en
|
# If we remove them here, they also get auto-removed in zh-Hans,
|
||||||
|
# so please keep them, even when not used in English file.
|
||||||
|
# See https://github.com/WeblateOrg/weblate/issues/9895
|
||||||
|
|
||||||
-heavy_stance = ""
|
-heavy_stance = ""
|
||||||
-agile_stance = ""
|
-agile_stance = ""
|
||||||
-defensive_stance = ""
|
-defensive_stance = ""
|
||||||
|
@ -378,6 +378,8 @@ impl LocalizationGuard {
|
|||||||
/// 3) Otherwise, return result from (1).
|
/// 3) Otherwise, return result from (1).
|
||||||
// NOTE: it's important that we only use one language at the time, because
|
// NOTE: it's important that we only use one language at the time, because
|
||||||
// otherwise we will get partially-translated message.
|
// otherwise we will get partially-translated message.
|
||||||
|
//
|
||||||
|
// TODO: return Cow<str>?
|
||||||
pub fn get_content(&self, content: &Content) -> String {
|
pub fn get_content(&self, content: &Content) -> String {
|
||||||
// Function to localize content for given language.
|
// Function to localize content for given language.
|
||||||
//
|
//
|
||||||
@ -389,6 +391,16 @@ impl LocalizationGuard {
|
|||||||
fn get_content_for_lang(lang: &Language, content: &Content) -> Result<String, String> {
|
fn get_content_for_lang(lang: &Language, content: &Content) -> Result<String, String> {
|
||||||
match content {
|
match content {
|
||||||
Content::Plain(text) => Ok(text.clone()),
|
Content::Plain(text) => Ok(text.clone()),
|
||||||
|
Content::Key(key) => {
|
||||||
|
lang.try_msg(key, None)
|
||||||
|
.map(Cow::into_owned)
|
||||||
|
.ok_or_else(|| format!("{key}"))
|
||||||
|
},
|
||||||
|
Content::Attr(key, attr) => {
|
||||||
|
lang.try_attr(key, attr, None)
|
||||||
|
.map(Cow::into_owned)
|
||||||
|
.ok_or_else(|| format!("{key}.{attr}"))
|
||||||
|
},
|
||||||
Content::Localized { key, seed, args } => {
|
Content::Localized { key, seed, args } => {
|
||||||
// flag to detect failure down the chain
|
// flag to detect failure down the chain
|
||||||
let mut is_arg_failure = false;
|
let mut is_arg_failure = false;
|
||||||
|
@ -1,25 +1,27 @@
|
|||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
// TODO: expose convinience macros ala 'fluent_args!'?
|
||||||
|
|
||||||
/// The type to represent generic localization request, to be sent from server
|
/// The type to represent generic localization request, to be sent from server
|
||||||
/// to client and then localized (or internationalized) there.
|
/// to client and then localized (or internationalized) there.
|
||||||
// TODO: This could be generalised to *any* in-game text, not just chat messages (hence it not being
|
// TODO: Ideally we would need to fully cover API of our `i18n::Language`, including
|
||||||
// called `ChatContent`). A few examples:
|
// Fluent values.
|
||||||
//
|
|
||||||
// - Signposts, both those appearing as overhead messages and those displayed 'in-world' on a shop
|
|
||||||
// sign
|
|
||||||
// - UI elements
|
|
||||||
// - In-game notes/books (we could add a variant that allows structuring complex, novel textual
|
|
||||||
// information as a syntax tree or some other intermediate format that can be localised by the
|
|
||||||
// client)
|
|
||||||
// TODO: We probably want to have this type be able to represent similar things to
|
|
||||||
// `fluent::FluentValue`, such as numeric values, so that they can be properly localised in whatever
|
|
||||||
// manner is required.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub enum Content {
|
pub enum Content {
|
||||||
|
/// Plain(text)
|
||||||
|
///
|
||||||
/// The content is a plaintext string that should be shown to the user
|
/// The content is a plaintext string that should be shown to the user
|
||||||
/// verbatim.
|
/// verbatim.
|
||||||
Plain(String),
|
Plain(String),
|
||||||
|
/// Key(i18n_key)
|
||||||
|
///
|
||||||
|
/// The content is defined just by the key
|
||||||
|
Key(String),
|
||||||
|
/// Attr(i18n_key, attr)
|
||||||
|
///
|
||||||
|
/// The content is the attribute of the key
|
||||||
|
Attr(String, String),
|
||||||
/// The content is a localizable message with the given arguments.
|
/// The content is a localizable message with the given arguments.
|
||||||
// TODO: reduce usages of random i18n as much as possible
|
// TODO: reduce usages of random i18n as much as possible
|
||||||
//
|
//
|
||||||
@ -49,6 +51,8 @@ impl<'a> From<&'a str> for Content {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A localisation argument for localised content (see [`Content::Localized`]).
|
/// A localisation argument for localised content (see [`Content::Localized`]).
|
||||||
|
// TODO: Do we want it to be Enum or just wrapper around Content, to add
|
||||||
|
// additional `impl From<T>` for our arguments?
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub enum LocalizationArg {
|
pub enum LocalizationArg {
|
||||||
/// The localisation argument is itself a section of content.
|
/// The localisation argument is itself a section of content.
|
||||||
@ -74,18 +78,20 @@ impl From<Content> for LocalizationArg {
|
|||||||
|
|
||||||
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
|
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
|
||||||
// discourage it)
|
// discourage it)
|
||||||
|
//
|
||||||
|
// Or not?
|
||||||
impl From<String> for LocalizationArg {
|
impl From<String> for LocalizationArg {
|
||||||
fn from(text: String) -> Self { Self::Content(Content::Plain(text)) }
|
fn from(text: String) -> Self { Self::Content(Content::Plain(text)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
|
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
|
||||||
// discourage it)
|
// discourage it)
|
||||||
|
//
|
||||||
|
// Or not?
|
||||||
impl<'a> From<&'a str> for LocalizationArg {
|
impl<'a> From<&'a str> for LocalizationArg {
|
||||||
fn from(text: &'a str) -> Self { Self::Content(Content::Plain(text.to_string())) }
|
fn from(text: &'a str) -> Self { Self::Content(Content::Plain(text.to_string())) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
|
|
||||||
// discourage it)
|
|
||||||
impl From<u64> for LocalizationArg {
|
impl From<u64> for LocalizationArg {
|
||||||
fn from(n: u64) -> Self { Self::Nat(n) }
|
fn from(n: u64) -> Self { Self::Nat(n) }
|
||||||
}
|
}
|
||||||
@ -118,7 +124,7 @@ impl Content {
|
|||||||
pub fn as_plain(&self) -> Option<&str> {
|
pub fn as_plain(&self) -> Option<&str> {
|
||||||
match self {
|
match self {
|
||||||
Self::Plain(text) => Some(text.as_str()),
|
Self::Plain(text) => Some(text.as_str()),
|
||||||
Self::Localized { .. } => None,
|
Self::Localized { .. } | Self::Attr { .. } | Self::Key { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,7 +219,9 @@ lazy_static! {
|
|||||||
|
|
||||||
static ref ROLES: Vec<String> = ["admin", "moderator"].iter().copied().map(Into::into).collect();
|
static ref ROLES: Vec<String> = ["admin", "moderator"].iter().copied().map(Into::into).collect();
|
||||||
|
|
||||||
/// List of item specifiers. Useful for tab completing
|
/// List of item's asset specifiers. Useful for tab completing.
|
||||||
|
/// Doesn't cover all items (like modulars), includes "fake" items like
|
||||||
|
/// TagExamples.
|
||||||
pub static ref ITEM_SPECS: Vec<String> = {
|
pub static ref ITEM_SPECS: Vec<String> = {
|
||||||
let mut items = try_all_item_defs()
|
let mut items = try_all_item_defs()
|
||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(|e| {
|
||||||
|
@ -10,7 +10,7 @@ pub enum ItemKey {
|
|||||||
Simple(String),
|
Simple(String),
|
||||||
ModularWeapon(modular::ModularWeaponKey),
|
ModularWeapon(modular::ModularWeaponKey),
|
||||||
ModularWeaponComponent(modular::ModularWeaponComponentKey),
|
ModularWeaponComponent(modular::ModularWeaponComponentKey),
|
||||||
TagExamples(Vec<ItemKey>),
|
TagExamples(Vec<ItemKey>, String),
|
||||||
Empty,
|
Empty,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,6 +24,10 @@ impl<T: ItemDesc + ?Sized> From<&T> for ItemKey {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|id| ItemKey::from(&*Arc::<ItemDef>::load_expect_cloned(id)))
|
.map(|id| ItemKey::from(&*Arc::<ItemDef>::load_expect_cloned(id)))
|
||||||
.collect(),
|
.collect(),
|
||||||
|
item_definition_id
|
||||||
|
.itemdef_id()
|
||||||
|
.unwrap_or("?modular?")
|
||||||
|
.to_owned(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
match item_definition_id {
|
match item_definition_id {
|
||||||
|
@ -26,7 +26,7 @@ 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};
|
||||||
use strum::{EnumString, IntoStaticStr};
|
use strum::{EnumIter, EnumString, IntoEnumIterator, IntoStaticStr};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
use vek::Rgb;
|
use vek::Rgb;
|
||||||
|
|
||||||
@ -105,7 +105,17 @@ pub enum MaterialKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, IntoStaticStr, EnumString,
|
Clone,
|
||||||
|
Copy,
|
||||||
|
Debug,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Hash,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
IntoStaticStr,
|
||||||
|
EnumString,
|
||||||
|
EnumIter,
|
||||||
)]
|
)]
|
||||||
#[strum(serialize_all = "snake_case")]
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum Material {
|
pub enum Material {
|
||||||
@ -479,6 +489,17 @@ type I18nId = String;
|
|||||||
// TODO: add hot-reloading similar to how ItemImgs does it?
|
// TODO: add hot-reloading similar to how ItemImgs does it?
|
||||||
// TODO: make it work with plugins (via Concatenate?)
|
// TODO: make it work with plugins (via Concatenate?)
|
||||||
/// To be used with ItemDesc::l10n
|
/// To be used with ItemDesc::l10n
|
||||||
|
///
|
||||||
|
/// NOTE: there is a limitation to this manifest, as it uses ItemKey and
|
||||||
|
/// ItemKey isn't uniquely identifies Item, when it comes to modular items.
|
||||||
|
///
|
||||||
|
/// If modular weapon has the same primary component and the same hand-ness,
|
||||||
|
/// we use the same model EVEN IF it has different secondary components, like
|
||||||
|
/// Staff with Heavy core or Light core.
|
||||||
|
///
|
||||||
|
/// Translations currently do the same, but *maybe* they shouldn't in which case
|
||||||
|
/// we should either extend ItemKey or use new identifier. We could use
|
||||||
|
/// ItemDefinitionId, but it's very generic and cumbersome.
|
||||||
pub struct ItemL10n {
|
pub struct ItemL10n {
|
||||||
/// maps ItemKey to i18n identifier
|
/// maps ItemKey to i18n identifier
|
||||||
map: HashMap<ItemKey, I18nId>,
|
map: HashMap<ItemKey, I18nId>,
|
||||||
@ -492,6 +513,24 @@ impl assets::Asset for ItemL10n {
|
|||||||
|
|
||||||
impl ItemL10n {
|
impl ItemL10n {
|
||||||
pub fn new_expect() -> Self { ItemL10n::load_expect("common.item_l10n").read().clone() }
|
pub fn new_expect() -> Self { ItemL10n::load_expect("common.item_l10n").read().clone() }
|
||||||
|
|
||||||
|
/// Returns (name, description) in Content form.
|
||||||
|
// TODO: after we remove legacy text from ItemDef, consider making this
|
||||||
|
// function non-fallible?
|
||||||
|
fn item_text_opt(&self, mut item_key: ItemKey) -> Option<(Content, Content)> {
|
||||||
|
// we don't put TagExamples into manifest
|
||||||
|
if let ItemKey::TagExamples(_, id) = item_key {
|
||||||
|
item_key = ItemKey::Simple(id.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = self.map.get(&item_key);
|
||||||
|
key.map(|key| {
|
||||||
|
(
|
||||||
|
Content::Key(key.to_owned()),
|
||||||
|
Content::Attr(key.to_owned(), "desc".to_owned()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@ -1437,12 +1476,12 @@ pub trait ItemDesc {
|
|||||||
fn l10n(&self, l10n: &ItemL10n) -> (Content, Content) {
|
fn l10n(&self, l10n: &ItemL10n) -> (Content, Content) {
|
||||||
let item_key: ItemKey = self.into();
|
let item_key: ItemKey = self.into();
|
||||||
|
|
||||||
let _key = l10n.map.get(&item_key);
|
l10n.item_text_opt(item_key).unwrap_or_else(|| {
|
||||||
(
|
(
|
||||||
// construct smth like Content::Attr
|
Content::Plain(self.name().to_string()),
|
||||||
todo!(),
|
Content::Plain(self.name().to_string()),
|
||||||
todo!(),
|
)
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1565,6 +1604,67 @@ pub fn try_all_item_defs() -> Result<Vec<String>, Error> {
|
|||||||
Ok(defs.ids().map(|id| id.to_string()).collect())
|
Ok(defs.ids().map(|id| id.to_string()).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Designed to return all possible items, including modulars.
|
||||||
|
/// And some impossible too, like ItemKind::TagExamples.
|
||||||
|
pub fn all_items_expect() -> Vec<Item> {
|
||||||
|
let defs = assets::load_dir::<RawItemDef>("common.items", true)
|
||||||
|
.expect("failed to load item asset directory");
|
||||||
|
|
||||||
|
// Grab all items from assets
|
||||||
|
let mut asset_items: Vec<Item> = defs
|
||||||
|
.ids()
|
||||||
|
.map(|id| Item::new_from_asset_expect(id))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut material_parse_table = HashMap::new();
|
||||||
|
for mat in Material::iter() {
|
||||||
|
if let Some(id) = mat.asset_identifier() {
|
||||||
|
material_parse_table.insert(id.to_owned(), mat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let primary_comp_pool = modular::PRIMARY_COMPONENT_POOL.clone();
|
||||||
|
|
||||||
|
// Grab weapon primary components
|
||||||
|
let mut primary_comps: Vec<Item> = primary_comp_pool
|
||||||
|
.values()
|
||||||
|
.flatten()
|
||||||
|
.map(|(item, _hand_rules)| item.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Grab modular weapons
|
||||||
|
let mut modular_items: Vec<Item> = primary_comp_pool
|
||||||
|
.keys()
|
||||||
|
.map(|(tool, mat_id)| {
|
||||||
|
let mat = material_parse_table
|
||||||
|
.get(mat_id)
|
||||||
|
.expect("unexpected material ident");
|
||||||
|
|
||||||
|
// get all weapons without imposing additional hand restrictions
|
||||||
|
let its = modular::generate_weapons(*tool, *mat, None)
|
||||||
|
.expect("failure during modular weapon generation");
|
||||||
|
|
||||||
|
its
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// 1. Append asset items, that should include pretty much everything,
|
||||||
|
// except modular items
|
||||||
|
// 2. Append primary weapon components, which are modular as well.
|
||||||
|
// 3. Finally append modular weapons that are made from (1) and (2)
|
||||||
|
// extend when we get some new exotic stuff
|
||||||
|
//
|
||||||
|
// P. s. I still can't wrap my head around the idea that you can put
|
||||||
|
// tag example into your inventory.
|
||||||
|
let mut all = Vec::new();
|
||||||
|
all.append(&mut asset_items);
|
||||||
|
all.append(&mut primary_comps);
|
||||||
|
all.append(&mut modular_items);
|
||||||
|
|
||||||
|
all
|
||||||
|
}
|
||||||
|
|
||||||
impl PartialEq<ItemDefinitionId<'_>> for ItemDefinitionIdOwned {
|
impl PartialEq<ItemDefinitionId<'_>> for ItemDefinitionIdOwned {
|
||||||
fn eq(&self, other: &ItemDefinitionId<'_>) -> bool {
|
fn eq(&self, other: &ItemDefinitionId<'_>) -> bool {
|
||||||
use ItemDefinitionId as DefId;
|
use ItemDefinitionId as DefId;
|
||||||
@ -1616,4 +1716,23 @@ mod tests {
|
|||||||
drop(item)
|
drop(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_item_l10n() { let _ = ItemL10n::new_expect(); }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Probably can't fail, but better safe than crashing production server
|
||||||
|
fn test_all_items() { let _ = all_items_expect(); }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// All items in Veloren should have localization.
|
||||||
|
// If no, add some common dummy i18n id.
|
||||||
|
fn ensure_item_localization() {
|
||||||
|
let manifest = ItemL10n::new_expect();
|
||||||
|
let items = all_items_expect();
|
||||||
|
for item in items {
|
||||||
|
let item_key: ItemKey = (&item).into();
|
||||||
|
let _ = manifest.item_text_opt(item_key).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -323,7 +323,7 @@ type PrimaryComponentPool = HashMap<(ToolKind, String), Vec<(Item, Option<Hands>
|
|||||||
type SecondaryComponentPool = HashMap<ToolKind, Vec<(Arc<ItemDef>, Option<Hands>)>>;
|
type SecondaryComponentPool = HashMap<ToolKind, Vec<(Arc<ItemDef>, Option<Hands>)>>;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref PRIMARY_COMPONENT_POOL: PrimaryComponentPool = {
|
pub static ref PRIMARY_COMPONENT_POOL: PrimaryComponentPool = {
|
||||||
let mut component_pool = HashMap::new();
|
let mut component_pool = HashMap::new();
|
||||||
|
|
||||||
// Load recipe book
|
// Load recipe book
|
||||||
@ -509,12 +509,13 @@ pub fn generate_weapons(
|
|||||||
ability_map,
|
ability_map,
|
||||||
msm,
|
msm,
|
||||||
);
|
);
|
||||||
weapons.push(Item::new_from_item_base(
|
let it = Item::new_from_item_base(
|
||||||
ItemBase::Modular(ModularBase::Tool),
|
ItemBase::Modular(ModularBase::Tool),
|
||||||
vec![comp.duplicate(ability_map, msm), secondary],
|
vec![comp.duplicate(ability_map, msm), secondary],
|
||||||
ability_map,
|
ability_map,
|
||||||
msm,
|
msm,
|
||||||
));
|
);
|
||||||
|
weapons.push(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -778,9 +778,11 @@ pub enum UnlockKind {
|
|||||||
Free,
|
Free,
|
||||||
/// The sprite requires that the opening character has a given item in their
|
/// The sprite requires that the opening character has a given item in their
|
||||||
/// inventory
|
/// inventory
|
||||||
|
// TODO: use ItemKey here?
|
||||||
Requires(ItemDefinitionIdOwned),
|
Requires(ItemDefinitionIdOwned),
|
||||||
/// The sprite will consume the given item from the opening character's
|
/// The sprite will consume the given item from the opening character's
|
||||||
/// inventory
|
/// inventory
|
||||||
|
// TODO: use ItemKey here?
|
||||||
Consumes(ItemDefinitionIdOwned),
|
Consumes(ItemDefinitionIdOwned),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ impl ItemImgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn img_ids(&self, item_key: ItemKey) -> Vec<Id> {
|
pub fn img_ids(&self, item_key: ItemKey) -> Vec<Id> {
|
||||||
if let ItemKey::TagExamples(keys) = item_key {
|
if let ItemKey::TagExamples(keys, _) = item_key {
|
||||||
return keys
|
return keys
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|k| self.map.get(k))
|
.filter_map(|k| self.map.get(k))
|
||||||
|
@ -2096,6 +2096,7 @@ impl Hud {
|
|||||||
},
|
},
|
||||||
BlockInteraction::Unlock(kind) => {
|
BlockInteraction::Unlock(kind) => {
|
||||||
let item_name = |item_id: &ItemDefinitionIdOwned| {
|
let item_name = |item_id: &ItemDefinitionIdOwned| {
|
||||||
|
// TODO: get ItemKey and use it with l10n?
|
||||||
item_id
|
item_id
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.itemdef_id()
|
.itemdef_id()
|
||||||
|
Loading…
Reference in New Issue
Block a user