From 9ff12e12433c16bbfc2c8267e73ec6f6492a4bb5 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Thu, 16 Mar 2023 18:59:06 +0100 Subject: [PATCH 01/12] Make translatables implement ComponentLike --- .../bukkit/towny/object/Translatable.java | 37 +++++++++++++++++- .../bukkit/towny/object/Translation.java | 38 ++++++++++++++++++- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java index 0d07cbc139..c3449bf5c3 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java @@ -9,11 +9,12 @@ import java.util.Locale; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class Translatable { +public class Translatable implements ComponentLike { private String key; private Object[] args; private boolean stripColors; @@ -37,6 +38,7 @@ public static Translatable of(String key, Object... args) { return new Translatable(key, args); } + @Deprecated public static Translatable literal(String text) { return new LiteralTranslatable(text); } @@ -49,6 +51,9 @@ public Object[] args() { return args; } + /** + * @deprecated Deprecated as of x, the locale is no longer guaranteed to be preserved due to Translatable now implementing ComponentLike. + */ @Nullable public Locale locale() { return this.locale; @@ -106,16 +111,34 @@ public Translatable append(Translatable translatable) { return this; } + // TODO: insert deprecation version in javadocs + + /** + * @deprecated Deprecated as of x, the locale is no longer guaranteed to be preserved due to Translatable now implementing ComponentLike. + * @see #translate(Locale) + * @see #component(Locale) + */ + @Deprecated public Translatable locale(@Nullable Locale locale) { this.locale = locale; return this; } + /** + * @deprecated Deprecated as of x, the locale is no longer guaranteed to be preserved due to Translatable now implementing ComponentLike. + * @see #translate(Locale) + * @see #component(Locale) + */ public Translatable locale(@NotNull Resident resident) { this.locale = Translation.getLocale(resident); return this; } + /** + * @deprecated Deprecated as of x, the locale is no longer guaranteed to be preserved due to Translatable now implementing ComponentLike. + * @see #translate(Locale) + * @see #component(Locale) + */ public Translatable locale(@NotNull CommandSender commandSender) { this.locale = Translation.getLocale(commandSender); return this; @@ -187,6 +210,18 @@ public String debug() { '}'; } + @Override + public @NotNull Component asComponent() { + List formattedArgs = new ArrayList<>(); + if (this.args != null) { + for (Object arg : this.args) + formattedArgs.add(arg instanceof ComponentLike like ? like.asComponent() : TownyComponents.miniMessage(arg.toString())); + } + + return Component.translatable(this.key(), formattedArgs); + } + + @Deprecated private static final class LiteralTranslatable extends Translatable { private LiteralTranslatable(String key) { diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java index 8863cf3821..5294154784 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java @@ -6,15 +6,23 @@ import com.palmergames.bukkit.towny.TownyUniverse; import com.palmergames.bukkit.towny.command.HelpMenu; import com.palmergames.bukkit.towny.event.TranslationLoadEvent; +import com.palmergames.bukkit.towny.utils.TownyComponents; import com.palmergames.bukkit.util.BukkitTools; import com.palmergames.bukkit.util.Colors; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.renderer.TranslatableComponentRenderer; +import net.kyori.adventure.translation.Translator; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.nio.file.Path; import java.nio.file.Paths; +import java.text.MessageFormat; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -32,8 +40,9 @@ public final class Translation { private Translation() {} private static Map> translations = new HashMap<>(); + private static final TranslatableComponentRenderer renderer = TranslatableComponentRenderer.usingTranslationSource(new TownyTranslator()); private static Locale defaultLocale = new Locale("en", "US"); // en-US here by default, in case of safe mode happening before translations are loaded. - private static Locale englishLocale = new Locale("en", "US"); // Our last-ditch fall back locale. + private static final Locale englishLocale = new Locale("en", "US"); // Our last-ditch fall back locale. public static void loadTranslationRegistry() { translations.clear(); @@ -234,6 +243,11 @@ public static Locale getLocale(Resident resident) { return BukkitTools.isOnline(resident.getName()) ? getLocale(resident.getPlayer()) : defaultLocale; } + @NotNull + public static Component render(@NotNull Component component, @NotNull Locale locale) { + return renderer.render(component, locale); + } + public static void addTranslations(Map> addedTranslations) { if (addedTranslations != null && !addedTranslations.isEmpty()) { for (String language : addedTranslations.keySet()) @@ -246,4 +260,26 @@ public static void addTranslations(Map> addedTransla } } } + + private static class TownyTranslator implements Translator { + + @Override + public @NotNull Key name() { + return Key.key("towny", "translations"); + } + + @Override + public @Nullable MessageFormat translate(@NotNull String key, @NotNull Locale locale) { + return null; + } + + @Override + public @Nullable Component translate(@NotNull TranslatableComponent component, @NotNull Locale locale) { + final String translated = of(component.key(), locale, component.args()); + if (translated.equals(component.key())) + return null; + + return TownyComponents.miniMessage(translated); + } + } } \ No newline at end of file From 0329ac17e723a03b6557c64ed0346c6677136257 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Sun, 19 Mar 2023 22:58:14 +0100 Subject: [PATCH 02/12] Auto translate translatable components using adventure --- .../src/main/java/com/palmergames/bukkit/towny/Towny.java | 3 ++- .../com/palmergames/bukkit/towny/object/Translation.java | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/Towny.java b/Towny/src/main/java/com/palmergames/bukkit/towny/Towny.java index cdcb817cf1..e3ad357bd3 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/Towny.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/Towny.java @@ -61,6 +61,7 @@ import com.palmergames.bukkit.towny.scheduling.impl.FoliaTaskScheduler; import io.papermc.lib.PaperLib; +import net.kyori.adventure.identity.Identity; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import org.bstats.bukkit.Metrics; import org.bstats.charts.SimplePie; @@ -153,7 +154,7 @@ public void onEnable() { // NOTE: Runs regardless if Towny errors out! // Important for safe mode. - adventure = BukkitAudiences.create(this); + adventure = BukkitAudiences.builder(this).componentRenderer(pointer -> pointer.getOrDefault(Identity.LOCALE, Translation.getDefaultLocale()), Translation.translationRenderer()).build(); // Check for plugins that we use or we develop, // print helpful information to startup log. diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java index 5294154784..3b38d16c36 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java @@ -12,11 +12,13 @@ import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.renderer.ComponentRenderer; import net.kyori.adventure.text.renderer.TranslatableComponentRenderer; import net.kyori.adventure.translation.Translator; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -248,6 +250,11 @@ public static Component render(@NotNull Component component, @NotNull Locale loc return renderer.render(component, locale); } + @ApiStatus.Internal + public static ComponentRenderer translationRenderer() { + return renderer; + } + public static void addTranslations(Map> addedTranslations) { if (addedTranslations != null && !addedTranslations.isEmpty()) { for (String language : addedTranslations.keySet()) From 7098f72bcf1e55fe7733d0b098e67cbcad3fd9f0 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Sat, 1 Apr 2023 20:44:05 +0200 Subject: [PATCH 03/12] rough around the edges --- .../bukkit/towny/TownyMessaging.java | 331 +++++++++++------- .../towny/command/TownyAdminCommand.java | 9 +- .../towny/confirmations/Confirmation.java | 20 +- .../confirmations/ConfirmationBuilder.java | 20 +- .../towny/exceptions/TownyException.java | 5 +- .../towny/listeners/TownyPlayerListener.java | 2 +- .../bukkit/towny/object/Translatable.java | 55 +-- .../bukkit/towny/object/Translation.java | 22 +- .../bukkit/towny/utils/MoneyUtil.java | 3 +- .../bukkit/towny/utils/TownyComponents.java | 24 +- 10 files changed, 316 insertions(+), 175 deletions(-) diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/TownyMessaging.java b/Towny/src/main/java/com/palmergames/bukkit/towny/TownyMessaging.java index 87de13988a..c91246dc1c 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/TownyMessaging.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/TownyMessaging.java @@ -21,6 +21,7 @@ import com.palmergames.util.Pair; import com.palmergames.util.StringMgmt; +import net.kyori.adventure.text.ComponentLike; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bukkit.Bukkit; @@ -39,10 +40,13 @@ import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.generator.WorldInfo; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import java.util.Optional; /** * Towny message handling class @@ -51,6 +55,7 @@ * */ +@SuppressWarnings("unused") public class TownyMessaging { private static final Logger LOGGER = LogManager.getLogger("Towny"); private static final Logger LOGGER_DEBUG = LogManager.getLogger("com.palmergames.bukkit.towny.debug"); @@ -76,9 +81,9 @@ public static void sendErrorMsg(String msg) { /** * Sends an Error message (red) to the Player or console and to the named Dev if * DevMode is enabled. - * + *

* Uses default_towny_prefix. - * + *

* If msg is empty nothing will be sent. * * @param sender the Object sending the message @@ -88,24 +93,7 @@ public static void sendErrorMsg(Object sender, String msg) { if (sender == null || msg.isEmpty()) return; - if (sender instanceof CommandSender toSend) { - sendMessage(toSend, Translatable.of("default_towny_prefix").stripColors(sender instanceof ConsoleCommandSender).append(Colors.Red + msg).forLocale(toSend)); - } else if (sender instanceof TownyObject townySender) { - if (townySender instanceof Resident resident) { - // Resident - sendMessage(resident, Translation.of("default_towny_prefix") + Colors.Red + msg); - } else if (townySender instanceof Town town) { - // Town - sendPrefixedTownMessage(town, Colors.Red + msg); - } else if (townySender instanceof Nation nation) { - // Nation - sendPrefixedNationMessage(nation, Colors.Red + msg); - } - } else { - sendErrorMsg(String.format("Unsupported TownyMessaging#sendErrorMsg sender class type: %s", sender.getClass().getName())); - } - - sendDevMsg(msg); + sendErrorMsg(sender, TownyComponents.miniMessage(msg)); } /** @@ -120,8 +108,6 @@ public static void sendMsg(String msg) { } /** - * Towny's main endpoint for Towny-Prefixed messages. - * * Sends a message (green) to the Player or console * and to the named Dev if DevMode is enabled. * Uses default_towny_prefix @@ -133,28 +119,24 @@ public static void sendMsg(CommandSender sender, String msg) { if (sender == null || msg.isEmpty()) return; - if (sender instanceof Player p) { - sendMessage(p, Translatable.of("default_towny_prefix").forLocale(p) + Colors.LightGreen + msg); - } else if (sender instanceof ConsoleCommandSender) { - sendMessage(sender, Translatable.of("default_towny_prefix").stripColors(true).defaultLocale() + Colors.strip(msg)); - } else { - sendMessage(sender, Translatable.of("default_towny_prefix").forLocale(sender) + Colors.LightGreen + msg); - } - - sendDevMsg(msg); + sendMsg(sender, TownyComponents.miniMessage(msg)); + } + + public static void sendDevMsg(final String msg) { + sendDevMsg(TownyComponents.miniMessage(msg)); } /** * Sends a message (red) to the named Dev (if DevMode is enabled) * Uses default_towny_prefix * - * @param msg the message to be sent + * @param message the message to be sent */ - public static void sendDevMsg(String msg) { + public static void sendDevMsg(final ComponentLike message) { if (TownySettings.isDevMode()) { Player townyDev = BukkitTools.getPlayerExact(TownySettings.getDevName()); if (townyDev != null) - sendMessage(townyDev, Translatable.of("default_towny_prefix").forLocale(townyDev) + " DevMode: " + Colors.Red + msg); + sendMessage(townyDev, Component.translatable("default_towny_prefix").append(Component.text(" DevMode: ")).append(message.asComponent().color(NamedTextColor.DARK_RED))); } } @@ -187,20 +169,14 @@ public static void sendDebugMsg(String msg) { * Send a message. This is the main end point for all String based messages * before being sent to the eventual reader. * - * @param sender the Object sending the message + * @param receiver the object receiving the message * @param line the String to send */ - public static void sendMessage(Object sender, String line) { + public static void sendMessage(Object receiver, String line) { if (line.isEmpty()) return; - if (sender instanceof Player player) { - Towny.getAdventure().player(player).sendMessage(TownyComponents.miniMessage(line)); - } else if (sender instanceof CommandSender commandSender) { - commandSender.sendMessage(Colors.strip(line)); - } else if (sender instanceof Resident resident) { - resident.sendMessage(TownyComponents.miniMessage(line)); - } + sendMessage(receiver, TownyComponents.miniMessage(line)); } /** @@ -481,7 +457,7 @@ public static void sendInvitationMessage(CommandSender player, String firstline, */ public static void sendConfirmationMessage(CommandSender sender, Confirmation confirmation) { final Translator translator = Translator.locale(sender); - Component firstLineComponent = translator.component("confirmation_prefix").append(confirmation.getTitle().locale(sender).component()); + Component firstLineComponent = translator.component("confirmation_prefix").append(confirmation.title()); Component lastLineComponent = translator.component("this_message_will_expire2", confirmation.getDuration()); // Create confirm button based on given params. @@ -534,12 +510,14 @@ public static void sendTownList(CommandSender sender, List } public static Component getPageNavigationFooter(String prefix, int page, String arg, int total, Translator translator) { + final String command = "/" + prefix + " " + (arg.isEmpty() ? "" : arg + " "); + Component backButton = Component.text("<<<", NamedTextColor.GOLD) - .clickEvent(ClickEvent.runCommand("/" + prefix + " " + (arg.isEmpty() ? "" : arg + " ") + (page - 1))) + .clickEvent(ClickEvent.runCommand(command + (page - 1))) .hoverEvent(HoverEvent.showText(translator.component("msg_hover_previous_page"))); Component forwardButton = Component.text(">>>", NamedTextColor.GOLD) - .clickEvent(ClickEvent.runCommand("/" + prefix + " " + (arg.isEmpty() ? "" : arg + " ") + (page + 1))) + .clickEvent(ClickEvent.runCommand(command + (page + 1))) .hoverEvent(HoverEvent.showText(translator.component("msg_hover_next_page"))); Component pageText = Component.text(" ").append(translator.component("list_page", page, total)).append(Component.text(" ")); @@ -601,8 +579,10 @@ public static void sendOutpostList(Player player, Town town, int page, int total for (int i = (page - 1) * 10; i < iMax; i++) { Location outpost = outposts.get(i); TownBlock tb = TownyAPI.getInstance().getTownBlock(outpost); + if (tb == null) continue; + String name = !tb.hasPlotObjectGroup() ? tb.getName() : tb.getPlotObjectGroup().getName(); TextComponent dash = Component.text(" - ", NamedTextColor.DARK_GRAY); TextComponent line = Component.text(Integer.toString(i + 1), NamedTextColor.GOLD) @@ -610,7 +590,7 @@ public static void sendOutpostList(Player player, Town town, int page, int total .append(dash); TextComponent outpostName = Component.text(name, NamedTextColor.GREEN); - TextComponent worldName = Component.text(outpost.getWorld().getName(), NamedTextColor.BLUE); + TextComponent worldName = Component.text(Optional.ofNullable(outpost.getWorld()).map(WorldInfo::getName).orElse("null"), NamedTextColor.BLUE); TextComponent coords = Component.text("(" + outpost.getBlockX() + "," + outpost.getBlockZ()+ ")", NamedTextColor.BLUE); if (!name.equalsIgnoreCase("")) { @@ -622,7 +602,7 @@ public static void sendOutpostList(Player player, Town town, int page, int total if (TownyEconomyHandler.isActive()) spawnCost = Translatable.of("msg_spawn_cost", TownyEconomyHandler.getFormattedBalance(town.getSpawnCost())); - line = line.hoverEvent(HoverEvent.showText(Translatable.of("msg_click_spawn", name.equalsIgnoreCase("") ? "outpost" : name).append("\n").append(spawnCost).locale(player).component())); + line = line.hoverEvent(HoverEvent.showText(Translatable.of("msg_click_spawn", name.equalsIgnoreCase("") ? "outpost" : name).append("\n").append(spawnCost))); outpostsFormatted[i % 10] = line; } @@ -638,10 +618,11 @@ public static void sendOutpostList(Player player, Town town, int page, int total } public static void sendJailList(Player player, Town town, int page, int total) { - Translator translator = Translator.locale(player); - int jailCount = town.getJails().size(); + final Translator translator = Translator.locale(player); + final List jails = town.getJails() == null ? new ArrayList<>() : new ArrayList<>(town.getJails()); + + int jailCount = jails.size(); int iMax = Math.min(page * 10, jailCount); - List jails = new ArrayList<>(town.getJails()); TextComponent[] jailsFormatted; @@ -671,8 +652,9 @@ public static void sendJailList(Player player, Town town, int page, int total) { if (jail.hasName()) line = line.append(dash).append(name); line = line.append(dash).append(coord).append(dash).append(cellCount); - - if (town.getPrimaryJail().getUUID().equals(jail.getUUID())) + + final Jail primaryJail = town.getPrimaryJail(); + if (primaryJail != null && primaryJail.getUUID().equals(jail.getUUID())) line = line.append(dash).append(Component.text("(Primary Jail)", NamedTextColor.RED)); jailsFormatted[i % 10] = line; @@ -734,66 +716,45 @@ public static void sendPlotGroupList(CommandSender sender, Town town, int page, audience.sendMessage(pageFooter); } - /* - * TRANSLATABLES FOLLOW - */ - /** - * Sends a message in green, prefixed by the default_towny_prefix to the sender, - * translated to the end-user's locale. - * - * @param sender CommandSender who will see the message. - * @param translatables Translatable object(s) which will be translated, joined with a space. - * @see #sendMsg(CommandSender, Translatable) - */ - public static void sendMsg(CommandSender sender, Translatable... translatables) { - sendMsg(sender, Translation.translateTranslatables(sender, translatables)); - } - - /** - * Sends a message in green, prefixed by the default_towny_prefix to the sender, - * translated to the end-user's locale. + * Send a message. This is the main end point for all component based messages + * before being sent to the eventual reader. * - * @param sender CommandSender who will see the message. - * @param translatable Translatable object which will be translated. + * @param receiver the object receiving the message + * @param message the message to send */ - public static void sendMsg(CommandSender sender, Translatable translatable) { - sendMsg(sender, translatable.locale(sender).translate()); - } + @SuppressWarnings("IfCanBeSwitch") // Newer language feature that jabel doesn't support + public static void sendMessage(final @NotNull Object receiver, final @NotNull ComponentLike message) { + final Component component = message.asComponent(); - /** - * Sends a message translated to the end-user's locale, with no prefix. - * - * @param sender CommandSender who will see the message. - * @param translatables Translatable... object(s) which will be translated. - * @see #sendMessage(CommandSender, Translatable) - */ - public static void sendMessage(CommandSender sender, Translatable... translatables) { - sendMessage(sender, Translation.translateTranslatables(sender, translatables)); + if (receiver instanceof ConsoleCommandSender) { + LOGGER.info(TownyComponents.plain(component)); + return; + } + + final Audience audience; + if (receiver instanceof CommandSender sender) + audience = Towny.getAdventure().sender(sender); + else if (receiver instanceof Audience aud) + audience = aud; + else + return; + + audience.sendMessage(component); } /** - * Sends a message translated to the end-user's locale, with no prefix. + * Sends a message in green, prefixed by the default_towny_prefix to the sender, + * translated to the end-user's locale. * * @param sender CommandSender who will see the message. - * @param translatable Translatable object which will be translated. - */ - public static void sendMessage(CommandSender sender, Translatable translatable) { - sendMessage(sender, translatable.locale(sender).translate()); - } - - /** - * Sends an Error message (red) to the sender - * and to the named Dev if DevMode is enabled. - * Uses default_towny_prefix. - * Translates to the end-user's locale. - * - * @param sender CommandSender who will receive the error message. - * @param translatables Translatable... object(s) to be translated using the locale of the end-user. - * @see #sendErrorMsg(CommandSender, Translatable) + * @param message Translatable object which will be translated. */ - public static void sendErrorMsg(CommandSender sender, Translatable... translatables) { - sendErrorMsg(sender, Translation.translateTranslatables(sender, translatables)); + public static void sendMsg(final @NotNull CommandSender sender, final @NotNull ComponentLike message) { + final Component component = message.asComponent(); + + sendMessage(sender, Component.translatable("default_towny_prefix").append(component.color(NamedTextColor.GREEN))); + sendDevMsg(component); } /** @@ -803,23 +764,46 @@ public static void sendErrorMsg(CommandSender sender, Translatable... translatab * Translates to the end-user's locale. * * @param sender CommandSender who will receive the error message. - * @param translatable Translatable object to be translated using the locale of the end-user. + * @param message message to be sent to the end-user. */ - public static void sendErrorMsg(CommandSender sender, Translatable translatable) { - sendErrorMsg(sender, translatable.locale(sender).translate()); + @SuppressWarnings("IfCanBeSwitch") + public static void sendErrorMsg(final @NotNull Object sender, final @NotNull ComponentLike message) { + final Component component = message.asComponent(); + + if (sender instanceof CommandSender toSend) { + sendMessage(toSend, Component.translatable("default_towny_prefix").append(component.color(NamedTextColor.DARK_RED))); + } else if (sender instanceof TownyObject townySender) { + if (townySender instanceof Resident resident) { + // Resident + sendMessage(resident, Component.translatable("default_towny_prefix").append(component.color(NamedTextColor.DARK_RED))); + } else if (townySender instanceof Town town) { + // Town + sendPrefixedTownMessage(town, component.color(NamedTextColor.DARK_RED)); + } else if (townySender instanceof Nation nation) { + // Nation + sendPrefixedNationMessage(nation, component.color(NamedTextColor.DARK_RED)); + } + } else { + sendErrorMsg(String.format("Unsupported TownyMessaging#sendErrorMsg sender class type: %s", sender.getClass().getName())); + } + + sendDevMsg(component); } /** - * Send a message to All online players and the log. + * Sends a message to all online players and the log. * Uses default_towny_prefix. Message is translated for the end-user. * - * @param translatable Translatable object to be messaged to the player using their locale. + * @param message message to be sent to all players using their locale. */ - public static void sendGlobalMessage(Translatable translatable) { + public static void sendGlobalMessage(ComponentLike message) { + final Component component = message.asComponent(); + for (Player player : Bukkit.getOnlinePlayers()) if (player != null && TownyAPI.getInstance().isTownyWorld(player.getWorld())) - sendMsg(player, translatable); - LOGGER.info("[Global Message] " + translatable.stripColors(true).translate()); + sendMsg(player, component); + + LOGGER.info("[Global Message] " + TownyComponents.plain(component)); } /** @@ -829,11 +813,13 @@ public static void sendGlobalMessage(Translatable translatable) { * @param nation Nation to pass the message to, prefix message with. * @param message Translatable object to be messaged to the player using their locale. */ - public static void sendPrefixedNationMessage(Nation nation, Translatable message) { - LOGGER.info(Colors.strip("[Nation Msg] " + StringMgmt.remUnderscore(nation.getName()) + ": " + message.translate())); + public static void sendPrefixedNationMessage(Nation nation, ComponentLike message) { + final Component component = message.asComponent(); + + LOGGER.info("[Nation Msg] " + StringMgmt.remUnderscore(nation.getName()) + ": " + TownyComponents.plain(component)); for (Player player : TownyAPI.getInstance().getOnlinePlayers(nation)) - sendMessage(player, Translatable.of("default_nation_prefix", StringMgmt.remUnderscore(nation.getName())).append(message)); + sendMessage(player, Translatable.of("default_nation_prefix", StringMgmt.remUnderscore(nation.getName())).append(component)); } /** @@ -843,11 +829,15 @@ public static void sendPrefixedNationMessage(Nation nation, Translatable message * @param town Town to pass the message to, and prefix message with. * @param message Translatable object to be messaged to the player using their locale. */ - public static void sendPrefixedTownMessage(Town town, Translatable message) { - LOGGER.info(Colors.strip("[Town Msg] " + StringMgmt.remUnderscore(town.getName()) + ": " + message.translate())); + public static void sendPrefixedTownMessage(Town town, ComponentLike message) { + final Component component = message.asComponent(); + final String plain = TownyComponents.plain(component); - for (Player player : TownyAPI.getInstance().getOnlinePlayers(town)) - sendMessage(player, Translatable.of("default_town_prefix", StringMgmt.remUnderscore(town.getName())).append(message)); + if (plain.isEmpty()) + return; + + LOGGER.info("[Town Msg] " + StringMgmt.remUnderscore(town.getName()) + ": " + plain); + town.sendMessage(Translatable.of("default_town_prefix", StringMgmt.remUnderscore(town.getName())).append(component)); } /** @@ -857,7 +847,7 @@ public static void sendPrefixedTownMessage(Town town, Translatable message) { * @param resident The resident. * @param message The translatable message to be sent to the resident and/or their town. */ - public static void sendPrefixedTownMessage(Resident resident, Translatable message) { + public static void sendPrefixedTownMessage(@NotNull Resident resident, @NotNull ComponentLike message) { Town town = resident.getTownOrNull(); if (town == null) @@ -873,8 +863,10 @@ public static void sendPrefixedTownMessage(Resident resident, Translatable messa * @param nation Nation which will receive the message. * @param message Translatable message to be shown to the town. */ - public static void sendNationMessagePrefixed(Nation nation, Translatable message) { - LOGGER.info(Colors.strip("[Nation Msg] " + StringMgmt.remUnderscore(nation.getName()) + ": " + message.translate())); + public static void sendNationMessagePrefixed(Nation nation, ComponentLike message) { + final Component component = message.asComponent(); + + LOGGER.info("[Nation Msg] " + StringMgmt.remUnderscore(nation.getName()) + ": " + TownyComponents.plain(component)); for (Player player : TownyAPI.getInstance().getOnlinePlayers(nation)) sendMsg(player, message); @@ -885,10 +877,12 @@ public static void sendNationMessagePrefixed(Nation nation, Translatable message * preceded by the default_towny_prefix * * @param town Town which will receive the message. - * @param message Translatable message to be shown to the town. + * @param message Message to be shown to the town. */ - public static void sendTownMessagePrefixed(Town town, Translatable message) { - LOGGER.info(Colors.strip("[Town Msg] " + StringMgmt.remUnderscore(town.getName())) + ": " + message.translate()); + public static void sendTownMessagePrefixed(Town town, ComponentLike message) { + final Component component = message.asComponent(); + + LOGGER.info("[Town Msg] " + StringMgmt.remUnderscore(town.getName()) + ": " + TownyComponents.plain(component)); for (Player player : TownyAPI.getInstance().getOnlinePlayers(town)) sendMsg(player, message); @@ -901,9 +895,10 @@ public static void sendTownMessagePrefixed(Town town, Translatable message) { * @param resident Resident to receive the message. * @param message Translatable message for the resident. */ - public static void sendMsg(Resident resident, Translatable message) { - if (resident.isOnline()) - sendMsg(resident.getPlayer(), message); + public static void sendMsg(Resident resident, ComponentLike message) { + final Player player = resident.getPlayer(); + if (player != null) + sendMsg(player, message); } /** @@ -912,8 +907,10 @@ public static void sendMsg(Resident resident, Translatable message) { * * @param message Translatable message to show the console. */ - public static void sendMsg(Translatable message) { - LOGGER.info(message.stripColors(true).translate()); + public static void sendMsg(ComponentLike message) { + final String plain = TownyComponents.plain(message.asComponent()); + if (!plain.isEmpty()) + LOGGER.info(plain); } /** @@ -922,15 +919,17 @@ public static void sendMsg(Translatable message) { * * @param message Translatable error message to show the console. */ - public static void sendErrorMsg(Translatable message) { - LOGGER.warn("Error: " + message.stripColors(true).translate()); + public static void sendErrorMsg(ComponentLike message) { + final String plain = TownyComponents.plain(message.asComponent()); + if (!plain.isEmpty()) + LOGGER.warn("Error: " + plain); } /** * Send a message to All online ops or players with towny.admin, and the log. * @param message Translatable message to send. */ - public static void sendMsgToOnlineAdmins(Translatable message) { + public static void sendMsgToOnlineAdmins(ComponentLike message) { sendMsg(message); for (Player player : Bukkit.getOnlinePlayers()) if (TownyUniverse.getInstance().getPermissionSource().isTownyAdmin(player)) @@ -982,4 +981,72 @@ public static void sendBossBarMessageToPlayer(Player player, Component component public static void sendBossBarMessageToPlayer(Player player, BossBar bossBar) { Towny.getAdventure().player(player).showBossBar(bossBar); } + + /* + * Bridge methods + */ + + private static void sendMsg$$bridge$$public(CommandSender sender, Translatable... translatables) { + sendMsg(sender, Translation.translateTranslatables(sender, translatables)); + } + + private static void sendMsg$$bridge$$public(CommandSender sender, Translatable translatable) { + sendMsg(sender, translatable); + } + + private static void sendMessage$$bridge$$public(CommandSender sender, Translatable... translatables) { + sendMessage(sender, Translation.translateTranslatables(sender, translatables)); + } + + private static void sendMessage$$bridge$$public(CommandSender sender, Translatable translatable) { + sendMessage(sender, translatable); + } + + private static void sendErrorMsg$$bridge$$public(CommandSender sender, Translatable... translatables) { + sendErrorMsg(sender, Translation.translateTranslatables(sender, translatables)); + } + + private static void sendErrorMsg$$bridge$$public(CommandSender sender, Translatable message) { + sendErrorMsg(sender, message); + } + + private static void sendGlobalMessage$$bridge$$public(Translatable message) { + sendGlobalMessage(message); + } + + private static void sendPrefixedNationMessage$$bridge$$public(Nation nation, Translatable message) { + sendPrefixedNationMessage(nation, message); + } + + private static void sendPrefixedTownMessage$$bridge$$public(Town town, Translatable message) { + sendPrefixedTownMessage(town, message); + } + + private static void sendPrefixedTownMessage$$bridge$$public(Resident resident, Translatable message) { + sendPrefixedTownMessage(resident, message); + } + + private static void sendNationMessagePrefixed$$bridge$$public(Nation nation, Translatable message) { + sendNationMessagePrefixed(nation, message); + } + + private static void sendTownMessagePrefixed$$bridge$$public(Town town, Translatable message) { + sendTownMessagePrefixed(town, message); + } + + private static void sendMsg$$bridge$$public(Resident resident, Translatable message) { + sendMsg(resident, message); + } + + private static void sendMsg$$bridge$$public(Translatable message) { + sendMsg(message); + } + + private static void sendErrorMsg$$bridge$$public(Translatable message) { + sendErrorMsg(message); + } + + private static void sendMsgToOnlineAdmins$$bridge$$public(Translatable message) { + sendMsgToOnlineAdmins(message); + } } diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyAdminCommand.java b/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyAdminCommand.java index b9682c116d..a948028dc2 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyAdminCommand.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/command/TownyAdminCommand.java @@ -66,6 +66,7 @@ import io.papermc.lib.PaperLib; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; @@ -2207,23 +2208,23 @@ private void adminToggleRegenerations(CommandSender sender, Optional ch private void adminToggleDevMode(CommandSender sender, Optional choice) throws NoPermissionException { checkPermOrThrow(sender, PermissionNodes.TOWNY_COMMAND_TOWNYADMIN_TOGGLE_DEVMODE.getNode()); TownySettings.setDevMode(choice.orElse(!TownySettings.isDevMode())); - TownyMessaging.sendMsg(sender, Translatable.of("msg_admin_toggle_devmode", (TownySettings.isDevMode() ? Translatable.literal(Colors.Green).append(Translatable.of("enabled")) : Translatable.literal(Colors.Red).append(Translatable.of("disabled"))))); } + TownyMessaging.sendMsg(sender, Translatable.of("msg_admin_toggle_devmode", (TownySettings.isDevMode() ? Component.translatable("enabled", NamedTextColor.DARK_GREEN) : Component.translatable("disabled", NamedTextColor.DARK_RED)))); } private void adminToggleDebug(CommandSender sender, Optional choice) throws NoPermissionException { checkPermOrThrow(sender, PermissionNodes.TOWNY_COMMAND_TOWNYADMIN_TOGGLE_DEBUG.getNode()); TownySettings.setDebug(choice.orElse(!TownySettings.getDebug())); TownyLogger.getInstance().refreshDebugLogger(); - TownyMessaging.sendMsg(sender, Translatable.of("msg_admin_toggle_debugmode", (TownySettings.getDebug() ? Translatable.literal(Colors.Green).append(Translatable.of("enabled")) : Translatable.literal(Colors.Red).append(Translatable.of("disabled"))))); } + TownyMessaging.sendMsg(sender, Translatable.of("msg_admin_toggle_debugmode", (TownySettings.getDebug() ? Component.translatable("enabled", NamedTextColor.DARK_GREEN) : Component.translatable("disabled", NamedTextColor.DARK_RED)))); } private void adminToggleTownWithDraw(CommandSender sender, Optional choice) throws NoPermissionException { checkPermOrThrow(sender, PermissionNodes.TOWNY_COMMAND_TOWNYADMIN_TOGGLE_TOWNWITHDRAW.getNode()); TownySettings.SetTownBankAllowWithdrawls(choice.orElse(!TownySettings.getTownBankAllowWithdrawls())); - TownyMessaging.sendMsg(sender, Translatable.of("msg_admin_toggle_townwithdraw", (TownySettings.getTownBankAllowWithdrawls() ? Translatable.literal(Colors.Green).append(Translatable.of("enabled")) : Translatable.literal(Colors.Red).append(Translatable.of("disabled"))))); } + TownyMessaging.sendMsg(sender, Translatable.of("msg_admin_toggle_townwithdraw", (TownySettings.getTownBankAllowWithdrawls() ? Component.translatable("enabled", NamedTextColor.DARK_GREEN) : Component.translatable("disabled", NamedTextColor.DARK_RED)))); } private void adminToggleNationWithdraw(CommandSender sender, Optional choice) throws NoPermissionException { checkPermOrThrow(sender, PermissionNodes.TOWNY_COMMAND_TOWNYADMIN_TOGGLE_NATIONWITHDRAW.getNode()); TownySettings.SetNationBankAllowWithdrawls(choice.orElse(!TownySettings.getNationBankAllowWithdrawls())); - TownyMessaging.sendMsg(sender, Translatable.of("msg_admin_toggle_nationwithdraw", (TownySettings.getNationBankAllowWithdrawls() ? Translatable.literal(Colors.Green).append(Translatable.of("enabled")) : Translatable.literal(Colors.Red).append(Translatable.of("disabled"))))); } + TownyMessaging.sendMsg(sender, Translatable.of("msg_admin_toggle_nationwithdraw", (TownySettings.getNationBankAllowWithdrawls() ? Component.translatable("enabled", NamedTextColor.DARK_GREEN) : Component.translatable("disabled", NamedTextColor.DARK_RED)))); } private void parseAdminToggleNPC(CommandSender sender, String[] split) throws TownyException { checkPermOrThrow(sender, PermissionNodes.TOWNY_COMMAND_TOWNYADMIN_TOGGLE_NPC.getNode()); diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/confirmations/Confirmation.java b/Towny/src/main/java/com/palmergames/bukkit/towny/confirmations/Confirmation.java index 6440f5b587..37f46f6b26 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/confirmations/Confirmation.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/confirmations/Confirmation.java @@ -2,6 +2,8 @@ import com.palmergames.bukkit.towny.event.CancellableTownyEvent; import com.palmergames.bukkit.towny.object.Translatable; +import com.palmergames.bukkit.towny.utils.TownyComponents; +import net.kyori.adventure.text.Component; /** * An object which stores information about confirmations. While this @@ -14,13 +16,13 @@ public class Confirmation { private final Runnable acceptHandler; private final Runnable cancelHandler; - private final Translatable title; + private final Component title; private final int duration; private final ConfirmationTransaction transaction; private final String confirmCommand; private final String cancelCommand; private final boolean isAsync; - private String pluginPrefix; + private final String pluginPrefix; private final CancellableTownyEvent event; /** @@ -91,9 +93,21 @@ public Runnable getCancelHandler() { * Gets the title of the confirmation message. * * @return The title of the confirmation message. + * @deprecated Deprecated as of todo insert version, Confirmation titles can now be any component + * @see #title() */ + @Deprecated public Translatable getTitle() { - return title; + return Translatable.literal(TownyComponents.plain(this.title)); + } + + /** + * Gets the title of the confirmation message. + * + * @return The title of the confirmation message. + */ + public Component title() { + return this.title; } /** diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/confirmations/ConfirmationBuilder.java b/Towny/src/main/java/com/palmergames/bukkit/towny/confirmations/ConfirmationBuilder.java index b22fd8c148..501483c6e4 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/confirmations/ConfirmationBuilder.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/confirmations/ConfirmationBuilder.java @@ -1,6 +1,9 @@ package com.palmergames.bukkit.towny.confirmations; import com.palmergames.bukkit.towny.object.Translatable; +import com.palmergames.bukkit.towny.utils.TownyComponents; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; import org.bukkit.command.CommandSender; import com.palmergames.bukkit.towny.TownySettings; @@ -12,7 +15,7 @@ public class ConfirmationBuilder { Runnable acceptHandler; Runnable cancelHandler; - Translatable title = Translatable.of("are_you_sure_you_want_to_continue"); + Component title = Component.translatable("are_you_sure_you_want_to_continue"); String confirmCommand = TownySettings.getConfirmCommand(); String cancelCommand = TownySettings.getCancelCommand(); String pluginPrefix = "towny"; @@ -34,20 +37,29 @@ public ConfirmationBuilder runOnCancel(Runnable cancelHandler) { /** * Sets the title of the confirmation to be sent. + * The input string will be converted to a component using minimessage serialization. * * @param title The title of the confirmation. * @return A builder reference of this object. */ public ConfirmationBuilder setTitle(String title) { - this.title = Translatable.literal(title); + this.title = TownyComponents.miniMessage(title); return this; } - public ConfirmationBuilder setTitle(Translatable title) { - this.title = title; + public ConfirmationBuilder setTitle(ComponentLike title) { + this.title = title.asComponent(); return this; } + /** + * TODO insert version + */ + @SuppressWarnings("unused") + private ConfirmationBuilder setTitle$$bridge$$public(Translatable title) { + return setTitle(title); + } + /** * Sets the duration the confirmation will run for. * diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/exceptions/TownyException.java b/Towny/src/main/java/com/palmergames/bukkit/towny/exceptions/TownyException.java index da1617010f..08a218428e 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/exceptions/TownyException.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/exceptions/TownyException.java @@ -36,9 +36,10 @@ public String getMessage() { return (String) message; } + // TODO can probably be exposed as a component to avoid instantly translating public String getMessage(CommandSender sender) { - if (message instanceof Translatable) - return ((Translatable) message).translate(Translation.getLocale(sender)); + if (message instanceof Translatable translatable) + return translatable.translate(Translation.getLocale(sender)); else return (String) message; } diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyPlayerListener.java b/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyPlayerListener.java index 20ab67ec74..3b85c46270 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyPlayerListener.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/listeners/TownyPlayerListener.java @@ -169,7 +169,7 @@ private void sendSafeModeMessage(Player player) { Translatable tipMsg = player.isOp() || player.hasPermission("towny.admin") ? Translatable.of("msg_safe_mode_admin") : Translatable.of("msg_safe_mode_player"); - TownyMessaging.sendErrorMsg(player, Translatable.of("msg_safe_mode_base"), tipMsg); + TownyMessaging.sendErrorMsg(player, Translatable.of("msg_safe_mode_base").append(" ").append(tipMsg)); } catch (Exception e) { // Safemode is affecting Towny's ability to use Translatables. String msg = player.isOp() || player.hasPermission("towny.admin") diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java index c3449bf5c3..867337aeef 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java @@ -4,19 +4,20 @@ import com.palmergames.bukkit.util.Colors; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Locale; +import java.util.stream.Collectors; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class Translatable implements ComponentLike { private String key; - private Object[] args; + private final List args = new ArrayList<>(); private boolean stripColors; private final List appended = new ArrayList<>(0); private Locale locale; @@ -27,7 +28,7 @@ private Translatable(String key) { private Translatable(String key, Object... args) { this.key = key; - this.args = args; + this.args.addAll(TownyComponents.convert(args)); } public static Translatable of(String key) { @@ -38,6 +39,9 @@ public static Translatable of(String key, Object... args) { return new Translatable(key, args); } + /** + * @deprecated As of TODO insert version, components and translatables can be used interchangeably so this is no longer needed. + */ @Deprecated public static Translatable literal(String text) { return new LiteralTranslatable(text); @@ -48,11 +52,11 @@ public String key() { } public Object[] args() { - return args; + return new Object[]{}; // todo impl } /** - * @deprecated Deprecated as of x, the locale is no longer guaranteed to be preserved due to Translatable now implementing ComponentLike. + * @deprecated Deprecated TODO insert version, the locale is no longer guaranteed to be preserved due to Translatable now implementing ComponentLike. */ @Nullable public Locale locale() { @@ -87,7 +91,8 @@ public Translatable key(String key) { } public Translatable args(Object... args) { - this.args = args; + this.args.clear(); + this.args.addAll(TownyComponents.convert(args)); return this; } @@ -114,7 +119,7 @@ public Translatable append(Translatable translatable) { // TODO: insert deprecation version in javadocs /** - * @deprecated Deprecated as of x, the locale is no longer guaranteed to be preserved due to Translatable now implementing ComponentLike. + * @deprecated Deprecated TODO insert version, the locale is no longer guaranteed to be preserved due to Translatable now implementing ComponentLike. * @see #translate(Locale) * @see #component(Locale) */ @@ -125,7 +130,7 @@ public Translatable locale(@Nullable Locale locale) { } /** - * @deprecated Deprecated as of x, the locale is no longer guaranteed to be preserved due to Translatable now implementing ComponentLike. + * @deprecated Deprecated TODO insert version, the locale is no longer guaranteed to be preserved due to Translatable now implementing ComponentLike. * @see #translate(Locale) * @see #component(Locale) */ @@ -135,7 +140,7 @@ public Translatable locale(@NotNull Resident resident) { } /** - * @deprecated Deprecated as of x, the locale is no longer guaranteed to be preserved due to Translatable now implementing ComponentLike. + * @deprecated Deprecated TODO insert version, the locale is no longer guaranteed to be preserved due to Translatable now implementing ComponentLike. * @see #translate(Locale) * @see #component(Locale) */ @@ -153,7 +158,13 @@ public String translate() { translateArgs(this.locale); String translated; - if (args == null) + + // TODO - dum + Object[] args = new String[this.args.size()]; + for (int i = 0; i < this.args.size(); i++) + args[i] = MiniMessage.miniMessage().serialize(this.args.get(i).asComponent()); + + if (args.length == 0) translated = locale == null ? Translation.of(key) : Translation.of(key, locale); else translated = locale == null ? Translation.of(key, args) : Translation.of(key, locale, args); @@ -169,7 +180,7 @@ public Component component(@NotNull Locale locale) { } public Component component() { - return TownyComponents.miniMessage(translate()); + return Translation.render(this.asComponent(), this.locale == null ? Translation.getDefaultLocale() : this.locale); } public String forLocale(Resident resident) { @@ -185,12 +196,9 @@ public String defaultLocale() { } private void translateArgs(@Nullable Locale locale) { - if (args == null) - return; - - for (int i = 0; i < args.length; i++) - if (args[i] instanceof Translatable) - args[i] = ((Translatable) args[i]).locale(locale).translate(); + for (int i = 0; i < args.size(); i++) + if (args.get(i) instanceof Translatable translatable) + args.set(i, translatable.locale(locale).component()); } @Override @@ -203,7 +211,7 @@ public String toString() { public String debug() { return "Translatable{" + "key='" + key + '\'' + - ", args=" + Arrays.toString(args) + + ", args=[" + this.args.stream().map(comp -> MiniMessage.miniMessage().serialize(comp.asComponent())).collect(Collectors.joining(", ")) + "]" + ", stripColors=" + stripColors + ", appended=" + appended() + ", locale=" + locale + @@ -212,15 +220,12 @@ public String debug() { @Override public @NotNull Component asComponent() { - List formattedArgs = new ArrayList<>(); - if (this.args != null) { - for (Object arg : this.args) - formattedArgs.add(arg instanceof ComponentLike like ? like.asComponent() : TownyComponents.miniMessage(arg.toString())); - } - - return Component.translatable(this.key(), formattedArgs); + return Component.translatable(this.key(), this.args); } + /** + * @deprecated As of TODO insert version, components and translatables can be used interchangeably so this is no longer needed. + */ @Deprecated private static final class LiteralTranslatable extends Translatable { diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java index 3b38d16c36..7dd2963cf5 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java @@ -12,6 +12,7 @@ import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.renderer.ComponentRenderer; import net.kyori.adventure.text.renderer.TranslatableComponentRenderer; import net.kyori.adventure.translation.Translator; @@ -25,10 +26,12 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.IllegalFormatException; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.logging.Level; @@ -282,11 +285,26 @@ private static class TownyTranslator implements Translator { @Override public @Nullable Component translate(@NotNull TranslatableComponent component, @NotNull Locale locale) { - final String translated = of(component.key(), locale, component.args()); + // TODO - dum + // will need some sort of fancy formatter for the %s in components or use messageformat + Object[] args = new String[component.args().size()]; + for (int i = 0; i < component.args().size(); i++) + args[i] = MiniMessage.miniMessage().serialize(component.args().get(i).asComponent()); + + final List children = new ArrayList<>(component.children()); + for (int i = 0; i < children.size(); i++) { + if (children.get(i) instanceof TranslatableComponent child) { + final Component newChild = translate(child, locale); + if (newChild != null) + children.set(i, newChild); + } + } + + final String translated = of(component.key(), locale, args); if (translated.equals(component.key())) return null; - return TownyComponents.miniMessage(translated); + return TownyComponents.miniMessage(translated).children(children); } } } \ No newline at end of file diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/MoneyUtil.java b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/MoneyUtil.java index 51ee6b3acf..d59373f5e6 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/MoneyUtil.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/MoneyUtil.java @@ -3,6 +3,7 @@ import com.palmergames.bukkit.towny.object.Translatable; import java.io.File; +import net.kyori.adventure.text.Component; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -83,7 +84,7 @@ public static void townDeposit(Player player, Resident resident, Town town, Nati } else { // Deposit into town from a nation member. resident.getAccount().payTo(amount, town, "Town Deposit from Nation member"); - TownyMessaging.sendPrefixedNationMessage(nation, Translatable.of("msg_xx_deposited_xx", resident.getName(), amount, Translatable.literal(town.getName() + " ").append(Translatable.of("town_sing")))); + TownyMessaging.sendPrefixedNationMessage(nation, Translatable.of("msg_xx_deposited_xx", resident.getName(), amount, Component.text(town.getName() + " ").append(Translatable.of("town_sing")))); } BukkitTools.fireEvent(new TownTransactionEvent(town, transaction)); diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/TownyComponents.java b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/TownyComponents.java index fe7d6f9465..2f16ba11f2 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/TownyComponents.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/TownyComponents.java @@ -1,15 +1,19 @@ package com.palmergames.bukkit.towny.utils; +import com.palmergames.bukkit.towny.object.Translation; import com.palmergames.bukkit.util.Colors; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.kyori.adventure.text.minimessage.tag.standard.StandardTags; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import net.kyori.adventure.translation.GlobalTranslator; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; import java.util.List; /** @@ -25,7 +29,7 @@ public static Component miniMessage(@NotNull String string) { } public static String plain(@NotNull Component component) { - return PlainTextComponentSerializer.plainText().serialize(component); + return PlainTextComponentSerializer.plainText().serialize(GlobalTranslator.render(component, Translation.getDefaultLocale())); } /** @@ -81,4 +85,22 @@ public static Component joinList(List components, Component delimiter return full; } + + /** + * Converts an array of objects to a list of components, via toString if needed and minimessage deserialization. + * @param objects The objects to convert + * @return The formatted list of components + */ + public static List convert(final @NotNull Object @NotNull... objects) { + final List components = new ArrayList<>(); + + for (Object object : objects) { + if (object instanceof ComponentLike like) + components.add(like.asComponent()); + else + components.add(miniMessage(object.toString())); + } + + return components; + } } From 8d08c20af39c939a1b2a5c7ae8f3b5f4c5e66e02 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Wed, 26 Apr 2023 18:24:37 +0200 Subject: [PATCH 04/12] Use flattener for translating components --- .../bukkit/towny/object/Translation.java | 91 +++++++++++++++---- 1 file changed, 71 insertions(+), 20 deletions(-) diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java index 7dd2963cf5..84fc72d13f 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java @@ -6,13 +6,15 @@ import com.palmergames.bukkit.towny.TownyUniverse; import com.palmergames.bukkit.towny.command.HelpMenu; import com.palmergames.bukkit.towny.event.TranslationLoadEvent; -import com.palmergames.bukkit.towny.utils.TownyComponents; import com.palmergames.bukkit.util.BukkitTools; import com.palmergames.bukkit.util.Colors; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TranslatableComponent; -import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.flattener.ComponentFlattener; +import net.kyori.adventure.text.flattener.FlattenerListener; +import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.renderer.ComponentRenderer; import net.kyori.adventure.text.renderer.TranslatableComponentRenderer; import net.kyori.adventure.translation.Translator; @@ -26,7 +28,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.text.MessageFormat; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -35,6 +36,8 @@ import java.util.Locale; import java.util.Map; import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; /** @@ -44,10 +47,12 @@ public final class Translation { private Translation() {} + private static final Pattern LOCALIZATION_PATTERN = Pattern.compile("%(?:(\\d+)\\$)?s"); private static Map> translations = new HashMap<>(); private static final TranslatableComponentRenderer renderer = TranslatableComponentRenderer.usingTranslationSource(new TownyTranslator()); private static Locale defaultLocale = new Locale("en", "US"); // en-US here by default, in case of safe mode happening before translations are loaded. private static final Locale englishLocale = new Locale("en", "US"); // Our last-ditch fall back locale. + private static final Map flatteners = new HashMap<>(); public static void loadTranslationRegistry() { translations.clear(); @@ -271,6 +276,50 @@ public static void addTranslations(Map> addedTransla } } + private static ComponentFlattener flattener(final @NotNull Locale locale) { + return flatteners.computeIfAbsent(locale, k -> ComponentFlattener.basic().toBuilder() + // Thanks to Paper for already having a flattener for translatables using the %s style + .complexMapper(TranslatableComponent.class, (translatable, consumer) -> { + final String translated = of(translatable.key(), locale); + + final Matcher matcher = LOCALIZATION_PATTERN.matcher(translated); + final List args = translatable.args(); + int argPosition = 0; + int lastIdx = 0; + while (matcher.find()) { + // append prior + if (lastIdx < matcher.start()) { + consumer.accept(Component.text(translated.substring(lastIdx, matcher.start()))); + } + lastIdx = matcher.end(); + + final String argIdx = matcher.group(1); + // calculate argument position + if (argIdx != null) { + try { + final int idx = Integer.parseInt(argIdx) - 1; + if (idx < args.size()) { + consumer.accept(args.get(idx)); + } + } catch (final NumberFormatException ex) { + // ignore, drop the format placeholder + } + } else { + final int idx = argPosition++; + if (idx < args.size()) { + consumer.accept(args.get(idx)); + } + } + } + + // append tail + if (lastIdx < translated.length()) { + consumer.accept(Component.text(translated.substring(lastIdx))); + } + }) + .build()); + } + private static class TownyTranslator implements Translator { @Override @@ -285,26 +334,28 @@ private static class TownyTranslator implements Translator { @Override public @Nullable Component translate(@NotNull TranslatableComponent component, @NotNull Locale locale) { - // TODO - dum - // will need some sort of fancy formatter for the %s in components or use messageformat - Object[] args = new String[component.args().size()]; - for (int i = 0; i < component.args().size(); i++) - args[i] = MiniMessage.miniMessage().serialize(component.args().get(i).asComponent()); + final TextComponent.Builder builder = Component.text(); - final List children = new ArrayList<>(component.children()); - for (int i = 0; i < children.size(); i++) { - if (children.get(i) instanceof TranslatableComponent child) { - final Component newChild = translate(child, locale); - if (newChild != null) - children.set(i, newChild); + flattener(locale).flatten(component, new FlattenerListener() { + Style currentStyle = Style.empty(); + + @Override + public void pushStyle(@NotNull Style style) { + currentStyle = style; } - } - - final String translated = of(component.key(), locale, args); - if (translated.equals(component.key())) - return null; + + @Override + public void component(@NotNull String text) { + builder.append(Component.text(text, currentStyle)); + } + + @Override + public void popStyle(@NotNull Style style) { + currentStyle = Style.empty(); + } + }); - return TownyComponents.miniMessage(translated).children(children); + return builder.build(); } } } \ No newline at end of file From 5c9becc1a3fbc95ec1f1f7711d60c32e8a201a96 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Wed, 26 Apr 2023 18:34:10 +0200 Subject: [PATCH 05/12] Make Translatable#translate less dum --- .../bukkit/towny/object/Translatable.java | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java index 867337aeef..f37679f7c6 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java @@ -155,23 +155,11 @@ public String translate(@NotNull Locale locale) { } public String translate() { - translateArgs(this.locale); + final Component translated = component(); - String translated; - - // TODO - dum - Object[] args = new String[this.args.size()]; - for (int i = 0; i < this.args.size(); i++) - args[i] = MiniMessage.miniMessage().serialize(this.args.get(i).asComponent()); - - if (args.length == 0) - translated = locale == null ? Translation.of(key) : Translation.of(key, locale); - else - translated = locale == null ? Translation.of(key, args) : Translation.of(key, locale, args); - - translated += appended(); - - return stripColors ? Colors.strip(translated) : translated; + return this.stripColors + ? TownyComponents.plain(translated) + : MiniMessage.miniMessage().serialize(translated); } public Component component(@NotNull Locale locale) { @@ -194,12 +182,6 @@ public String forLocale(CommandSender sender) { public String defaultLocale() { return translate(Translation.getDefaultLocale()); } - - private void translateArgs(@Nullable Locale locale) { - for (int i = 0; i < args.size(); i++) - if (args.get(i) instanceof Translatable translatable) - args.set(i, translatable.locale(locale).component()); - } @Override public String toString() { From 6da2e2cd59c2fde11f45fcee8388526768c6abc2 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Wed, 26 Apr 2023 18:39:14 +0200 Subject: [PATCH 06/12] Re-implement args method --- .../com/palmergames/bukkit/towny/object/Translatable.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java index f37679f7c6..d85def86b1 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java @@ -52,7 +52,12 @@ public String key() { } public Object[] args() { - return new Object[]{}; // todo impl + Object[] arr = new Object[args.size()]; + + for (int i = 0; i < args.size(); i++) + arr[i] = args.get(i); + + return arr; } /** From 50c0df40125a6da04b66e1a2780f8138744db725 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Wed, 26 Apr 2023 19:38:33 +0200 Subject: [PATCH 07/12] Undeprecate locale methods on Translatable --- .../bukkit/towny/object/Translatable.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java index d85def86b1..af94e27fd3 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java @@ -61,7 +61,7 @@ public Object[] args() { } /** - * @deprecated Deprecated TODO insert version, the locale is no longer guaranteed to be preserved due to Translatable now implementing ComponentLike. + * @return The set locale that this translatable will be translated in. */ @Nullable public Locale locale() { @@ -121,21 +121,22 @@ public Translatable append(Translatable translatable) { return this; } - // TODO: insert deprecation version in javadocs - /** - * @deprecated Deprecated TODO insert version, the locale is no longer guaranteed to be preserved due to Translatable now implementing ComponentLike. + * Sets the locale that will be used if {@link #translate()} or {@link #component()} is invoked without a locale. + * If this translatable is converted to a {@link Component} the locale will be lost however, so it is encouraged to chain this with one of the translation methods. + * * @see #translate(Locale) * @see #component(Locale) */ - @Deprecated public Translatable locale(@Nullable Locale locale) { this.locale = locale; return this; } /** - * @deprecated Deprecated TODO insert version, the locale is no longer guaranteed to be preserved due to Translatable now implementing ComponentLike. + * Sets the locale that will be used if {@link #translate()} or {@link #component()} is invoked without a locale to the locale of the given resident. + * If this translatable is converted to a {@link Component} the locale will be lost however, so it is encouraged to chain this with one of the translation methods. + * * @see #translate(Locale) * @see #component(Locale) */ @@ -145,7 +146,9 @@ public Translatable locale(@NotNull Resident resident) { } /** - * @deprecated Deprecated TODO insert version, the locale is no longer guaranteed to be preserved due to Translatable now implementing ComponentLike. + * Sets the locale that will be used if {@link #translate()} or {@link #component()} is invoked without a locale to the locale of the given command sender. + * If this translatable is converted to a {@link Component} the locale will be lost however, so it is encouraged to chain this with one of the translation methods. + * * @see #translate(Locale) * @see #component(Locale) */ From 9a7b9bcc9cecb3de7e83aeb93df3009731021b65 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Wed, 26 Apr 2023 21:46:08 +0200 Subject: [PATCH 08/12] Handle MM tags in translations & fallback style --- .../com/palmergames/bukkit/towny/object/Translation.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java index 84fc72d13f..9b61446cc0 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translation.java @@ -6,6 +6,7 @@ import com.palmergames.bukkit.towny.TownyUniverse; import com.palmergames.bukkit.towny.command.HelpMenu; import com.palmergames.bukkit.towny.event.TranslationLoadEvent; +import com.palmergames.bukkit.towny.utils.TownyComponents; import com.palmergames.bukkit.util.BukkitTools; import com.palmergames.bukkit.util.Colors; import net.kyori.adventure.key.Key; @@ -289,7 +290,7 @@ private static ComponentFlattener flattener(final @NotNull Locale locale) { while (matcher.find()) { // append prior if (lastIdx < matcher.start()) { - consumer.accept(Component.text(translated.substring(lastIdx, matcher.start()))); + consumer.accept(TownyComponents.miniMessage(translated.substring(lastIdx, matcher.start())).applyFallbackStyle(translatable.style())); } lastIdx = matcher.end(); @@ -314,7 +315,7 @@ private static ComponentFlattener flattener(final @NotNull Locale locale) { // append tail if (lastIdx < translated.length()) { - consumer.accept(Component.text(translated.substring(lastIdx))); + consumer.accept(TownyComponents.miniMessage(translated.substring(lastIdx)).applyFallbackStyle(translatable.style())); } }) .build()); From c847bef4157c8aba1c759176dd4918ad212bbf77 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Wed, 26 Apr 2023 21:46:32 +0200 Subject: [PATCH 09/12] Change return type to ComponentLike --- .../com/palmergames/bukkit/towny/utils/TownyComponents.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/TownyComponents.java b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/TownyComponents.java index 2f16ba11f2..27981c6066 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/utils/TownyComponents.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/utils/TownyComponents.java @@ -91,12 +91,12 @@ public static Component joinList(List components, Component delimiter * @param objects The objects to convert * @return The formatted list of components */ - public static List convert(final @NotNull Object @NotNull... objects) { - final List components = new ArrayList<>(); + public static List convert(final @NotNull Object @NotNull... objects) { + final List components = new ArrayList<>(); for (Object object : objects) { if (object instanceof ComponentLike like) - components.add(like.asComponent()); + components.add(like); else components.add(miniMessage(object.toString())); } From ec168e22950cf18e141477451e297a95a42012b1 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Wed, 14 Jun 2023 22:59:54 +0200 Subject: [PATCH 10/12] Add new static method for creating a translatable --- .../palmergames/bukkit/towny/object/Translatable.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java index af94e27fd3..cc3071eacc 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java @@ -32,10 +32,18 @@ private Translatable(String key, Object... args) { } public static Translatable of(String key) { - return new Translatable(key); + return translatable(key); } public static Translatable of(String key, Object... args) { + return translatable(key, args); + } + + public static Translatable translatable(String key) { + return new Translatable(key); + } + + public static Translatable translatable(String key, Object... args) { return new Translatable(key, args); } From ae53b4e48b3fd9e627ac18ecb71cbb2ec32b55a8 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Wed, 14 Jun 2023 23:25:04 +0200 Subject: [PATCH 11/12] Remove LiteralTranslatable class --- .../bukkit/towny/object/Translatable.java | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java index cc3071eacc..1376bdb7c7 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java @@ -52,7 +52,12 @@ public static Translatable translatable(String key, Object... args) { */ @Deprecated public static Translatable literal(String text) { - return new LiteralTranslatable(text); + return new Translatable(text) { + @Override + public String translate() { + return stripColors() ? Colors.strip(key() + appended()) : key() + appended(); + } + }; } public String key() { @@ -220,24 +225,4 @@ public String debug() { public @NotNull Component asComponent() { return Component.translatable(this.key(), this.args); } - - /** - * @deprecated As of TODO insert version, components and translatables can be used interchangeably so this is no longer needed. - */ - @Deprecated - private static final class LiteralTranslatable extends Translatable { - - private LiteralTranslatable(String key) { - super(key); - } - - private LiteralTranslatable(String key, Object... args) { - super(key, args); - } - - @Override - public String translate() { - return stripColors() ? Colors.strip(key() + appended()) : key() + appended(); - } - } } From 861daac6e78191fba2eab33a515a7fb6a4e6ef77 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Thu, 15 Jun 2023 00:09:59 +0200 Subject: [PATCH 12/12] Fix appending --- .../bukkit/towny/object/Translatable.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java index 1376bdb7c7..4894658642 100644 --- a/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java +++ b/Towny/src/main/java/com/palmergames/bukkit/towny/object/Translatable.java @@ -11,6 +11,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.ComponentLike; import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.translation.GlobalTranslator; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -19,7 +20,7 @@ public class Translatable implements ComponentLike { private String key; private final List args = new ArrayList<>(); private boolean stripColors; - private final List appended = new ArrayList<>(0); + private final List appended = new ArrayList<>(); private Locale locale; private Translatable(String key) { @@ -91,14 +92,8 @@ public String appended() { StringBuilder converted = new StringBuilder(); - for (Object object : this.appended) { - if (object instanceof String string) - converted.append(string); - else if (object instanceof Translatable translatable) - converted.append(translatable.locale(this.locale).translate()); - else if (object instanceof Component component) - converted.append(TownyComponents.toLegacy(component)); - } + for (ComponentLike object : this.appended) + converted.append(MiniMessage.miniMessage().serialize(GlobalTranslator.render(object.asComponent(), this.locale == null ? Translation.getDefaultLocale() : this.locale))); return converted.toString(); } @@ -120,7 +115,7 @@ public Translatable stripColors(boolean strip) { } public Translatable append(String append) { - appended.add(append); + appended.add(Component.text(append)); return this; } @@ -223,6 +218,10 @@ public String debug() { @Override public @NotNull Component asComponent() { - return Component.translatable(this.key(), this.args); + return Component.translatable() + .key(this.key) + .args(this.args) + .append(this.appended) + .build(); } }