diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/GroupConfigSubSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/GroupConfigSubSettingGui.java index 045fdcbe..ec8f8617 100644 --- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/GroupConfigSubSettingGui.java +++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/GroupConfigSubSettingGui.java @@ -83,7 +83,7 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen ItemMeta selectItemMeta = selectItem.getItemMeta(); assert selectItemMeta != null; - ComponentUtil.INSTANCE.setMessageName(selectItemMeta, materialSelectionName); + ComponentUtil.INSTANCE.setMessageName(selectItemMeta, materialSelectionName, name); selectItem.setItemMeta(selectItemMeta); this.materialSelection = new GuiItem(selectItem, (event) -> { @@ -100,7 +100,7 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen ItemMeta selectGroupMeta = selectGroup.getItemMeta(); assert selectGroupMeta != null; - ComponentUtil.INSTANCE.setMessageName(selectGroupMeta, selectGroupName); + ComponentUtil.INSTANCE.setMessageName(selectGroupMeta, selectGroupName, name); selectGroup.setItemMeta(selectGroupMeta); this.groupSelection = new GuiItem(selectGroup, (event) -> { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DebugToggleExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DebugToggleExecutor.kt index 5c7bfee6..d42c8d22 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DebugToggleExecutor.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DebugToggleExecutor.kt @@ -9,11 +9,16 @@ import net.md_5.bungee.api.chat.hover.content.Text import org.bukkit.command.Command import org.bukkit.command.CommandSender import org.bukkit.entity.Player +import xyz.alexcrea.cuanvil.lang.Lang import xyz.alexcrea.cuanvil.lang.Message import xyz.alexcrea.cuanvil.lang.MsgCommand +import xyz.alexcrea.cuanvil.lang.MsgError +import xyz.alexcrea.cuanvil.lang.MsgUI +import xyz.alexcrea.cuanvil.lang.MsgWarning import xyz.alexcrea.cuanvil.util.ComponentUtil.serializePlain +import java.util.Locale -class DebugToggleExecutor : CASubCommand { +class DebugToggleExecutor: CASubCommand { override fun description(): Message { return MsgCommand.DEBUG_DESCRIPTION @@ -51,6 +56,10 @@ class DebugToggleExecutor : CASubCommand { MsgCommand.DEBUG_LOG_CLEARED.send(sender) } + "lang" -> { + executeLanguageDebug(sender, args) + } + else -> { MsgCommand.SHARED_UNKNOWN_SUB_COMMAND.send(sender) return false @@ -106,14 +115,142 @@ class DebugToggleExecutor : CASubCommand { } } + private fun executeLanguageDebug(sender: CommandSender, args: Array) { + if(args.size > 1 && "details".contentEquals(args[1], ignoreCase = true)) + detailedLangDebug(sender) + else + simpleLangDebug(sender) + + } + + private fun simpleLangDebug(sender: CommandSender) { + var validCount = 0 + // load key from all provider class + MsgCommand.DEBUG_DATA_HEADER + MsgUI.SHARED_CONFIG_NO_EDIT_PERM + MsgError.LOAD_LISTENERS + MsgWarning.ANVIL_GENERIC_EXCEPTION + + val registeredKeys = Message.getValues() + + for(message in registeredKeys) + if(Lang.has(message.key)) validCount++ + + val valid = (100.0 * validCount) / registeredKeys.size + sender.sendMessage("Translated (${Lang.currentLang()}): ${"%.1f".format(Locale.ROOT, valid)}% ($validCount/${registeredKeys.size})") + } + + private fun detailedLangDebug(sender: CommandSender) { + simpleLangDebug(sender) + + val stb = StringBuilder("Report of potential issue for language ${Lang.currentLang()}:\n") + + var hadAny = false + val keySet = mutableSetOf() + val registeredKeys = Message.getValues() + + for(message in registeredKeys) { + val key = message.key + if(!Lang.has(key)) { + stb.append("Missing key inside translation file: $key\n") + hadAny = true + } else if(hashParamIssue(message, stb)) + hadAny = true + + if(keySet.contains(key)) { + stb.append("Duplicate registered key: $key\n") + hadAny = true + } else keySet.add(key) + } + + for(key in Lang.getKeys()) { + if(!keySet.contains(key)) { + stb.append("Found unregistered key: $key\n") + hadAny = true + } + } + + if(hadAny) { + val message = TextComponent(MsgCommand.DEBUG_LANG_COPY.legacy()) + + message.clickEvent = ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, stb.toString()) + message.hoverEvent = HoverEvent( + HoverEvent.Action.SHOW_TEXT, + Text(MsgCommand.SHARED_HOVER_COPY.legacy()) + ) + + sender.spigot().sendMessage(message); + } else { + sender.sendMessage("No additional issue found") + } + } + + private fun hashParamIssue(message: Message, stb: StringBuilder): Boolean { + val section = Lang.getSection(message.key) + val texts = if(section == null) + listOf(Lang.getTranslated(message.key)) + else + section.getValues(false).map { it.value.toString() } + + val textParams = ArrayList() + + for(text in texts) { + var index = 0 + while(true) { + index = text.indexOf('%', index) + //TODO add \% to "ignore" % as param inside param finder + if(index > 0 && text[index-1] == '\\') { + index++ + continue + } + if(index++ < 0) break + + var end = text.indexOf(' ', index) + if(end == -1) end = text.length + val param = text.substring(index, end) + if(!textParams.contains(param)) textParams.add(param) + } + } + + var hadIssue = false + // Check all parameter are valid + val usedParam = mutableSetOf() + for(textParam in textParams) { + var found = false + for(param in message.params) { + if(param.isEmpty()) continue + if(textParam.startsWith(param)) { + found = true + usedParam.add(param) + break + } + } + + if(!found) { + hadIssue = true + stb.append("Did not found param %$textParam in register list for ${message.key}\n") + } + } + + for(param in message.params) { + if(usedParam.contains(param)) continue + if("unused".contentEquals(param)) continue + hadIssue = true + stb.append("Param %$param is not used for key ${message.key}\n") + } + + return hadIssue + } + override fun tabCompleter(sender: CommandSender, args: Array, list: MutableList) { if(!allowed(sender)) return list.addAll( when(args.size) { - 1 -> listOf("toggle", "get", "get-and-clear", "clear") + 1 -> listOf("toggle", "get", "get-and-clear", "clear", "lang") 2 -> when(args[0].lowercase()) { "toggle" -> listOf("default", "verbose") + "lang" -> listOf("details") else -> listOf() } diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt index e12aed14..8c22d880 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt @@ -3,7 +3,6 @@ package xyz.alexcrea.cuanvil.dependency import io.delilaheve.CustomAnvil import net.kyori.adventure.text.Component import org.bukkit.Bukkit -import org.bukkit.ChatColor import org.bukkit.command.CommandSender import org.bukkit.entity.HumanEntity import org.bukkit.entity.Player @@ -32,7 +31,6 @@ import xyz.alexcrea.cuanvil.lang.MsgWarning import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_OUTPUT_SLOT import xyz.alexcrea.cuanvil.util.MetricsUtil.trackError import java.lang.IllegalStateException -import java.util.logging.Level @Suppress("UnstableApiUsage") object DependencyManager { @@ -166,7 +164,7 @@ object DependencyManager { trackError(e) // Finally, warn the player - MsgWarning.DEPENDENCY_GENERIC_EXCEPTION.send(target) + MsgWarning.ANVIL_GENERIC_EXCEPTION.send(target) } private fun logExceptionAndClear(view: AnvilView, e: Exception) { diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Lang.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Lang.kt index f164b9af..b559af19 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Lang.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Lang.kt @@ -3,6 +3,7 @@ package xyz.alexcrea.cuanvil.lang import io.delilaheve.CustomAnvil import org.bukkit.configuration.ConfigurationSection import xyz.alexcrea.cuanvil.config.ConfigHolder +import java.util.stream.Stream object Lang { @@ -51,6 +52,18 @@ object Lang { return default.getSection(key) } + fun currentLang(): String { + return lang.name + } + + fun has(key: String): Boolean { + return lang.has(key) + } + + fun getKeys(): Collection { + return lang.getFilteredKeys() + } + /* * Config Options & get */ diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Language.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Language.kt index 0a008bf0..88b36b99 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Language.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Language.kt @@ -55,6 +55,45 @@ class Language(private val id: String, private val default: Boolean = false) { return conf.getConfigurationSection(key) } + fun has(key: String): Boolean { + if(conf.isString(key)) return true + + // we want at all child key as valid numbers and valid key if claimed to be multi line + val section = getSection(key) ?: return false + for(key in section.getKeys(false)) { + if(key.toUIntOrNull() == null) return false + if(!section.isString(key)) return false + } + + return true + } + + fun getFilteredKeys(): Collection { + val result = ArrayList() + + // First pass we ignore key from root + for(root in conf.getKeys(false)) { + val section = conf.getConfigurationSection(root) ?: continue + + exploreDeeper(section, root, result) + } + + return result + } + + private fun exploreDeeper(section: ConfigurationSection, root: String, result: ArrayList) { + for(key in section.getKeys(false)) { + val newRoot = "$root.$key" + if(has(key)) { + result.add(newRoot) + continue + } + + val newSection = section.getConfigurationSection(key) ?: continue + exploreDeeper(newSection, newRoot, result) + } + } + val name: String get() = conf.getString("name", id)!! } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Message.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Message.kt index 640ae297..64d9242d 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Message.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Message.kt @@ -10,10 +10,23 @@ import xyz.alexcrea.cuanvil.util.ComponentUtil.send import xyz.alexcrea.cuanvil.util.ComponentUtil.serializeLegacy import xyz.alexcrea.cuanvil.util.ComponentUtil.serializePlain import xyz.alexcrea.cuanvil.util.MiniMessageUtil +import java.util.Collections import java.util.logging.Level import kotlin.math.min -open class Message(val key: String, vararg val params: String) { +open class Message(val key: String, vararg val params: String, register: Boolean = true) { + + companion object { + private val values = ArrayList() + + fun getValues(): Collection { + return Collections.unmodifiableCollection(values) + } + } + + init { + if(register) values.add(this) + } protected fun replaceParameters(stb: StringBuilder, vararg values: Any) { // replace all placeholder thingy %key -> value diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Msg.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Msg.kt deleted file mode 100644 index 7823f79d..00000000 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/Msg.kt +++ /dev/null @@ -1,4 +0,0 @@ -package xyz.alexcrea.cuanvil.lang - -object Msg { -} \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgCommand.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgCommand.kt index 2eed326b..d135634e 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgCommand.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgCommand.kt @@ -20,6 +20,7 @@ object MsgCommand { val DEBUG_LOG_CLEARED = Message("debug.log-cleared") val DEBUG_TOGGLED = Message("debug.toggled", "type") val DEBUG_COPY = Message("debug.copy") + val DEBUG_LANG_COPY = Message("debug.copy-lang") val DEBUG_DATA_HEADER = Message("debug.data.header") val DEBUG_DATA_LINE_COUNT = Message("debug.data.line-count", "count") @@ -45,8 +46,8 @@ object MsgCommand { // Enchant val ENCHANT_DESCRIPTION = Message("enchant.description") - val ENCHANT_REMOVE = Message("enchant.remove.", "name") - val ENCHANT_SET = Message("enchant.set.", "name", "level") + val ENCHANT_REMOVE = Message("enchant.removed", "name") + val ENCHANT_SET = Message("enchant.set", "name", "level") val ENCHANT_MISSING_PARAMETER_WARNING = Message("enchant.warning.missing_parameter") val ENCHANT_NOT_FOUND_WARNING = Message("enchant.warning.not_found", "path") diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgError.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgError.kt index f0b32e26..f5d6e826 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgError.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgError.kt @@ -16,7 +16,7 @@ object MsgError { 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") + val RELOAD_HARD_FAIL = ErrorMessage("reload.resource.hard-fail") /* * ---- diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgUI.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgUI.kt index 6036069e..1a876e6e 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgUI.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgUI.kt @@ -16,44 +16,44 @@ object MsgUI { val ELEMENT_LIST_CANCELLED_NEW = Message("element-list.cancelled-new", "type") val ELEMENT_LIST_DUPLICATED_NEW = Message("element-list.duplicated-new", "type") - val UNIT_REPAIR_TITLE = Message("unit-repair.title") - val UNIT_REPAIR_ELEMENT_TITLE = Message("unit-repair.element_title") + val UNIT_REPAIR_TITLE = Message("unit-repair.title", "unused", "page", "max_page") + val UNIT_REPAIR_ELEMENT_TITLE = Message("unit-repair.element.title", "type", "page", "max_page") val UNIT_REPAIR_NEW_TITLE = Message("unit-repair.new.title") val UNIT_REPAIR_NEW_DESCRIPTION = Message("unit-repair.new.description") - val UNIT_REPAIR_NEW_ELEMENT_TITLE = Message("unit-repair.element.new.title", "name") + val UNIT_REPAIR_NEW_ELEMENT_TITLE = Message("unit-repair.element.new.title", "unused") val UNIT_REPAIR_NEW_ELEMENT_DESCRIPTION = Message("unit-repair.element.new.description", "name") val UNIT_REPAIR_NEW_ELEMENT_CANNOT_REPAIR = Message("unit-repair.element.new.cannot-damage") val UNIT_REPAIR_NEW_ELEMENT_SAME_TYPE = Message("unit-repair.element.new.same-type") - val CUSTOM_RECIPE_TITLE = Message("custom-recipe.title") + val CUSTOM_RECIPE_TITLE = Message("custom-recipe.title", "unused", "page", "max_page") val CUSTOM_RECIPE_ELEMENT_DELETE_TITLE = Message("custom-recipe.element.delete.title", "type") - val CUSTOM_RECIPE_ELEMENT_DELETE_DESCRIPTION = Message("custom-recipe.element.delete.description", "type") + val CUSTOM_RECIPE_ELEMENT_DELETE_DESCRIPTION = Message("custom-recipe.element.delete.description", "unused") - val ENCHANTMENT_LEVEL_COST_TITLE = Message("enchant-level-cost.title") + val ENCHANTMENT_LEVEL_COST_TITLE = Message("enchant-level-cost.title", "unused", "page", "max_page") - val ENCHANTMENT_LEVEL_LIMIT_TITLE = Message("enchant-level-limit.title") + val ENCHANTMENT_LEVEL_LIMIT_TITLE = Message("enchant-level-limit.title", "unused", "page", "max_page") - val ENCHANTMENT_MERGE_LIMIT_TITLE = Message("enchant-merge-limit.title") + val ENCHANTMENT_MERGE_LIMIT_TITLE = Message("enchant-merge-limit.title", "unused", "page", "max_page") - val ENCHANTMENT_CONFLICT_TITLE = Message("enchant-conflict.title") + val ENCHANTMENT_CONFLICT_TITLE = Message("enchant-conflict.title", "unused", "page", "max_page") val ENCHANTMENT_CONFLICT_ELEMENT_ENCHANTMENTS = Message("enchant-conflict.element.selected-enchantments", "group") val ENCHANTMENT_CONFLICT_ELEMENT_SUB_GROUPS = Message("enchant-conflict.element.selected-sub-groups", "group") val ENCHANTMENT_CONFLICT_ELEMENT_DELETE_TITLE = Message("enchant-conflict.element.delete.title", "type") - val ENCHANTMENT_CONFLICT_ELEMENT_DELETE_DESCRIPTION = Message("enchant-conflict.element.delete.description", "type") + val ENCHANTMENT_CONFLICT_ELEMENT_DELETE_DESCRIPTION = Message("enchant-conflict.element.delete.description", "unused") - val MATERIAL_GROUP_TITLE = Message("material-group.title") + val MATERIAL_GROUP_TITLE = Message("material-group.title", "unused", "page", "max_page") val MATERIAL_GROUP_ELEMENT_SELECTED_MATERIALS = Message("material-group.element.selected-materials", "group") val MATERIAL_GROUP_ELEMENT_SELECTED_SUB_GROUPS = Message("material-group.element.selected-sub-groups", "group") val MATERIAL_GROUP_ELEMENT_DELETE_TITLE = Message("material-group.element.delete.title", "type") - val MATERIAL_GROUP_ELEMENT_DELETE_DESCRIPTION = Message("material-group.element.delete.description", "type") + val MATERIAL_GROUP_ELEMENT_DELETE_DESCRIPTION = Message("material-group.element.delete.description", "unused") val MATERIAL_GROUP_ELEMENT_DELETE_BUTTON_NAME = Message("material-group.element.delete.button.name") val MATERIAL_GROUP_ELEMENT_DELETE_BUTTON_LORE = Message("material-group.element.delete.button.lore") - val MATERIAL_SELECT_CONFIRM_TITLE = Message("material-select.confirm.title","name") - val MATERIAL_SELECT_CONFIRM_DESCRIPTION = Message("material-select.confirm.description","name") + val MATERIAL_SELECT_CONFIRM_TITLE = Message("material-select.new.confirm.title","name") + val MATERIAL_SELECT_CONFIRM_DESCRIPTION = Message("material-select.new.confirm.description","name") } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgWarning.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgWarning.kt index 486f589d..fb9d6e64 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgWarning.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/lang/MsgWarning.kt @@ -15,6 +15,6 @@ object MsgWarning { val LOAD_LEGACY_SPIGOT = Message("load.legacy.spigot") val LOAD_LEGACY_SPIGOT_OLD = Message("load.legacy.spigot-old") - val DEPENDENCY_GENERIC_EXCEPTION = Message("config-ui.shared.no-permission") + val ANVIL_GENERIC_EXCEPTION = Message("anvil.generic") } \ No newline at end of file diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/ComponentUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/ComponentUtil.kt index bd8014a6..261e085a 100644 --- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/ComponentUtil.kt +++ b/src/main/kotlin/xyz/alexcrea/cuanvil/util/ComponentUtil.kt @@ -41,8 +41,8 @@ object ComponentUtil { meta.lore = this.map {obj -> obj.serializeLegacy()} } - fun ItemMeta.setMessageName(message: Message) { - this.setComponentDisplayName(message.formattedConcatenated()) + fun ItemMeta.setMessageName(message: Message, vararg params: Any) { + this.setComponentDisplayName(message.formattedConcatenated(*params)) } } \ No newline at end of file diff --git a/src/main/resources/lang/en.yml b/src/main/resources/lang/en.yml index 26d7efbc..87527ea1 100644 --- a/src/main/resources/lang/en.yml +++ b/src/main/resources/lang/en.yml @@ -14,6 +14,8 @@ warning: 1: "If replace too expensive is not working this is likely because of spigot" 2: "As native nms is not supported for spigot starting 26.1" update.available: "An update may be available: %version" + anvil: + generic: "[CustomAnvil] Error while handling the anvil." error: load: @@ -53,6 +55,7 @@ command: log-cleared: "Log Cleared" toggled: "Debug toggled to %type" copy: "Click to copy log data" + copy-lang: "Click to copy detailed lang issues" data: header: "Debug Log data:" line-count: "Found %count lines" @@ -122,14 +125,14 @@ config-ui: duplicated-new: "Please enter a %type name that do not already exist..." unit-repair: - tile: "Unit Repair Config" + title: "Unit Repair Config (%page/%max_page)" new: title: "Select unit repair item." description: 1: "Click here with an item to set the item" 2: "You like to be an unit repair item" element: - title: "%type Unit repair" + title: "%type Unit repair (%page/%max_page)" new: title: "Select item to be repaired." description: @@ -139,23 +142,23 @@ config-ui: same-type: "Item can't repair something of the same type." custom-recipe: - title: "Custom Recipe Config" + title: "Custom Recipe Config (%page/%max_page)" element: delete: title: "Delete %type?" description: "Confirm that you want to delete this recipe." enchant-level-cost: - title: "Enchantment Level Limit" + title: "Enchantment Level Limit (%page/%max_page)" enchant-level-limit: - title: "Enchantment Level Limit" + title: "Enchantment Level Limit (%page/%max_page)" enchant-merge-limit: - title: "Enchantment Maximum Merge Level" + title: "Enchantment Maximum Merge Level (%page/%max_page)" enchant-conflict: - title: "Conflict Config" + title: "Conflict Config (%page/%max_page)" element: selected-enchantments: "%group" # likely need page and max page selected-sub-groups: "%group Groups" @@ -164,7 +167,7 @@ config-ui: description: "Confirm that you want to delete this conflict." material-group: - title: "Group Config" + title: "Group Config (%page/%max_page)" element: selected-materials: "%group Materials" selected-sub-groups: "%group Groups"