Keep item_name as its component: translate it when drawn

The decoded plain string replaced the component on save and on the way to
LAN guests, so a translatable name (explorer maps' filled_map.*, the
ominous banner in tests without a language) came back as its key, and
spill's ominous banner test failed. The component now stays in the patch
as loaded and is resolved in the current language when a name is drawn.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Ludovic MarieandClaude Opus 5.5 committed 2026-10-26 14:05:17 +01:00
1 parent ad7203fc8a
commit d983ef89b4
6 files changed
+25 -20

No files matched your search

+3 -2
View File
@@ -1657,7 +1657,7 @@ impl Game<'_> {
let name = if let Some(n) = stack.custom_name() {
n.to_owned()
} else if let Some(n) = stack.item_name() {
n.to_owned()
n
} else if stack.data().is_some_and(|d| d.ominous_banner) {
self.translate("block.minecraft.ominous_banner", "Ominous Banner")
} else if stack.data().and_then(|d| d.potion.as_ref()).is_some()
@@ -1684,8 +1684,9 @@ impl Game<'_> {
// ItemContainerContents.addToTooltip.
let hover_name = |s: &ItemStack| {
s.custom_name()
.map(str::to_owned)
.or_else(|| s.item_name())
.map_or_else(|| self.pack.names.display_item(&s.item), str::to_owned)
.unwrap_or_else(|| self.pack.names.display_item(&s.item))
};
if display.shows("minecraft:container") {
for text in
+7 -11
View File
@@ -18,8 +18,6 @@ pub struct StackData {
pub repair_cost: u32,
/// `minecraft:custom_name` as plain text.
pub custom_name: Option<String>,
/// `minecraft:item_name` as plain text.
pub item_name: Option<String>,
/// `minecraft:potion_contents.potion`.
pub potion: Option<Arc<str>>,
/// `minecraft:trim`: (material, pattern).
@@ -75,7 +73,6 @@ const MODELLED: &[&str] = &[
"minecraft:enchantments",
"minecraft:firework_explosion",
"minecraft:fireworks",
"minecraft:item_name",
"minecraft:map_id",
"minecraft:map_post_processing",
"minecraft:ominous_bottle_amplifier",
@@ -207,8 +204,13 @@ impl ItemStack {
pub fn custom_name(&self) -> Option<&str> {
self.data().and_then(|d| d.custom_name.as_deref())
}
pub fn item_name(&self) -> Option<&str> {
self.data().and_then(|d| d.item_name.as_deref())
/// `minecraft:item_name` in the current language (`getItemName`'s
/// component). It stays in the patch as loaded, so a translatable name
/// saves and reaches clients unchanged; the ominous banner's is modelled
/// by `ominous_banner` instead.
pub fn item_name(&self) -> Option<String> {
let name = self.data()?.other("minecraft:item_name")?;
Some(crate::entity_nbt::name_text(name).plain())
}
pub fn potion(&self) -> Option<&str> {
self.data().and_then(|d| d.potion.as_deref())
@@ -479,11 +481,6 @@ pub fn components_tag(damage: u32, data: Option<&StackData>) -> Option<Tag> {
if let Some(name) = &d.custom_name {
entries.push(("minecraft:custom_name", Tag::String(name.as_str().into())));
}
if let Some(name) = &d.item_name
&& !d.ominous_banner
{
entries.push(("minecraft:item_name", Tag::String(name.as_str().into())));
}
if let Some(potion) = &d.potion {
entries.push((
"minecraft:potion_contents",
@@ -686,7 +683,6 @@ pub fn decode_components(components: Option<&Tag>) -> (u32, Option<Box<StackData
.unwrap_or(0)
.max(0) as u32,
custom_name: c.get("minecraft:custom_name").and_then(read_text),
item_name: c.get("minecraft:item_name").and_then(read_text),
potion,
map_id: c.get("minecraft:map_id").and_then(int).map(|v| v as i32),
banner_patterns: crate::banner::layers_from_nbt(c.get("minecraft:banner_patterns")),
+5 -2
View File
@@ -306,7 +306,10 @@ mod tests {
.push((Arc::from("minecraft:sharpness"), 2));
d.repair_cost = 9;
d.custom_name = Some("Keeper".to_owned());
d.item_name = Some("Fine Diamond Chestplate".to_owned());
// A translatable item name reaches the client as the same component.
let item_name = Tag::compound([("translate", Tag::String("filled_map.monument".into()))]);
d.other
.push(("minecraft:item_name".into(), item_name.clone()));
d.trim = Some((Arc::from("minecraft:gold"), Arc::from("minecraft:sentry")));
let id = |registry: &str, name: &str| {
builtin
@@ -336,7 +339,7 @@ mod tests {
);
assert_eq!(d.repair_cost, 9);
assert_eq!(d.custom_name.as_deref(), Some("Keeper"));
assert_eq!(d.item_name.as_deref(), Some("Fine Diamond Chestplate"));
assert_eq!(d.other("minecraft:item_name"), Some(&item_name));
assert_eq!(
d.trim,
Some((Arc::from("minecraft:gold"), Arc::from("minecraft:sentry")))
+5 -2
View File
@@ -190,7 +190,7 @@ pub fn name_line(stack: &ItemStack, translate: &dyn Fn(&str) -> Option<String>)
return name.to_owned();
}
if let Some(name) = stack.item_name() {
return name.to_owned();
return name;
}
if stack.data().is_some_and(|d| d.ominous_banner) {
return translate("block.minecraft.ominous_banner")
@@ -616,7 +616,10 @@ mod tests {
banner.data_mut().custom_name = Some("Captain's Standard".into());
assert_eq!(name_line(&banner, &translate), "Captain's Standard");
let mut custom_item = ItemStack::new("minecraft:diamond_sword", 1);
custom_item.data_mut().item_name = Some("Excalibur".into());
custom_item.data_mut().other.push((
"minecraft:item_name".into(),
Tag::String("Excalibur".into()),
));
assert_eq!(name_line(&custom_item, &translate), "Excalibur");
custom_item.data_mut().custom_name = Some("Custom Blade".into());
assert_eq!(name_line(&custom_item, &translate), "Custom Blade");
+3 -2
View File
@@ -633,10 +633,11 @@ fn network_stack(
horizon_protocol::item::Component::Text(horizon_protocol::text::literal(name)),
));
}
if let Some(name) = s.item_name() {
// The component as loaded, so a translatable name stays one.
if let Some(name) = s.data().and_then(|d| d.other("minecraft:item_name")) {
stack.added.push((
horizon_protocol::item::component::ITEM_NAME,
horizon_protocol::item::Component::Text(horizon_protocol::text::literal(name)),
horizon_protocol::item::Component::Text(name.clone()),
));
}
Some(stack)
+2 -1
View File
@@ -174,8 +174,9 @@ pub fn increased_repair_cost(cost: u32) -> u32 {
pub fn hover_name(names: &dyn Fn(&str) -> String, stack: &ItemStack) -> String {
stack
.custom_name()
.map(str::to_owned)
.or_else(|| stack.item_name())
.map_or_else(|| names(&stack.item), str::to_owned)
.unwrap_or_else(|| names(&stack.item))
}
/// `AnvilMenu.createResult`. `name` is the rename box text (None = untouched).
pub fn anvil_result(