From 0f20e4ded8c0675cf572f85c9bd277aac49356ee Mon Sep 17 00:00:00 2001 From: Technofied <40795318+Technofied@users.noreply.github.com> Date: Sat, 11 Apr 2026 13:57:42 +0800 Subject: [PATCH] feat: messages.yml --- .../ChestshopDatabasePlugin.java | 9 +- .../command/FindCommand.java | 70 +++++---------- .../command/ReloadCommand.java | 12 +-- .../command/ResyncCommand.java | 24 ++--- .../chestshopdatabase/gui/ShopResultsGUI.java | 11 +-- .../gui/dialog/FilterDialog.java | 31 ++++--- .../gui/dialog/FindDialog.java | 60 +++++++------ .../gui/dialog/SortDialog.java | 82 +++++++++-------- .../settings/MessageContainer.java | 30 ++++++- .../src/main/resources/messages.yml | 90 +++++++++++++++++++ 10 files changed, 264 insertions(+), 155 deletions(-) create mode 100644 chestshop-database-bukkit/src/main/resources/messages.yml diff --git a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/ChestshopDatabasePlugin.java b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/ChestshopDatabasePlugin.java index f5f9ed8..c24b1e5 100644 --- a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/ChestshopDatabasePlugin.java +++ b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/ChestshopDatabasePlugin.java @@ -98,7 +98,7 @@ public void onEnable() { discoverer = new ItemDiscoverer(50, Duration.ofMinutes(5), 50, getServer(), getLogger()); BukkitScheduler scheduler = getServer().getScheduler(); executorState = new ExecutorState(databaseExecutor, scheduler.getMainThreadExecutor(this)); - gui = new ShopResultsGUI(this, this.replacements, () -> this.settings); + gui = new ShopResultsGUI(this, this.replacements, () -> this.settings, () -> this.messageContainer); SqlSessionFactory sessionFactory = MariaDatabase.buildSessionFactory(this.databaseSettings); Supplier sessionSupplier = () -> new DatabaseSession(sessionFactory, @@ -136,6 +136,11 @@ public void onDisable() { getLogger().info("Plugin disabled"); } + @NotNull + public MessageContainer messages() { + return this.messageContainer; + } + private void registerCommands(@NotNull SqlSessionFactory sessionFactory) { PluginManager pluginManager = getServer().getPluginManager(); Supplier sessionSupplier = () -> new DatabaseSession(sessionFactory, @@ -160,7 +165,7 @@ private void registerCommands(@NotNull SqlSessionFactory sessionFactory) { this.messageContainer); List commands = List.of( findCommand, - new ResyncCommand(this, resyncTaskFactory), + new ResyncCommand(this, resyncTaskFactory, this.messageContainer), new ReloadCommand(this) ); var csdb = Commands.literal("csdb"); diff --git a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/command/FindCommand.java b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/command/FindCommand.java index d5fa1c8..9009ad9 100644 --- a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/command/FindCommand.java +++ b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/command/FindCommand.java @@ -21,8 +21,7 @@ import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.dialog.Dialog; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import org.bukkit.Tag; import org.bukkit.World; import org.bukkit.block.Block; @@ -64,9 +63,7 @@ private LiteralArgumentBuilder baseFindCommand() { } ItemStack inMainHand = player.getInventory().getItemInMainHand().asOne(); if (inMainHand.isEmpty()) { - player.sendMessage(Component.text( - "You must hold an item in your hand or specify an item code!", - NamedTextColor.RED)); + player.sendMessage(messages.messageFor("find.command.need-item")); return Command.SINGLE_SUCCESS; } processCommandWithItem(player, inMainHand); @@ -101,22 +98,16 @@ private LiteralArgumentBuilder buildToggleHologram() { } Block block = player.getTargetBlockExact(5); if (block == null || !Tag.SIGNS.isTagged(block.getType())) { - player.sendMessage(Component.text( - "You must be looking at a shop sign!", - NamedTextColor.RED)); + player.sendMessage(messages.messageFor("find.command.sign-target")); return Command.SINGLE_SUCCESS; } Sign sign = (Sign) block.getState(false); if (!ChestShopSign.isValid(sign)) { - player.sendMessage(Component.text( - "You must be looking at a shop sign!", - NamedTextColor.RED)); + player.sendMessage(messages.messageFor("find.command.sign-target")); return Command.SINGLE_SUCCESS; } if (!ChestShopSign.canAccess(player, sign)) { - player.sendMessage(Component.text( - "You do not have access to this shop sign!", - NamedTextColor.RED)); + player.sendMessage(messages.messageFor("find.command.sign-no-access")); return Command.SINGLE_SUCCESS; } World world = sign.getWorld(); @@ -158,18 +149,14 @@ private LiteralArgumentBuilder buildToggleHologram() { .whenComplete((success, ex) -> { if (ex != null) { ex.printStackTrace(); - player.sendMessage(Component.text( - "Internal error occurred!", - NamedTextColor.RED)); + player.sendMessage(messages.messageFor("find.command.internal-error")); return; } - player.sendMessage(Component.text( - "Visibility toggled to " + visible, - NamedTextColor.AQUA)); + player.sendMessage(messages.messageResolving("find.command.visibility-toggled", + Placeholder.unparsed("value", String.valueOf(visible)))); if (!success) { - player.sendMessage(Component.text( - "Failed to update hologram!", - NamedTextColor.RED)); + player.sendMessage(messages.messageFor( + "find.command.visibility-toggled-failed")); } }); return Command.SINGLE_SUCCESS; @@ -187,22 +174,16 @@ private LiteralArgumentBuilder buildToggleVisibility() { } Block block = player.getTargetBlockExact(5); if (block == null || !Tag.SIGNS.isTagged(block.getType())) { - player.sendMessage(Component.text( - "You must be looking at a shop sign!", - NamedTextColor.RED)); + player.sendMessage(messages.messageFor("find.command.sign-target")); return Command.SINGLE_SUCCESS; } Sign sign = (Sign) block.getState(false); if (!ChestShopSign.isValid(sign)) { - player.sendMessage(Component.text( - "You must be looking at a shop sign!", - NamedTextColor.RED)); + player.sendMessage(messages.messageFor("find.command.sign-target")); return Command.SINGLE_SUCCESS; } if (!ChestShopSign.canAccess(player, sign)) { - player.sendMessage(Component.text( - "You do not have access to this shop sign!", - NamedTextColor.RED)); + player.sendMessage(messages.messageFor("find.command.sign-no-access")); return Command.SINGLE_SUCCESS; } UUID world = sign.getWorld().getUID(); @@ -219,14 +200,11 @@ private LiteralArgumentBuilder buildToggleVisibility() { .whenComplete((unused, ex) -> { if (ex != null) { ex.printStackTrace(); - player.sendMessage(Component.text( - "Internal error occurred!", - NamedTextColor.RED)); + player.sendMessage(messages.messageFor("find.command.internal-error")); return; } - player.sendMessage(Component.text( - "Visibility toggled to " + visible, - NamedTextColor.AQUA)); + player.sendMessage(messages.messageResolving("find.command.visibility-toggled", + Placeholder.unparsed("value", String.valueOf(visible)))); }); return Command.SINGLE_SUCCESS; })); @@ -245,14 +223,11 @@ private LiteralArgumentBuilder buildTogglePreview() { previewHandler.setVisible(player, visible).whenComplete((unused, ex) -> { if (ex != null) { ex.printStackTrace(); - player.sendMessage(Component.text( - "Internal error occurred!", - NamedTextColor.RED)); + player.sendMessage(messages.messageFor("find.command.internal-error")); return; } - player.sendMessage(Component.text( - "Preview visibility toggled to " + visible, - NamedTextColor.AQUA)); + player.sendMessage(messages.messageResolving("find.command.preview-toggled", + Placeholder.unparsed("value", String.valueOf(visible)))); }); return Command.SINGLE_SUCCESS; })); @@ -267,8 +242,8 @@ private void processCommandWithItem(@NotNull Player player, @NotNull ItemStack i ); this.discoverer.discoverCodeFromItemStack(itemStack, code -> { if (code == null || code.isEmpty()) { - player.sendMessage(Component.text("Unknown item: ", NamedTextColor.RED) - .append(itemStack.effectiveName())); + player.sendMessage(messages.messageResolving("find.unknown-item-display", + Placeholder.component("item", itemStack.effectiveName()))); return; } FindState findState = new FindState( @@ -297,7 +272,8 @@ private void processCommandWithItemCode(@NotNull Player player, @NotNull String String normalized = this.shopState.normalizeItemCode(itemCode); this.discoverer.discoverItemStackFromCode(normalized, item -> { if (item == null || item.isEmpty()) { - player.sendMessage(Component.text("Unknown item: " + itemCode, NamedTextColor.RED)); + player.sendMessage(messages.messageResolving("find.unknown-item-code", + Placeholder.unparsed("code", itemCode))); return; } FindState findState = new FindState( diff --git a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/command/ReloadCommand.java b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/command/ReloadCommand.java index da12793..1a65a53 100644 --- a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/command/ReloadCommand.java +++ b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/command/ReloadCommand.java @@ -6,8 +6,6 @@ import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import net.kyori.adventure.audience.Audience; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; import org.jetbrains.annotations.NotNull; public record ReloadCommand(@NotNull ChestshopDatabasePlugin plugin) implements CommandBean.Single { @@ -18,19 +16,15 @@ public record ReloadCommand(@NotNull ChestshopDatabasePlugin plugin) implements .requires(source -> source.getSender().hasPermission("csdb.reload")) .executes(ctx -> { Audience audience = ctx.getSource().getSender(); - audience.sendMessage(Component.text("Reloading CSDB messages and settings", - NamedTextColor.GREEN)); + audience.sendMessage(plugin.messages().messageFor("command.reload.start")); plugin.reload().whenComplete((success, error) -> { if (error != null) { error.printStackTrace(); } if (!success || error != null) { - audience.sendMessage(Component.text( - "Reload unsuccessful, check console for errors", - NamedTextColor.RED)); + audience.sendMessage(plugin.messages().messageFor("command.reload.failure")); } else { - audience.sendMessage(Component.text("Reload successful", - NamedTextColor.GREEN)); + audience.sendMessage(plugin.messages().messageFor("command.reload.success")); } }); return Command.SINGLE_SUCCESS; diff --git a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/command/ResyncCommand.java b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/command/ResyncCommand.java index 0f8e4fb..0971938 100644 --- a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/command/ResyncCommand.java +++ b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/command/ResyncCommand.java @@ -8,8 +8,9 @@ import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.util.Tick; +import io.github.md5sha256.chestshopdatabase.settings.MessageContainer; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import org.bukkit.command.CommandSender; import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitScheduler; @@ -20,7 +21,8 @@ public record ResyncCommand( @NotNull Plugin plugin, - @NotNull ResyncTaskFactory taskFactory + @NotNull ResyncTaskFactory taskFactory, + @NotNull MessageContainer messages ) implements CommandBean.Single { @@ -32,29 +34,31 @@ public record ResyncCommand( .executes(ctx -> { CommandSender sender = ctx.getSource().getSender(); BukkitScheduler scheduler = plugin.getServer().getScheduler(); - sender.sendMessage(Component.text("Resync queued", - NamedTextColor.GREEN)); + sender.sendMessage(messages.messageFor("resync.queued")); int chunksPerInterval = ctx.getArgument("chunksPerTick", Integer.class); taskFactory.triggerResync(chunksPerInterval, 1).thenAccept(progress -> { int ticks = Tick.tick().fromDuration(Duration.ofSeconds(30)); BukkitTask bukkitTask = scheduler.runTaskTimer(plugin, () -> { - sender.sendMessage(Component.text(formatProgress(progress), - NamedTextColor.AQUA)); + sender.sendMessage(formatProgress(progress)); }, ticks, ticks); progress.chainOnComplete(() -> { bukkitTask.cancel(); - sender.sendMessage(String.format("Resync complete! (%d)", progress.total())); + sender.sendMessage(messages.messageResolving("resync.complete", + Placeholder.unparsed("total", String.valueOf(progress.total())))); }); }); return Command.SINGLE_SUCCESS; })); } - private String formatProgress(@NotNull TaskProgress progress) { + private Component formatProgress(@NotNull TaskProgress progress) { int done = progress.completed(); int total = progress.total(); - double percent = 100D * done / total; - return String.format("Resync Progress: %d/%d (%.2f%%)", done, total, percent); + double percent = total == 0 ? 0 : 100D * done / total; + return messages.messageResolving("resync.progress", + Placeholder.unparsed("done", String.valueOf(done)), + Placeholder.unparsed("total", String.valueOf(total)), + Placeholder.unparsed("percent", String.format("%.2f", percent))); } } diff --git a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/ShopResultsGUI.java b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/ShopResultsGUI.java index 24c0b7e..33ee547 100644 --- a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/ShopResultsGUI.java +++ b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/ShopResultsGUI.java @@ -12,11 +12,11 @@ import io.github.md5sha256.chestshopdatabase.ReplacementRegistry; import io.github.md5sha256.chestshopdatabase.model.Shop; import io.github.md5sha256.chestshopdatabase.model.ShopType; +import io.github.md5sha256.chestshopdatabase.settings.MessageContainer; import io.github.md5sha256.chestshopdatabase.settings.Settings; import io.github.md5sha256.chestshopdatabase.util.BlockPosition; import io.github.md5sha256.chestshopdatabase.util.SimpleItemStack; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; import org.bukkit.Material; import org.bukkit.entity.HumanEntity; @@ -35,7 +35,8 @@ public record ShopResultsGUI(@NotNull Plugin plugin, @NotNull ReplacementRegistry replacements, - @NotNull Supplier settings) { + @NotNull Supplier settings, + @NotNull Supplier messages) { private static String distanceString(Shop shop, @Nullable BlockPosition queryPosition) { @@ -164,7 +165,7 @@ public ChestGui createGui(@NotNull Component title, StaticPane footerPane = new StaticPane(0, 5, 9, 1, Pane.Priority.LOWEST); ItemStack backItem = ItemStack.of(Material.ARROW); backItem.editMeta(meta -> { - Component displayName = Component.text("Back", NamedTextColor.RED) + Component displayName = messages.get().messageFor("gui.results.back") .decoration(TextDecoration.ITALIC, false); meta.displayName(displayName); }); @@ -184,9 +185,9 @@ public ChestGui createGui(@NotNull Component title, Pane.Priority.HIGH, mainPane, this.plugin); - Component nextPageComp = Component.text("Next Page") + Component nextPageComp = messages.get().messageFor("gui.results.next-page") .decoration(TextDecoration.ITALIC, false); - Component prevPageComp = Component.text("Prev Page") + Component prevPageComp = messages.get().messageFor("gui.results.prev-page") .decoration(TextDecoration.ITALIC, false); ItemStack nextButton = ItemStack.of(Material.PAPER); nextButton.editMeta(meta -> meta.displayName(nextPageComp)); diff --git a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/dialog/FilterDialog.java b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/dialog/FilterDialog.java index f676cfb..372a83b 100644 --- a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/dialog/FilterDialog.java +++ b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/dialog/FilterDialog.java @@ -2,6 +2,7 @@ import io.github.md5sha256.chestshopdatabase.gui.FindState; import io.github.md5sha256.chestshopdatabase.model.ShopType; +import io.github.md5sha256.chestshopdatabase.settings.MessageContainer; import io.github.md5sha256.chestshopdatabase.util.DialogUtil; import io.papermc.paper.dialog.Dialog; import io.papermc.paper.registry.data.dialog.ActionButton; @@ -12,7 +13,6 @@ import io.papermc.paper.registry.data.dialog.input.SingleOptionDialogInput; import io.papermc.paper.registry.data.dialog.type.DialogType; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; import org.jetbrains.annotations.NotNull; import java.util.Arrays; @@ -25,32 +25,34 @@ public class FilterDialog { @NotNull - private static DialogBase createShopFiltersBase(@NotNull Set includedTypes) { + private static DialogBase createShopFiltersBase(@NotNull Set includedTypes, + @NotNull MessageContainer messages) { var options = List.of(SingleOptionDialogInput.OptionEntry.create("enabled", - Component.text("On", NamedTextColor.GREEN), + messages.messageFor("dialog.option.on"), true), SingleOptionDialogInput.OptionEntry.create("disabled", - Component.text("Off", NamedTextColor.RED), + messages.messageFor("dialog.option.off"), false)); Stream typeInputs = Arrays.stream(ShopType.values()) .map(type -> DialogInput.singleOption(type.name(), - Component.text(type.displayName()), + messages.messageFor( + "find.filter.shop-type." + type.name().toLowerCase()), options) .build()); Stream emptyFullInputs = Stream.of( DialogInput.singleOption( "show_empty", - Component.text("Empty Shops"), + messages.messageFor("find.filter.empty-shops"), options).build(), DialogInput.singleOption( "show_full", - Component.text("Full Shops"), + messages.messageFor("find.filter.full-shops"), options).build() ); - return DialogBase.builder(Component.text("Select Shop Types")) + return DialogBase.builder(messages.messageFor("find.filter.title")) .canCloseWithEscape(true) .inputs(Stream.concat(typeInputs, emptyFullInputs).toList()) .build(); @@ -58,20 +60,21 @@ private static DialogBase createShopFiltersBase(@NotNull Set includedT @NotNull public static Dialog createFiltersDialog(@NotNull FindState state, - @NotNull Supplier prevDialog) { + @NotNull Supplier prevDialog, + @NotNull MessageContainer messages) { Set includedTypes = state.shopTypes(); - ActionButton saveButton = ActionButton.builder(Component.text("Save")) - .tooltip(Component.text("Save selection and return to previous menu")) + ActionButton saveButton = ActionButton.builder(messages.messageFor("dialog.common.save")) + .tooltip(messages.messageFor("dialog.common.tooltip.save-return")) .action(DialogAction.customClick(applyFilters(state, prevDialog), DialogUtil.DEFAULT_CALLBACK_OPTIONS)) .build(); - ActionButton backButton = ActionButton.builder(Component.text("Back")) - .tooltip(Component.text("Return to previous menu")) + ActionButton backButton = ActionButton.builder(messages.messageFor("dialog.common.back")) + .tooltip(messages.messageFor("dialog.common.tooltip.back")) .action(DialogUtil.openDialogAction(prevDialog)) .build(); return Dialog.create(factory -> factory.empty() - .base(createShopFiltersBase(includedTypes)) + .base(createShopFiltersBase(includedTypes, messages)) .type(DialogType.confirmation(saveButton, backButton)) ); } diff --git a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/dialog/FindDialog.java b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/dialog/FindDialog.java index 4163cb4..f75a1f7 100644 --- a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/dialog/FindDialog.java +++ b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/dialog/FindDialog.java @@ -21,7 +21,7 @@ import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickCallback; -import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; @@ -33,18 +33,20 @@ public class FindDialog { - private static final String MESSAGE_FIND_QUERYING = "find.querying"; - @NotNull - private static DialogBase createMainPageBase(@Nullable ChestshopItem item) { + private static DialogBase createMainPageBase(@Nullable ChestshopItem item, + @NotNull MessageContainer messages) { if (item == null) { - return DialogBase.builder(Component.text("Find ChestShops")) + return DialogBase.builder(messages.messageFor("find.dialog.title")) .canCloseWithEscape(true).build(); } ItemStack itemStack = item.itemStack(); Component name = itemStack.getDataOrDefault(DataComponentTypes.CUSTOM_NAME, itemStack.effectiveName()); - var builder = DialogBase.builder(Component.text("Find ChestShops for ").append(name)) + + var builder = DialogBase.builder( + messages.messageResolving("find.dialog.title-with-item", + Placeholder.component("item", name))) .canCloseWithEscape(true); var nameBody = DialogBody.plainMessage(name); @@ -52,19 +54,19 @@ private static DialogBase createMainPageBase(@Nullable ChestshopItem item) { return builder.body(List.of(itemBody, nameBody)).build(); } - private static Dialog waitScreen() { + private static Dialog waitScreen(@NotNull MessageContainer messages) { return Dialog.create(factory -> factory .empty() - .base(waitScreenBase()) + .base(waitScreenBase(messages)) .type(DialogType.notice()) ); } - private static DialogBase waitScreenBase() { - return DialogBase.builder(Component.text("Chest Shop Query")) + private static DialogBase waitScreenBase(@NotNull MessageContainer messages) { + return DialogBase.builder(messages.messageFor("find.dialog.query-title")) .afterAction(DialogBase.DialogAfterAction.CLOSE) .canCloseWithEscape(true) - .body(List.of(DialogBody.plainMessage(Component.text("Querying...")))) + .body(List.of(DialogBody.plainMessage(messages.messageFor("find.dialog.query-body")))) .build(); } @@ -78,27 +80,27 @@ private static void submit( @NotNull Predicate isBedrockPlayer, @NotNull MessageContainer messages) { if (!(audience instanceof Player player)) { - audience.showDialog(waitScreen()); + audience.showDialog(waitScreen(messages)); return; } if (isBedrockPlayer.test(player)) { - player.sendMessage(messages.messageFor(MESSAGE_FIND_QUERYING)); + player.sendMessage(messages.messageFor("find.querying")); } else { - audience.showDialog(waitScreen()); + audience.showDialog(waitScreen(messages)); } taskFactory.findTask(findState).whenComplete((res, ex) -> { audience.closeDialog(); if (ex != null) { ex.printStackTrace(); - audience.sendMessage(Component.text("Internal error when querying shops!", - NamedTextColor.RED)); + audience.sendMessage(messages.messageFor("find.error.query")); return; } if (res.isEmpty()) { - audience.sendMessage(Component.text("No shops found!", NamedTextColor.RED)); + audience.sendMessage(messages.messageFor("find.empty-results")); return; } - Component title = Component.text("Shop results for " + findState.item().itemCode()); + Component title = messages.messageResolving("find.results-title", + Placeholder.unparsed("item_code", findState.item().itemCode())); ChestGui chestGui = resultsGUI.createGui(title, res, findState.item().itemStack(), findState.queryPosition()); plugin.getServer().getScheduler().runTaskLater(plugin, () -> chestGui.show(player), 1); }); @@ -146,10 +148,10 @@ public static Dialog createMainPageDialog( submit(view, audience, findState, taskFactory, resultsGUI, plugin, isBedrockPlayer, messages); }, ClickCallback.Options.builder().uses(1).build()); - ActionButton submitButton = ActionButton.builder(Component.text("Search", NamedTextColor.GREEN)) + ActionButton submitButton = ActionButton.builder(messages.messageFor("dialog.common.search")) .action(submitAction) .build(); - ActionButton exitButton = ActionButton.builder(Component.text("Exit")) + ActionButton exitButton = ActionButton.builder(messages.messageFor("dialog.common.exit")) .action(DialogUtil.CLOSE_DIALOG_ACTION) .build(); ActionButton spacerButton = ActionButton.builder(Component.text("")) @@ -158,33 +160,35 @@ public static Dialog createMainPageDialog( .build(); List actions = List.of( - ActionButton.builder(Component.text("Buy Cheap")) + ActionButton.builder(messages.messageFor("find-main.buy-cheap")) .action(buyCheapAction) .build(), - ActionButton.builder(Component.text("Buy Nearby")) + ActionButton.builder(messages.messageFor("find-main.buy-nearby")) .action(buyNearbyAction) .build(), - ActionButton.builder(Component.text("Sell for Best Price")) + ActionButton.builder(messages.messageFor("find-main.sell-best-price")) .action(sellBestPriceAction).build(), spacerButton, - ActionButton.builder(Component.text("Filters")) + ActionButton.builder(messages.messageFor("find-main.filters")) .action(DialogUtil.openDialogAction(() -> FilterDialog.createFiltersDialog( findState, () -> createMainPageDialog(findState, taskFactory, resultsGUI, plugin, - isBedrockPlayer, messages)))) + isBedrockPlayer, messages), + messages))) .build(), - ActionButton.builder(Component.text("Sorting")) + ActionButton.builder(messages.messageFor("find-main.sorting")) .action(DialogUtil.openDialogAction(() -> SortDialog.createSortDialog( findState, () -> createMainPageDialog(findState, taskFactory, resultsGUI, plugin, - isBedrockPlayer, messages)))) + isBedrockPlayer, messages), + messages))) .build(), submitButton ); return Dialog.create(factory -> factory.empty() - .base(createMainPageBase(findState.item())) + .base(createMainPageBase(findState.item(), messages)) .type(DialogType.multiAction(actions) .exitAction(exitButton) .columns(1) diff --git a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/dialog/SortDialog.java b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/dialog/SortDialog.java index c941d2b..0297354 100644 --- a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/dialog/SortDialog.java +++ b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/gui/dialog/SortDialog.java @@ -2,6 +2,7 @@ import io.github.md5sha256.chestshopdatabase.gui.FindState; import io.github.md5sha256.chestshopdatabase.model.ShopAttribute; +import io.github.md5sha256.chestshopdatabase.settings.MessageContainer; import io.github.md5sha256.chestshopdatabase.util.DialogUtil; import io.github.md5sha256.chestshopdatabase.util.SortDirection; import io.papermc.paper.dialog.Dialog; @@ -14,7 +15,6 @@ import io.papermc.paper.registry.data.dialog.input.SingleOptionDialogInput; import io.papermc.paper.registry.data.dialog.type.DialogType; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -24,23 +24,26 @@ public class SortDialog { public static Dialog createSortDialog(@NotNull FindState findState, - @NotNull Supplier prevDialog) { + @NotNull Supplier prevDialog, + @NotNull MessageContainer messages) { var buttons = List.of( - ActionButton.builder(Component.text("Sorting Direction")) + ActionButton.builder(messages.messageFor("find.sort.direction-button")) .action(DialogUtil.openDialogAction(() -> createSortDirectionDialog( - findState, () -> createSortDialog(findState, prevDialog)))) + findState, () -> createSortDialog(findState, prevDialog, messages), + messages))) .build(), - ActionButton.builder(Component.text("Sorting Priority")) + ActionButton.builder(messages.messageFor("find.sort.priority-button")) .action(DialogUtil.openDialogAction(() -> createSortPriorityDialog( - findState, () -> createSortDialog(findState, prevDialog)))) + findState, () -> createSortDialog(findState, prevDialog, messages), + messages))) .build() ); - var backButton = ActionButton.builder(Component.text("Back")) - .tooltip(Component.text("Return to the previous menu")) + var backButton = ActionButton.builder(messages.messageFor("dialog.common.back")) + .tooltip(messages.messageFor("dialog.common.tooltip.back-sort")) .action(DialogUtil.openDialogAction(prevDialog)).build(); - return Dialog.create(factory -> factory.empty().base(sortingBase()) + return Dialog.create(factory -> factory.empty().base(sortingBase(messages)) .type(DialogType.multiAction(buttons) .columns(1) .exitAction(backButton) @@ -48,96 +51,103 @@ public static Dialog createSortDialog(@NotNull FindState findState, } private static Dialog createSortDirectionDialog(@NotNull FindState findState, - @NotNull Supplier prevDialog) { + @NotNull Supplier prevDialog, + @NotNull MessageContainer messages) { - var saveButton = ActionButton.builder(Component.text("Save")) + var saveButton = ActionButton.builder(messages.messageFor("dialog.common.save")) .action(DialogAction.customClick(applyShopAttributeSortDirections(findState, prevDialog), DialogUtil.DEFAULT_CALLBACK_OPTIONS)) - .tooltip(Component.text("Save selection and return to the previous menu")) + .tooltip(messages.messageFor("dialog.common.tooltip.save-return-sort")) .build(); - var backButton = ActionButton.builder(Component.text("Back")) - .tooltip(Component.text("Return to the previous menu")) + var backButton = ActionButton.builder(messages.messageFor("dialog.common.back")) + .tooltip(messages.messageFor("dialog.common.tooltip.back-sort")) .action(DialogUtil.openDialogAction(prevDialog)).build(); var attributes = findState.selectedAttributes().stream().sorted().toList(); return Dialog.create(factory -> - factory.empty().base(setSortingDirectionBase(attributes)) + factory.empty().base(setSortingDirectionBase(attributes, messages)) .type(DialogType.confirmation(saveButton, backButton)) ); } private static Dialog createSortPriorityDialog(@NotNull FindState findState, - @NotNull Supplier prevDialog) { + @NotNull Supplier prevDialog, + @NotNull MessageContainer messages) { - var saveButton = ActionButton.builder(Component.text("Save")) + var saveButton = ActionButton.builder(messages.messageFor("dialog.common.save")) .action(DialogAction.customClick(applyShopAttributeSortPriority(findState, prevDialog), DialogUtil.DEFAULT_CALLBACK_OPTIONS)) - .tooltip(Component.text("Save selection and return to the previous menu")) + .tooltip(messages.messageFor("dialog.common.tooltip.save-return-sort")) .build(); - var backButton = ActionButton.builder(Component.text("Back")) - .tooltip(Component.text("Return to the previous menu")) + var backButton = ActionButton.builder(messages.messageFor("dialog.common.back")) + .tooltip(messages.messageFor("dialog.common.tooltip.back-sort")) .action(DialogUtil.openDialogAction(prevDialog)).build(); var attributes = findState.selectedAttributes().stream().sorted().toList(); return Dialog.create(factory -> - factory.empty().base(setPrioritiesBase(attributes)) + factory.empty().base(setPrioritiesBase(attributes, messages)) .type(DialogType.confirmation(saveButton, backButton)) ); } @NotNull - private static DialogBase sortingBase() { - Component description = Component.text( - "Configure how shops should be sorted"); - return DialogBase.builder(Component.text("Shop Sorting")) + private static DialogBase sortingBase(@NotNull MessageContainer messages) { + Component description = messages.messageFor("find.sort.description"); + return DialogBase.builder(messages.messageFor("find.sort.title")) .body(List.of(DialogBody.plainMessage(description))) .build(); } @NotNull - private static DialogBase setSortingDirectionBase(@NotNull List attributes) { + private static DialogBase setSortingDirectionBase(@NotNull List attributes, + @NotNull MessageContainer messages) { var options = List.of(SingleOptionDialogInput.OptionEntry.create("ascending", - Component.text("Ascending", NamedTextColor.AQUA), + messages.messageFor("find.sort.ascending"), true), SingleOptionDialogInput.OptionEntry.create("descending", - Component.text("Descending", NamedTextColor.BLUE), + messages.messageFor("find.sort.descending"), false), SingleOptionDialogInput.OptionEntry.create("disabled", - Component.text("Off", NamedTextColor.RED), + messages.messageFor("find.sort.off"), false)); var directions = attributes.stream().map(attribute -> DialogInput.singleOption(attribute.name(), - Component.text(attribute.displayName()), + messages.messageFor(attributeMessageKey(attribute)), options ).build() ).toList(); - return DialogBase.builder(Component.text("Set Sorting Directions")) + return DialogBase.builder(messages.messageFor("find.sort.directions-title")) .inputs(directions) .build(); } - private static DialogBase setPrioritiesBase(@NotNull List attributes) { + private static DialogBase setPrioritiesBase(@NotNull List attributes, + @NotNull MessageContainer messages) { var directions = attributes.stream().map(attribute -> DialogInput.numberRange(attribute.name(), - Component.text(attribute.displayName()), + messages.messageFor(attributeMessageKey(attribute)), 0, 100 ).initial(0f).step(1f).build() ).toList(); - Component message = Component.text( - "Set the priority of each characteristic as a tiebreaker. Higher priority = used to tiebreak first."); - return DialogBase.builder(Component.text("Set Priorities")) + Component message = messages.messageFor("find.sort.priorities-description"); + return DialogBase.builder(messages.messageFor("find.sort.priorities-title")) .body(List.of(DialogBody.plainMessage(message))) .inputs(directions) .build(); } + @NotNull + private static String attributeMessageKey(@NotNull ShopAttribute attribute) { + return "find.sort.attribute." + attribute.name().toLowerCase(); + } + @NotNull private static DialogActionCallback applyShopAttributeSortDirections(@NotNull FindState findState, @NotNull Supplier prevDialog) { diff --git a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/settings/MessageContainer.java b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/settings/MessageContainer.java index 6e68e77..8d92365 100644 --- a/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/settings/MessageContainer.java +++ b/chestshop-database-bukkit/src/main/java/io/github/md5sha256/chestshopdatabase/settings/MessageContainer.java @@ -3,6 +3,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.spongepowered.configurate.ConfigurateException; import org.spongepowered.configurate.ConfigurationNode; @@ -17,6 +18,8 @@ public class MessageContainer { private final Map messages = new ConcurrentHashMap<>(); + /** Raw MiniMessage templates for keys that use {@link #messageResolving(String, TagResolver...)}. */ + private final Map miniMessageRaw = new ConcurrentHashMap<>(); public String plaintextMessageFor(@Nonnull String key) { return PlainTextComponentSerializer.plainText().serialize(messageFor(key)); @@ -35,6 +38,19 @@ public Component messageFor(@Nonnull String key) { return this.messages.getOrDefault(key, Component.text(key)); } + /** + * Deserialize the raw MiniMessage template for {@code key} with tag resolvers. + * Falls back to {@link #messageFor(String)} if no raw template was stored for the key. + */ + @Nonnull + public Component messageResolving(@Nonnull String key, @Nonnull TagResolver... resolvers) { + String raw = this.miniMessageRaw.get(key); + if (raw == null) { + return messageFor(key); + } + return MiniMessage.miniMessage().deserialize(raw, resolvers); + } + @Nonnull public Component prefixedMessageFor(@Nonnull String key) { return prefix().appendSpace().append(messageFor(key)); @@ -51,12 +67,15 @@ public void setMessage(@Nonnull String key, @Nonnull Component message) { public void clear() { this.messages.clear(); + this.miniMessageRaw.clear(); } public void load(@Nonnull ConfigurationNode root) throws ConfigurateException { Map temp = new HashMap<>(); - loadInto("", root, temp); + Map rawTemp = new HashMap<>(); + loadInto("", root, temp, rawTemp); this.messages.putAll(temp); + this.miniMessageRaw.putAll(rawTemp); } public void save(@Nonnull ConfigurationNode root) throws ConfigurateException { @@ -67,7 +86,8 @@ public void save(@Nonnull ConfigurationNode root) throws ConfigurateException { private void loadInto(String path, ConfigurationNode root, - Map temp) throws ConfigurateException { + Map temp, + Map rawTemp) throws ConfigurateException { if (!root.empty()) { if (root.isList()) { List strings = root.getList(String.class, Collections.emptyList()); @@ -87,8 +107,10 @@ private void loadInto(String path, } else { String raw = root.getString(); if (raw != null) { - Component component = MiniMessage.miniMessage().deserialize(raw.trim()); + String trimmed = raw.trim(); + Component component = MiniMessage.miniMessage().deserialize(trimmed); temp.put(path, component); + rawTemp.put(path, trimmed); } } } @@ -96,7 +118,7 @@ private void loadInto(String path, String key = entry.getKey().toString(); ConfigurationNode node = entry.getValue(); String newPath = path.isEmpty() ? key : path + "." + key; - loadInto(newPath, node, temp); + loadInto(newPath, node, temp, rawTemp); } } diff --git a/chestshop-database-bukkit/src/main/resources/messages.yml b/chestshop-database-bukkit/src/main/resources/messages.yml new file mode 100644 index 0000000..8cab873 --- /dev/null +++ b/chestshop-database-bukkit/src/main/resources/messages.yml @@ -0,0 +1,90 @@ +# Player-facing MiniMessage strings (https://docs.advntr.dev/minimessage/format.html) + +find: + querying: "Querying..." + command: + need-item: "You must hold an item in your hand or specify an item code!" + sign-target: "You must be looking at a shop sign!" + sign-no-access: "You do not have access to this shop sign!" + internal-error: "Internal error occurred!" + visibility-toggled: "Visibility toggled to " + visibility-toggled-failed: "Failed to update hologram!" + preview-toggled: "Preview visibility toggled to " + unknown-item-display: "Unknown item: " + unknown-item-code: "Unknown item: " + error: + query: "Internal error when querying shops!" + empty-results: "No shops found!" + results-title: "Shop results for " + dialog: + title: "Find ChestShops" + title-with-item: "Find ChestShops for " + query-title: "Chest Shop Query" + query-body: "Querying..." + filter: + title: "Select Shop Types" + empty-shops: "Empty Shops" + full-shops: "Full Shops" + shop-type: + buy: "Buy" + sell: "Sell" + both: "Buy & Sell" + sort: + title: "Shop Sorting" + description: "Configure how shops should be sorted" + direction-button: "Sorting Direction" + priority-button: "Sorting Priority" + directions-title: "Set Sorting Directions" + priorities-title: "Set Priorities" + priorities-description: "Set the priority of each characteristic as a tiebreaker. Higher priority = used to tiebreak first." + ascending: "Ascending" + descending: "Descending" + off: "Off" + attribute: + buy_price: "Buy Price" + sell_price: "Sell Price" + unit_buy_price: "Unit Buy Price" + unit_sell_price: "Unit Sell Price" + stock: "Stock" + remaining_capacity: "Remaining Capacity" + quantity: "Quantity" + distance: "Distance" + +dialog: + common: + back: "Back" + save: "Save" + exit: "Exit" + search: "Search" + tooltip: + back: "Return to previous menu" + back-sort: "Return to the previous menu" + save-return: "Save selection and return to previous menu" + save-return-sort: "Save selection and return to the previous menu" + option: + "on": "On" + "off": "Off" + +find-main: + buy-cheap: "Buy Cheap" + buy-nearby: "Buy Nearby" + sell-best-price: "Sell for Best Price" + filters: "Filters" + sorting: "Sorting" + +gui: + results: + back: "Back" + next-page: "Next Page" + prev-page: "Prev Page" + +command: + reload: + start: "Reloading CSDB messages and settings" + success: "Reload successful" + failure: "Reload unsuccessful, check console for errors" + +resync: + queued: "Resync queued" + progress: "Resync Progress: / (%)" + complete: "Resync complete! ()"