From 50dc76a796f42a015145de9ef7332e6ecee84803 Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Sat, 14 Sep 2024 21:27:22 +0100 Subject: [PATCH] Continued work on new translations system --- .../main/kotlin/dev/kordex/core/Exceptions.kt | 3 +- .../dev/kordex/core/commands/Command.kt | 12 +++--- .../kordex/core/commands/CommandContext.kt | 29 ++++++-------- .../application/ApplicationCommand.kt | 21 +++++----- .../DummyAutocompleteCommandContext.kt | 3 +- .../application/message/MessageCommand.kt | 19 ++++++++-- .../application/slash/SlashCommand.kt | 22 ++++++++--- .../commands/application/slash/SlashGroup.kt | 15 ++++---- .../commands/application/slash/_Functions.kt | 3 +- .../converters/impl/EnumChoiceConverter.kt | 38 ++++++++++--------- .../core/commands/chat/ChatCommandParser.kt | 24 ++++++------ .../converters/SlashCommandConverter.kt | 2 + .../converters/builders/ConverterBuilder.kt | 5 ++- .../types/SingleNamedInputConverter.kt | 2 +- .../dev/kordex/core/components/Component.kt | 3 +- .../core/components/ComponentContext.kt | 3 +- .../kordex/core/components/forms/ModalForm.kt | 17 ++++++--- .../core/components/forms/widgets/Widget.kt | 3 +- .../dev/kordex/core/events/EventContext.kt | 30 +++++++-------- .../kotlin/dev/kordex/core/i18n/types/Key.kt | 8 +++- 20 files changed, 146 insertions(+), 116 deletions(-) diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/Exceptions.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/Exceptions.kt index dc5a5cff3f..84c3241c68 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/Exceptions.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/Exceptions.kt @@ -14,7 +14,6 @@ import dev.kordex.core.commands.chat.ChatCommand import dev.kordex.core.commands.converters.builders.ConverterBuilder import dev.kordex.core.events.EventHandler import dev.kordex.core.extensions.Extension -import dev.kordex.core.i18n.toKey import dev.kordex.core.i18n.types.Bundle import dev.kordex.core.i18n.types.Key import dev.kordex.parser.StringParser @@ -93,7 +92,7 @@ public class EventHandlerRegistrationException(public val reason: String) : Kord * @param name The command name * @param reason Why this command is considered invalid. */ -public class InvalidCommandException(public val name: String?, public val reason: String) : KordExException() { +public class InvalidCommandException(public val name: Key?, public val reason: String) : KordExException() { override val message: String = toString() override fun toString(): String { diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/Command.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/Command.kt index fbb0dca897..0c329a9694 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/Command.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/Command.kt @@ -21,6 +21,7 @@ import dev.kordex.core.builders.ExtensibleBotBuilder import dev.kordex.core.commands.events.CommandEvent import dev.kordex.core.extensions.Extension import dev.kordex.core.i18n.TranslationsProvider +import dev.kordex.core.i18n.generated.CoreTranslations import dev.kordex.core.i18n.types.Bundle import dev.kordex.core.i18n.types.Key import dev.kordex.core.koin.KordExKoinComponent @@ -88,7 +89,7 @@ public abstract class Command(public val extension: Extension) : Lockable, KordE */ @Throws(InvalidCommandException::class) public open fun validate() { - if (!::name.isInitialized || name.isEmpty()) { + if (!::name.isInitialized || name.key.isEmpty()) { throw InvalidCommandException(null, "No command name given.") } @@ -120,16 +121,13 @@ public abstract class Command(public val extension: Extension) : Lockable, KordE if (missingPerms.isNotEmpty()) { throw DiscordRelayedException( - context.translate( - "commands.error.missingBotPermissions", - null, - - replacements = arrayOf( + CoreTranslations.Commands.Error.missingBotPermissions + .withLocale(context.getLocale()) + .translate( missingPerms .map { it.translate(context.getLocale()) } .joinToString() ) - ) ) } } diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/CommandContext.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/CommandContext.kt index 469b7ff6ee..1c6f5025f5 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/CommandContext.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/CommandContext.kt @@ -20,6 +20,7 @@ import dev.kordex.core.checks.interactionFor import dev.kordex.core.checks.userFor import dev.kordex.core.i18n.TranslationsProvider import dev.kordex.core.i18n.types.Bundle +import dev.kordex.core.i18n.types.Key import dev.kordex.core.koin.KordExKoinComponent import dev.kordex.core.sentry.SentryContext import dev.kordex.core.types.TranslatableContext @@ -41,7 +42,7 @@ import java.util.* public abstract class CommandContext( public open val command: Command, public open val eventObj: Event, - public open val commandName: String, + public open val commandName: Key, public open val cache: MutableStringKeyedMap, ) : KordExKoinComponent, TranslatableContext { /** Translations provider, for retrieving translations. **/ @@ -96,32 +97,26 @@ public abstract class CommandContext( } public override suspend fun translate( - key: String, - bundleName: String?, + key: Key, + bundle: Bundle?, replacements: Array, ): String { val locale = getLocale() - return translationsProvider.translate( - key = key, - bundleName = bundleName, - locale = locale, - replacements = replacements - ) + return key.withBundle(bundle) + .withLocale(locale) + .translateArray(replacements) } public override suspend fun translate( - key: String, - bundleName: String?, + key: Key, + bundle: Bundle?, replacements: Map, ): String { val locale = getLocale() - return translationsProvider.translate( - key = key, - bundleName = bundleName, - locale = locale, - replacements = replacements - ) + return key.withBundle(bundle) + .withLocale(locale) + .translateNamed(replacements) } } diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/ApplicationCommand.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/ApplicationCommand.kt index 32bfe3fab4..a2b8870607 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/ApplicationCommand.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/ApplicationCommand.kt @@ -22,6 +22,7 @@ import dev.kordex.core.checks.types.CheckWithCache import dev.kordex.core.commands.Command import dev.kordex.core.commands.application.slash.SlashCommand import dev.kordex.core.extensions.Extension +import dev.kordex.core.i18n.types.Key import dev.kordex.core.koin.KordExKoinComponent import dev.kordex.core.utils.MutableStringKeyedMap import dev.kordex.core.utils.getLocale @@ -117,14 +118,14 @@ public abstract class ApplicationCommand( * @param lowerCase Provide `true` to lower-case all the translations. Discord requires this for some fields. */ public fun localize( - key: String, + key: Key, lowerCase: Boolean = false, ): Localized { - var default = translationsProvider.translate( - key = key, - bundleName = this.resolvedBundle, - locale = translationsProvider.defaultLocale - ) + val bundledKey = key.withBundle(resolvedBundle) + + var default = bundledKey + .withLocale(translationsProvider.defaultLocale) + .translate() if (lowerCase) { default = default.lowercase(translationsProvider.defaultLocale) @@ -132,11 +133,9 @@ public abstract class ApplicationCommand( val translations = bot.settings.i18nBuilder.applicationCommandLocales .associateWith { locale -> - val result = translationsProvider.translate( - key = key, - bundleName = this.resolvedBundle, - locale = locale.asJavaLocale() - ) + val result = bundledKey + .withLocale(locale.asJavaLocale()) + .translate() if (lowerCase) { result.lowercase(locale.asJavaLocale()) diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/DummyAutocompleteCommandContext.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/DummyAutocompleteCommandContext.kt index c7bae25f0e..9bacb67a92 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/DummyAutocompleteCommandContext.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/DummyAutocompleteCommandContext.kt @@ -16,11 +16,12 @@ import dev.kord.core.entity.interaction.GuildAutoCompleteInteraction import dev.kord.core.event.interaction.AutoCompleteInteractionCreateEvent import dev.kordex.core.commands.Command import dev.kordex.core.commands.CommandContext +import dev.kordex.core.i18n.types.Key public class DummyAutocompleteCommandContext( command: Command, private val event: AutoCompleteInteractionCreateEvent, - commandName: String, + commandName: Key, ) : CommandContext(command, event, commandName, mutableMapOf()) { override suspend fun populate() { error("This should never be called.") diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/message/MessageCommand.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/message/MessageCommand.kt index 893b894d69..f6a9add634 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/message/MessageCommand.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/message/MessageCommand.kt @@ -16,6 +16,7 @@ import dev.kordex.core.components.ComponentRegistry import dev.kordex.core.components.forms.ModalForm import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.impl.SENTRY_EXTENSION_NAME +import dev.kordex.core.i18n.generated.CoreTranslations import dev.kordex.core.sentry.BreadcrumbType import dev.kordex.core.types.FailureReason import dev.kordex.core.utils.MutableStringKeyedMap @@ -139,19 +140,29 @@ public abstract class MessageCommand, M : ModalF logger.info { "Error submitted to Sentry: $sentryId" } if (extension.bot.extensions.containsKey(SENTRY_EXTENSION_NAME)) { - context.translate("commands.error.user.sentry.slash", null, replacements = arrayOf(sentryId)) + CoreTranslations.Commands.Error.User.Sentry.slash + .withLocale(context.getLocale()) + .translate(sentryId) } else { - context.translate("commands.error.user", null) + CoreTranslations.Commands.Error.user + .withLocale(context.getLocale()) + .translate() } } else { - context.translate("commands.error.user", null) + CoreTranslations.Commands.Error.user + .withLocale(context.getLocale()) + .translate() } respondText(context, errorMessage, FailureReason.ExecutionError(t)) } else { respondText( context, - context.translate("commands.error.user", null), + + CoreTranslations.Commands.Error.user + .withLocale(context.getLocale()) + .translate(), + FailureReason.ExecutionError(t) ) } diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/SlashCommand.kt index 19241ecb1b..32b1ec041a 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/SlashCommand.kt @@ -24,6 +24,8 @@ import dev.kordex.core.components.ComponentRegistry import dev.kordex.core.components.forms.ModalForm import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.impl.SENTRY_EXTENSION_NAME +import dev.kordex.core.i18n.generated.CoreTranslations +import dev.kordex.core.i18n.types.Key import dev.kordex.core.sentry.BreadcrumbType import dev.kordex.core.types.FailureReason import dev.kordex.core.utils.MutableStringKeyedMap @@ -55,7 +57,7 @@ public abstract class SlashCommand, A : Argumen public val componentRegistry: ComponentRegistry by inject() /** Command description, as displayed on Discord. **/ - public open lateinit var description: String + public open lateinit var description: Key /** Command body, to be called when the command is executed. **/ public lateinit var body: suspend C.(M?) -> Unit @@ -321,19 +323,29 @@ public abstract class SlashCommand, A : Argumen kxLogger.info { "Error submitted to Sentry: $sentryId" } if (extension.bot.extensions.containsKey(SENTRY_EXTENSION_NAME)) { - context.translate("commands.error.user.sentry.slash", null, replacements = arrayOf(sentryId)) + CoreTranslations.Commands.Error.User.Sentry.slash + .withLocale(context.getLocale()) + .translate(sentryId) } else { - context.translate("commands.error.user", null) + CoreTranslations.Commands.Error.user + .withLocale(context.getLocale()) + .translate() } } else { - context.translate("commands.error.user", null) + CoreTranslations.Commands.Error.user + .withLocale(context.getLocale()) + .translate() } respondText(context, errorMessage, FailureReason.ExecutionError(t)) } else { respondText( context, - context.translate("commands.error.user", null), + + CoreTranslations.Commands.Error.user + .withLocale(context.getLocale()) + .translate(), + FailureReason.ExecutionError(t) ) } diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/SlashGroup.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/SlashGroup.kt index 7382382be7..c5e6caafea 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/SlashGroup.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/SlashGroup.kt @@ -11,6 +11,7 @@ package dev.kordex.core.commands.application.slash import dev.kordex.core.InvalidCommandException import dev.kordex.core.commands.application.Localized import dev.kordex.core.i18n.TranslationsProvider +import dev.kordex.core.i18n.types.Key import dev.kordex.core.koin.KordExKoinComponent import io.github.oshai.kotlinlogging.KLogger import io.github.oshai.kotlinlogging.KotlinLogging @@ -24,7 +25,7 @@ import java.util.* * @param parent Parent slash command that this group belongs to */ public class SlashGroup( - public val name: String, + public val name: Key, public val parent: SlashCommand<*, *, *>, ) : KordExKoinComponent { /** Translations provider, for retrieving translations. **/ @@ -37,7 +38,7 @@ public class SlashGroup( public val subCommands: MutableList> = mutableListOf() /** Command group description, which is required and shown on Discord. **/ - public lateinit var description: String + public lateinit var description: Key /** * A [Localized] version of [name]. @@ -66,11 +67,11 @@ public class SlashGroup( // Only slash commands need this to be lower-cased. if (!descriptionTranslationCache.containsKey(locale)) { - descriptionTranslationCache[locale] = translationsProvider.translate( - key = this.description, - bundleName = this.parent.resolvedBundle, - locale = locale - ).lowercase() + descriptionTranslationCache[locale] = description + .withBundle(this.parent.resolvedBundle) + .withLocale(locale) + .translate() + .lowercase(locale) } return descriptionTranslationCache[locale]!! diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/_Functions.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/_Functions.kt index 60a19289bf..25b281656d 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/_Functions.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/_Functions.kt @@ -15,6 +15,7 @@ import dev.kordex.core.InvalidCommandException import dev.kordex.core.annotations.ExtensionDSL import dev.kordex.core.commands.Arguments import dev.kordex.core.components.forms.ModalForm +import dev.kordex.core.i18n.types.Key private const val SUBCOMMAND_AND_GROUP_LIMIT: Int = 25 @@ -30,7 +31,7 @@ private const val SUBCOMMAND_AND_GROUP_LIMIT: Int = 25 * @param body Lambda used to build the [SlashGroup] object. */ @ExtensionDSL -public suspend fun SlashCommand<*, *, *>.group(name: String, body: suspend SlashGroup.() -> Unit): SlashGroup { +public suspend fun SlashCommand<*, *, *>.group(name: Key, body: suspend SlashGroup.() -> Unit): SlashGroup { if (parentCommand != null) { error("Command groups may not be nested inside subcommands.") } diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/converters/impl/EnumChoiceConverter.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/converters/impl/EnumChoiceConverter.kt index 106aabba86..9cd2aaacf7 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/converters/impl/EnumChoiceConverter.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/converters/impl/EnumChoiceConverter.kt @@ -20,7 +20,11 @@ import dev.kordex.core.commands.CommandContext import dev.kordex.core.commands.application.slash.converters.ChoiceConverter import dev.kordex.core.commands.application.slash.converters.ChoiceEnum import dev.kordex.core.commands.converters.Validator +import dev.kordex.core.i18n.generated.CoreTranslations +import dev.kordex.core.i18n.types.Bundle +import dev.kordex.core.i18n.types.Key import dev.kordex.core.utils.getIgnoringCase +import dev.kordex.core.utils.withContext import dev.kordex.parser.StringParser import io.github.oshai.kotlinlogging.KotlinLogging @@ -35,7 +39,9 @@ import io.github.oshai.kotlinlogging.KotlinLogging types = [ConverterType.SINGLE, ConverterType.DEFAULTING, ConverterType.OPTIONAL, ConverterType.CHOICE], imports = [ "dev.kordex.core.commands.converters.impl.getEnum", - "dev.kordex.core.commands.application.slash.converters.ChoiceEnum" + "dev.kordex.core.commands.application.slash.converters.ChoiceEnum", + "dev.kordex.core.i18n.types.Bundle", + "dev.kordex.core.i18n.types.Key", ], builderGeneric = "E", @@ -45,12 +51,12 @@ import io.github.oshai.kotlinlogging.KotlinLogging ], builderFields = [ - "public lateinit var typeName: String", - "public var bundle: String? = null" + "public lateinit var typeName: Key", + "public var bundle: Bundle? = null", ], builderInitStatements = [ - "choices(argMap)" + "choices(argMap)", ], builderSuffixedWhere = "E : Enum, E : ChoiceEnum", @@ -61,16 +67,16 @@ import io.github.oshai.kotlinlogging.KotlinLogging "argMap = enumValues().associateBy { it.readableName }", ], - functionSuffixedWhere = "E : Enum, E : ChoiceEnum" + functionSuffixedWhere = "E : Enum, E : ChoiceEnum", ) public class EnumChoiceConverter( - typeName: String, + typeName: Key, private val getter: suspend (String) -> E?, choices: Map, override var validator: Validator = null, - override val bundle: String? = null, + override val bundle: Bundle? = null, ) : ChoiceConverter(choices) where E : Enum, E : ChoiceEnum { - override val signatureTypeString: String = typeName + override val signatureTypeString: Key = typeName private val logger = KotlinLogging.logger { } @@ -88,14 +94,12 @@ public class EnumChoiceConverter( try { val result = getter.invoke(arg) ?: throw DiscordRelayedException( - context.translate( - "converters.choice.invalidChoice", - - replacements = arrayOf( + CoreTranslations.Converters.Choice.invalidChoice + .withLocale(context.getLocale()) + .translate( arg, choices.entries.joinToString { "**${it.key}** -> `${it.value}`" } ) - ) ) this.parsed = result @@ -103,14 +107,12 @@ public class EnumChoiceConverter( logger.warn(e) { "Failed to get enum value for argument: $arg" } throw DiscordRelayedException( - context.translate( - "converters.choice.invalidChoice", - - replacements = arrayOf( + CoreTranslations.Converters.Choice.invalidChoice + .withLocale(context.getLocale()) + .translate( arg, choices.entries.joinToString { "**${it.key}** -> `${it.value}`" } ) - ) ) } diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/chat/ChatCommandParser.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/chat/ChatCommandParser.kt index 53ce494fe3..7638751ce3 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/chat/ChatCommandParser.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/chat/ChatCommandParser.kt @@ -14,7 +14,6 @@ package dev.kordex.core.commands.chat -import com.ibm.icu.text.PluralRules.Operand.c import dev.kordex.core.ArgumentParsingException import dev.kordex.core.DiscordRelayedException import dev.kordex.core.ExtensibleBot @@ -28,17 +27,14 @@ import dev.kordex.core.commands.getDefaultTranslatedDisplayName import dev.kordex.core.i18n.KORDEX_BUNDLE import dev.kordex.core.i18n.TranslationsProvider import dev.kordex.core.i18n.generated.CoreTranslations -import dev.kordex.core.i18n.generated.CoreTranslations.Extensions.Help.Paginator.Title.arguments import dev.kordex.core.koin.KordExKoinComponent import dev.kordex.core.utils.MutableStringKeyedMap import dev.kordex.core.utils.withContext import dev.kordex.parser.StringParser import io.github.oshai.kotlinlogging.KotlinLogging -import jdk.jpackage.internal.Arguments.CLIOptions.context import org.koin.core.component.inject import java.util.* import kotlin.collections.set -import kotlin.contracts.contract private val logger = KotlinLogging.logger {} @@ -137,8 +133,8 @@ public open class ChatCommandParser : KordExKoinComponent { val doRelay = when (val c = argument.converter) { is SingleConverter<*> -> c.required || hasKwargs - is DefaultingConverter<*> -> (c.required || c.outputError || hasKwargs) - && throwable is DiscordRelayedException + is DefaultingConverter<*> -> (c.required || c.outputError || hasKwargs) && + throwable is DiscordRelayedException is OptionalConverter<*> -> if (throwable is DiscordRelayedException) { c.required || c.outputError || hasKwargs @@ -190,8 +186,7 @@ public open class ChatCommandParser : KordExKoinComponent { when (val t = throwable) { is ArgumentParsingException -> throw t - is DiscordRelayedException -> { - throw ArgumentParsingException( + is DiscordRelayedException -> throw ArgumentParsingException( CoreTranslations.ArgumentParser.Error.errorInArgument .withLocale(context.getLocale()) .translate( @@ -211,10 +206,9 @@ public open class ChatCommandParser : KordExKoinComponent { arguments, parser ) - } else -> { - logger.warn(t) { "Exception thrown by argument: ${argument.displayName.key}"} + logger.warn(t) { "Exception thrown by argument: ${argument.displayName.key}" } throw t } @@ -226,7 +220,7 @@ public open class ChatCommandParser : KordExKoinComponent { argument: Argument<*>, parser: StringParser, context: ChatCommandContext<*>, - ) : Nothing { + ): Nothing { val c = argument.converter throw ArgumentParsingException( @@ -259,7 +253,7 @@ public open class ChatCommandParser : KordExKoinComponent { context: ChatCommandContext<*>, numArgs: Int, numParsed: Int, - ) : Nothing { + ): Nothing { val c = argument.converter throw ArgumentParsingException( @@ -597,7 +591,11 @@ public open class ChatCommandParser : KordExKoinComponent { append("[") } - append(it.displayName) + append( + it.displayName + .withLocale(locale) + .translate() + ) if (it.converter.showTypeInSignature) { append(": ") diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/converters/SlashCommandConverter.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/converters/SlashCommandConverter.kt index eb40efe5ce..3cf9282c85 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/converters/SlashCommandConverter.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/converters/SlashCommandConverter.kt @@ -21,6 +21,8 @@ public interface SlashCommandConverter { * Return a slash command option that corresponds to this converter. * * Only applicable to converter types that make sense for slash commands. + * + * TODO: Create wrapping option builder types to store Key objects with their contexts */ public suspend fun toSlashOption(arg: Argument<*>): OptionsBuilder diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/converters/builders/ConverterBuilder.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/converters/builders/ConverterBuilder.kt index 01d8355a3a..0e02b82c94 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/converters/builders/ConverterBuilder.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/converters/builders/ConverterBuilder.kt @@ -15,14 +15,15 @@ import dev.kordex.core.commands.converters.AutoCompleteCallback import dev.kordex.core.commands.converters.Converter import dev.kordex.core.commands.converters.Mutator import dev.kordex.core.commands.converters.Validator +import dev.kordex.core.i18n.types.Key /** Base abstract class for all converter builders. **/ public abstract class ConverterBuilder { /** Converter display name. Required. **/ - public open lateinit var name: String + public open lateinit var name: Key /** Converter description. Required. **/ - public open lateinit var description: String + public open lateinit var description: Key /** Mutator, used to mutate the parsed value before it's presented. **/ public open var mutator: Mutator = null diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/converters/types/SingleNamedInputConverter.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/converters/types/SingleNamedInputConverter.kt index 4a150745e4..1e6cbcb7ad 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/converters/types/SingleNamedInputConverter.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/converters/types/SingleNamedInputConverter.kt @@ -10,6 +10,6 @@ package dev.kordex.core.commands.converters.types import dev.kordex.core.commands.converters.Converter -public abstract class SingleNamedInputConverter ( +public abstract class SingleNamedInputConverter( public override val required: Boolean = true, ) : Converter() diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/components/Component.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/components/Component.kt index f60d041471..3321be0323 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/components/Component.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/components/Component.kt @@ -14,6 +14,7 @@ import dev.kordex.core.ExtensibleBot import dev.kordex.core.builders.ExtensibleBotBuilder import dev.kordex.core.commands.application.ApplicationCommandRegistry import dev.kordex.core.i18n.TranslationsProvider +import dev.kordex.core.i18n.types.Bundle import dev.kordex.core.koin.KordExKoinComponent import dev.kordex.core.sentry.SentryAdapter import org.koin.core.component.inject @@ -42,7 +43,7 @@ public abstract class Component : KordExKoinComponent { public val sentry: SentryAdapter by inject() /** Translation bundle, to retrieve translations from. **/ - public open var bundle: String? = null + public open var bundle: Bundle? = null /** Validation function, called to ensure the component is valid, throws exceptions if not. **/ public abstract fun validate() diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/components/ComponentContext.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/components/ComponentContext.kt index 360d88b456..3ea10f9976 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/components/ComponentContext.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/components/ComponentContext.kt @@ -24,6 +24,7 @@ import dev.kordex.core.checks.channelFor import dev.kordex.core.checks.guildFor import dev.kordex.core.checks.userFor import dev.kordex.core.i18n.TranslationsProvider +import dev.kordex.core.i18n.types.Bundle import dev.kordex.core.koin.KordExKoinComponent import dev.kordex.core.sentry.SentryContext import dev.kordex.core.sentry.captures.SentryBreadcrumbCapture @@ -48,7 +49,7 @@ public abstract class ComponentContext( /** Translations provider, for retrieving translations. **/ public val translationsProvider: TranslationsProvider by inject() - override val bundle: String? + override val bundle: Bundle? get() = component.bundle /** Configured bot settings object. **/ diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/components/forms/ModalForm.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/components/forms/ModalForm.kt index 8576876b96..5c02a20218 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/components/forms/ModalForm.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/components/forms/ModalForm.kt @@ -29,6 +29,8 @@ import dev.kordex.core.components.forms.widgets.Widget import dev.kordex.core.events.EventContext import dev.kordex.core.events.ModalInteractionCompleteEvent import dev.kordex.core.i18n.TranslationsProvider +import dev.kordex.core.i18n.types.Bundle +import dev.kordex.core.i18n.types.Key import dev.kordex.core.koin.KordExKoinComponent import dev.kordex.core.utils.waitFor import org.koin.core.component.inject @@ -43,10 +45,10 @@ import kotlin.time.Duration.Companion.minutes */ public abstract class ModalForm : Form(), KordExKoinComponent { /** The modal's title, shown on Discord. **/ - public abstract var title: String + public abstract var title: Key /** Translation bundle to use for this modal's title and widgets. **/ - public open var bundle: String? = null + public open var bundle: Bundle? = null /** @suppress Internal reference. **/ protected val bot: ExtensibleBot by inject() @@ -116,7 +118,7 @@ public abstract class ModalForm : Form(), KordExKoinComponent { } /** Given a ModalBuilder, apply this modal's widgets for display on Discord. **/ - public suspend fun applyToBuilder(builder: ModalBuilder, locale: Locale, resolvedBundle: String?) { + public suspend fun applyToBuilder(builder: ModalBuilder, locale: Locale, resolvedBundle: Bundle?) { val appliedWidgets = mutableSetOf>() grid.forEach { row -> @@ -144,8 +146,11 @@ public abstract class ModalForm : Form(), KordExKoinComponent { } /** Return a translated modal title using the given locale, and the given bundle if the modal doesn't have one. **/ - public fun translateTitle(locale: Locale, otherBundle: String?): String = - translations.translate(key = title, bundleName = bundle ?: otherBundle, locale = locale) + public fun translateTitle(locale: Locale, otherBundle: Bundle?): String = + title + .withBundle(bundle ?: otherBundle) + .withLocale(locale) + .translate() /** * Convenience function to send this modal to the given [interaction] and await its completion, running the provided @@ -157,7 +162,7 @@ public abstract class ModalForm : Form(), KordExKoinComponent { */ public suspend fun sendAndAwait( locale: Locale, - bundle: String?, + bundle: Bundle?, interaction: ModalParentInteractionBehavior, callback: suspend (ModalSubmitInteraction?) -> T, ): T { diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/components/forms/widgets/Widget.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/components/forms/widgets/Widget.kt index 9ce86d9a19..4b3c3a7ffc 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/components/forms/widgets/Widget.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/components/forms/widgets/Widget.kt @@ -9,6 +9,7 @@ package dev.kordex.core.components.forms.widgets import dev.kord.rest.builder.component.ActionRowBuilder +import dev.kordex.core.i18n.types.Bundle import java.util.* /** Abstract type representing a grid-based widget. **/ @@ -29,7 +30,7 @@ public abstract class Widget { "${this::class.simpleName}@${hashCode()} ($width x $height)" /** Function called to apply this widget to a Discord action row. **/ - public abstract suspend fun apply(builder: ActionRowBuilder, locale: Locale, bundle: String?) + public abstract suspend fun apply(builder: ActionRowBuilder, locale: Locale, bundle: Bundle?) /** Function called to ensure that this widget was set up correctly. **/ public abstract fun validate() diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/events/EventContext.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/events/EventContext.kt index 911de3c03a..ad1f868334 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/events/EventContext.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/events/EventContext.kt @@ -14,6 +14,8 @@ import dev.kordex.core.checks.guildFor import dev.kordex.core.checks.interactionFor import dev.kordex.core.checks.userFor import dev.kordex.core.i18n.TranslationsProvider +import dev.kordex.core.i18n.types.Bundle +import dev.kordex.core.i18n.types.Key import dev.kordex.core.koin.KordExKoinComponent import dev.kordex.core.sentry.SentryContext import dev.kordex.core.types.TranslatableContext @@ -42,7 +44,7 @@ public open class EventContext( override var resolvedLocale: Locale? = null - override val bundle: String? + override val bundle: Bundle? get() = eventHandler.extension.bundle /** @@ -50,18 +52,15 @@ public open class EventContext( * configured locale resolvers. */ public override suspend fun translate( - key: String, - bundleName: String?, + key: Key, + bundleName: Bundle?, replacements: Array, ): String { val locale: Locale = getLocale() - return translationsProvider.translate( - key = key, - bundleName = bundleName, - locale = locale, - replacements = replacements - ) + return key.withBundle(bundleName) + .withLocale(locale) + .translateArray(replacements) } /** @@ -69,18 +68,15 @@ public open class EventContext( * configured locale resolvers. */ public override suspend fun translate( - key: String, - bundleName: String?, + key: Key, + bundleName: Bundle?, replacements: Map, ): String { val locale: Locale = getLocale() - return translationsProvider.translate( - key = key, - bundleName = bundleName, - locale = locale, - replacements = replacements - ) + return key.withBundle(bundleName) + .withLocale(locale) + .translateNamed(replacements) } override suspend fun getLocale(): Locale { diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/i18n/types/Key.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/i18n/types/Key.kt index 089098c5be..73b8afbe61 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/i18n/types/Key.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/i18n/types/Key.kt @@ -79,7 +79,10 @@ public data class Key( copy(bundle = null, locale = null) public fun translate(vararg replacements: Any?): String = - translations.translate(this, replacements.toList().toTypedArray()) + translateArray(replacements.toList().toTypedArray()) + + public fun translateArray(replacements: Array): String = + translations.translate(this, replacements) public fun translateNamed(replacements: Map): String = translations.translateNamed(this, replacements) @@ -90,6 +93,9 @@ public data class Key( public fun translateLocale(locale: Locale, vararg replacements: Any?): String = withLocale(locale).translate(*replacements) + public fun translateArrayLocale(locale: Locale, replacements: Array): String = + withLocale(locale).translateArray(replacements) + public fun translateNamedLocale(locale: Locale, replacements: Map): String = withLocale(locale).translateNamed(replacements)