summaryrefslogtreecommitdiff
path: root/platform-paper/src
diff options
context:
space:
mode:
authorSho Sakuma <me@m1sk9.dev>2026-01-24 00:48:13 +0900
committerSho Sakuma <me@m1sk9.dev>2026-01-24 00:48:37 +0900
commita32fda5e0cf79c4e443290d149ddd3f76a12e8b4 (patch)
treed0556a66581883b6b7316cc0650230f90d192cf4 /platform-paper/src
parent60a8cd9b262ae4935d0938a2cbdf5a6330ef7f3a (diff)
downloadLunaticChat-a32fda5e0cf79c4e443290d149ddd3f76a12e8b4.tar.gz
LunaticChat-a32fda5e0cf79c4e443290d149ddd3f76a12e8b4.tar.bz2
LunaticChat-a32fda5e0cf79c4e443290d149ddd3f76a12e8b4.zip
refactor: Changed to simple key management
Diffstat (limited to 'platform-paper/src')
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt23
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/ReplyCommand.kt5
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/TellCommand.kt7
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/LanguageConfig.kt58
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/LanguageManager.kt164
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/MessageKey.kt53
-rw-r--r--platform-paper/src/main/resources/languages/en.yml1
-rw-r--r--platform-paper/src/main/resources/languages/ja.yml2
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: "自分自身にメッセージを送信することはできません"