From 640bd1bfd842a39a4e3350774c4013efe63d3d3a Mon Sep 17 00:00:00 2001 From: alexcrea Date: Thu, 13 Aug 2026 13:10:22 +0200 Subject: [PATCH] better translation system --- .../{PaperSpigotUtil.kt => PlatformUtil.kt} | 17 ++- src/main/kotlin/io/delilaheve/CustomAnvil.kt | 51 ++++--- .../cuanvil/command/DiagnosticExecutor.kt | 3 +- .../kotlin/xyz/alexcrea/cuanvil/lang/Lang.kt | 53 +------- .../xyz/alexcrea/cuanvil/lang/Language.kt | 1 - .../xyz/alexcrea/cuanvil/lang/Message.kt | 128 ++++++++++++++++++ .../kotlin/xyz/alexcrea/cuanvil/lang/Msg.kt | 4 + .../xyz/alexcrea/cuanvil/lang/MsgError.kt | 26 ++++ .../xyz/alexcrea/cuanvil/lang/MsgWarning.kt | 17 +++ .../xyz/alexcrea/cuanvil/util/MetricsUtil.kt | 4 +- src/main/resources/lang/en.yml | 36 +++++ 11 files changed, 257 insertions(+), 83 deletions(-) rename nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/util/{PaperSpigotUtil.kt => PlatformUtil.kt} (88%) create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/lang/Message.kt create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/lang/Msg.kt create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgError.kt create mode 100644 src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgWarning.kt diff --git a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/util/PaperSpigotUtil.kt b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/util/PlatformUtil.kt similarity index 88% rename from nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/util/PaperSpigotUtil.kt rename to nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/util/PlatformUtil.kt index 20972bfa..3b47adaa 100644 --- a/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/util/PaperSpigotUtil.kt +++ b/nms/nms-common/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/util/PlatformUtil.kt @@ -2,7 +2,7 @@ package xyz.alexcrea.cuanvil.dependency.util import net.kyori.adventure.text.Component import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer -import org.bukkit.inventory.ItemStack +import org.bukkit.command.CommandSender import org.bukkit.inventory.meta.ItemMeta // Mostly made for paper, spigot and folia support @@ -100,4 +100,19 @@ object PlatformUtil { } } + /** + * Try to send paper component to the player + * + * @param component The used component + * @return true if sent, else otherwise + */ + fun CommandSender.sendPaperMessage(component: Component): Boolean { + if(isPaper) { + this.sendMessage(component) + return true + } + + return false + } + } diff --git a/src/main/kotlin/io/delilaheve/CustomAnvil.kt b/src/main/kotlin/io/delilaheve/CustomAnvil.kt index 8c284ef4..df000bce 100644 --- a/src/main/kotlin/io/delilaheve/CustomAnvil.kt +++ b/src/main/kotlin/io/delilaheve/CustomAnvil.kt @@ -16,7 +16,8 @@ import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry import xyz.alexcrea.cuanvil.gui.config.MainConfigGui import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant import xyz.alexcrea.cuanvil.lang.Lang -import xyz.alexcrea.cuanvil.lang.Lang.translate +import xyz.alexcrea.cuanvil.lang.MsgError +import xyz.alexcrea.cuanvil.lang.MsgWarning import xyz.alexcrea.cuanvil.listener.AnvilCloseListener import xyz.alexcrea.cuanvil.listener.AnvilResultListener import xyz.alexcrea.cuanvil.listener.ChatEventListener @@ -111,12 +112,12 @@ open class CustomAnvil : JavaPlugin() { /** * Error Logging handler */ - @JvmStatic fun logError(message: String, throwable: Throwable? = null, track: Boolean = true) { - instance.logger.log(Level.SEVERE, message, throwable) - addToLogQueue(message) + @JvmStatic fun logError(message: String, throwable: Throwable? = null, track: Boolean = true, level: Level = Level.SEVERE) { + instance.logger.log(level, message, throwable) + addToLogQueue("Error: $message") - if(track && throwable != null) { - MetricsUtil.trackError(throwable) + if(track) { + MetricsUtil.trackError(message, throwable) } } } @@ -168,7 +169,7 @@ open class CustomAnvil : JavaPlugin() { try { legacyCheck() } catch (e: Exception) { - logError("error.load.legacy.failed".translate(), e) + MsgError.LOAD_LEGACY_FAILED.log(e) if(trySafeStart()) return } @@ -177,7 +178,7 @@ open class CustomAnvil : JavaPlugin() { try { CustomAnvilCommand(this) } catch (e: Exception) { - logError("error.load.command-register".translate(), e) + MsgError.LOAD_COMMAND_REGISTER.log(e) if(trySafeStart()) return } @@ -186,7 +187,7 @@ open class CustomAnvil : JavaPlugin() { try { DependencyManager.loadDependency() } catch (e: Exception) { - logError("error.load.compatibility".translate(), e) + MsgError.LOAD_COMPATIBILITY.log(e) if(tryDirtyStart()) return } @@ -194,7 +195,7 @@ open class CustomAnvil : JavaPlugin() { try { registerListeners() } catch (e: Exception) { - logError("error.load.listeners".translate(), e) + MsgError.LOAD_LISTENERS.log(e) if(tryDirtyStart()) return } @@ -215,18 +216,15 @@ open class CustomAnvil : JavaPlugin() { val potentialPlugin = Bukkit.getPluginManager().getPlugin("UnsafeEnchantsPlus") if (potentialPlugin != null) { Bukkit.getPluginManager().disablePlugin(potentialPlugin) - logger.warning("warning.load.legacy.old-name.1".translate()) - logger.warning("warning.load.legacy.old-name.2".translate()) + MsgWarning.LOAD_LEGACY_OLD_NAME.log() } val isPaper = PlatformUtil.isPaper if(!isPaper) { - logger.warning("warning.load.legacy.spigot.1".translate()) - logger.warning("warning.load.legacy.spigot.2".translate()) - if(MinecraftVersionUtil.isTooNewForSpigot) { - logger.warning("warning.load.legacy.spigot-old.1".translate()) - logger.warning("warning.load.legacy.spigot-old.1".translate()) - } + MsgWarning.LOAD_LEGACY_SPIGOT.log() + if(MinecraftVersionUtil.isTooNewForSpigot) + MsgWarning.LOAD_LEGACY_SPIGOT_OLD.log() + } val loader = if(isPaper) "paper" else "spigot" @@ -238,13 +236,13 @@ open class CustomAnvil : JavaPlugin() { UpdateUtils.currentMinecraftVersion().toString()) .setFeatured(featured) .setOnError { - logger.log(Level.WARNING, "error.load.update.check-fail".translate(), it) + MsgError.LOAD_UPDATE_CHECK_FAIL.log(it, level = Level.WARNING, track = false) } .checkVersion { latestVer: String? -> CustomAnvil.latestVer = latestVer if(latestVer == null || version.contains(latestVer)) return@checkVersion - logger.warning("warning.load.update.available".translate(Pair("version", latestVer))) + MsgWarning.LOAD_UPDATE_AVAILABLE.log(latestVer) } } @@ -263,7 +261,7 @@ open class CustomAnvil : JavaPlugin() { try { loadEnchantmentSystem() } catch (e: Exception) { - logError("error.load.enchant-system".translate(), e) + MsgError.LOAD_ENCHANT_SYSTEM.log(e) tryDirtyStart() } } @@ -278,7 +276,7 @@ open class CustomAnvil : JavaPlugin() { // Load config if (!ConfigHolder.loadNonDefaultConfig()) { - logError("error.load.non-default-config".translate()) + MsgError.LOAD_NON_DEFAULT_CONFIG.log() server.pluginManager.disablePlugin(this) return } @@ -332,16 +330,15 @@ open class CustomAnvil : JavaPlugin() { try { val configReader = FileReader(resourceFile) yamlConfig.load(configReader) - } catch (_: Exception) { + } catch (e: Exception) { + MsgError.RELOAD_FAIL.log(e, resourceFile.path) if (hardFailSafe) { // This is important and may impact gameplay if it does not load. // Failsafe is to stop the plugin - logError("error.reload.resource.fail".translate(Pair("path", resourceFile.path))) - logError("error.reload.resource.hard-fail".translate()) + MsgError.RELOAD_HARD_FAIL.log() Bukkit.getPluginManager().disablePlugin(this) - } else { - logError("error.reload.resource.fail".translate(Pair("path", resourceFile.path))) } + return null } return yamlConfig diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt index c2293afc..4998f14d 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt @@ -122,8 +122,7 @@ class DiagnosticExecutor : CASubCommand { if (sender is HumanEntity) { if (hasError) - sender.spigot() - .sendMessage(TextComponent(ChatColor.RED.toString() + "There was an error running the diagnostic")) + sender.sendMessage(ChatColor.RED.toString() + "There was an error running the diagnostic") val message = TextComponent(ChatColor.GREEN.toString() + "Click to copy diagnostic data") message.clickEvent = ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, stb.toString()) diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Lang.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Lang.kt index b89ca6bd..db8a2b29 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Lang.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Lang.kt @@ -18,60 +18,13 @@ object Lang { if(default != lang) default.reload() } - fun String.translate(): String { - val value = lang.get(this) + fun getTranslated(key: String): String { + val value = lang.get(key) if(value != null) return value CustomAnvil.log("Missing language data for ${lang.name} using default") - return default.get(this) ?: this - } - - fun String.translate(vararg params: Pair): String { - return translate( - params.asSequence() - .map { Pair(it.first, it.second.toString()) } - .toMap() - ) - } - - fun String.translate(vararg params: Pair): String { - return translate( - params.asSequence() - .map { Pair(it.first.toString(), it.second.toString()) } - .toMap() - ) - } - - fun String.translate(vararg params: Pair): String { - return translate(params.toMap()) - } - - fun String.translate(vararg params: Pair): String { - return translate( - params.asSequence() - .map { Pair(it.first.toString(), it.second) } - .toMap() - ) - } - - fun String.translate(params: Map): String { - val builder = StringBuilder(translate()) - - // replace all placeholder thingy %key -> value - for((key, replacement) in params) { - var current = 0 - while(true) { - current = builder.indexOf('%', current) + 1 - if(current < 0 || current + key.length > builder.length) break // may be able to be removed if bound checked in startsWith ? - if(!builder.startsWith(key, current, false)) continue - - builder.replace(current - 1, current + key.length, replacement) - current = current - 1 + replacement.length - } - } - - return builder.toString() + return default.get(key) ?: key } /* diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Language.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Language.kt index d06f9aba..d6054ba2 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Language.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Language.kt @@ -7,7 +7,6 @@ import java.io.File import java.io.InputStreamReader import java.util.logging.Level - class Language(private val id: String, private val default: Boolean = false) { private val resourcePath: String diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Message.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Message.kt new file mode 100644 index 00000000..be4b8f41 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Message.kt @@ -0,0 +1,128 @@ +package xyz.alexcrea.cuanvil.lang + +import io.delilaheve.CustomAnvil +import net.kyori.adventure.text.Component +import org.bukkit.command.CommandSender +import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.sendPaperMessage +import xyz.alexcrea.cuanvil.util.MiniMessageUtil +import java.util.logging.Level +import kotlin.math.min + +interface MessageLike { + + fun log(vararg params: Any) + + fun send(destination: CommandSender, vararg params: Any) +} + +enum class MessageType { + DEFAULT, + WARNING, + ERROR, +} + +open class Message(val key: String, vararg val params: String) : MessageLike { + + protected fun replaceParameters(stb: StringBuilder, vararg values: Any) { + // replace all placeholder thingy %key -> value + + for(i in 0 until min(params.size, values.size)) { + val key = params[i] + val replacement = values[i].toString() + + var current = 0 + while(true) { + current = stb.indexOf('%', current) + 1 + if(current < 0 || current + key.length > stb.length) break // may be able to be removed if bound checked in startsWith ? + if(!stb.startsWith(key, current, false)) continue + + stb.replace(current - 1, current + key.length, replacement) + current = current - 1 + replacement.length + } + } + } + + fun unformatted(vararg params: Any): String { + val translated = Lang.getTranslated(key) + if(params.isEmpty()) return translated + + val stb = StringBuilder(translated) + replaceParameters(stb, *params) + return stb.toString() + } + + fun formatted(vararg params: Any): Component { + val unformatted = unformatted(*params) + + return MiniMessageUtil.mm.deserialize(unformatted) + } + + override fun log(vararg params: Any) { + val text = unformatted(*params) + + CustomAnvil.instance.logger.info(text) + } + + override fun send(destination: CommandSender, vararg params: Any) { + val message = formatted(*params) + if(!destination.sendPaperMessage(message)) + destination.sendMessage(MiniMessageUtil.legacy_mm.serialize(message)) + } +} + +class WarningMessage(key: String, vararg params: String) : Message("warning.$key", *params) { + + override fun log(vararg params: Any) { + val text = unformatted(*params) + + CustomAnvil.instance.logger.warning(text) + } + +} + +class ErrorMessage(key: String, vararg params: String) : Message("error.$key", *params) { + + override fun log(vararg params: Any) { + val text = unformatted(*params) + + CustomAnvil.logError(text) + } + + fun log(e: Throwable, vararg params: Any, level: Level = Level.SEVERE, track: Boolean = true) { + val text = unformatted(*params) + + CustomAnvil.logError(text, e, track, level) + } +} + +class MultiLineMessage(type: MessageType, baseKey: String, count: Int, vararg params: String) : MessageLike { + + private val messages = ArrayList() + + init { + for(i in 1 until count + 1) { + val message = createNew(type, "$baseKey.$i", *params) + messages.add(message) + } + } + + private fun createNew(type: MessageType, key: String, vararg params: String): MessageLike { + return when(type) { + MessageType.DEFAULT -> Message(key, *params) + MessageType.WARNING -> WarningMessage(key, *params) + MessageType.ERROR -> ErrorMessage(key, *params) + } + } + + override fun log(vararg params: Any) { + for(message in messages) { + message.log(*params) + } + } + + override fun send(destination: CommandSender, vararg params: Any) { + for(message in messages) { + message.send(destination, *params) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Msg.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Msg.kt new file mode 100644 index 00000000..7823f79d --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Msg.kt @@ -0,0 +1,4 @@ +package xyz.alexcrea.cuanvil.lang + +object Msg { +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgError.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgError.kt new file mode 100644 index 00000000..6e5e26fb --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgError.kt @@ -0,0 +1,26 @@ +package xyz.alexcrea.cuanvil.lang + +object MsgError { + + /* + * ----------------- + * Load and reload + * ----------------- + */ + val LOAD_UPDATE_CHECK_FAIL = ErrorMessage("load.update.check-fail") + val LOAD_LEGACY_FAILED = ErrorMessage("load.legacy.failed") + val LOAD_COMMAND_REGISTER = ErrorMessage("load.command-register") + val LOAD_COMPATIBILITY = ErrorMessage("load.compatibility") + val LOAD_LISTENERS = ErrorMessage("load.listeners") + val LOAD_ENCHANT_SYSTEM = ErrorMessage("load.enchant-system") + val LOAD_NON_DEFAULT_CONFIG = ErrorMessage("load.non-default-config") + + val RELOAD_FAIL = ErrorMessage("reload.resource.fail", "path") + val RELOAD_HARD_FAIL = ErrorMessage("reload.resource.hardfail") + + /* + * ---------- + * Commands + * ---------- + */ +} diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgWarning.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgWarning.kt new file mode 100644 index 00000000..aa7af304 --- /dev/null +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgWarning.kt @@ -0,0 +1,17 @@ +package xyz.alexcrea.cuanvil.lang + +object MsgWarning { + + /* + * --------------- + * Load and reload + * --------------- + */ + val LOAD_UPDATE_AVAILABLE = WarningMessage("load.update.available", "version") + + val LOAD_LEGACY_OLD_NAME = MultiLineMessage(MessageType.WARNING, "load.legacy.old-name", 2) + val LOAD_LEGACY_SPIGOT = MultiLineMessage(MessageType.WARNING, "load.legacy.spigot", 2) + val LOAD_LEGACY_SPIGOT_OLD = MultiLineMessage(MessageType.WARNING, "load.legacy.spigot", 2) + + +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt index 1763db56..88004112 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/MetricsUtil.kt @@ -74,8 +74,8 @@ object MetricsUtil { lastError = e } - fun trackError(message: String) { - ERROR_TRACKER?.trackError(message) + fun trackError(message: String, cause: Throwable? = null) { + trackError(RuntimeException(message, cause)) } } diff --git a/src/main/resources/lang/en.yml b/src/main/resources/lang/en.yml index d466dd53..b0c96009 100644 --- a/src/main/resources/lang/en.yml +++ b/src/main/resources/lang/en.yml @@ -1,4 +1,5 @@ name: English +last-updated: 2.1.0 warning: load: @@ -27,3 +28,38 @@ error: resource: fail: "Resource %path Could not be loaded or reloaded." hard-fail: "Disabling plugin." + +command: + shared: + no-diag-permission: "You do not have permission to diagnostic this server" + # I try to avoid using & for color but this is for a bungee text component + hover-copy: "§7Click to copy" + warning: + missing-subcmd: "Need to specify a subcommand. for example %example1 or %example2" + root: + warning: + unknown-sub: "Invalid subcommand. run `%command help` to see available commands" + error: + generic: "Error running this command" + debug: + description: "Used to toggle debug logs and retrieve them" + log-cleared: "Log Cleared" + toggled: "Debug toggled to %type" + # I try to avoid using & for color but this is for a bungee text component + copy: "§aClick to copy log data" + data: + header: "Debug Log data:" + line-count: "Found %count lines" + warning: + unspecified-type: "Need to specify which type of debug to toggle: \"default\" or \"verbose\"" + no-log: "No log to show ? make sure you tried with debug log toggled (%command)" + diagnostic: + description: "Basic diagnostic of this plugin" + had-error: "There was an error running the diagnostic" + # I try to avoid using & for color but this is for a bungee text component + copy: "§aClick to copy diagnostic data" + config: + description: "Used to edit the configuration of the plugin" + + warning: + legacy-name: "/ca gui has been moved to /ca config" \ No newline at end of file