diff options
| author | Sho Sakuma <me@m1sk9.dev> | 2026-01-24 00:48:13 +0900 |
|---|---|---|
| committer | Sho Sakuma <me@m1sk9.dev> | 2026-01-24 00:48:37 +0900 |
| commit | a32fda5e0cf79c4e443290d149ddd3f76a12e8b4 (patch) | |
| tree | d0556a66581883b6b7316cc0650230f90d192cf4 | |
| parent | 60a8cd9b262ae4935d0938a2cbdf5a6330ef7f3a (diff) | |
| download | LunaticChat-a32fda5e0cf79c4e443290d149ddd3f76a12e8b4.tar.gz LunaticChat-a32fda5e0cf79c4e443290d149ddd3f76a12e8b4.tar.bz2 LunaticChat-a32fda5e0cf79c4e443290d149ddd3f76a12e8b4.zip | |
refactor: Changed to simple key management
8 files changed, 158 insertions, 155 deletions
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt index 9664340..7dcc84e 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt @@ -4,9 +4,13 @@ import dev.m1sk9.lunaticChat.engine.converter.GoogleIMEClient import dev.m1sk9.lunaticChat.paper.command.core.CommandRegistry import dev.m1sk9.lunaticChat.paper.command.handler.DirectMessageHandler import dev.m1sk9.lunaticChat.paper.command.impl.DirectMessageNoticeToggleCommand +import dev.m1sk9.lunaticChat.paper.command.impl.LunaticChatCommand import dev.m1sk9.lunaticChat.paper.command.impl.ReplyCommand import dev.m1sk9.lunaticChat.paper.command.impl.RomajiConvertToggleCommand import dev.m1sk9.lunaticChat.paper.command.impl.TellCommand +import dev.m1sk9.lunaticChat.paper.command.setting.SettingHandlerRegistry +import dev.m1sk9.lunaticChat.paper.command.setting.handler.DirectMessageNoticeSettingHandler +import dev.m1sk9.lunaticChat.paper.command.setting.handler.JapaneseConversionSettingHandler import dev.m1sk9.lunaticChat.paper.common.SpyPermissionManager import dev.m1sk9.lunaticChat.paper.common.UpdateCheckResult import dev.m1sk9.lunaticChat.paper.common.UpdateChecker @@ -187,6 +191,25 @@ class LunaticChat : private fun registerCommands(configuration: LunaticChatConfiguration) { commandRegistry = CommandRegistry(this) + // Initialize setting handler registry for /lc setting command + val settingHandlerRegistry = SettingHandlerRegistry() + + // Register setting handlers + settingHandlerRegistry.register( + DirectMessageNoticeSettingHandler(playerSettingsManager!!, languageManager), + ) + + if (configuration.features.japaneseConversion.enabled) { + settingHandlerRegistry.register( + JapaneseConversionSettingHandler(playerSettingsManager!!, languageManager), + ) + } + + // Register new /lc command with setting subcommand + commandRegistry.register( + LunaticChatCommand(this, settingHandlerRegistry, languageManager), + ) + commandRegistry.registerAll( TellCommand(this, directMessageHandler, languageManager), DirectMessageNoticeToggleCommand(this, playerSettingsManager!!, languageManager), diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/ReplyCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/ReplyCommand.kt index 0108f37..af7a32c 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/ReplyCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/ReplyCommand.kt @@ -13,7 +13,6 @@ import dev.m1sk9.lunaticChat.paper.command.core.LunaticCommand import dev.m1sk9.lunaticChat.paper.command.handler.DirectMessageHandler import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager import dev.m1sk9.lunaticChat.paper.i18n.MessageFormatter -import dev.m1sk9.lunaticChat.paper.i18n.MessageKey import io.papermc.paper.command.brigadier.CommandSourceStack import io.papermc.paper.command.brigadier.Commands import kotlinx.coroutines.runBlocking @@ -31,7 +30,7 @@ class ReplyCommand( private val languageManager: LanguageManager, ) : LunaticCommand(plugin) { override val description: String - get() = languageManager.getMessage(MessageKey.CommandDescriptionReply) + get() = languageManager.getMessage("commandDescription.reply") override fun buildCommand(): LiteralArgumentBuilder<CommandSourceStack> = Commands @@ -59,7 +58,7 @@ class ReplyCommand( dmHandler.getReplyTarget(sender) ?: return CommandResult.Failure( MessageFormatter.formatError( - languageManager.getMessage(MessageKey.ReplyTargetNotFound), + languageManager.getMessage("replyTargetNotFound"), ), ) diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/TellCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/TellCommand.kt index d958b23..1238138 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/TellCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/TellCommand.kt @@ -15,7 +15,6 @@ import dev.m1sk9.lunaticChat.paper.command.core.LunaticCommand import dev.m1sk9.lunaticChat.paper.command.handler.DirectMessageHandler import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager import dev.m1sk9.lunaticChat.paper.i18n.MessageFormatter -import dev.m1sk9.lunaticChat.paper.i18n.MessageKey import io.papermc.paper.command.brigadier.CommandSourceStack import io.papermc.paper.command.brigadier.Commands import kotlinx.coroutines.runBlocking @@ -35,7 +34,7 @@ class TellCommand( private val languageManager: LanguageManager, ) : LunaticCommand(plugin) { override val description: String - get() = languageManager.getMessage(MessageKey.CommandDescriptionTell) + get() = languageManager.getMessage("commandDescription.tell") override fun buildCommand(): LiteralArgumentBuilder<CommandSourceStack> = Commands @@ -70,14 +69,14 @@ class TellCommand( Bukkit.getPlayer(targetName) ?: return CommandResult.Failure( MessageFormatter.formatError( - languageManager.getMessage(MessageKey.TellTargetOffline(targetName)), + languageManager.getMessage("tellTargetOffline", mapOf("target" to targetName)), ), ) if (recipient.uniqueId == sender.uniqueId) { return CommandResult.Failure( MessageFormatter.formatError( - languageManager.getMessage(MessageKey.TellYourself), + languageManager.getMessage("tellYourself"), ), ) } diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/LanguageConfig.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/LanguageConfig.kt deleted file mode 100644 index cc531ab..0000000 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/LanguageConfig.kt +++ /dev/null @@ -1,58 +0,0 @@ -package dev.m1sk9.lunaticChat.paper.i18n - -import kotlinx.serialization.Serializable - -/** - * Represents the structure of a language file. - * This data class mirrors the YAML structure of language files (en.yml, ja.yml). - * - * @property commandDescription Command descriptions for various commands - * @property directMessageNoticeStatus Status message for direct message notifications - * @property directMessageNoticeToggle Toggle message for direct message notifications - * @property replyTargetNotFound Error message when reply target is not found - * @property romajiConversionStatus Status message for romaji conversion - * @property romajiConversionToggle Toggle message for romaji conversion - * @property tellTargetOffline Error message when tell target is offline - * @property tellYourself Error message when trying to message yourself - * @property toggle Toggle state messages (on/off) - */ -@Serializable -data class LanguageConfig( - val commandDescription: CommandDescriptions, - val directMessageNoticeStatus: String, - val directMessageNoticeToggle: String, - val replyTargetNotFound: String, - val romajiConversionStatus: String, - val romajiConversionToggle: String, - val tellTargetOffline: String, - val tellYourself: String, - val toggle: ToggleMessages, -) - -/** - * Command descriptions for all LunaticChat commands. - * - * @property jp Description for the /jp command - * @property notice Description for the /notice command - * @property reply Description for the /reply command - * @property tell Description for the /tell command - */ -@Serializable -data class CommandDescriptions( - val jp: String, - val notice: String, - val reply: String, - val tell: String, -) - -/** - * Toggle state messages. - * - * @property on Message for "enabled" state - * @property off Message for "disabled" state - */ -@Serializable -data class ToggleMessages( - val on: String, - val off: String, -) diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/LanguageManager.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/LanguageManager.kt index f355808..42072fa 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/LanguageManager.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/LanguageManager.kt @@ -5,11 +5,28 @@ import org.bukkit.plugin.java.JavaPlugin import java.util.logging.Logger /** - * Manages language files and provides message retrieval with type safety. + * Represents a value in a YAML structure. + */ +private sealed class YamlValue { + data class StringValue( + val value: String, + ) : YamlValue() + + data class MapValue( + val value: Map<String, YamlValue>, + ) : YamlValue() + + data class ListValue( + val value: List<YamlValue>, + ) : YamlValue() +} + +/** + * Manages language files and provides message retrieval with string-based keys. * * This class handles: * - Loading language files from resources/languages/ - * - Caching language configurations in memory + * - Caching flattened language messages in memory * - Providing message retrieval with placeholder substitution * - Fallback to English if the selected language is unavailable * @@ -22,7 +39,7 @@ class LanguageManager( private val logger: Logger, private val selectedLanguage: Language, ) { - private val languageCache = mutableMapOf<Language, LanguageConfig>() + private val languageCache = mutableMapOf<Language, Map<String, String>>() /** * Initializes the language manager by loading all language files. @@ -33,9 +50,9 @@ class LanguageManager( fun initialize() { Language.entries.forEach { lang -> try { - val config = loadLanguageFile(lang) - languageCache[lang] = config - logger.info("Loaded language file: ${lang.fileName}") + val messages = loadLanguageFile(lang) + languageCache[lang] = messages + logger.info("Loaded language file: ${lang.fileName} (${messages.size} keys)") } catch (e: Exception) { logger.warning("Failed to load ${lang.fileName}: ${e.message}") if (lang == Language.EN) { @@ -46,52 +63,125 @@ class LanguageManager( } /** - * Loads a language file from resources/languages/. + * Loads a language file from resources/languages/ and flattens it to a key-value map. * * @param language The language to load - * @return The parsed LanguageConfig + * @return A flattened map of message keys to their values * @throws IllegalStateException if the language file is not found */ - private fun loadLanguageFile(language: Language): LanguageConfig { + private fun loadLanguageFile(language: Language): Map<String, String> { val stream = plugin.getResource("languages/${language.fileName}") ?: throw IllegalStateException("Language file not found: languages/${language.fileName}") val yamlContent = stream.bufferedReader().use { it.readText() } - return Yaml.default.decodeFromString(LanguageConfig.serializer(), yamlContent) + val yamlNode = Yaml.default.parseToYamlNode(yamlContent) + + val rootMap = yamlNodeToMap(yamlNode) + return flattenYaml(rootMap) + } + + /** + * Converts a YamlNode to a type-safe YamlValue structure. + */ + private fun yamlNodeToMap(node: com.charleskorn.kaml.YamlNode): Map<String, YamlValue> = + when (node) { + is com.charleskorn.kaml.YamlMap -> { + val result = mutableMapOf<String, YamlValue>() + node.entries.forEach { entry -> + val key = entry.key.content + val value = yamlNodeToValue(entry.value) + result[key] = value + } + result + } + else -> throw IllegalStateException("Root YAML node must be a map") + } + + /** + * Converts a YamlNode to a type-safe YamlValue. + */ + private fun yamlNodeToValue(node: com.charleskorn.kaml.YamlNode): YamlValue = + when (node) { + is com.charleskorn.kaml.YamlMap -> { + val result = mutableMapOf<String, YamlValue>() + node.entries.forEach { entry -> + val key = entry.key.content + val value = yamlNodeToValue(entry.value) + result[key] = value + } + YamlValue.MapValue(result) + } + is com.charleskorn.kaml.YamlList -> { + YamlValue.ListValue(node.items.map { yamlNodeToValue(it) }) + } + is com.charleskorn.kaml.YamlScalar -> YamlValue.StringValue(node.content) + else -> YamlValue.StringValue(node.contentToString()) + } + + /** + * Flattens a nested map into dot-notation keys. + * Example: {"toggle": {"on": "有効"}} -> {"toggle.on": "有効"} + */ + private fun flattenYaml( + map: Map<String, YamlValue>, + prefix: String = "", + ): Map<String, String> { + val result = mutableMapOf<String, String>() + + map.forEach { (key, value) -> + val fullKey = if (prefix.isEmpty()) key else "$prefix.$key" + + when (value) { + is YamlValue.MapValue -> { + result.putAll(flattenYaml(value.value, fullKey)) + } + is YamlValue.StringValue -> result[fullKey] = value.value + is YamlValue.ListValue -> { + // Lists are converted to comma-separated strings for simplicity + result[fullKey] = value.value.joinToString(", ") { yamlValueToString(it) } + } + } + } + + return result } /** - * Retrieves a message for the given message key. + * Converts a YamlValue to String for flattening purposes. + */ + private fun yamlValueToString(value: YamlValue): String = + when (value) { + is YamlValue.StringValue -> value.value + is YamlValue.MapValue -> value.value.toString() + is YamlValue.ListValue -> value.value.toString() + } + + /** + * Retrieves a message for the given string key with optional placeholder substitution. * If the selected language is unavailable, falls back to English. * - * @param key The message key to retrieve - * @return The formatted message with placeholders substituted + * @param key The message key to retrieve (e.g., "commandDescription.tell" or "toggle.on") + * @param placeholders Optional map of placeholder names to their replacement values + * @return The formatted message with placeholders substituted, or the key itself if not found */ - fun getMessage(key: MessageKey): String { - val config = languageCache[selectedLanguage] ?: languageCache[Language.EN]!! - - return when (key) { - MessageKey.CommandDescriptionTell -> config.commandDescription.tell - MessageKey.CommandDescriptionReply -> config.commandDescription.reply - MessageKey.CommandDescriptionJp -> config.commandDescription.jp - MessageKey.CommandDescriptionNotice -> config.commandDescription.notice - is MessageKey.DirectMessageNoticeStatus -> - config.directMessageNoticeStatus.replace("{toggle}", key.toggle) - is MessageKey.DirectMessageNoticeToggle -> - config.directMessageNoticeToggle.replace("{toggle}", key.toggle) - is MessageKey.RomajiConversionStatus -> - config.romajiConversionStatus.replace("{toggle}", key.toggle) - is MessageKey.RomajiConversionToggle -> - config.romajiConversionToggle.replace("{toggle}", key.toggle) - MessageKey.ReplyTargetNotFound -> config.replyTargetNotFound - is MessageKey.TellTargetOffline -> - config.tellTargetOffline.replace("{target}", key.targetName) - MessageKey.TellYourself -> config.tellYourself - MessageKey.ToggleOn -> config.toggle.on - MessageKey.ToggleOff -> config.toggle.off - MessageKey.PlayerOnlyCommand -> "This command can only be executed by players." + fun getMessage( + key: String, + placeholders: Map<String, String> = emptyMap(), + ): String { + val messages = languageCache[selectedLanguage] ?: languageCache[Language.EN]!! + var message = + messages[key] ?: run { + logger.warning("Message key not found: $key") + return key + } + + // Replace placeholders + placeholders.forEach { (placeholder, value) -> + message = message.replace("{$placeholder}", value) } + + return message } /** @@ -100,5 +190,5 @@ class LanguageManager( * @param enabled The toggle state * @return The translated "on" or "off" text */ - fun getToggleText(enabled: Boolean): String = getMessage(if (enabled) MessageKey.ToggleOn else MessageKey.ToggleOff) + fun getToggleText(enabled: Boolean): String = getMessage(if (enabled) "toggle.on" else "toggle.off") } diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/MessageKey.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/MessageKey.kt deleted file mode 100644 index b6fe8dc..0000000 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/MessageKey.kt +++ /dev/null @@ -1,53 +0,0 @@ -package dev.m1sk9.lunaticChat.paper.i18n - -/** - * Type-safe message keys for LunaticChat i18n system. - * - * This sealed class ensures compile-time safety when referencing messages, - * preventing typos and missing translations. Each message key can have - * associated parameters that will be substituted in the final message. - */ -sealed class MessageKey { - // Command descriptions - data object CommandDescriptionTell : MessageKey() - - data object CommandDescriptionReply : MessageKey() - - data object CommandDescriptionJp : MessageKey() - - data object CommandDescriptionNotice : MessageKey() - - // Toggle messages (with placeholder) - data class DirectMessageNoticeStatus( - val toggle: String, - ) : MessageKey() - - data class DirectMessageNoticeToggle( - val toggle: String, - ) : MessageKey() - - data class RomajiConversionStatus( - val toggle: String, - ) : MessageKey() - - data class RomajiConversionToggle( - val toggle: String, - ) : MessageKey() - - // Error messages - data object ReplyTargetNotFound : MessageKey() - - data class TellTargetOffline( - val targetName: String, - ) : MessageKey() - - data object TellYourself : MessageKey() - - // Toggle values - data object ToggleOn : MessageKey() - - data object ToggleOff : MessageKey() - - // System messages - data object PlayerOnlyCommand : MessageKey() -} diff --git a/platform-paper/src/main/resources/languages/en.yml b/platform-paper/src/main/resources/languages/en.yml index 17743f2..95eca34 100644 --- a/platform-paper/src/main/resources/languages/en.yml +++ b/platform-paper/src/main/resources/languages/en.yml @@ -19,6 +19,7 @@ commandDescription: directMessageNoticeStatus: "Direct message notifications are currently {toggle}" directMessageNoticeToggle: "Direct message notifications have been set to {toggle}" +playerOnlyCommand: "This command can only be executed by players." replyTargetNotFound: "Reply target player not found." romajiConversionStatus: "Romaji-to-kana conversion is currently {toggle}" romajiConversionToggle: "Romaji-to-kana conversion has been set to {toggle}" diff --git a/platform-paper/src/main/resources/languages/ja.yml b/platform-paper/src/main/resources/languages/ja.yml index e8770f8..6fdd242 100644 --- a/platform-paper/src/main/resources/languages/ja.yml +++ b/platform-paper/src/main/resources/languages/ja.yml @@ -23,8 +23,10 @@ commandDescription: directMessageNoticeToggle: "ダイレクトメッセージ通知を{toggle}にしました" directMessageNoticeStatus: "現在ダイレクトメッセージ通知は{toggle}です" +playerOnlyCommand: "このコマンドはプレイヤーのみが実行できます" replyTargetNotFound: "返信対象のプレイヤーが見つかりません" romajiConversionToggle: "かな・ローマ字変換機能を{toggle}にしました" romajiConversionStatus: "現在かな・ローマ字変換機能は{toggle}です" +settingsAvailableValues: "使用可能な設定値: " tellTargetOffline: "プレイヤー '{target}' は現在オフラインです" tellYourself: "自分自身にメッセージを送信することはできません" |
