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 ba2ccd2383..91ef72041f 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.generated.CoreTranslations.NsfwLevel.default import dev.kordex.core.i18n.types.Key import dev.kordex.core.koin.KordExKoinComponent import dev.kordex.core.utils.MutableStringKeyedMap diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/components/forms/widgets/TextInputWidget.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/components/forms/widgets/TextInputWidget.kt index 1412a1316a..972da7e00b 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/components/forms/widgets/TextInputWidget.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/components/forms/widgets/TextInputWidget.kt @@ -14,6 +14,7 @@ import dev.kordex.core.i18n.TranslationsProvider import dev.kordex.core.i18n.types.Key import dev.kordex.core.koin.KordExKoinComponent import io.github.oshai.kotlinlogging.KotlinLogging +import org.apache.commons.validator.GenericValidator.maxLength import org.koin.core.component.inject import java.util.* 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 2560fd7916..56f0c17eb7 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 @@ -124,7 +124,11 @@ public data class Key( copy(bundle = null, locale = null) public fun translate(vararg replacements: Any?): String = - translateArray(replacements.toList().toTypedArray()) + if (replacements.isNotEmpty() || ordinalPlaceholders.isNotEmpty()) { + translateArray(replacements.toList().toTypedArray()) + } else { + translateNamed() + } public fun translateArray(replacements: Array): String { val allReplacements = when (presetPlaceholderPosition) { diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/utils/deltas/ChangeSet.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/utils/deltas/ChangeSet.kt index 737f376868..d3bac8bcb2 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/utils/deltas/ChangeSet.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/utils/deltas/ChangeSet.kt @@ -14,7 +14,6 @@ import dev.kord.core.entity.VoiceState import kotlin.reflect.KClass import kotlin.reflect.KProperty -@Suppress("ERROR_SUPPRESSION") public class ChangeSet(public val clazz: KClass<*>) { private val changes: MutableMap, Change<*>> = mutableMapOf() diff --git a/modules/functionality/func-mappings/src/main/kotlin/dev/kordex/modules/func/mappings/MappingsExtension.kt b/modules/functionality/func-mappings/src/main/kotlin/dev/kordex/modules/func/mappings/MappingsExtension.kt index 931d56796c..881a3d4894 100644 --- a/modules/functionality/func-mappings/src/main/kotlin/dev/kordex/modules/func/mappings/MappingsExtension.kt +++ b/modules/functionality/func-mappings/src/main/kotlin/dev/kordex/modules/func/mappings/MappingsExtension.kt @@ -421,7 +421,7 @@ class MappingsExtension : Extension() { val pagesObj = Pages() val pageTitle = MappingsTranslations.Response.Info.title .withLocale(locale) - .translateNamed("mappings" to "Mojang") + .translateNamed("mappings" to "Hashed Mojang") pages.forEach { pagesObj.addPage( @@ -535,7 +535,7 @@ class MappingsExtension : Extension() { val pagesObj = Pages() val pageTitle = MappingsTranslations.Response.Info.title .withLocale(locale) - .translateNamed("mappings" to "Mojang") + .translateNamed("mappings" to "Yarn") pages.forEach { pagesObj.addPage( diff --git a/modules/functionality/func-tags/build.gradle.kts b/modules/functionality/func-tags/build.gradle.kts index 80ccfff1dc..537e334726 100644 --- a/modules/functionality/func-tags/build.gradle.kts +++ b/modules/functionality/func-tags/build.gradle.kts @@ -13,6 +13,13 @@ metadata { description = "KordEx extra module that provides a set of commands for storing and retrieving tagged text snippets" } +getTranslations( + "func-tags", + "dev.kordex.modules.func.tags.i18n", + "kordex.func-tags", + "TagsTranslations" +) + repositories { maven { name = "Sonatype Snapshots" diff --git a/modules/functionality/func-tags/src/main/kotlin/dev/kordex/modules/func/tags/TagsExtension.kt b/modules/functionality/func-tags/src/main/kotlin/dev/kordex/modules/func/tags/TagsExtension.kt index f6174dd2a3..7ffdc65fc1 100644 --- a/modules/functionality/func-tags/src/main/kotlin/dev/kordex/modules/func/tags/TagsExtension.kt +++ b/modules/functionality/func-tags/src/main/kotlin/dev/kordex/modules/func/tags/TagsExtension.kt @@ -23,13 +23,17 @@ import dev.kordex.core.components.forms.ModalForm import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.ephemeralSlashCommand import dev.kordex.core.extensions.publicSlashCommand +import dev.kordex.core.i18n.toKey +import dev.kordex.core.i18n.types.Key import dev.kordex.core.utils.FilterStrategy import dev.kordex.core.utils.suggestStringMap import dev.kordex.modules.dev.unsafe.annotations.UnsafeAPI +import dev.kordex.modules.dev.unsafe.commands.slash.InitialSlashCommandResponse import dev.kordex.modules.dev.unsafe.extensions.unsafeSubCommand import dev.kordex.modules.func.tags.config.TagsConfig import dev.kordex.modules.func.tags.data.Tag import dev.kordex.modules.func.tags.data.TagsData +import dev.kordex.modules.func.tags.i18n.generated.TagsTranslations import org.koin.core.component.inject internal const val POSITIVE_EMOTE = "\uD83D\uDC4D" @@ -45,8 +49,8 @@ class TagsExtension : Extension() { override suspend fun setup() { publicSlashCommand(::GetTagArgs) { - name = "tag" - description = "Retrieve a tag and send it" + name = TagsTranslations.Command.Tag.name + description = TagsTranslations.Command.Tag.description tagsConfig.getUserCommandChecks().forEach(::check) @@ -55,7 +59,12 @@ class TagsExtension : Extension() { if (tag == null) { respond { - content = "$NEGATIVE_EMOTE Unknown tag: ${arguments.tagKey}" + content = TagsTranslations.Response.Tag.unknown + .withLocale(getLocale()) + .translateNamed( + "emote" to NEGATIVE_EMOTE, + "tag" to arguments.tagKey + ) } return@action @@ -77,21 +86,23 @@ class TagsExtension : Extension() { } ephemeralSlashCommand { - name = "list-tags" - description = "Commands for listing tags by various criteria" + name = TagsTranslations.Command.ListTags.name + description = TagsTranslations.Command.ListTags.description tagsConfig.getUserCommandChecks().forEach(::check) ephemeralSubCommand(::ByCategoryArgs) { - name = "by-category" - description = "List tags by matching their category" + name = TagsTranslations.Command.ListTags.ByCategory.name + description = TagsTranslations.Command.ListTags.ByCategory.description action { val tags = tagsData.getTagsByCategory(arguments.category, guild?.id) if (tags.isEmpty()) { respond { - content = "$NEGATIVE_EMOTE Tag not found" + content = TagsTranslations.Response.Tag.noneFound + .withLocale(getLocale()) + .translateNamed("emote" to NEGATIVE_EMOTE) } return@action @@ -118,15 +129,17 @@ class TagsExtension : Extension() { } ephemeralSubCommand(TagsExtension::ByKeyArgs) { - name = "by-key" - description = "List tags by matching their key" + name = TagsTranslations.Command.ListTags.ByKey.name + description = TagsTranslations.Command.ListTags.ByKey.description action { val tags = tagsData.getTagsByPartialKey(arguments.key, guild?.id) if (tags.isEmpty()) { respond { - content = "$NEGATIVE_EMOTE Tag not found" + content = TagsTranslations.Response.Tag.noneFound + .withLocale(getLocale()) + .translateNamed("emote" to NEGATIVE_EMOTE) } return@action @@ -153,15 +166,17 @@ class TagsExtension : Extension() { } ephemeralSubCommand(TagsExtension::ByTitleArgs) { - name = "by-title" - description = "List tags by matching their title" + name = TagsTranslations.Command.ListTags.ByTitle.name + description = TagsTranslations.Command.ListTags.ByTitle.description action { val tags = tagsData.getTagsByPartialTitle(arguments.title, guild?.id) if (tags.isEmpty()) { respond { - content = "$NEGATIVE_EMOTE Tag not found" + content = TagsTranslations.Response.Tag.noneFound + .withLocale(getLocale()) + .translateNamed("emote" to NEGATIVE_EMOTE) } return@action @@ -189,8 +204,8 @@ class TagsExtension : Extension() { } ephemeralSlashCommand { - name = "manage-tags" - description = "Tag management commands" + name = TagsTranslations.Command.ManageTags.name + description = TagsTranslations.Command.ManageTags.description allowInDms = false @@ -202,8 +217,8 @@ class TagsExtension : Extension() { @OptIn(KordUnsafe::class) unsafeSubCommand(::SetArgs) { - name = "set" - description = "Create or replace a tag" + name = TagsTranslations.Command.ManageTags.Set.name + description = TagsTranslations.Command.ManageTags.Set.description initialResponse = dev.kordex.modules.dev.unsafe.commands.slash.InitialSlashCommandResponse.None @@ -213,10 +228,10 @@ class TagsExtension : Extension() { this@unsafeSubCommand.componentRegistry.register(modalObj) event.interaction.modal( - modalObj.translateTitle(getLocale(), bundle), + modalObj.translateTitle(getLocale()), modalObj.id ) { - modalObj.applyToBuilder(this, getLocale(), bundle) + modalObj.applyToBuilder(this, getLocale()) } interactionResponse = modalObj.awaitCompletion { @@ -238,30 +253,40 @@ class TagsExtension : Extension() { tagsConfig.getLoggingChannel(guild!!.asGuild()).createMessage { allowedMentions { } - content = "**Tag created/updated by ${user.mention}**\n\n" + content = TagsTranslations.Logging.tagSet + .withLocale(getLocale()) + .translateNamed( + "user" to user.mention + ) tagsConfig.getTagFormatter().invoke(this, tag) } respondEphemeral { - content = "$POSITIVE_EMOTE Tag set: ${tag.title}" + TagsTranslations.Response.Tag.set + .withLocale(getLocale()) + .translateNamed( + "emote" to POSITIVE_EMOTE, + "tag" to tag.title + ) } } } @OptIn(KordUnsafe::class) unsafeSubCommand(::EditArgs) { - name = "edit" - description = "Edit an existing tag" - - initialResponse = dev.kordex.modules.dev.unsafe.commands.slash.InitialSlashCommandResponse.None + name = TagsTranslations.Command.ManageTags.Edit.name + description = TagsTranslations.Command.ManageTags.Edit.description + initialResponse = InitialSlashCommandResponse.None action { var tag = tagsData.getTagByKey(arguments.key, arguments.guild?.id) if (tag == null) { ackEphemeral { - content = "$NEGATIVE_EMOTE Tag not found" + content = TagsTranslations.Response.Tag.noneFound + .withLocale(getLocale()) + .translateNamed("emote" to NEGATIVE_EMOTE) } return@action @@ -278,10 +303,10 @@ class TagsExtension : Extension() { this@unsafeSubCommand.componentRegistry.register(modalObj) event.interaction.modal( - modalObj.translateTitle(getLocale(), bundle), + modalObj.translateTitle(getLocale()), modalObj.id ) { - modalObj.applyToBuilder(this, getLocale(), bundle) + modalObj.applyToBuilder(this, getLocale()) } interactionResponse = modalObj.awaitCompletion { @@ -304,10 +329,10 @@ class TagsExtension : Extension() { tag = tag.copy(color = arguments.colour!!) } - if (modalObj.imageUrl.value.isNullOrBlank()) { - tag = tag.copy(image = null) + tag = if (modalObj.imageUrl.value.isNullOrBlank()) { + tag.copy(image = null) } else { - tag = tag.copy(image = modalObj.imageUrl.value) + tag.copy(image = modalObj.imageUrl.value) } tagsData.setTag(tag) @@ -315,22 +340,33 @@ class TagsExtension : Extension() { tagsConfig.getLoggingChannel(guild!!.asGuild()).createMessage { allowedMentions { } - content = "**Tag edited by ${user.mention}**\n\n" + content = TagsTranslations.Logging.tagEdited + .withLocale(getLocale()) + .translateNamed( + "user" to user.mention + ) tagsConfig.getTagFormatter().invoke(this, tag) } respondEphemeral { - content = "$POSITIVE_EMOTE Tag edited: ${tag.title}" + content = TagsTranslations.Response.Tag.edited + .withLocale(getLocale()) + .translateNamed( + "emote" to POSITIVE_EMOTE, + "tag" to tag.title + ) } } } ephemeralSubCommand(TagsExtension::FindArgs) { - name = "find" - description = "Find tags, by the given key and guild ID" + name = TagsTranslations.Command.ManageTags.Find.name + description = TagsTranslations.Command.ManageTags.Find.description action { + val locale = getLocale() + val tags = tagsData.findTags( category = arguments.category, guildId = arguments.guild?.id, @@ -339,12 +375,17 @@ class TagsExtension : Extension() { if (tags.isEmpty()) { respond { - content = "$NEGATIVE_EMOTE No tags found for that query." + content = TagsTranslations.Response.Tag.noneFound + .withLocale(locale) + .translateNamed("emote" to NEGATIVE_EMOTE) } return@action } + val na = TagsTranslations.Response.Words.na + .translateLocale(locale) + editingPaginator { timeoutSeconds = 60 @@ -353,13 +394,15 @@ class TagsExtension : Extension() { chunks.forEach { chunk -> page { description = chunk.joinToString("\n\n") { - """ - **Key:** `${it.key}` - **Title:** `${it.title}` - **Category:** `${it.category}` - **Guild ID:** `${it.guildId ?: "N/A"}` - **Image:** `${it.image ?: "N/A"}` - """.trimIndent() + TagsTranslations.Response.Find.chunk + .withLocale(locale) + .translateNamed( + "key" to it.key, + "title" to it.title, + "category" to it.category, + "serverId" to (it.guildId ?: na), + "imageUrl" to (it.image ?: na), + ) } } } @@ -368,8 +411,8 @@ class TagsExtension : Extension() { } ephemeralSubCommand(TagsExtension::ByKeyAndOptionalGuildArgs) { - name = "delete" - description = "Delete a tag, by key and guild ID" + name = TagsTranslations.Command.ManageTags.Delete.name + description = TagsTranslations.Command.ManageTags.Delete.description action { val tag = tagsData.deleteTagByKey(arguments.key, arguments.guild?.id) @@ -378,7 +421,11 @@ class TagsExtension : Extension() { tagsConfig.getLoggingChannel(guild!!.asGuild()).createMessage { allowedMentions { } - content = "**Tag removed by ${user.mention}**\n\n" + content = TagsTranslations.Logging.tagDeleted + .withLocale(getLocale()) + .translateNamed( + "user" to user.mention + ) tagsConfig.getTagFormatter().invoke(this, tag) } @@ -386,9 +433,16 @@ class TagsExtension : Extension() { respond { content = if (tag == null) { - "$NEGATIVE_EMOTE Tag not found" + TagsTranslations.Response.Tag.noneFound + .withLocale(getLocale()) + .translateNamed("emote" to NEGATIVE_EMOTE) } else { - "$POSITIVE_EMOTE Deleted tag: ${tag.title}" + TagsTranslations.Response.Tag.deleted + .withLocale(getLocale()) + .translateNamed( + "emote" to POSITIVE_EMOTE, + "tag" to tag.title + ) } } } @@ -412,42 +466,42 @@ class TagsExtension : Extension() { internal class ByKeyAndOptionalGuildArgs : Arguments() { val key by string { - name = "key" - description = "Tag key to match by" + name = TagsTranslations.Arguments.Get.Key.name + description = TagsTranslations.Arguments.Get.Key.description } val guild by optionalGuild { - name = "guild" - description = "Optional guild to match by - \"this\" for the current guild" + name = TagsTranslations.Arguments.Get.Server.name + description = TagsTranslations.Arguments.Get.Server.description } } internal class FindArgs : Arguments() { val category by optionalString { - name = "category" - description = "Optional category to match by" + name = TagsTranslations.Arguments.Get.Category.name + description = TagsTranslations.Arguments.Get.Category.description } val key by optionalString { - name = "key" - description = "Optional tag key to match by" + name = TagsTranslations.Arguments.Get.Key.name + description = TagsTranslations.Arguments.Get.Key.description } val guild by optionalGuild { - name = "guild" - description = "Optional guild to match by - \"this\" for the current guild" + name = TagsTranslations.Arguments.Get.Server.name + description = TagsTranslations.Arguments.Get.Server.description } } internal class SetArgs(tagsData: TagsData) : Arguments() { val key by string { - name = "key" - description = "Unique tag key" + name = TagsTranslations.Arguments.Set.Key.name + description = TagsTranslations.Arguments.Set.Key.description } val category by string { - name = "category" - description = "Category to use for this tag - specify a new one to create it" + name = TagsTranslations.Arguments.Set.Category.name + description = TagsTranslations.Arguments.Set.Category.description autoComplete { val categories = tagsData.getAllCategories(data.guildId.value) @@ -460,30 +514,30 @@ class TagsExtension : Extension() { } val colour by optionalColor { - name = "colour" - description = "Optional embed colour - use hex codes, RGB integers or Discord colour constants" + name = TagsTranslations.Arguments.Set.Colour.name + description = TagsTranslations.Arguments.Set.Colour.description } val guild by optionalGuild { - name = "guild" - description = "Optional guild to limit the tag to - \"this\" for the current guild" + name = TagsTranslations.Arguments.Set.Server.name + description = TagsTranslations.Arguments.Set.Server.description } } internal class EditArgs(tagsData: TagsData) : Arguments() { val key by string { - name = "key" - description = "Tag key to use for matching (this can't be edited)" + name = TagsTranslations.Arguments.Edit.Key.name + description = TagsTranslations.Arguments.Edit.Key.description } val guild by optionalGuild { - name = "guild" - description = "Optional guild to use for matching (this can't be edited)" + name = TagsTranslations.Arguments.Edit.Server.name + description = TagsTranslations.Arguments.Edit.Server.description } val category by optionalString { - name = "category" - description = "Category to use for this tag - specify a new one to create it" + name = TagsTranslations.Arguments.Set.Category.name + description = TagsTranslations.Arguments.Set.Category.description autoComplete { val categories = tagsData.getAllCategories(data.guildId.value) @@ -496,15 +550,15 @@ class TagsExtension : Extension() { } val colour by optionalColor { - name = "colour" - description = "Use hex codes, RGB integers (0 to clear) or Discord colour constants" + name = TagsTranslations.Arguments.Set.Colour.name + description = TagsTranslations.Arguments.Set.Colour.description } } internal class ByCategoryArgs(tagsData: TagsData) : Arguments() { val category by string { - name = "category" - description = "Category to match by" + name = TagsTranslations.Arguments.Get.Key.name + description = TagsTranslations.Arguments.Get.Key.description autoComplete { val categories = tagsData.getAllCategories(data.guildId.value) @@ -524,52 +578,66 @@ class TagsExtension : Extension() { private val initialDescription: String? = null, private val initialImageUrl: String? = null, ) : ModalForm() { - override var title: String = if (!isEditing) { - "Create tag" - } else { - "Edit tag" - } + if (key != null) { - ": $key" - } else { - "" + override var title: Key = when { + !isEditing && key != null -> + TagsTranslations.Modal.Title.createWithTag + .withNamedPlaceholders("tag" to key) + + !isEditing && key == null -> + TagsTranslations.Modal.Title.create + + isEditing && key != null -> + TagsTranslations.Modal.Title.editWithTag + .withNamedPlaceholders("tag" to key) + + isEditing && key == null -> + TagsTranslations.Modal.Title.edit + + else -> error("Should be unreachable!") } val tagTitle = lineText { - label = "Title" - initialValue = initialTagTitle + label = TagsTranslations.Modal.Input.title + + initialValue = initialTagTitle?.toKey() + translateInitialValue = false } val description = paragraphText { - label = "Tag content" - initialValue = initialDescription + label = TagsTranslations.Modal.Input.content + + initialValue = initialDescription?.toKey() + translateInitialValue = false } val imageUrl = lineText { - label = "Image URL" - initialValue = initialImageUrl + label = TagsTranslations.Modal.Input.imageUrl + + initialValue = initialImageUrl?.toKey() + translateInitialValue = false required = false } } internal class ByKeyArgs : Arguments() { val key by string { - name = "key" - description = "Partial key to match by" + name = TagsTranslations.Arguments.Partial.Key.name + description = TagsTranslations.Arguments.Partial.Key.description } } internal class ByTitleArgs : Arguments() { val title by string { - name = "title" - description = "Partial title to match by" + name = TagsTranslations.Arguments.Partial.Title.name + description = TagsTranslations.Arguments.Partial.Title.description } } internal class GetTagArgs(tagsData: TagsData) : Arguments() { val tagKey by string { - name = "tag" - description = "Tag to retrieve" + name = TagsTranslations.Arguments.Get.Tag.name + description = TagsTranslations.Arguments.Get.Tag.description autoComplete { val input = focusedOption.value @@ -592,7 +660,7 @@ class TagsExtension : Extension() { .toList() if (category != null) { - potentialTags = potentialTags.filter { it.category.startsWith(category!!, true) } + potentialTags = potentialTags.filter { it.category.startsWith(category, true) } } val foundKeys: MutableList = mutableListOf() @@ -622,8 +690,8 @@ class TagsExtension : Extension() { } val userToMention by optionalMember { - name = "user" - description = "User to mention along with this tag." + name = TagsTranslations.Arguments.User.name + description = TagsTranslations.Arguments.User.description } } diff --git a/test-bot/src/main/kotlin/dev/kordex/test/bot/Translations.kt b/test-bot/src/main/kotlin/dev/kordex/test/bot/Translations.kt new file mode 100644 index 0000000000..904f5c51bc --- /dev/null +++ b/test-bot/src/main/kotlin/dev/kordex/test/bot/Translations.kt @@ -0,0 +1,106 @@ +/* + * Copyrighted (Kord Extensions, 2024). Licensed under the EUPL-1.2 + * with the specific provision (EUPL articles 14 & 15) that the + * applicable law is the (Republic of) Irish law and the Jurisdiction + * Dublin. + * Any redistribution must include the specific provision above. + */ + +package dev.kordex.test.bot + +import dev.kordex.core.i18n.types.Bundle +import dev.kordex.core.i18n.types.Key + +/** Written by hand for the sake of these tests. **/ +internal object Translations { + val bundle = Bundle("test.strings") + + object Check { + val simple = Key("check.simple") + .withBundle(bundle) + + val positionalParameters = Key("check.positionalParameters") + .withBundle(bundle) + + val namedParameters = Key("check.namedParameters") + .withBundle(bundle) + } + + object Command { + object Fruit { + object Argument { + val name = Key("command.fruit.argument.name") + .withBundle(bundle) + + val count = Key("command.fruit.argument.count") + .withBundle(bundle) + } + + val response = Key("command.fruit.response") + .withBundle(bundle) + } + + object Apple { + object Argument { + val name = Key("command.apple.argument.name") + .withBundle(bundle) + + val count = Key("command.apple.argument.count") + .withBundle(bundle) + } + + val response = Key("command.apple.response") + .withBundle(bundle) + } + + val apple = Key("command.apple") + .withBundle(bundle) + + val banana = Key("command.banana") + .withBundle(bundle) + + val bananaFlat = Key("command.banana-flat") + .withBundle(bundle) + + val bananaSub = Key("command.banana-sub") + .withBundle(bundle) + + val bananaGroup = Key("command.banana-group") + .withBundle(bundle) + + val fruit = Key("command.fruit") + .withBundle(bundle) + } + + object Modal { + object Line { + val placeholder = Key("modal.line.placeholder") + .withBundle(bundle) + } + + object Paragraph { + val placeholder = Key("modal.paragraph.placeholder") + .withBundle(bundle) + } + + val title = Key("modal.title") + .withBundle(bundle) + + val line = Key("modal.line") + .withBundle(bundle) + + val paragraph = Key("modal.paragraph") + .withBundle(bundle) + } + + object Validation { + val simple = Key("validation.simple") + .withBundle(bundle) + + val positionalParameters = Key("validation.positionalParameters") + .withBundle(bundle) + + val namedParameters = Key("validation.namedParameters") + .withBundle(bundle) + } +} diff --git a/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/ArgumentTestExtension.kt b/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/ArgumentTestExtension.kt index 2b22d4d76f..2014cf73da 100644 --- a/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/ArgumentTestExtension.kt +++ b/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/ArgumentTestExtension.kt @@ -22,6 +22,7 @@ import dev.kordex.core.commands.application.slash.converters.impl.stringChoice import dev.kordex.core.commands.converters.impl.* import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.publicSlashCommand +import dev.kordex.core.i18n.toKey import dev.kordex.core.utils.suggestStringCollection import dev.kordex.core.utils.suggestStringMap @@ -30,8 +31,8 @@ public class ArgumentTestExtension : Extension() { override suspend fun setup() { publicSlashCommand(::TagArgs) { - name = "test-tag" - description = "Test the tags converter" + name = "test-tag".toKey() + description = "Test the tags converter".toKey() action { respond { @@ -42,8 +43,8 @@ public class ArgumentTestExtension : Extension() { } publicSlashCommand(::EmojiArguments) { - name = "test-emoji" - description = "Test the emoji converter" + name = "test-emoji".toKey() + description = "Test the emoji converter".toKey() action { respond { @@ -59,8 +60,8 @@ public class ArgumentTestExtension : Extension() { } publicSlashCommand(::OptionalArgs) { - name = "optional-autocomplete" - description = "Check whether autocomplete works with an optional converter." + name = "optional-autocomplete".toKey() + description = "Check whether autocomplete works with an optional converter.".toKey() action { respond { @@ -70,8 +71,8 @@ public class ArgumentTestExtension : Extension() { } publicSlashCommand(::LengthConstrainedArgs) { - name = "length-constrained" - description = "Check if length limits work" + name = "length-constrained".toKey() + description = "Check if length limits work".toKey() action { respond { @@ -86,8 +87,8 @@ public class ArgumentTestExtension : Extension() { } publicSlashCommand(::AttachmentArguments) { - name = "attachment" - description = "Check attachment command options." + name = "attachment".toKey() + description = "Check attachment command options.".toKey() action { respond { @@ -103,8 +104,8 @@ public class ArgumentTestExtension : Extension() { } publicSlashCommand(::ChannelArguments) { - name = "channel" - description = "Check channel command options." + name = "channel".toKey() + description = "Check channel command options.".toKey() action { respond { @@ -116,8 +117,8 @@ public class ArgumentTestExtension : Extension() { } publicSlashCommand(::AutocompleteArguments) { - name = "autocomplete" - description = "Test auto-completion events" + name = "autocomplete".toKey() + description = "Test auto-completion events".toKey() action { respond { @@ -134,15 +135,15 @@ public class ArgumentTestExtension : Extension() { override val parseForAutocomplete: Boolean = true public val channel: Channel? by optionalChannel { - name = "channel" - description = "Channel to select a tag from" + name = "channel".toKey() + description = "Channel to select a tag from".toKey() requireChannelType(ChannelType.GuildForum) } public val tag: ForumTag? by optionalTag { - name = "tag" - description = "Tag to use" + name = "tag".toKey() + description = "Tag to use".toKey() channelGetter = { channel?.asChannelOfOrNull() @@ -152,8 +153,8 @@ public class ArgumentTestExtension : Extension() { public inner class OptionalArgs : Arguments() { public val response: String? by optionalString { - name = "response" - description = "Text to receive" + name = "response".toKey() + description = "Text to receive".toKey() autoComplete { suggestStringMap( @@ -169,15 +170,15 @@ public class ArgumentTestExtension : Extension() { public inner class LengthConstrainedArgs : Arguments() { public val name: String by string { - name = "name" - description = "The user's name." + name = "name".toKey() + description = "The user's name.".toKey() minLength = 3 maxLength = 10 } public val lastName: String? by optionalString { - name = "last_name" - description = "The user's last name." + name = "last-name".toKey() + description = "The user's last name.".toKey() minLength = 4 maxLength = 15 } @@ -185,20 +186,20 @@ public class ArgumentTestExtension : Extension() { public inner class AttachmentArguments : Arguments() { public val file: Attachment by attachment { - name = "file" - description = "An attached file." + name = "file".toKey() + description = "An attached file.".toKey() } public val optionalFile: Attachment? by optionalAttachment { - name = "optional_file" - description = "An optional file." + name = "optional-file".toKey() + description = "An optional file.".toKey() } } public inner class ChannelArguments : Arguments() { public val channel: Channel by channel { - name = "channel" - description = "A text channel" + name = "channel".toKey() + description = "A text channel".toKey() requireChannelType(ChannelType.GuildText) } @@ -206,8 +207,8 @@ public class ArgumentTestExtension : Extension() { public inner class EmojiArguments : Arguments() { public val emoji: Emoji by emoji { - name = "emoji" - description = "A custom or Unicode emoji" + name = "emoji".toKey() + description = "A custom or Unicode emoji".toKey() } } @@ -215,17 +216,17 @@ public class ArgumentTestExtension : Extension() { override val parseForAutocomplete: Boolean = true public val one: String by stringChoice { - name = "one" - description = "Choice argument" + name = "one".toKey() + description = "Choice argument".toKey() - choice("O", "o") - choice("T", "t") - choice("F", "f") + choice("O".toKey(), "o") + choice("T".toKey(), "t") + choice("F".toKey(), "f") } public val two: String by string { - name = "two" - description = "Autocomplete argument" + name = "two".toKey() + description = "Autocomplete argument".toKey() autoComplete { suggestStringCollection( diff --git a/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/I18nTestExtension.kt b/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/I18nTestExtension.kt index bb345538ea..4353e28dfb 100644 --- a/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/I18nTestExtension.kt +++ b/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/I18nTestExtension.kt @@ -13,6 +13,7 @@ package dev.kordex.test.bot.extensions import dev.kord.common.asJavaLocale import dev.kord.core.behavior.interaction.suggestString import dev.kord.core.event.interaction.ChatInputCommandInteractionCreateEvent +import dev.kordex.core.annotations.NotTranslated import dev.kordex.core.checks.types.CheckContextWithCache import dev.kordex.core.checks.userFor import dev.kordex.core.commands.Arguments @@ -23,15 +24,18 @@ import dev.kordex.core.commands.converters.impl.string import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.ephemeralSlashCommand import dev.kordex.core.extensions.publicSlashCommand +import dev.kordex.core.i18n.toKey +import dev.kordex.core.i18n.types.Bundle +import dev.kordex.test.bot.Translations +@OptIn(NotTranslated::class) public class I18nTestExtension : Extension() { override val name: String = "kordex.test-i18n" - override val bundle: String = "test" override suspend fun setup() { publicSlashCommand { - name = "command.banana-flat" - description = "Translated banana" + name = Translations.Command.bananaFlat + description = "Translated banana".toKey() action { val commandLocale = getLocale() @@ -41,17 +45,19 @@ public class I18nTestExtension : Extension() { "Command locale (`$commandLocale`) does not match interaction locale (`$interactionLocale`)" } - respond { content = "Text: ${translate("command.banana")}" } + respond { + content = "Text: ${Translations.Command.banana.translateLocale(getLocale())}" + } } } publicSlashCommand { - name = "command.banana-sub" - description = "Translated banana subcommand" + name = Translations.Command.bananaSub + description = "Translated banana subcommand".toKey() publicSubCommand { - name = "command.banana" - description = "Translated banana" + name = Translations.Command.banana + description = "Translated banana".toKey() action { val commandLocale = getLocale() @@ -61,21 +67,23 @@ public class I18nTestExtension : Extension() { "Command locale (`$commandLocale`) does not match interaction locale (`$interactionLocale`)" } - respond { content = "Text: ${translate("command.banana")}" } + respond { + content = "Text: ${Translations.Command.banana.translateLocale(getLocale())}" + } } } } publicSlashCommand { - name = "command.banana-group" - description = "Translated banana group" + name = Translations.Command.bananaGroup + description = "Translated banana group".toKey() - group("command.banana") { - description = "Translated banana group" + group(Translations.Command.banana) { + description = "Translated banana group".toKey() publicSubCommand { - name = "command.banana" - description = "Translated banana" + name = Translations.Command.banana + description = "Translated banana".toKey() action { val commandLocale = getLocale() @@ -85,44 +93,46 @@ public class I18nTestExtension : Extension() { "Command locale (`$commandLocale`) does not match interaction locale (`$interactionLocale`)" } - respond { content = "Text: ${translate("command.banana")}" } + respond { + content = "Text: ${Translations.Command.banana.translateLocale(getLocale())}" + } } } } } publicSlashCommand(::I18nTestArguments) { - name = "command.fruit" - description = "command.fruit" + name = Translations.Command.fruit + description = Translations.Command.fruit action { respond { - content = translate("command.fruit.response", arrayOf(arguments.fruit)) + content = Translations.Command.Fruit.response + .withLocale(getLocale()) + .translate(arguments.fruit) } } } publicSlashCommand(::I18nTestNamedArguments) { - name = "command.apple" - description = "command.apple" + name = Translations.Command.apple + description = Translations.Command.apple action { respond { - content = translate( - "command.apple.response", - - mapOf( + content = Translations.Command.Apple.response + .withLocale(getLocale()) + .translateNamed( "name" to arguments.name, "appleCount" to arguments.count ) - ) } } } ephemeralSlashCommand { - name = "test-translated-checks" - description = "Command that always fails, to check CheckContext translations." + name = "test-translated-checks".toKey() + description = "Command that always fails, to check CheckContext translations.".toKey() check { translatedChecks() @@ -137,8 +147,8 @@ public class I18nTestExtension : Extension() { } ephemeralSlashCommand(::I18nTestValidations) { - name = "test-translated-validations" - description = "Command with arguments that always fail validations." + name = "test-translated-validations".toKey() + description = "Command with arguments that always fail validations.".toKey() action { // This command is expected to always fail, in order to test argument validations. @@ -151,51 +161,116 @@ public class I18nTestExtension : Extension() { private suspend fun CheckContextWithCache.translatedChecks() { val user = userFor(event) - this.defaultBundle = bundle if (user == null) { - fail("Could not get user.") + fail("Could not get user.".toKey()) return } fail( buildList { // Translate, with default bundle - add(translate("check.simple")) + add( + Translations.Check.simple + .withLocale(locale) + .translate() + ) + // Translate with a different bundle - add(translate("check.simple", "custom")) + add( + Translations.Check.simple + .withBundle(Bundle("custom")) + .withLocale(locale) + .translate() + ) + // Translate with default bundle, and positional parameters - add(translate("check.positionalParameters", arrayOf(user.mention, user.id))) + add( + Translations.Check.positionalParameters + .withLocale(locale) + .translate(user.mention, user.id) + ) + // Translate with a different bundle, and positional parameters - add(translate("check.positionalParameters", "custom", arrayOf(user.mention, user.id))) + add( + Translations.Check.positionalParameters + .withBundle(Bundle("custom")) + .withLocale(locale) + .translate(user.mention, user.id) + ) + // Translate with default bundle, named parameters - add(translate("check.namedParameters", replacements = mapOf("user" to user.mention, "id" to user.id))) + add( + Translations.Check.namedParameters + .withLocale(locale) + .translateNamed("user" to user.mention, "id" to user.id) + ) + // Translate with a different bundle, and named parameters - add(translate("check.namedParameters", "custom", mapOf("user" to user.mention, "id" to user.id))) + add( + Translations.Check.namedParameters + .withBundle(Bundle("custom")) + .withLocale(locale) + .translateNamed("user" to user.mention, "id" to user.id) + ) }.joinToString("\n") ) } private inner class I18nTestValidations : Arguments() { val name by string { - name = "name" - description = "Will always fail to validate." + name = "name".toKey() + description = "Will always fail to validate.".toKey() + validate { - defaultBundle = bundle + val locale = context.getLocale() + fail( buildList { // Translate, with default bundle - add(translate("validation.simple")) + add( + Translations.Validation.simple + .withLocale(locale) + .translate() + ) + // Translate with a different bundle - add(translate("validation.simple", "custom")) + add( + Translations.Validation.simple + .withBundle(Bundle("custom")) + .withLocale(locale) + .translate() + ) + // Translate with default bundle, and positional parameters - add(translate("validation.positionalParameters", arrayOf(value))) + add( + Translations.Validation.positionalParameters + .withLocale(locale) + .translate(value) + ) + // Translate with a different bundle, and positional parameters - add(translate("validation.positionalParameters", "custom", arrayOf(value))) + add( + Translations.Validation.positionalParameters + .withBundle(Bundle("custom")) + .withLocale(locale) + .translate(value) + ) + // Translate with default bundle, named parameters - add(translate("validation.namedParameters", mapOf("value" to value))) + add( + Translations.Validation.namedParameters + .withLocale(locale) + .translateNamed("value" to value) + ) + // Translate with a different bundle, and named parameters - add(translate("validation.namedParameters", "custom", mapOf("value" to value))) + add( + Translations.Validation.namedParameters + .withBundle(Bundle("custom")) + .withLocale(locale) + .translateNamed("value" to value) + ) }.joinToString("\n") ) } @@ -205,8 +280,8 @@ public class I18nTestExtension : Extension() { internal class I18nTestArguments : Arguments() { val fruit by string { - name = "command.fruit" - description = "command.fruit" + name = Translations.Command.fruit + description = Translations.Command.fruit autoComplete { suggestString { @@ -218,12 +293,12 @@ internal class I18nTestArguments : Arguments() { internal class I18nTestNamedArguments : Arguments() { val name by string { - name = "command.apple.argument.name" - description = "command.apple.argument.name" + name = Translations.Command.Apple.Argument.name + description = Translations.Command.Apple.Argument.name } val count by int { - name = "command.apple.argument.count" - description = "command.apple.argument.count" + name = Translations.Command.Apple.Argument.count + description = Translations.Command.Apple.Argument.count } } diff --git a/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/ModalTestExtension.kt b/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/ModalTestExtension.kt index 0dfd73be15..799d629b13 100644 --- a/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/ModalTestExtension.kt +++ b/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/ModalTestExtension.kt @@ -20,15 +20,17 @@ import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.publicMessageCommand import dev.kordex.core.extensions.publicSlashCommand import dev.kordex.core.extensions.publicUserCommand +import dev.kordex.core.i18n.toKey +import dev.kordex.core.i18n.types.Key +import dev.kordex.test.bot.Translations public class ModalTestExtension : Extension() { override val name: String = "kordex.modals" - override val bundle: String = "test.strings" @Suppress("StringLiteralDuplication") override suspend fun setup() { publicUserCommand(::Modal) { - name = "Modal" + name = "Modal".toKey() action { modal -> respond { @@ -54,7 +56,7 @@ public class ModalTestExtension : Extension() { } publicMessageCommand(::Modal) { - name = "Modal" + name = "Modal".toKey() action { modal -> respond { @@ -80,18 +82,17 @@ public class ModalTestExtension : Extension() { } publicSlashCommand { - name = "modals" - description = "Modal testing commands" + name = "modals".toKey() + description = "Modal testing commands".toKey() publicSubCommand { - name = "button" - description = "Test a modal response to a button" + name = "button".toKey() + description = "Test a modal response to a button".toKey() action { respond { components { publicButton(::Modal) { - bundle = "test.strings" label = "Modal!" action { modal -> @@ -122,8 +123,8 @@ public class ModalTestExtension : Extension() { } publicSubCommand(::Args, ::Modal) { - name = "command" - description = "Test a modal response to a command" + name = "command".toKey() + description = "Test a modal response to a command".toKey() action { modal -> respond { @@ -157,22 +158,22 @@ public class ModalTestExtension : Extension() { public inner class Args : Arguments() { public val str: String by string { - name = "string" - description = "A string argument" + name = "string".toKey() + description = "A string argument".toKey() } } public inner class Modal : ModalForm() { - override var title: String = "modal.title" + override var title: Key = Translations.Modal.title public val line: LineTextWidget = lineText { - label = "modal.line" - placeholder = "modal.line.placeholder" + label = Translations.Modal.line + placeholder = Translations.Modal.Line.placeholder } public val paragraph: ParagraphTextWidget = paragraphText { - label = "modal.paragraph" - placeholder = "modal.paragraph.placeholder" + label = Translations.Modal.paragraph + placeholder = Translations.Modal.Paragraph.placeholder } } } diff --git a/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/PaginatorTestExtension.kt b/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/PaginatorTestExtension.kt index 0249dc64c7..6401d7836c 100644 --- a/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/PaginatorTestExtension.kt +++ b/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/PaginatorTestExtension.kt @@ -13,18 +13,19 @@ package dev.kordex.test.bot.extensions import dev.kordex.core.commands.application.slash.publicSubCommand import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.publicSlashCommand +import dev.kordex.core.i18n.toKey public class PaginatorTestExtension : Extension() { override val name: String = "kordex.test-paginator" override suspend fun setup() { publicSlashCommand { - name = "paginator" - description = "Paginator testing commands." + name = "paginator".toKey() + description = "Paginator testing commands.".toKey() publicSubCommand { - name = "default" - description = "Test a default-grouped paginator with pages." + name = "default".toKey() + description = "Test a default-grouped paginator with pages.".toKey() action { editingPaginator { @@ -44,8 +45,8 @@ public class PaginatorTestExtension : Extension() { } publicSubCommand { - name = "chunked" - description = "Test a chunked default-group paginator with pages." + name = "chunked".toKey() + description = "Test a chunked default-group paginator with pages.".toKey() action { editingPaginator { @@ -87,8 +88,8 @@ public class PaginatorTestExtension : Extension() { } publicSubCommand { - name = "chunked-small" - description = "Test a chunked default-group paginator with one page." + name = "chunked-small".toKey() + description = "Test a chunked default-group paginator with one page.".toKey() action { editingPaginator { @@ -103,20 +104,20 @@ public class PaginatorTestExtension : Extension() { } publicSubCommand { - name = "custom-one" - description = "Test a custom-grouped paginator with pages, approach 1." + name = "custom-one".toKey() + description = "Test a custom-grouped paginator with pages, approach 1.".toKey() action { - editingPaginator("custom") { - page(group = "custom") { + editingPaginator("custom".toKey()) { + page(group = "custom".toKey()) { description = "Page one!" } - page(group = "custom") { + page(group = "custom".toKey()) { description = "Page two!" } - page(group = "custom") { + page(group = "custom".toKey()) { description = "Page three!" } }.send() @@ -124,20 +125,20 @@ public class PaginatorTestExtension : Extension() { } publicSubCommand { - name = "custom-two" - description = "Test a custom-grouped paginator with pages, approach 2." + name = "custom-two".toKey() + description = "Test a custom-grouped paginator with pages, approach 2.".toKey() action { - editingPaginator("custom") { - page("custom") { + editingPaginator("custom".toKey()) { + page("custom".toKey()) { description = "Page one!" } - page("custom") { + page("custom".toKey()) { description = "Page two!" } - page("custom") { + page("custom".toKey()) { description = "Page three!" } }.send() @@ -145,11 +146,11 @@ public class PaginatorTestExtension : Extension() { } publicSubCommand { - name = "custom-pageless" - description = "Test a custom-grouped paginator without pages." + name = "custom-pageless".toKey() + description = "Test a custom-grouped paginator without pages.".toKey() action { - editingPaginator("custom") { }.send() + editingPaginator("custom".toKey()) { }.send() } } } diff --git a/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/SelectorTestExtension.kt b/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/SelectorTestExtension.kt index c625f71434..8b8cf36944 100644 --- a/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/SelectorTestExtension.kt +++ b/test-bot/src/main/kotlin/dev/kordex/test/bot/extensions/SelectorTestExtension.kt @@ -16,18 +16,19 @@ import dev.kordex.core.commands.application.slash.publicSubCommand import dev.kordex.core.components.* import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.publicSlashCommand +import dev.kordex.core.i18n.toKey public class SelectorTestExtension : Extension() { override val name: String = "kordex.test-selectors" override suspend fun setup() { publicSlashCommand { - name = "selector" - description = "Test selectors." + name = "selector".toKey() + description = "Test selectors.".toKey() publicSubCommand { - name = "public" - description = "Test public selectors." + name = "public".toKey() + description = "Test public selectors.".toKey() action { respond { @@ -88,8 +89,8 @@ public class SelectorTestExtension : Extension() { } ephemeralSubCommand { - name = "mentionable" - description = "Test mentionable selectors." + name = "mentionable".toKey() + description = "Test mentionable selectors.".toKey() action { respond { @@ -133,8 +134,8 @@ public class SelectorTestExtension : Extension() { } ephemeralSubCommand { - name = "ephemeral" - description = "Test ephemeral selectors." + name = "ephemeral".toKey() + description = "Test ephemeral selectors.".toKey() action { respond {