From 0e81da9d7c1c4fe75ecad939ea3beb451357dcc4 Mon Sep 17 00:00:00 2001 From: Silent Date: Tue, 15 Feb 2022 18:55:13 +0100 Subject: [PATCH] localization --- pom.xml | 2 +- src/main/java/tsp/headdb/HeadDB.java | 47 ++++++++++++--- src/main/java/tsp/headdb/api/Head.java | 21 ++++++- src/main/java/tsp/headdb/api/LocalHead.java | 2 +- .../tsp/headdb/command/CommandHeadDB.java | 21 ++++--- .../java/tsp/headdb/database/Category.java | 2 +- .../tsp/headdb/inventory/InventoryUtils.java | 58 ++++++++++++------- .../java/tsp/headdb/inventory/PagedPane.java | 6 +- .../java/tsp/headdb/util/Localization.java | 58 +++++++++++++++++++ src/main/resources/messages.yml | 19 ++++++ 10 files changed, 190 insertions(+), 46 deletions(-) create mode 100644 src/main/java/tsp/headdb/util/Localization.java create mode 100644 src/main/resources/messages.yml diff --git a/pom.xml b/pom.xml index 9878c36..60939a7 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ tsp.headdb HeadDB - 3.1.1 + 3.2.0 jar HeadDB diff --git a/src/main/java/tsp/headdb/HeadDB.java b/src/main/java/tsp/headdb/HeadDB.java index 5b30b66..bf5378f 100644 --- a/src/main/java/tsp/headdb/HeadDB.java +++ b/src/main/java/tsp/headdb/HeadDB.java @@ -11,22 +11,28 @@ import tsp.headdb.listener.JoinListener; import tsp.headdb.listener.MenuListener; import tsp.headdb.listener.PagedPaneListener; import tsp.headdb.storage.PlayerDataFile; +import tsp.headdb.util.Localization; import tsp.headdb.util.Log; import tsp.headdb.util.Metrics; import javax.annotation.Nullable; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; public class HeadDB extends JavaPlugin { private static HeadDB instance; private Economy economy; private PlayerDataFile playerData; + private Localization localization; @Override public void onEnable() { instance = this; Log.info("Loading HeadDB - " + getDescription().getVersion()); saveDefaultConfig(); + createLocalizationFile(); this.playerData = new PlayerDataFile("player_data.json"); this.playerData.load(); @@ -62,13 +68,8 @@ public class HeadDB extends JavaPlugin { this.playerData.save(); } - private Economy setupEconomy() { - if (!this.getServer().getPluginManager().isPluginEnabled("Vault")) return null; - - RegisteredServiceProvider economyProvider = this.getServer().getServicesManager().getRegistration(Economy.class); - if (economyProvider == null) return null; - - return this.economy = economyProvider.getProvider(); + public Localization getLocalization() { + return localization; } @Nullable @@ -84,4 +85,36 @@ public class HeadDB extends JavaPlugin { return instance; } + private Economy setupEconomy() { + if (!this.getServer().getPluginManager().isPluginEnabled("Vault")) return null; + + RegisteredServiceProvider economyProvider = this.getServer().getServicesManager().getRegistration(Economy.class); + if (economyProvider == null) return null; + + return this.economy = economyProvider.getProvider(); + } + + private void createLocalizationFile() { + if (getClass().getResource("messages.yml") == null || new File(getDataFolder() + "/messages.yml").exists()) { + // File exists or not default available + return; + } + + try { + saveResource("messages.yml", false); + File messagesFile = new File(getDataFolder() + "/messages.yml"); + if (!messagesFile.exists()) { + messagesFile = new File(getClass().getResource("messages.yml").toURI()); + messagesFile.createNewFile(); + } + this.localization = new Localization(messagesFile); + this.localization.load(); + Log.debug("Localization loaded from jar file."); + } catch (URISyntaxException | IOException ex) { + Log.error("Failed to load localization!"); + Log.error(ex); + } + } + + } diff --git a/src/main/java/tsp/headdb/api/Head.java b/src/main/java/tsp/headdb/api/Head.java index db754f1..f07d0d9 100644 --- a/src/main/java/tsp/headdb/api/Head.java +++ b/src/main/java/tsp/headdb/api/Head.java @@ -5,7 +5,9 @@ import com.mojang.authlib.properties.Property; import org.apache.commons.lang.Validate; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.SkullMeta; +import tsp.headdb.HeadDB; import tsp.headdb.database.Category; import tsp.headdb.util.Log; import tsp.headdb.util.Utils; @@ -23,6 +25,7 @@ public class Head { private Category category; private int id; private List tags; + private ItemStack menuItem; private ItemStack itemStack; public Head() {} @@ -31,8 +34,8 @@ public class Head { this.id = id; } - public ItemStack getItemStack() { - if (itemStack == null) { + public ItemStack getMenuItem() { + if (menuItem == null) { Validate.notNull(name, "name must not be null!"); Validate.notNull(uuid, "uuid must not be null!"); Validate.notNull(value, "value must not be null!"); @@ -61,7 +64,19 @@ public class Head { )); item.setItemMeta(meta); - itemStack = item; + menuItem = item; + } + + return menuItem; + } + + public ItemStack getItemStack() { + if (itemStack == null) { + itemStack = menuItem; + ItemMeta meta = itemStack.getItemMeta(); + meta.setDisplayName(HeadDB.getInstance().getLocalization().getMessage("head.name")); + meta.setLore(HeadDB.getInstance().getLocalization().getData().getStringList("head.lore")); + itemStack.setItemMeta(meta); } return itemStack; diff --git a/src/main/java/tsp/headdb/api/LocalHead.java b/src/main/java/tsp/headdb/api/LocalHead.java index 0460c12..e684903 100644 --- a/src/main/java/tsp/headdb/api/LocalHead.java +++ b/src/main/java/tsp/headdb/api/LocalHead.java @@ -22,7 +22,7 @@ public class LocalHead extends Head { } @Override - public ItemStack getItemStack() { + public ItemStack getMenuItem() { Validate.notNull(uuid, "uuid must not be null!"); ItemStack item = new ItemStack(Material.PLAYER_HEAD); diff --git a/src/main/java/tsp/headdb/command/CommandHeadDB.java b/src/main/java/tsp/headdb/command/CommandHeadDB.java index 22d4f81..0456e87 100644 --- a/src/main/java/tsp/headdb/command/CommandHeadDB.java +++ b/src/main/java/tsp/headdb/command/CommandHeadDB.java @@ -9,6 +9,7 @@ import org.bukkit.inventory.ItemStack; import tsp.headdb.HeadDB; import tsp.headdb.api.Head; import tsp.headdb.api.HeadAPI; +import tsp.headdb.util.Localization; import tsp.headdb.util.Utils; import java.util.concurrent.TimeUnit; @@ -18,24 +19,26 @@ public class CommandHeadDB implements CommandExecutor { @Override public boolean onCommand(CommandSender sender, Command command, String s, String[] args) { + Localization localization = HeadDB.getInstance().getLocalization(); if (args.length == 0) { if (!sender.hasPermission("headdb.open")) { - Utils.sendMessage(sender, "&cNo permission!"); + Utils.sendMessage(sender, localization.getMessage("noPermission")); return true; } if (!(sender instanceof Player)) { - Utils.sendMessage(sender, "&cOnly players may open the database."); + Utils.sendMessage(sender, localization.getMessage("onlyPlayers")); return true; } Player player = (Player) sender; - Utils.sendMessage(player, "&7Opening &cHead Database"); + Utils.sendMessage(player, localization.getMessage("databaseOpen")); HeadAPI.openDatabase(player); return true; } String sub = args[0]; if (sub.equalsIgnoreCase("info") || sub.equalsIgnoreCase("i")) { + // These should not be customizable Utils.sendMessage(sender, "&7Running &cHeadDB - " + HeadDB.getInstance().getDescription().getVersion()); Utils.sendMessage(sender, "&7Created by &c" + HeadDB.getInstance().getDescription().getAuthors()); Utils.sendMessage(sender, "&7There are currently &c" + HeadAPI.getHeads().size() + " &7heads in the database."); @@ -44,7 +47,7 @@ public class CommandHeadDB implements CommandExecutor { if (sub.equalsIgnoreCase("search") || sub.equalsIgnoreCase("s")) { if (!sender.hasPermission("headdb.search")) { - Utils.sendMessage(sender, "&cNo permission!"); + Utils.sendMessage(sender, localization.getMessage("noPermission")); return true; } if (args.length < 2) { @@ -52,7 +55,7 @@ public class CommandHeadDB implements CommandExecutor { return true; } if (!(sender instanceof Player)) { - Utils.sendMessage(sender, "&cOnly players may open the database."); + Utils.sendMessage(sender, localization.getMessage("onlyPlayers")); return true; } Player player = (Player) sender; @@ -72,7 +75,7 @@ public class CommandHeadDB implements CommandExecutor { if (sub.equalsIgnoreCase("tagsearch") || sub.equalsIgnoreCase("ts")) { if (!sender.hasPermission("headdb.tagsearch")) { - Utils.sendMessage(sender, "&cNo permission!"); + Utils.sendMessage(sender, localization.getMessage("noPermission")); return true; } if (args.length < 2) { @@ -80,7 +83,7 @@ public class CommandHeadDB implements CommandExecutor { return true; } if (!(sender instanceof Player)) { - Utils.sendMessage(sender, "&cOnly players may open the database."); + Utils.sendMessage(sender, localization.getMessage("onlyPlayers")); return true; } Player player = (Player) sender; @@ -93,7 +96,7 @@ public class CommandHeadDB implements CommandExecutor { if (sub.equalsIgnoreCase("give") || sub.equalsIgnoreCase("g")) { if (!sender.hasPermission("headdb.give")) { - Utils.sendMessage(sender, "&cNo permission!"); + Utils.sendMessage(sender, localization.getMessage("noPermission")); return true; } if (args.length < 3) { @@ -104,7 +107,7 @@ public class CommandHeadDB implements CommandExecutor { int id = Integer.parseInt(args[1]); Player target = Bukkit.getPlayer(args[2]); if (target == null) { - Utils.sendMessage(sender, "&cPlayer is not online!"); + Utils.sendMessage(sender, localization.getMessage("invalidPlayer")); return true; } diff --git a/src/main/java/tsp/headdb/database/Category.java b/src/main/java/tsp/headdb/database/Category.java index 29114a3..0257c03 100644 --- a/src/main/java/tsp/headdb/database/Category.java +++ b/src/main/java/tsp/headdb/database/Category.java @@ -47,7 +47,7 @@ public enum Category { public ItemStack getItem() { if (item.containsKey(this)) { - return item.get(this).getItemStack(); + return item.get(this).getMenuItem(); } item.put(this, HeadAPI.getHeads(this).get(0)); diff --git a/src/main/java/tsp/headdb/inventory/InventoryUtils.java b/src/main/java/tsp/headdb/inventory/InventoryUtils.java index 8a706f3..a5428be 100644 --- a/src/main/java/tsp/headdb/inventory/InventoryUtils.java +++ b/src/main/java/tsp/headdb/inventory/InventoryUtils.java @@ -14,6 +14,7 @@ import tsp.headdb.api.HeadAPI; import tsp.headdb.api.LocalHead; import tsp.headdb.database.Category; import tsp.headdb.event.PlayerHeadPurchaseEvent; +import tsp.headdb.util.Localization; import tsp.headdb.util.Utils; import net.milkbowl.vault.economy.Economy; @@ -29,6 +30,7 @@ import java.util.Map; */ public class InventoryUtils { + private static final Localization localization = HeadDB.getInstance().getLocalization(); private static final Map uiLocation = new HashMap<>(); private static final Map uiItem = new HashMap<>(); @@ -56,7 +58,7 @@ public class InventoryUtils { int id = HeadDB.getInstance().getConfig().getInt("ui.category." + category + ".head"); Head head = HeadAPI.getHeadByID(id); if (head != null) { - uiItem.put(category, head.getItemStack()); + uiItem.put(category, head.getMenuItem()); return uiItem.get(category); } } @@ -79,11 +81,12 @@ public class InventoryUtils { } public static void openLocalMenu(Player player) { - PagedPane pane = new PagedPane(4, 6, Utils.colorize("&c&lHeadDB &8- &aLocal Heads")); - List heads = HeadAPI.getLocalHeads(); + + PagedPane pane = new PagedPane(4, 6, + replace(localization.getMessage("menu.local"), heads.size(), "Local", "None", player)); for (LocalHead localHead : heads) { - pane.addButton(new Button(localHead.getItemStack(), e -> { + pane.addButton(new Button(localHead.getMenuItem(), e -> { if (e.getClick() == ClickType.SHIFT_LEFT) { purchaseHead(player, localHead, 64, "local", localHead.getName()); return; @@ -93,7 +96,7 @@ public class InventoryUtils { return; } if (e.getClick() == ClickType.RIGHT) { - Utils.sendMessage(player, "&cLocal heads can not be added to favorites!"); + Utils.sendMessage(player, localization.getMessage("localFavorites")); } })); } @@ -102,11 +105,12 @@ public class InventoryUtils { } public static void openFavoritesMenu(Player player) { - PagedPane pane = new PagedPane(4, 6, Utils.colorize("&c&lHeadDB &8- &eFavorites: " + player.getName())); - List heads = HeadAPI.getFavoriteHeads(player.getUniqueId()); + + PagedPane pane = new PagedPane(4, 6, + replace(localization.getMessage("menu.favorites"), heads.size(), "Favorites", "None", player)); for (Head head : heads) { - pane.addButton(new Button(head.getItemStack(), e -> { + pane.addButton(new Button(head.getMenuItem(), e -> { if (e.getClick() == ClickType.SHIFT_LEFT) { purchaseHead(player, head, 64, head.getCategory().getName(), head.getName()); return; @@ -127,11 +131,12 @@ public class InventoryUtils { } public static PagedPane openSearchDatabase(Player player, String search) { - PagedPane pane = new PagedPane(4, 6, Utils.colorize("&c&lHeadDB &8- &eSearch: " + search)); - List heads = HeadAPI.getHeadsByName(search); + + PagedPane pane = new PagedPane(4, 6, + replace(localization.getMessage("menu.search"), heads.size(), "None", search, player)); for (Head head : heads) { - pane.addButton(new Button(head.getItemStack(), e -> { + pane.addButton(new Button(head.getMenuItem(), e -> { if (e.getClick() == ClickType.SHIFT_LEFT) { purchaseHead(player, head, 64, head.getCategory().getName(), head.getName()); return; @@ -141,7 +146,7 @@ public class InventoryUtils { } if (e.getClick() == ClickType.RIGHT) { if (!player.hasPermission("headdb.favorites")) { - Utils.sendMessage(player, "&cYou do not have permission for favorites!"); + Utils.sendMessage(player, localization.getMessage("noPermission")); Utils.playSound(player, "noPermission"); return; } @@ -158,11 +163,12 @@ public class InventoryUtils { } public static void openTagSearchDatabase(Player player, String tag) { - PagedPane pane = new PagedPane(4, 6, Utils.colorize("&c&lHeadDB &8- &eTag Search: " + tag)); - List heads = HeadAPI.getHeadsByTag(tag); + + PagedPane pane = new PagedPane(4, 6, + replace(localization.getMessage("menu.tagSearch"), heads.size(), "None", tag, player)); for (Head head : heads) { - pane.addButton(new Button(head.getItemStack(), e -> { + pane.addButton(new Button(head.getMenuItem(), e -> { if (e.getClick() == ClickType.SHIFT_LEFT) { purchaseHead(player, head, 64, head.getCategory().getName(), head.getName()); return; @@ -172,7 +178,7 @@ public class InventoryUtils { } if (e.getClick() == ClickType.RIGHT) { if (!player.hasPermission("headdb.favorites")) { - Utils.sendMessage(player, "&cYou do not have permission for favorites!"); + Utils.sendMessage(player, localization.getMessage("noPermission")); Utils.playSound(player, "noPermission"); return; } @@ -188,11 +194,12 @@ public class InventoryUtils { } public static void openCategoryDatabase(Player player, Category category) { - PagedPane pane = new PagedPane(4, 6, Utils.colorize("&c&lHeadDB &8- &e" + category.getName())); - List heads = HeadAPI.getHeads(category); + + PagedPane pane = new PagedPane(4, 6, + replace(localization.getMessage("menu.category"), heads.size(), category.getName(), "None", player)); for (Head head : heads) { - pane.addButton(new Button(head.getItemStack(), e -> { + pane.addButton(new Button(head.getMenuItem(), e -> { if (e.getClick() == ClickType.SHIFT_LEFT) { purchaseHead(player, head, 64, head.getCategory().getName(), head.getName()); return; @@ -202,7 +209,7 @@ public class InventoryUtils { } if (e.getClick() == ClickType.RIGHT) { if (!player.hasPermission("headdb.favorites")) { - Utils.sendMessage(player, "&cYou do not have permission for favorites!"); + Utils.sendMessage(player, localization.getMessage("noPermission")); Utils.playSound(player, "noPermission"); return; } @@ -218,7 +225,8 @@ public class InventoryUtils { } public static void openDatabase(Player player) { - Inventory inventory = Bukkit.createInventory(null, 54, Utils.colorize("&c&lHeadDB &8(" + HeadAPI.getHeads().size() + ")")); + Inventory inventory = Bukkit.createInventory(null, 54, + replace(localization.getMessage("menu.main"), HeadAPI.getHeads().size(), "Main", "None", player)); for (Category category : Category.cache) { ItemStack item = getUIItem(category.getName(), category.getItem()); @@ -345,4 +353,12 @@ public class InventoryUtils { } } + private static String replace(String message, int size, String category, String search, Player player) { + return message + .replace("%size%", String.valueOf(size)) + .replace("%category%", category) + .replace("%search%", search) + .replace("%player%", player.getName()); + } + } diff --git a/src/main/java/tsp/headdb/inventory/PagedPane.java b/src/main/java/tsp/headdb/inventory/PagedPane.java index c05ae81..fde3210 100644 --- a/src/main/java/tsp/headdb/inventory/PagedPane.java +++ b/src/main/java/tsp/headdb/inventory/PagedPane.java @@ -220,7 +220,7 @@ public class PagedPane implements InventoryHolder { "&7Previous: &c%d", getCurrentPage() - 1 ); - ItemStack itemStack = setMeta(HeadAPI.getHeadByValue("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODY1MmUyYjkzNmNhODAyNmJkMjg2NTFkN2M5ZjI4MTlkMmU5MjM2OTc3MzRkMThkZmRiMTM1NTBmOGZkYWQ1ZiJ9fX0=").getItemStack(), name, lore); + ItemStack itemStack = setMeta(HeadAPI.getHeadByValue("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODY1MmUyYjkzNmNhODAyNmJkMjg2NTFkN2M5ZjI4MTlkMmU5MjM2OTc3MzRkMThkZmRiMTM1NTBmOGZkYWQ1ZiJ9fX0=").getMenuItem(), name, lore); controlBack = new Button(itemStack, event -> selectPage(currentIndex - 1)); inventory.setItem(inventory.getSize() - 8, itemStack); } @@ -236,7 +236,7 @@ public class PagedPane implements InventoryHolder { "&7Next: &c%d", getCurrentPage() + 1 ); - ItemStack itemStack = setMeta(HeadAPI.getHeadByValue("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmEzYjhmNjgxZGFhZDhiZjQzNmNhZThkYTNmZTgxMzFmNjJhMTYyYWI4MWFmNjM5YzNlMDY0NGFhNmFiYWMyZiJ9fX0=").getItemStack(), name, lore); + ItemStack itemStack = setMeta(HeadAPI.getHeadByValue("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmEzYjhmNjgxZGFhZDhiZjQzNmNhZThkYTNmZTgxMzFmNjJhMTYyYWI4MWFmNjM5YzNlMDY0NGFhNmFiYWMyZiJ9fX0=").getMenuItem(), name, lore); controlNext = new Button(itemStack, event -> selectPage(getCurrentPage())); inventory.setItem(inventory.getSize() - 2, itemStack); } @@ -247,7 +247,7 @@ public class PagedPane implements InventoryHolder { "&3&lPage &a&l%d &7/ &c&l%d", getCurrentPage(), getPageAmount() ); - ItemStack itemStack = setMeta(HeadAPI.getHeadByValue("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2Q5MWY1MTI2NmVkZGM2MjA3ZjEyYWU4ZDdhNDljNWRiMDQxNWFkYTA0ZGFiOTJiYjc2ODZhZmRiMTdmNGQ0ZSJ9fX0=").getItemStack(), + ItemStack itemStack = setMeta(HeadAPI.getHeadByValue("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2Q5MWY1MTI2NmVkZGM2MjA3ZjEyYWU4ZDdhNDljNWRiMDQxNWFkYTA0ZGFiOTJiYjc2ODZhZmRiMTdmNGQ0ZSJ9fX0=").getMenuItem(), name, "&7Left-Click to go to the &cMain Menu", "&7Right-Click to go to a &6Specific Page"); diff --git a/src/main/java/tsp/headdb/util/Localization.java b/src/main/java/tsp/headdb/util/Localization.java new file mode 100644 index 0000000..019556d --- /dev/null +++ b/src/main/java/tsp/headdb/util/Localization.java @@ -0,0 +1,58 @@ +package tsp.headdb.util; + +import org.apache.commons.lang.Validate; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.File; +import java.util.function.UnaryOperator; + +/** + * Localization class for a messages file + * + * @author TheSilentPro + */ +public class Localization { + + private final File file; + @Nullable + private FileConfiguration data; + + public Localization(@Nonnull File file) { + Validate.notNull(file, "File can not be null."); + + this.file = file; + this.data = null; + } + + public String getMessage(@Nonnull String key) { + Validate.notNull(data, "File data is null."); + Validate.notNull(key, "Message key can not be null."); + + return Utils.colorize(data.getString(key)); + } + + public String getMessage(@Nonnull String key, @Nonnull UnaryOperator function) { + Validate.notNull(data, "File data is null."); + Validate.notNull(key, "Message key can not be null."); + Validate.notNull(function, "Function can not be null."); + + return function.apply(getMessage(key)); + } + + public void load() { + this.data = YamlConfiguration.loadConfiguration(file); + } + + public File getFile() { + return file; + } + + @Nullable + public FileConfiguration getData() { + return data; + } + +} diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml new file mode 100644 index 0000000..8225fca --- /dev/null +++ b/src/main/resources/messages.yml @@ -0,0 +1,19 @@ +noPermission: "&cNo permission!" +onlyPlayers: "&c&cOnly players may open the database." +databaseOpen: "&7Opening &cHead Database" +invalidPlayer: "&cPlayer is not online!" +localFavorites: "&cLocal heads can not be added to favorites!" + +menu: + main: "&c&lHeadDB &7(%size%)" + category: "&c&lHeadDB &8- &e%category%" + tagSearch: "&c&lHeadDB &8- &eTag Search: %search%" + search: "&c&lHeadDB &8- &eSearch: %search%" + favorites: "&c&lHeadDB &8- &eFavorites: %player%" + local: "&c&lHeadDB &8- &aLocal Heads &7(%size%)" + +# Head item given to players +head: + name: "%name%" + lore: + - '' \ No newline at end of file