add lang debug command and fix found translation issue

This commit is contained in:
alexcrea 2026-08-17 13:54:41 +02:00
parent f373cae100
commit fbd3bf2eb2
Signed by: alexcrea
GPG key ID: E59DF23EE2A2266C
13 changed files with 240 additions and 40 deletions

View file

@ -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) -> {

View file

@ -9,9 +9,14 @@ 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 {
@ -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<out String>) {
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<String>()
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<String>()
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<String>()
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<out String>, list: MutableList<String>) {
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()
}

View file

@ -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) {

View file

@ -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<String> {
return lang.getFilteredKeys()
}
/*
* Config Options & get
*/

View file

@ -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<String> {
val result = ArrayList<String>()
// 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<String>) {
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)!!
}

View file

@ -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<Message>()
fun getValues(): Collection<Message> {
return Collections.unmodifiableCollection(values)
}
}
init {
if(register) values.add(this)
}
protected fun replaceParameters(stb: StringBuilder, vararg values: Any) {
// replace all placeholder thingy %key -> value

View file

@ -1,4 +0,0 @@
package xyz.alexcrea.cuanvil.lang
object Msg {
}

View file

@ -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")

View file

@ -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")
/*
* ----

View file

@ -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")
}

View file

@ -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")
}

View file

@ -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))
}
}

View file

@ -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: "<white>[<yellow>CustomAnvil<white>] <red>Error while handling the anvil."
error:
load:
@ -53,6 +55,7 @@ command:
log-cleared: "Log Cleared"
toggled: "Debug toggled to %type"
copy: "<green>Click to copy log data"
copy-lang: "<green>Click to copy detailed lang issues"
data:
header: "Debug Log data:"
line-count: "Found %count lines"
@ -122,14 +125,14 @@ config-ui:
duplicated-new: "<red>Please enter a %type name that do not already exist..."
unit-repair:
tile: "Unit Repair Config"
title: "Unit Repair Config <reset>(%page/%max_page)"
new:
title: "Select unit repair item."
description:
1: "<gray>Click here with an item to set the item"
2: "<gray>You like to be an unit repair item"
element:
title: "<yellow>%type <red>Unit repair"
title: "<yellow>%type <red>Unit repair <reset>(%page/%max_page)"
new:
title: "Select item to be repaired."
description:
@ -139,23 +142,23 @@ config-ui:
same-type: "<red>Item can't repair something of the same type."
custom-recipe:
title: "Custom Recipe Config"
title: "Custom Recipe Config <reset>(%page/%max_page)"
element:
delete:
title: "<red>Delete <yellow>%type<red>?"
description: "<gray>Confirm that you want to delete this recipe."
enchant-level-cost:
title: "<dark_gray>Enchantment Level Limit"
title: "<dark_gray>Enchantment Level Limit <reset>(%page/%max_page)"
enchant-level-limit:
title: "<dark_gray>Enchantment Level Limit"
title: "<dark_gray>Enchantment Level Limit <reset>(%page/%max_page)"
enchant-merge-limit:
title: "<dark_gray>Enchantment Maximum Merge Level"
title: "<dark_gray>Enchantment Maximum Merge Level <reset>(%page/%max_page)"
enchant-conflict:
title: "Conflict Config"
title: "Conflict Config <reset>(%page/%max_page)"
element:
selected-enchantments: "<yellow>%group<dark_purple>" # likely need page and max page
selected-sub-groups: "<yellow>%group <red>Groups"
@ -164,7 +167,7 @@ config-ui:
description: "<gray>Confirm that you want to delete this conflict."
material-group:
title: "Group Config"
title: "Group Config <reset>(%page/%max_page)"
element:
selected-materials: "<yellow>%group <red>Materials"
selected-sub-groups: "<yellow>%group <red>Groups"