From a7d03c63515c1507cf045727458d7d306417afac Mon Sep 17 00:00:00 2001 From: NoComment <67918617+NoComment1105@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:24:59 +0000 Subject: [PATCH] Translations (#429) * Translations 2 electric boogaloo * Translate moooaaarrrrrr * Translate everything that's already in the bundle * Translate some of the moderation packages * Translate Moderation Commands * Translate Moderation Utilities * Translate Report * Translate Gallery channels * Translate GitHub, Announcements and Info commands. Clean up public utilities * Translate news channels * Translate Reminders * Translate Role Menus * Translate Tags and online notification * Translate utility config command * Translate About Commands, Checks and Utilities * Fix some errors and incorrect strings * Fix missing translation keys --- build.gradle.kts | 27 + docs/commands.md | 190 +-- gradle/libs.versions.toml | 6 +- settings.gradle.kts | 10 + .../org/hyacinthbots/lilybot/LilyBot.kt | 48 +- .../extensions/config/ConfigExtension.kt | 5 +- .../config/commands/ConfigClearCommand.kt | 40 +- .../config/commands/ConfigViewCommand.kt | 155 ++- .../extensions/config/utils/ConfigEmbeds.kt | 126 +- .../extensions/logging/config/LoggingArgs.kt | 30 +- .../logging/config/LoggingCommand.kt | 29 +- .../logging/config/PublicLoggingModal.kt | 15 +- .../logging/events/MemberLogging.kt | 25 +- .../logging/events/MessageDelete.kt | 35 +- .../extensions/logging/events/MessageEdit.kt | 22 +- .../moderation/commands/ClearCommands.kt | 79 +- .../moderation/commands/LockingCommands.kt | 134 +- .../moderation/commands/ModUtilities.kt | 197 ++- .../moderation/commands/ModerationCommands.kt | 538 ++++---- .../extensions/moderation/commands/Report.kt | 83 +- .../moderation/config/ModerationArgs.kt | 41 +- .../moderation/config/ModerationCommand.kt | 22 +- .../moderation/events/ModerationEvents.kt | 105 +- .../extensions/threads/AutoThreading.kt | 176 +-- .../extensions/threads/ThreadControl.kt | 159 ++- .../utility/commands/GalleryChannel.kt | 54 +- .../extensions/utility/commands/Github.kt | 207 ++- .../utility/commands/GuildAnnouncements.kt | 44 +- .../utility/commands/InfoCommands.kt | 59 +- .../utility/commands/NewsChannelPublishing.kt | 76 +- .../utility/commands/PublicUtilities.kt | 128 +- .../extensions/utility/commands/Reminders.kt | 274 ++-- .../extensions/utility/commands/RoleMenu.kt | 249 ++-- .../utility/commands/StartupHooks.kt | 3 +- .../extensions/utility/commands/Tags.kt | 245 ++-- .../extensions/utility/config/UtilityArgs.kt | 21 +- .../utility/config/UtilityCommand.kt | 11 +- .../utility/events/UtilityEvents.kt | 274 ++-- .../lilybot/utils/ResponseHelper.kt | 49 +- .../lilybot/utils/_ConfigUtils.kt | 33 +- .../lilybot/utils/_PermissionUtils.kt | 50 +- .../org/hyacinthbots/lilybot/utils/_Utils.kt | 4 +- .../translations/lilybot/strings.properties | 1174 +++++++++++++++++ 43 files changed, 3279 insertions(+), 1973 deletions(-) create mode 100644 src/main/resources/translations/lilybot/strings.properties diff --git a/build.gradle.kts b/build.gradle.kts index 40ba2bbd..4dd3ea04 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,4 @@ +import dev.kordex.gradle.plugins.kordex.DataCollection import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.KotlinVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -12,6 +13,7 @@ plugins { alias(libs.plugins.git.hooks) alias(libs.plugins.grgit) alias(libs.plugins.blossom) + alias(libs.plugins.kord.extensions.plugin) } group = "org.hyacinthbots.lilybot" @@ -32,6 +34,16 @@ repositories { url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") } + maven { + name = "Kord Snapshots" + url = uri("https://repo.kord.dev/snapshots") + } + + maven { + name = "Kord Extensions (Releases)" + url = uri("https://releases-repo.kordex.dev") + } + maven { name = "Kord Extensions (Snapshots)" url = uri("https://snapshots-repo.kordex.dev") @@ -67,6 +79,21 @@ dependencies { implementation(libs.docgenerator) } +kordEx { + addDependencies = false + addRepositories = false + kordExVersion = libs.versions.kord.extensions + + bot { + dataCollection(DataCollection.None) + } + + i18n { + classPackage = "lilybot.i18n" + translationBundle = "lilybot.strings" + } +} + application { mainClass.set(className) } diff --git a/docs/commands.md b/docs/commands.md index e0320353..36f6a3b8 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -1,7 +1,7 @@ ## Slash Commands ### Command name: `about copyright` -**Description**: Library, licencing, and copyright information +**Description**: Library, licensing, and copyright information * **Arguments**: None @@ -51,7 +51,7 @@ None --- ### Command name: `auto-threading add-roles` -**Description**: Add extra to threads in auto-threaded channels +**Description**: Add extra roles to threads in auto-threaded channels **Additional Information**: This command will add roles to be pinged alongside the default ping role for this auto-threaded channel @@ -62,7 +62,7 @@ None --- ### Command name: `auto-threading remove-roles` -**Description**: Remove extra from threads in auto-threaded channels +**Description**: Remove extra roles from threads in auto-threaded channels **Additional Information**: This command will remove roles that have been added to be pinged alongside the default ping role for this auto-threaded channel @@ -78,7 +78,7 @@ None **Required Member Permissions**: Manage Messages * **Arguments**: - * `messages` - Number of messages to delete - Int + * `message-count` - The number of messages to clear - Int * `author` - The author of the messages to clear - Optional User --- @@ -94,7 +94,7 @@ None --- ### Command name: `clear after` -**Description**: Clear messages before a given message ID +**Description**: clear messages after a given message ID **Required Member Permissions**: Manage Messages @@ -105,7 +105,7 @@ None --- ### Command name: `clear between` -**Description**: Clear messages between 2 message IDs +**Description**: Clear messages between 2 message ID's **Required Member Permissions**: Manage Messages @@ -124,7 +124,7 @@ None * `enable-delete-logs` - Enable logging of message deletions - Boolean * `enable-edit-logs` - Enable logging of message edits - Boolean * `enable-member-logging` - Enable logging of members joining and leaving the guild - Boolean - * `enable-public-member-logging` - Enable logging of members joining and leaving the guild with a public message and ping if enabled - Boolean + * `enable-public-member-logging` - Enable logging of members joining and leaving the guild with a public message - Boolean * `message-logs` - The channel for logging message deletions - Optional Channel * `member-log` - The channel for logging members joining and leaving the guild - Optional Channel * `public-member-log` - The channel for the public logging of members joining and leaving the guild - Optional Channel @@ -137,14 +137,14 @@ None * **Arguments**: * `enable-moderation` - Whether to enable the moderation system - Boolean - * `moderator-role` - The role of your moderators, used for pinging in message logs. - Optional Role - * `action-log` - The channel used to store moderator actions. - Optional Channel + * `moderator-role` - The role of your moderators, used for pinging in message logs - Optional Role + * `action-log` - The channel used to store moderator actions - Optional Channel * `quick-timeout-length` - The length of timeouts to use for quick timeouts - Coalescing Optional Duration - * `warn-auto-punishments` - Whether to automatically punish users for reach a certain threshold on warns - Optional Boolean - * `log-publicly` - Whether to log moderation publicly or not. - Optional Boolean - * `dm-default` - The default value for whether to DM a user in a ban action or not. - Optional Boolean + * `warn-auto-punishments` - Whether to automatically punish users for reaching a certain threshold on warns - Optional Boolean + * `log-publicly` - Whether to log moderation publicly or not - Optional Boolean + * `dm-default` - The default value for whether to DM a user in a moderation action - Optional Boolean * `ban-dm-message` - A custom message to send to users when they are banned. - Optional String - * `auto-invite-moderator-role` - Silently ping moderators to invite them to new threads. - Optional Boolean + * `auto-invite-moderator-role` - Silent ping moderators to invite them to new threads. - Optional Boolean * `log-member-role-changes` - Whether to log changes to the roles members have in a guild. - Optional Boolean --- @@ -156,7 +156,7 @@ None * **Arguments**: * `utility-log` - The channel to log various utility actions too. - Optional Channel * `log-channel-updates` - Whether to log changes made to channels in this guild. - Defaulting Boolean - * `log-event-updates` - Whether to log changes made to scheduled events in this guild. - Defaulting Boolean + * `log-event-updates` - Whether to log changes made to events in this guild. - Defaulting Boolean * `log-invite-updates` - Whether to log changes made to invites in this guild. - Defaulting Boolean * `log-role-updates` - Whether to log changes made to roles in this guild. - Defaulting Boolean @@ -171,12 +171,12 @@ None --- ### Command name: `config view` -**Description**: View the current config that you have set +**Description**: View the current config you have set **Required Member Permissions**: Manage Server * **Arguments**: - * `config-type` - The type of config to clear - String Choice + * `config-type` - The type of config to view - String Choice --- ### Command name: `gallery-channel set` @@ -206,30 +206,30 @@ None * **Arguments**: * `issue-number` - The issue number you would like to search for - Int - * `repository` - The GitHub repository you would like to search if no default is set - Optional String + * `repository` - The GitHub repository you would like to search, if no default is set. - Optional String --- ### Command name: `github repo` **Description**: Search GitHub for a specific repository * **Arguments**: - * `repository` - The GitHub repository you would like to search if no default is set - Optional String + * `repository` - The GitHub repository you would like to search, if no default is set. - Optional String --- ### Command name: `github user` -**Description**: Search GitHub for a User/Organisation +**Description**: Search GitHub for a User/Organization * **Arguments**: - * `username` - The name of the User/Organisation you wish to search for - String + * `username` - The name of the User/Organisation you wissh to search for - String --- ### Command name: `github default-repo` -**Description**: Set the default repo to look up issues in. +**Description**: Set the default repo to look up issues in **Required Member Permissions**: Moderate Members * **Arguments**: - * `default-repo` - The default repo to look up issues in - String + * `default-repo` - Set the default repo to look up issues in - String --- ### Command name: `github remove-default-repo` @@ -246,7 +246,7 @@ None **Required Member Permissions**: Administrator * Arguments: - * `target-guild` - The guild to send the announcement too - Optional Snowflake + * `target-guild` - The ID of the target guild to send the announcement too. - Optional Snowflake --- ### Command name: `help` @@ -277,11 +277,11 @@ None **Required Member Permissions**: Moderate Members * **Arguments**: - * `reason` - Reason for locking the server - Defaulting String + * `reason` - Reason for locking the channel - Defaulting String --- ### Command name: `unlock channel` -**Description**: Unlock a channel so everyone can send messages again +**Description**: Unlock a channel so everyone can type messages again **Required Member Permissions**: Moderate Members @@ -303,11 +303,11 @@ None **Required Member Permissions**: Moderate Members * Arguments: - * `message` - The text of the message to be sent. - String - * `channel` - The channel the message should be sent in. - Optional Channel - * `embed` - If the message should be sent as an embed. - Defaulting Boolean + * `message` - The text of the message to be sent - String + * `channel` - The channel the message should be sent in - Optional Channel + * `embed` - If the message should be sent as an embed - Defaulting Boolean * `timestamp` - If the message should be sent with a timestamp. Only works with embeds. - Defaulting Boolean - * `color` - The color of the embed. Can be either a hex code or one of Discord's supported colors. Embeds only - Defaulting Color + * `color` - The color of the embed. Can be either a hex code or one of Discord's support colors. Embeds only - Defaulting Color --- ### Command name: `edit-say` @@ -318,7 +318,7 @@ None * Arguments: * `message-to-edit` - The ID of the message you'd like to edit - Snowflake * `new-content` - The new content of the message - Optional String - * `new-color` - The new color of the embed. Embeds only - Optional Color + * `new-color` - The new color of the embed. Can be either a hex code or one of Discord's support colors. Embeds only - Optional Color * `channel-of-message` - The channel of the message - Optional Channel * `timestamp` - Whether to timestamp the embed or not. Embeds only - Optional Boolean @@ -354,11 +354,11 @@ None **Required Member Permissions**: Ban Members * Arguments: - * `user` - Person to ban - User + * `user` - Person to action - User * `delete-message-days` - The number of days worth of messages to delete - Int - * `reason` - The reason for the ban - Defaulting String - * `soft-ban` - Weather to soft-ban this user (unban them once messages are deleted) - Defaulting Boolean - * `dm` - Whether to send a direct message to the user about the ban - Optional Boolean + * `reason` - The reason for the action - Defaulting String + * `soft-ban` - Whether to soft-ban this user (unban them once messages are deleted) - Defaulting Boolean + * `dm` - Whether to send the user a direct message about the action - Optional Boolean * `image` - An image you'd like to provide as extra context for the action - Optional Attachment --- @@ -368,11 +368,11 @@ None **Required Member Permissions**: Ban Members * **Arguments**: - * `user` - Person to ban - User + * `user` - Person to action - User * `delete-message-days` - The number of days worth of messages to delete - Int - * `duration` - The duration of the temporary ban. - Coalescing Duration - * `reason` - The reason for the ban - Defaulting String - * `dm` - Whether to send a direct message to the user about the ban - Optional Boolean + * `duration` - The duration of the action - Coalescing Duration + * `reason` - The reason for the action - Defaulting String + * `dm` - Whether to send the user a direct message about the action - Optional Boolean * `image` - An image you'd like to provide as extra context for the action - Optional Attachment --- @@ -390,8 +390,8 @@ None **Required Member Permissions**: Ban Members * Arguments: - * `user` - Person to un-ban - User - * `reason` - The reason for the un-ban - Defaulting String + * `user` - Person to action - User + * `reason` - The reason for the action - Defaulting String --- ### Command name: `kick` @@ -400,9 +400,9 @@ None **Required Member Permissions**: Kick Members * Arguments: - * `user` - Person to kick - User - * `reason` - The reason for the Kick - Defaulting String - * `dm` - Whether to send a direct message to the user about the kick - Optional Boolean + * `user` - Person to action - User + * `reason` - The reason for the action - Defaulting String + * `dm` - Whether to send the user a direct message about the action - Optional Boolean * `image` - An image you'd like to provide as extra context for the action - Optional Attachment --- @@ -412,10 +412,10 @@ None **Required Member Permissions**: Moderate Members * Arguments: - * `user` - Person to timeout - User + * `user` - Person to action - User * `duration` - Duration of timeout - Coalescing Optional Duration - * `reason` - Reason for timeout - Defaulting String - * `dm` - Whether to send a direct message to the user about the timeout - Optional Boolean + * `reason` - The reason for the action - Defaulting String + * `dm` - Whether to send the user a direct message about the action - Optional Boolean * `image` - An image you'd like to provide as extra context for the action - Optional Attachment --- @@ -425,8 +425,8 @@ None **Required Member Permissions**: Moderate Members * Arguments: - * `user` - Person to remove timeout from - User - * `dm` - Whether to dm the user about this or not - Optional Boolean + * `user` - Person to action - User + * `dm` - Whether to send the user a direct message about the action - Optional Boolean --- ### Command name: `warn` @@ -435,20 +435,20 @@ None **Required Member Permissions**: Moderate Members * Arguments: - * `user` - Person to warn - User - * `reason` - Reason for warning - Defaulting String - * `dm` - Whether to send a direct message to the user about the warning - Optional Boolean + * `user` - Person to action - User + * `reason` - The reason for the action - Defaulting String + * `dm` - Whether to send the user a direct message about the action - Optional Boolean * `image` - An image you'd like to provide as extra context for the action - Optional Attachment --- -### Command name: `remove-warn` +### Command name: `remove-warning` **Description**: Removes a user's warnings **Required Member Permissions**: Moderate Members * Arguments: - * `user` - Person to remove warn from - User - * `dm` - Whether to send a direct message to the user about the warning - Defaulting Boolean + * `user` - Person to action - User + * `dm` - Whether to send the user a direct message about the action - Defaulting Boolean --- ### Command name: `news-publishing set` @@ -466,18 +466,18 @@ None **Required Member Permissions**: Manage Server * **Arguments**: - * `channel` - The channel to stop auto-publishing for - Channel + * `channel` - The channel to set auto-publishing for - Channel --- ### Command name: `news-publishing list` -**Description**: List Auto-publishing channels +**Description**: List auto-publishing channels **Required Member Permissions**: Manage Server * **Arguments**: None --- -### Command name: `news-publishing remove-all` +### Command name: `news-publishing remove-al` **Description**: Remove all auto-publishing channels for this guild **Required Member Permissions**: Manage Server @@ -492,7 +492,7 @@ None None --- ### Command name: `nickname request` -**Description**: Request a new nickname for the server! +**Description**: Request a new nickname for this server! * **Arguments**: * `nickname` - The new nickname you would like - String @@ -508,11 +508,11 @@ None **Description**: Set a reminder for some time in the future! * **Arguments**: - * `time` - How long until reminding? Format: 1d12h30m / 3d / 26m30s - Coalescing Duration + * `time` - How long until you need reminding? Format: 1d12h30m / 3d / 26m30s - Coalescing Duration * `remind-in-dm` - Whether to remind in DMs or not - Boolean * `custom-message` - A message to attach to your reminder - Optional String - * `repeat` - Whether to repeat the reminder or not - Defaulting Boolean - * `repeat-interval` - The interval to repeat the reminder at. Format: 1d / 1h / 5d - Coalescing Optional Duration + * `repeat` - Whether to repeat the number or not - Defaulting Boolean + * `repeat-interval` - The interval to repeating the reminder at. Format: 1d / 4h / 5d - Coalescing Optional Duration --- ### Command name: `reminder list` @@ -522,14 +522,14 @@ None None --- ### Command name: `reminder remove` -**Description**: Remove a reminder you have set from this guild +**Description**: The parent command for all reminder commands * **Arguments**: - * `reminder-number` - The number of the reminder to remove. Use '/reminder list' to get this - Long + * `reminder-number` - The number of the reminder to remove. Use `/reminder list` to get this - Long --- ### Command name: `reminder remove-all` -**Description**: Remove all a specific type of reminder from this guild +**Description**: Remove all of a specific reminder type from this guild * **Arguments**: * `reminder-type` - The type of reminder to remove - String Choice @@ -551,11 +551,11 @@ None * **Arguments**: * `user` - The user to remove the reminder for - User - * `reminder-number` - The number of the reminder to remove. Use '/reminder mod-list' to get this - Long + * `reminder-number` - The number of the reminder to remove. Use `/reminder mod-list` to get this. - Long --- ### Command name: `reminder mod-remove-all` -**Description**: Remove all a specific type of reminder for a user, if you're a moderator +**Description**: Remove all of a specific reminder type for a user, if you're a moderator **Required Member Permissions**: Moderate Members @@ -572,14 +572,14 @@ None --- ### Command name: `role-menu create` -**Description**: Create a new role menu in this channel. A channel can have any number of role menus. +**Description**: Create a new role menu in this channel. A channel can have any number of role menus **Required Member Permissions**: Manage Roles * **Arguments**: * `role` - The first role to start the menu with. Add more via `/role-menu add` - Role * `content` - The content of the embed or message. - String - * `embed` - If the message containing the role menu should be sent as an embed. - Defaulting Boolean + * `embed` - If the message containing the role menu should be sent as an embed - Defaulting Boolean * `color` - The color for the message to be. Embed only. - Defaulting Color --- @@ -599,8 +599,8 @@ None **Required Member Permissions**: Manage Messages * **Arguments**: - * `menu-id` - The message ID of the menu you'd like to edit. - Snowflake - * `role` - The role you'd like to remove from the selected role menu. - Role + * `menu-id` - The message ID of the role menu you'd like to edit. - Snowflake + * `role` - The role you'd like to remove from the selected role menu, - Role --- ### Command name: `role-menu pronouns` @@ -646,12 +646,12 @@ None **Description**: Call a tag from this guild! Use /tag-help for more info. * Arguments: - * `name` - The name of the tag you want to call - String + * `name` - The name of the tag - String * `user` - The user to mention with the tag (optional) - Optional User --- ### Command name: `tag-help` -**Description**: Explains how the tag command works! +**Description**: Explains how the tag command works * Arguments: None @@ -662,8 +662,8 @@ None **Required Member Permissions**: Moderate Members * Arguments: - * `name` - The name of the tag you're making - String - * `title` - The title of the tag embed you're making - String + * `name` - The name of the tag - String + * `title` - The title of the tag embed - String * `value` - The content of the tag embed you're making - String * `appearance` - The appearance of the tag embed you're making - String Choice @@ -683,10 +683,10 @@ None **Required Member Permissions**: Moderate Members * Arguments: - * `name` - The name of the tag you're editing - String - * `new-name` - The new name for the tag you're editing - Optional String - * `new-title` - The new title for the tag you're editing - Optional String - * `new-value` - The new value for the tag you're editing - Optional String + * `name` - The name of the tag - String + * `new-name` - The new name for the tag - Optional String + * `new-title` - The new title for the tag - Optional String + * `new-value` - The new value for the tag - Optional String * `new-appearance` - The new appearance for the tag you're editing - Optional String --- @@ -707,62 +707,62 @@ None **Description**: Archive this thread * **Arguments**: - * `lock` - Whether to lock the thread if you are a moderator. Default is false - Defaulting Boolean + * `lock` - Whether to lock the thread if you are a moderator. Default is false. - Defaulting Boolean --- ### Command name: `thread transfer` **Description**: Transfer ownership of this thread * **Arguments**: - * `new-owner` - The user you want to transfer ownership of the thread to - Member + * `new-owner` - new-owner - Member --- -### Command name: `thread prevent-archiving` +### Command name: `thread prevent-archived` **Description**: Stop a thread from being archived * **Arguments**: None --- ### Command name: `blocks` -**Description**: Get a list of the configured blocks +**Description**: Get a list of the configured blocks. * Arguments: - * `channel` - Channel representing a welcome channel - Channel + * `channel` - Message channel representing a Welcome Channel. - Channel --- ### Command name: `welcome-channels delete` -**Description**: Delete a welcome channel configuration +**Description**: Delete a Welcome Channel configuration. * **Arguments**: - * `channel` - Channel representing a welcome channel - Channel + * `channel` - Message channel representing a Welcome Channel. - Channel --- ### Command name: `welcome-channels get` -**Description**: Get the url for a welcome channel, if it's configured +**Description**: Get the url for a configured Welcome Channel. * **Arguments**: - * `channel` - Channel representing a welcome channel - Channel + * `channel` - Message channel representing a Welcome Channel. - Channel --- ### Command name: `welcome-channels refresh` -**Description**: Manually repopulate the given welcome channel +**Description**: Manually repopulate the given Welcome Channel. * **Arguments**: - * `channel` - Channel representing a welcome channel - Channel - * `clear` - Whether to clear the channel before repopulating it - Defaulting Boolean + * `channel` - Message channel representing a Welcome Channel. - Channel + * `clear` - Whether to clear and repopulate the channel instead of updating it. - Defaulting Boolean --- ### Command name: `welcome-channels set` -**Description**: Set the URL for a welcome channel, and populate it +**Description**: Set the URL for a Welcome Channel and populate it. * **Arguments**: - * `channel` - Channel representing a welcome channel - Channel - * `url` - Public link to a YAML file used to configure a welcome channel - String - * `clear` - Whether to clear the channel before repopulating it - Defaulting Boolean + * `channel` - Message channel representing a Welcome Channel. - Channel + * `url` - Public link to a Welcome Channel configuration YAML file. - String + * `clear` - Whether to clear and repopulate the channel instead of updating it. - Defaulting Boolean --- ### Command name: `url-safety-check` -**Description**: Check whether a given domain is a known unsafe domain. +**Description**: Check whether a given domain is a known unsafe domain * Arguments: * `domain` - Domain to check - String diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5f34fb27..780a2f9f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,12 +8,13 @@ grgit = "5.3.0" blossom = "2.1.0" # Libraries -kord-extensions = "2.2.1-20241015.125602-7" +kord-extensions = "2.3.0-20241020.143126-1" +kordex-plugin = "1.5.3" logging = "7.0.0" logback = "1.5.10" github-api = "1.326" kmongo = "5.1.0" -docgenerator = "0.2.2-20241016.171703-3" +docgenerator = "0.3.0-beta.1" [libraries] kord-extensions-core = { module = "dev.kordex:kord-extensions", version.ref = "kord-extensions" } @@ -38,3 +39,4 @@ detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } git-hooks = { id = "com.github.jakemarsden.git-hooks", version.ref = "git-hooks" } grgit = { id = "org.ajoberstar.grgit", version.ref = "grgit" } blossom = { id = "net.kyori.blossom", version.ref = "blossom" } +kord-extensions-plugin = { id = "dev.kordex.gradle.kordex", version.ref = "kordex-plugin"} diff --git a/settings.gradle.kts b/settings.gradle.kts index 9e813ec3..407fd248 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1,11 @@ +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + + maven("https://snapshots-repo.kordex.dev") + maven("https://releases-repo.kordex.dev") + } +} + rootProject.name = "LilyBot" diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt b/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt index 6b7de708..90637ad4 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt @@ -9,6 +9,7 @@ import dev.kord.rest.builder.message.actionRow import dev.kord.rest.builder.message.embed import dev.kordex.core.ExtensibleBot import dev.kordex.core.checks.hasPermission +import dev.kordex.core.i18n.SupportedLocales import dev.kordex.core.time.TimestampType import dev.kordex.core.time.toDiscord import dev.kordex.data.api.DataCollection @@ -19,6 +20,7 @@ import dev.kordex.modules.pluralkit.extPluralKit import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime +import lilybot.i18n.Translations import org.hyacinthbots.docgenerator.docsGenerator import org.hyacinthbots.docgenerator.enums.CommandTypes import org.hyacinthbots.docgenerator.enums.SupportedFileFormat @@ -91,39 +93,28 @@ suspend fun main() { general { message { locale -> embed { - title = "Info about LilyBot" + title = Translations.About.embedTitle.translate() thumbnail { url = "https://github.com/HyacinthBots/LilyBot/blob/main/docs/lily-logo-transparent.png?raw=true" } - description = - "Lily is a FOSS multi-purpose bot for Discord created by the HyacinthBots organization. " + - "Use `/help` for support or `/invite` to get an invite link." + description = Translations.About.embedDesc.translate() field { - name = "How can I support the continued development of Lily?" - - value = "Lily is developed primarily by NoComment#6411 in their free time. Hyacinth " + - "doesn't have the resources to invest in hosting, so financial donations via " + - "[Buy Me a Coffee](https://buymeacoffee.com/Hyacinthbots) help keep Lily afloat. " + - "Currently, we run lily on a Hetzner cloud server, which we can afford in our " + - "current situation. We also have domain costs for our website.\n\nContributions of " + - "code & documentation are also incredibly appreciated, and you can read our " + - "[contributing guide]($HYACINTH_GITHUB/LilyBot/blob/main/CONTRIBUTING.md) or " + - "[development guide]($HYACINTH_GITHUB/LilyBot/blob/main/docs/development-guide.md) " + - "to get started." + name = Translations.About.howSupportTitle.translate() + value = Translations.About.howSupportValue.translate(HYACINTH_GITHUB) } field { - name = "Version" + name = Translations.About.version.translate() // To avoid IntelliJ shouting about build errors, use https://plugins.jetbrains.com/plugin/9407-pebble value = "${BuildInfo.LILY_VERSION} (${BuildInfo.BUILD_ID})" inline = true } field { - name = "Up Since" + name = Translations.About.upSince.translate() value = "${ UptimeCollection().get()?.onTime?.toLocalDateTime(TimeZone.UTC) ?.time.toString().split(".")[0] @@ -133,14 +124,8 @@ suspend fun main() { } field { - name = "Useful links" - value = - "Website: Coming Soon™️\n" + - "GitHub: ${HYACINTH_GITHUB}\n" + - "Buy Me a Coffee: https://buymeacoffee.com/HyacinthBots\n" + - "Twitter: https://twitter.com/HyacinthBots\n" + - "Email: `hyacinthbots@outlook.com`\n" + - "Discord: https://discord.gg/hy2329fcTZ" + name = Translations.Utility.InfoCommands.Help.usefulFieldName.translate() + value = Translations.Utility.InfoCommands.Help.usefulFieldValue.translate(HYACINTH_GITHUB) } } @@ -149,15 +134,15 @@ suspend fun main() { "https://discord.com/api/oauth2/authorize?client_id=876278900836139008&" + "permissions=1151990787078&scope=bot%20applications.commands" ) { - label = "extensions.about.buttons.invite" + label = Translations.Utility.InfoCommands.Help.Button.invite.translate() } linkButton("$HYACINTH_GITHUB/LilyBot/blob/main/docs/privacy-policy.md") { - label = "Privacy Policy" + label = Translations.Utility.InfoCommands.Help.Button.privacy.translate() } linkButton("$HYACINTH_GITHUB/.github/blob/main/terms-of-service.md") { - label = "Terms of Service" + label = Translations.Utility.InfoCommands.Help.Button.tos.translate() } } } @@ -229,6 +214,13 @@ suspend fun main() { // } } + i18n { + interactionUserLocaleResolver() + interactionGuildLocaleResolver() + + applicationCommandLocale(SupportedLocales.ENGLISH) + } + docsGenerator { enabled = true fileFormat = SupportedFileFormat.MARKDOWN diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/ConfigExtension.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/ConfigExtension.kt index 454b113b..b4cc38e5 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/ConfigExtension.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/ConfigExtension.kt @@ -3,6 +3,7 @@ package org.hyacinthbots.lilybot.extensions.config import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.ephemeralSlashCommand import dev.kordex.modules.dev.unsafe.annotations.UnsafeAPI +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.extensions.config.commands.configClearCommand import org.hyacinthbots.lilybot.extensions.config.commands.configViewCommand import org.hyacinthbots.lilybot.extensions.logging.config.loggingCommand @@ -15,8 +16,8 @@ class ConfigExtension : Extension() { @OptIn(UnsafeAPI::class) override suspend fun setup() { ephemeralSlashCommand { - name = "config" - description = "Configure Lily's settings" + name = Translations.Config.name + description = Translations.Config.name loggingCommand() diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigClearCommand.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigClearCommand.kt index 2e637d52..b185c557 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigClearCommand.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigClearCommand.kt @@ -10,6 +10,7 @@ import dev.kordex.core.commands.application.slash.EphemeralSlashCommandContext import dev.kordex.core.commands.application.slash.SlashCommand import dev.kordex.core.commands.application.slash.converters.impl.stringChoice import dev.kordex.core.commands.application.slash.ephemeralSubCommand +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection @@ -18,8 +19,8 @@ import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms suspend fun SlashCommand<*, *, *>.configClearCommand() = ephemeralSubCommand(::ClearArgs) { - name = "clear" - description = "Clear a config type" + name = Translations.Config.Clear.name + description = Translations.Config.Clear.description requirePermission(Permission.ManageGuild) @@ -42,12 +43,12 @@ suspend fun SlashCommand<*, *, *>.configClearCommand() = ephemeralSubCommand(::C respond { embed { title = if (arguments.config == ConfigType.ALL.name) { - "All configs cleared" + Translations.Config.Clear.all.translate() } else { - "Config cleared: ${arguments.config}" + Translations.Config.Clear.Embed.title.translate(arguments.config) } footer { - text = "Config cleared by ${user.asUserOrNull()?.username}" + text = Translations.Config.configuredBy.translate(user.asUserOrNull()?.username) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } } @@ -64,11 +65,12 @@ suspend fun SlashCommand<*, *, *>.configClearCommand() = ephemeralSubCommand(::C * @since 5.0.0 */ private suspend fun EphemeralSlashCommandContext<*, *>.clearConfig(type: ConfigType, args: ClearArgs) { + val obj = Translations.Config.Clear when (type) { ConfigType.MODERATION -> { ModerationConfigCollection().getConfig(guild!!.id) ?: run { respond { - content = "No moderation configuration exists to clear" + content = obj.noConfigMod.translate() } return } @@ -79,7 +81,7 @@ private suspend fun EphemeralSlashCommandContext<*, *>.clearConfig(type: ConfigT ConfigType.LOGGING -> { LoggingConfigCollection().getConfig(guild!!.id) ?: run { respond { - content = "No logging configuration exists to clear" + content = obj.noConfigLogging.translate() } } logClear(args) @@ -89,7 +91,7 @@ private suspend fun EphemeralSlashCommandContext<*, *>.clearConfig(type: ConfigT ConfigType.UTILITY -> { UtilityConfigCollection().getConfig(guild!!.id) ?: run { respond { - content = "No utility configuration exists to clear" + content = obj.noConfigUtility.translate() } } logClear(args) @@ -118,18 +120,19 @@ suspend fun EphemeralSlashCommandContext<*, *>.logClear(arguments: ClearArgs) { if (utilityLog == null) { respond { - content = "Consider setting a utility config to log changes to configurations." + content = Translations.Config.considerUtility.translate() } return } utilityLog.createMessage { embed { - title = "Configuration Cleared: ${arguments.config[0]}${ + title = Translations.Config.Clear.Embed.title.translate( + arguments.config[0] + arguments.config.substring(1, arguments.config.length).lowercase() - }" + ) footer { - text = "Config cleared by ${user.asUserOrNull()?.username}" + text = Translations.Config.Clear.footer.translate(user.asUserOrNull()?.username) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } } @@ -137,14 +140,15 @@ suspend fun EphemeralSlashCommandContext<*, *>.logClear(arguments: ClearArgs) { } class ClearArgs : Arguments() { + private val choiceObj = Translations.Config.Arguments.Clear.Choice val config by stringChoice { - name = "config-type" - description = "The type of config to clear" + name = Translations.Config.Arguments.Clear.name + description = Translations.Config.Arguments.Clear.description choices = mutableMapOf( - "moderation" to ConfigType.MODERATION.name, - "logging" to ConfigType.LOGGING.name, - "utility" to ConfigType.UTILITY.name, - "all" to ConfigType.ALL.name + choiceObj.moderation to ConfigType.MODERATION.name, + choiceObj.logging to ConfigType.LOGGING.name, + choiceObj.utility to ConfigType.UTILITY.name, + choiceObj.all to ConfigType.ALL.name ) } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigViewCommand.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigViewCommand.kt index d9c517e2..f5bae6e7 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigViewCommand.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/commands/ConfigViewCommand.kt @@ -9,6 +9,7 @@ import dev.kordex.core.commands.application.slash.SlashCommand import dev.kordex.core.commands.application.slash.converters.impl.stringChoice import dev.kordex.core.commands.application.slash.ephemeralSubCommand import kotlinx.datetime.Clock +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection @@ -16,8 +17,8 @@ import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.interval suspend fun SlashCommand<*, *, *>.configViewCommand() = ephemeralSubCommand(::ViewArgs) { - name = "view" - description = "View the current config that you have set" + name = Translations.Config.View.name + description = Translations.Config.View.description requirePermission(Permission.ManageGuild) @@ -32,67 +33,78 @@ suspend fun SlashCommand<*, *, *>.configViewCommand() = ephemeralSubCommand(::Vi val config = ModerationConfigCollection().getConfig(guild!!.id) if (config == null) { respond { - content = "There is no moderation config for this guild" + content = Translations.Config.View.noModConfig.translate() } return@action } respond { + val obj = Translations.Config.Moderation.Embed embed { - title = "Current moderation config" - description = "This is the current moderation config for this guild" + title = Translations.Config.View.CurrentConfig.modTitle.translate() + description = Translations.Config.View.CurrentConfig.modDescription.translate() field { - name = "Enabled/Disabled" - value = if (config.enabled) "Enabled" else "Disabled" + name = + Translations.Basic.enabled.translate() + "/" + Translations.Basic.disabled.translate() + value = + if (config.enabled) { + Translations.Basic.enabled.translate() + } else { + Translations.Basic.disabled.translate() + } } field { - name = "Moderators" - value = config.role?.let { guild!!.getRoleOrNull(it)?.mention } ?: "Disabled" + name = obj.moderatorsFieldName.translate() + value = config.role?.let { guild!!.getRoleOrNull(it)?.mention } + ?: Translations.Basic.disabled.translate() } field { - name = "Action log" + name = obj.actionLogFieldName.translate() value = - config.channel?.let { guild!!.getChannelOrNull(it)?.mention } ?: "Disabled" + config.channel?.let { guild!!.getChannelOrNull(it)?.mention } + ?: Translations.Basic.disabled.translate() } field { - name = "Log publicly" + name = obj.logPubliclyFieldName.translate() value = when (config.publicLogging) { - true -> "Enabled" - false -> "Disabled" - null -> "Disabled" - } + true -> Translations.Basic.enabled + false -> Translations.Basic.disabled + null -> Translations.Basic.disabled + }.translate() } field { - name = "Quick timeout length" - value = config.quickTimeoutLength.interval() ?: "No quick timeout length set" + name = Translations.Config.Moderation.Embed.QuickTimeoutLength.name.translate() + value = config.quickTimeoutLength.interval() + ?: Translations.Config.Moderation.Embed.QuickTimeoutLength.disabled.translate() } field { - name = "Warning Auto-punishments" + name = obj.warningAutoPunishmentsName.translate() value = when (config.autoPunishOnWarn) { - true -> "Enabled" - false -> "Disabled" - null -> "Disabled" - } + true -> Translations.Basic.enabled + false -> Translations.Basic.disabled + null -> Translations.Basic.disabled + }.translate() } field { - name = "Ban DM Message" - value = config.banDmMessage ?: "No custom Ban DM message set" + name = Translations.Config.Moderation.Embed.BanDmMessage.name.translate() + value = config.banDmMessage + ?: Translations.Config.Moderation.Embed.BanDmMessage.disabled.translate() } field { - name = "Auto-invite Moderator Role" + name = obj.autoInviteRoleName.translate() value = when (config.autoInviteModeratorRole) { - true -> "Enabled" - false -> "Disabled" - null -> "Disabled" - } + true -> Translations.Basic.enabled + false -> Translations.Basic.disabled + null -> Translations.Basic.disabled + }.translate() } field { - name = "Log member role changes" + name = obj.memberRoleChangesName.translate() value = when (config.logMemberRoleChanges) { - true -> "Enabled" - false -> "Disabled" - null -> "Disabled" - } + true -> Translations.Basic.enabled + false -> Translations.Basic.disabled + null -> Translations.Basic.disabled + }.translate() } timestamp = Clock.System.now() } @@ -103,43 +115,50 @@ suspend fun SlashCommand<*, *, *>.configViewCommand() = ephemeralSubCommand(::Vi val config = LoggingConfigCollection().getConfig(guild!!.id) if (config == null) { respond { - content = "There is no logging config for this guild" + content = Translations.Config.View.noLoggingConfig.translate() } return@action } respond { + val obj = Translations.Config.Logging.Embed embed { - title = "Current logging config" - description = "This is the current logging config for this guild" + title = Translations.Config.View.CurrentConfig.loggingTitle.translate() + description = Translations.Config.View.CurrentConfig.loggingDescription.translate() field { - name = "Message delete logs" + name = obj.messageDeleteFieldName.translate() value = if (config.enableMessageDeleteLogs) { - "Enabled\n" + - "* ${guild!!.getChannelOrNull(config.messageChannel!!)?.mention ?: "Unable to get channel mention"} (" + - "${guild!!.getChannelOrNull(config.messageChannel)?.name ?: "Unable to get channel name"})" + "${Translations.Basic.enabled.translate()}\n" + + "* ${guild!!.getChannelOrNull(config.messageChannel!!)?.mention + ?: Translations.Config.UnableTo.mention.translate()} (" + + "${guild!!.getChannelOrNull(config.messageChannel)?.name + ?: Translations.Config.UnableTo.name.translate()})" } else { - "Disabled" + Translations.Basic.disabled.translate() } } field { - name = "Message edit logs" + name = obj.messageEditFieldName.translate() value = if (config.enableMessageEditLogs) { - "Enabled\n" + - "* ${guild!!.getChannelOrNull(config.messageChannel!!)?.mention ?: "Unable to get channel mention"} (" + - "${guild!!.getChannelOrNull(config.messageChannel)?.name ?: "Unable to get channel mention"})" + "${Translations.Basic.enabled.translate()}\n" + + "* ${guild!!.getChannelOrNull(config.messageChannel!!)?.mention + ?: Translations.Config.UnableTo.mention.translate()} (" + + "${guild!!.getChannelOrNull(config.messageChannel)?.name + ?: Translations.Config.UnableTo.name.translate()})" } else { - "Disabled" + Translations.Basic.disabled.translate() } } field { - name = "Member logs" + name = obj.memberFieldName.translate() value = if (config.enableMemberLogs) { - "Enabled\n" + - "* ${guild!!.getChannelOrNull(config.memberLog!!)?.mention ?: "Unable to get channel mention"} (" + - "${guild!!.getChannelOrNull(config.memberLog)?.name ?: "Unable to get channel mention."})" + "${Translations.Basic.enabled.translate()}\n" + + "* ${guild!!.getChannelOrNull(config.memberLog!!)?.mention + ?: Translations.Config.UnableTo.mention.translate()} (" + + "${guild!!.getChannelOrNull(config.memberLog)?.name + ?: Translations.Config.UnableTo.name.translate()})" } else { - "Disabled" + Translations.Basic.disabled.translate() } } timestamp = Clock.System.now() @@ -151,36 +170,38 @@ suspend fun SlashCommand<*, *, *>.configViewCommand() = ephemeralSubCommand(::Vi val config = UtilityConfigCollection().getConfig(guild!!.id) if (config == null) { respond { - content = "There is no utility config for this guild" + content = Translations.Config.View.noUtilityConfig.translate() } return@action } respond { + val obj = Translations.Config.Utility.Embed embed { - title = "Current utility config" - description = "This is the current utility config for this guild" + title = Translations.Config.View.CurrentConfig.utilityTitle.translate() + description = Translations.Config.View.CurrentConfig.utilityDescription.translate() field { - name = "Channel" + name = obj.utilityFieldName.translate() value = "${ - config.utilityLogChannel?.let { guild!!.getChannelOrNull(it)?.mention } ?: "None" + config.utilityLogChannel?.let { guild!!.getChannelOrNull(it)?.mention } + ?: Translations.Basic.none.translate() } ${config.utilityLogChannel?.let { guild!!.getChannelOrNull(it)?.name } ?: ""}" } field { - name = "Log Channel updates" + name = obj.channelUpdates.translate() value = config.logChannelUpdates.toString() } field { - name = "Log Event updates" + name = obj.eventUpdates.translate() value = config.logEventUpdates.toString() } field { - name = "Log Invite updates" + name = obj.inviteUpdates.translate() value = config.logInviteUpdates.toString() } field { - name = "Log Role updates" + name = obj.roleUpdates.translate() value = config.logRoleUpdates.toString() } timestamp = Clock.System.now() @@ -193,12 +214,12 @@ suspend fun SlashCommand<*, *, *>.configViewCommand() = ephemeralSubCommand(::Vi class ViewArgs : Arguments() { val config by stringChoice { - name = "config-type" - description = "The type of config to clear" + name = Translations.Config.Arguments.Clear.name + description = Translations.Config.Arguments.View.description choices = mutableMapOf( - "moderation" to ConfigType.MODERATION.name, - "logging" to ConfigType.LOGGING.name, - "utility" to ConfigType.UTILITY.name, + Translations.Config.Arguments.Clear.Choice.moderation to ConfigType.MODERATION.name, + Translations.Config.Arguments.Clear.Choice.logging to ConfigType.LOGGING.name, + Translations.Config.Arguments.Clear.Choice.utility to ConfigType.UTILITY.name, ) } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/utils/ConfigEmbeds.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/utils/ConfigEmbeds.kt index 1ad952ef..92297d90 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/utils/ConfigEmbeds.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/utils/ConfigEmbeds.kt @@ -5,6 +5,7 @@ package org.hyacinthbots.lilybot.extensions.config.utils import dev.kord.core.behavior.GuildBehavior import dev.kord.core.behavior.UserBehavior import dev.kord.rest.builder.message.EmbedBuilder +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.extensions.logging.config.LoggingArgs import org.hyacinthbots.lilybot.extensions.moderation.config.ModerationArgs @@ -13,156 +14,161 @@ import org.hyacinthbots.lilybot.utils.interval import org.hyacinthbots.lilybot.utils.trimmedContents suspend fun EmbedBuilder.utilityEmbed(arguments: UtilityArgs, user: UserBehavior) { - title = "Configuration: Utility" + val obj = Translations.Config.Utility.Embed + title = obj.title.translate() field { - name = "Utility Log" + name = obj.utilityFieldName.translate() value = if (arguments.utilityLogChannel != null) { "${arguments.utilityLogChannel!!.mention} ${arguments.utilityLogChannel!!.data.name.value}" } else { - "Disabled" + Translations.Basic.disabled.translate() } } field { - name = "Log Channel updates" - value = if (arguments.logChannelUpdates) "Yes" else "No" + name = obj.channelUpdates.translate() + value = if (arguments.logChannelUpdates) Translations.Basic.yes.translate() else Translations.Basic.no.translate() } field { - name = "Log Event updates" - value = if (arguments.logEventUpdates) "Yes" else "No" + name = obj.eventUpdates.translate() + value = if (arguments.logEventUpdates) Translations.Basic.yes.translate() else Translations.Basic.no.translate() } field { - name = "Log Invite updates" - value = if (arguments.logInviteUpdates) "Yes" else "No" + name = obj.inviteUpdates.translate() + value = if (arguments.logInviteUpdates) Translations.Basic.yes.translate() else Translations.Basic.no.translate() } field { - name = "Log Role updates" - value = if (arguments.logRoleUpdates) "Yes" else "No" + name = obj.roleUpdates.translate() + value = if (arguments.logRoleUpdates) Translations.Basic.yes.translate() else Translations.Basic.no.translate() } footer { - text = "Configured by ${user.asUserOrNull()?.username}" + text = Translations.Config.configuredBy.translate(user.asUserOrNull()?.username) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } } suspend fun EmbedBuilder.moderationEmbed(arguments: ModerationArgs, user: UserBehavior) { - title = "Configuration: Moderation" + val obj = Translations.Config.Moderation.Embed + title = obj.title.translate() field { - name = "Moderators" - value = arguments.moderatorRole?.mention ?: "Disabled" + name = obj.moderatorsFieldName.translate() + value = arguments.moderatorRole?.mention ?: Translations.Basic.disabled.translate() } field { - name = "Action log" - value = arguments.modActionLog?.mention ?: "Disabled" + name = obj.actionLogFieldName.translate() + value = arguments.modActionLog?.mention ?: Translations.Basic.disabled.translate() } field { - name = "Log publicly" + name = obj.logPubliclyFieldName.translate() value = when (arguments.logPublicly) { - true -> "Enabled" - false -> "Disabled" - null -> "Disabled" - } + true -> Translations.Basic.enabled + false -> Translations.Basic.disabled + null -> Translations.Basic.disabled + }.translate() } field { - name = "Quick timeout length" - value = arguments.quickTimeoutLength.interval() ?: "No quick timeout length set" + name = Translations.Config.Moderation.Embed.QuickTimeoutLength.name.translate() + value = arguments.quickTimeoutLength.interval() + ?: Translations.Config.Moderation.Embed.QuickTimeoutLength.disabled.translate() } field { - name = "Warning Auto-punishments" + name = obj.warningAutoPunishmentsName.translate() value = when (arguments.warnAutoPunishments) { - true -> "Enabled" - false -> "Disabled" - null -> "Disabled" - } + true -> Translations.Basic.enabled + false -> Translations.Basic.disabled + null -> Translations.Basic.disabled + }.translate() } field { - name = "DM Default" + name = Translations.Config.Moderation.Embed.DmDefault.name.translate() value = when (arguments.dmDefault) { - true -> "DM argument will default to true" - false -> "DM argument will default to false" - null -> "DM argument will default to false" - } + true -> Translations.Config.Moderation.Embed.DmDefault.`true` + false -> Translations.Config.Moderation.Embed.DmDefault.`false` + null -> Translations.Config.Moderation.Embed.DmDefault.`false` + }.translate() } field { - name = "Ban DM Message" - value = arguments.banDmMessage ?: "No custom Ban DM message set" + name = Translations.Config.Moderation.Embed.BanDmMessage.name.translate() + value = arguments.banDmMessage ?: Translations.Config.Moderation.Embed.BanDmMessage.disabled.translate() } field { - name = "Auto-invite Moderator Role" + name = obj.autoInviteRoleName.translate() value = when (arguments.autoInviteModeratorRole) { - true -> "Enabled" - false -> "Disabled" - null -> "Disabled" - } + true -> Translations.Basic.enabled + false -> Translations.Basic.disabled + null -> Translations.Basic.disabled + }.translate() } field { - name = "Log member role changes" + name = obj.memberRoleChangesName.translate() value = when (arguments.logMemberRoleChanges) { - true -> "Enabled" - false -> "Disabled" - null -> "Disabled" - } + true -> Translations.Basic.enabled + false -> Translations.Basic.disabled + null -> Translations.Basic.disabled + }.translate() } footer { - text = "Configured by ${user.asUserOrNull()?.username}" + text = Translations.Config.configuredBy.translate(user.asUserOrNull()?.username) + icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } } suspend fun EmbedBuilder.loggingEmbed(arguments: LoggingArgs, guild: GuildBehavior?, user: UserBehavior) { - title = "Configuration: Logging" + val obj = Translations.Config.Logging.Embed + title = obj.title.translate() field { - name = "Message Delete Logs" + name = obj.messageDeleteFieldName.translate() value = if (arguments.enableMessageDeleteLogs && arguments.messageLogs != null) { arguments.messageLogs!!.mention } else { - "Disabled" + Translations.Basic.disabled.translate() } } field { - name = "Message Edit Logs" + name = obj.messageEditFieldName.translate() value = if (arguments.enableMessageEditLogs && arguments.messageLogs != null) { arguments.messageLogs!!.mention } else { - "Disabled" + Translations.Basic.disabled.translate() } } field { - name = "Member Logs" + name = obj.memberFieldName.translate() value = if (arguments.enableMemberLogging && arguments.memberLog != null) { arguments.memberLog!!.mention } else { - "Disabled" + Translations.Basic.disabled.translate() } } field { - name = "Public Member logs" + name = Translations.Config.Logging.Embed.PublicMemberField.name.translate() value = if (arguments.enablePublicMemberLogging && arguments.publicMemberLog != null) { arguments.publicMemberLog!!.mention } else { - "Disabled" + Translations.Basic.disabled.translate() } } if (arguments.enableMemberLogging && arguments.publicMemberLog != null) { val config = LoggingConfigCollection().getConfig(guild!!.id) if (config != null) { field { - name = "Join Message" + name = Translations.Config.Logging.Embed.PublicMemberField.joinMessage.translate() value = config.publicMemberLogData?.joinMessage.trimmedContents(256)!! } field { - name = "Leave Message" + name = Translations.Config.Logging.Embed.PublicMemberField.leaveMessage.translate() value = config.publicMemberLogData?.leaveMessage.trimmedContents(256)!! } field { - name = "Ping on join" + name = Translations.Config.Logging.Embed.PublicMemberField.pingOnJoin.translate() value = config.publicMemberLogData?.pingNewUsers.toString() } } } footer { - text = "Configured by ${user.asUserOrNull()?.username}" + text = Translations.Config.configuredBy.translate(user.asUserOrNull()?.username) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingArgs.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingArgs.kt index 9ecb08d1..6e24a558 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingArgs.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingArgs.kt @@ -3,41 +3,41 @@ package org.hyacinthbots.lilybot.extensions.logging.config import dev.kordex.core.commands.Arguments import dev.kordex.core.commands.converters.impl.boolean import dev.kordex.core.commands.converters.impl.optionalChannel +import lilybot.i18n.Translations class LoggingArgs : Arguments() { val enableMessageDeleteLogs by boolean { - name = "enable-delete-logs" - description = "Enable logging of message deletions" + name = Translations.Config.Arguments.Logging.EnableDelete.name + description = Translations.Config.Arguments.Logging.EnableDelete.description } val enableMessageEditLogs by boolean { - name = "enable-edit-logs" - description = "Enable logging of message edits" + name = Translations.Config.Arguments.Logging.EnableEdit.name + description = Translations.Config.Arguments.Logging.EnableEdit.description } val enableMemberLogging by boolean { - name = "enable-member-logging" - description = "Enable logging of members joining and leaving the guild" + name = Translations.Config.Arguments.Logging.EnableMember.name + description = Translations.Config.Arguments.Logging.EnableMember.description } val enablePublicMemberLogging by boolean { - name = "enable-public-member-logging" - description = - "Enable logging of members joining and leaving the guild with a public message and ping if enabled" + name = Translations.Config.Arguments.Logging.EnablePublicMember.name + description = Translations.Config.Arguments.Logging.EnablePublicMember.description } val messageLogs by optionalChannel { - name = "message-logs" - description = "The channel for logging message deletions" + name = Translations.Config.Arguments.Logging.MessageLog.name + description = Translations.Config.Arguments.Logging.MessageLog.description } val memberLog by optionalChannel { - name = "member-log" - description = "The channel for logging members joining and leaving the guild" + name = Translations.Config.Arguments.Logging.MemberLog.name + description = Translations.Config.Arguments.Logging.MemberLog.description } val publicMemberLog by optionalChannel { - name = "public-member-log" - description = "The channel for the public logging of members joining and leaving the guild" + name = Translations.Config.Arguments.Logging.PublicMemberLog.name + description = Translations.Config.Arguments.Logging.PublicMemberLog.description } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingCommand.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingCommand.kt index 91c25685..0653e7b7 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingCommand.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/LoggingCommand.kt @@ -13,6 +13,7 @@ import dev.kordex.core.utils.botHasPermissions 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 lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.database.entities.LoggingConfigData import org.hyacinthbots.lilybot.database.entities.PublicMemberLogData @@ -22,8 +23,8 @@ import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms @OptIn(UnsafeAPI::class) suspend fun SlashCommand<*, *, *>.loggingCommand() = unsafeSubCommand(::LoggingArgs) { - name = "logging" - description = "Configure Lily's logging system" + name = Translations.Config.Logging.name + description = Translations.Config.Logging.description initialResponse = InitialSlashCommandResponse.None @@ -39,8 +40,7 @@ suspend fun SlashCommand<*, *, *>.loggingCommand() = unsafeSubCommand(::LoggingA if (loggingConfig != null) { ackEphemeral() respondEphemeral { - content = "You already have a logging configuration set. " + - "Please clear it before attempting to set a new one." + content = Translations.Config.configAlreadyExists.translate("Logging") } return@action } @@ -48,19 +48,19 @@ suspend fun SlashCommand<*, *, *>.loggingCommand() = unsafeSubCommand(::LoggingA if (arguments.enableMemberLogging && arguments.memberLog == null) { ackEphemeral() respondEphemeral { - content = "You must specify a channel to log members joining and leaving to!" + content = Translations.Config.Logging.memberMissing.translate() } return@action } else if ((arguments.enableMessageDeleteLogs || arguments.enableMessageEditLogs) && arguments.messageLogs == null ) { ackEphemeral() - respondEphemeral { content = "You must specify a channel to log deleted/edited messages to!" } + respondEphemeral { content = Translations.Config.Logging.editMissing.translate() } return@action } else if (arguments.enablePublicMemberLogging && arguments.publicMemberLog == null) { ackEphemeral() respondEphemeral { - content = "You must specify a channel to publicly log members joining and leaving to!" + content = Translations.Config.Logging.publicMemberMissing.translate() } return@action } @@ -71,8 +71,7 @@ suspend fun SlashCommand<*, *, *>.loggingCommand() = unsafeSubCommand(::LoggingA if (memberLog?.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) != true) { ackEphemeral() respondEphemeral { - content = "The member log you've selected is invalid, or I can't view it. " + - "Please attempt to resolve this and try again." + content = Translations.Config.invalidChannel.translate("member log") } return@action } @@ -84,8 +83,7 @@ suspend fun SlashCommand<*, *, *>.loggingCommand() = unsafeSubCommand(::LoggingA if (messageLog?.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) != true) { ackEphemeral() respondEphemeral { - content = "The message log you've selected is invalid, or I can't view it. " + - "Please attempt to resolve this and try again." + content = Translations.Config.invalidChannel.translate("message log") } return@action } @@ -101,8 +99,7 @@ suspend fun SlashCommand<*, *, *>.loggingCommand() = unsafeSubCommand(::LoggingA ) { ackEphemeral() respondEphemeral { - content = "The public member log you've selected is invalid, or I can't view it. " + - "Please attempt to resolve this and try again." + content = Translations.Config.invalidChannel.translate("public memeber log") } return@action } @@ -115,10 +112,10 @@ suspend fun SlashCommand<*, *, *>.loggingCommand() = unsafeSubCommand(::LoggingA this@unsafeSubCommand.componentRegistry.register(modalObj) event.interaction.modal( - modalObj.title, + modalObj.title.translate(), modalObj.id ) { - modalObj.applyToBuilder(this, getLocale(), null) + modalObj.applyToBuilder(this, getLocale()) } modalObj.awaitCompletion { modalSubmitInteraction -> @@ -155,7 +152,7 @@ suspend fun SlashCommand<*, *, *>.loggingCommand() = unsafeSubCommand(::LoggingA if (utilityLog == null) { respondEphemeral { - content = "Consider setting a utility config to log changes to configurations." + content = Translations.Config.considerUtility.translate() } return@action } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/PublicLoggingModal.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/PublicLoggingModal.kt index 28928317..a111c277 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/PublicLoggingModal.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/config/PublicLoggingModal.kt @@ -1,24 +1,25 @@ package org.hyacinthbots.lilybot.extensions.logging.config import dev.kordex.core.components.forms.ModalForm +import lilybot.i18n.Translations class PublicLoggingModal : ModalForm() { - override var title = "Public logging configuration" + override var title = Translations.Config.Logging.Modal.title val joinMessage = paragraphText { - label = "What would you like sent when a user joins" - placeholder = "Welcome to the server!" + label = Translations.Config.Logging.Modal.JoinMessage.label + placeholder = Translations.Config.Logging.Modal.JoinMessage.placeholder required = true } val leaveMessage = paragraphText { - label = "What would you like sent when a user leaves" - placeholder = "Adiós amigo!" + label = Translations.Config.Logging.Modal.LeaveMessage.label + placeholder = Translations.Config.Logging.Modal.LeaveMessage.placeholder required = true } val ping = lineText { - label = "Type `yes` to ping new users when they join" - placeholder = "Defaults to false if input is invalid or not `yes`" + label = Translations.Config.Logging.Modal.Ping.label + placeholder = Translations.Config.Logging.Modal.Ping.placeholder } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MemberLogging.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MemberLogging.kt index 3218a63c..2a6529d9 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MemberLogging.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MemberLogging.kt @@ -16,6 +16,7 @@ import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.event import dev.kordex.core.utils.botHasPermissions import kotlinx.datetime.Clock +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.LeftMemberFlagCollection import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.database.collections.ModerationActionCollection @@ -49,16 +50,16 @@ class MemberLogging : Extension() { memberLog?.createEmbed { author { - name = "User joined the server!" + name = Translations.Events.MemberLogging.MemberJoin.embedAuthor.translate() icon = event.member.avatar?.cdnUrl?.toUrl() } field { - name = "Welcome:" + name = Translations.Events.MemberLogging.MemberJoin.embedWelcome.translate() value = "${event.member.mention} (${event.member.username})" inline = true } field { - name = "ID:" + name = Translations.Events.MemberLogging.MemberEvent.embedId.translate() value = event.member.id.toString() inline = false } @@ -77,13 +78,13 @@ class MemberLogging : Extension() { if (config.publicMemberLogData?.pingNewUsers == true) content = event.member.mention embed { author { - name = "Welcome ${event.member.username}" + name = Translations.Events.MemberLogging.MemberJoin.publicEmbedAuthor.translate(event.member.username) icon = event.member.avatar?.cdnUrl?.toUrl() } description = if (config.publicMemberLogData?.joinMessage != null) { config.publicMemberLogData.joinMessage } else { - "Welcome to the server!" + Translations.Events.MemberLogging.MemberJoin.publicEmbedWelcomeMessage.translate() } timestamp = Clock.System.now() color = DISCORD_GREEN @@ -107,16 +108,16 @@ class MemberLogging : Extension() { memberLog?.createEmbed { author { - name = "User left the server!" + name = Translations.Events.MemberLogging.MemberLeave.embedAuthor.translate() icon = event.user.avatar?.cdnUrl?.toUrl() } field { - name = "Goodbye:" + name = Translations.Events.MemberLogging.MemberLeave.embedGoodbye.translate() value = event.user.username inline = true } field { - name = "ID:" + name = Translations.Events.MemberLogging.MemberEvent.embedId.translate() value = event.user.id.toString() } timestamp = Clock.System.now() @@ -129,8 +130,8 @@ class MemberLogging : Extension() { val targetUser = event.kord.getUser(kickData.targetUserId)!! val actioner = kickData.data.actioner?.let { event.kord.getUser(it) }!! getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, event.guild)?.createEmbed { - title = "Kicked a user" - description = "${targetUser.mention} has been kicked" + title = Translations.Moderation.ModCommands.Kick.response.translate() + description = Translations.Moderation.ModCommands.Kick.embedDesc.translate(targetUser.mention) image = kickData.data.imageUrl baseModerationEmbed(kickData.data.reason, targetUser, actioner) dmNotificationStatusEmbedField(kickData.data.dmOutcome != null, kickData.data.dmOutcome) @@ -147,13 +148,13 @@ class MemberLogging : Extension() { publicLog?.createEmbed { author { - name = "Goodbye ${event.user.username}" + name = Translations.Events.MemberLogging.MemberLeave.publicEmbedAuthor.translate(event.user.username) icon = event.user.avatar?.cdnUrl?.toUrl() } description = if (config.publicMemberLogData?.leaveMessage != null) { config.publicMemberLogData.leaveMessage } else { - "Farewell!" + Translations.Events.MemberLogging.MemberLeave.publicEmbedGoodbyeMessage.translate() } timestamp = Clock.System.now() color = DISCORD_RED diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MessageDelete.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MessageDelete.kt index dc2e036f..db83ac68 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MessageDelete.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MessageDelete.kt @@ -18,6 +18,7 @@ import dev.kordex.modules.pluralkit.events.UnProxiedMessageDeleteEvent import io.ktor.client.request.forms.ChannelProvider import io.ktor.utils.io.jvm.javaio.toByteReadChannel import kotlinx.datetime.Clock +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.utils.attachmentsAndProxiedMessageInfo import org.hyacinthbots.lilybot.utils.generateBulkDeleteFile @@ -65,7 +66,7 @@ class MessageDelete : Extension() { requiredConfigs(ConfigOptions.MESSAGE_DELETE_LOGGING_ENABLED, ConfigOptions.MESSAGE_LOG) failIf { event.message?.author?.id == kord.selfId || - event.message?.author?.isBot == true + event.message?.author?.isBot == true } } @@ -82,7 +83,7 @@ class MessageDelete : Extension() { action { val messageLog = - getLoggingChannelWithPerms(ConfigOptions.MESSAGE_LOG, event.getGuildOrNull()!!) ?: return@action + getLoggingChannelWithPerms(ConfigOptions.MESSAGE_LOG, event.getGuildOrNull()!!) ?: return@action val messages = generateBulkDeleteFile(event.messages) @@ -101,16 +102,18 @@ class MessageDelete : Extension() { */ private suspend fun UserMessageCreateBuilder.bulkDeleteEmbed(event: MessageBulkDeleteEvent, messages: String?) { embed { - title = "Bulk Message Delete" - description = "A Bulk delete of messages occurred" + title = Translations.Events.MessageDelete.Bulk.embedTitle.translate() + description = Translations.Events.MessageDelete.Bulk.embedDescription.translate() field { - name = "Location" + name = Translations.Events.MessageDelete.Bulk.embedLocation.translate() value = "${event.channel.mention} " + - "(${event.channel.asChannelOfOrNull()?.name - ?: "Could not get channel name"})" + "(${ + event.channel.asChannelOfOrNull()?.name + ?: Translations.Events.MessageEvent.failedContents.translate() + })" } field { - name = "Number of messages" + name = Translations.Events.MessageDelete.Bulk.embedNumber.translate() value = event.messages.size.toString() } color = DISCORD_PINK @@ -122,7 +125,7 @@ class MessageDelete : Extension() { ChannelProvider { messages.byteInputStream().toByteReadChannel() } ) } else { - content = "The messages from this event could not be gathered and logged." + content = Translations.Events.MessageDelete.Bulk.embedFailedContent.translate() } } @@ -144,18 +147,20 @@ class MessageDelete : Extension() { messageLog.createEmbed { author { - name = "Message deleted" + name = Translations.Events.MessageDelete.Single.embedAuthor.translate() icon = proxiedMessage?.member?.avatarUrl ?: message.author?.avatar?.cdnUrl?.toUrl() } - description = - "Location: ${message.channel.mention} " + - "(${message.channel.asChannelOfOrNull()?.name ?: "Could not get channel name"})" + description = Translations.Events.MessageEvent.location.translate( + message.channel.mention, + message.channel.asChannelOfOrNull()?.name + ?: Translations.Events.MessageDelete.noChannelName.translate() + ) color = DISCORD_PINK timestamp = Clock.System.now() field { - name = "Message contents" - value = message.trimmedContents().ifNullOrEmpty { "Failed to retrieve previous message contents" } + name = Translations.Events.MessageDelete.Single.embedContents.translate() + value = message.trimmedContents().ifNullOrEmpty { Translations.Events.MessageEvent.failedContents.translate() } inline = false } attachmentsAndProxiedMessageInfo(guild, message, proxiedMessage) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MessageEdit.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MessageEdit.kt index ecb05524..c4b7e670 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MessageEdit.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/logging/events/MessageEdit.kt @@ -17,6 +17,7 @@ import dev.kordex.modules.pluralkit.api.PKMessage import dev.kordex.modules.pluralkit.events.ProxiedMessageUpdateEvent import dev.kordex.modules.pluralkit.events.UnProxiedMessageUpdateEvent import kotlinx.datetime.Clock +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.utils.attachmentsAndProxiedMessageInfo import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms @@ -86,30 +87,31 @@ class MessageEdit : Extension() { embed { color = DISCORD_YELLOW author { - name = "Message Edited" + name = Translations.Events.MessageEdit.embedAuthor.translate() icon = proxiedMessage?.member?.avatarUrl ?: message.author?.avatar?.cdnUrl?.toUrl() } - description = - "Location: ${message.channel.mention} " + - "(${message.channel.asChannelOfOrNull()?.name - ?: "Could not get channel name"})" + description = Translations.Events.MessageEvent.location.translate( + message.channel.mention, + message.channel.asChannelOfOrNull()?.name + ?: Translations.Events.MessageDelete.noChannelName.translate() + ) timestamp = Clock.System.now() field { - name = "Previous contents" - value = old?.trimmedContents().ifNullOrEmpty { "Failed to retrieve previous message contents" } + name = Translations.Events.MessageEdit.embedPrevious.translate() + value = old?.trimmedContents().ifNullOrEmpty { Translations.Events.MessageEvent.failedContents.translate() } inline = false } field { - name = "New contents" - value = message.trimmedContents().ifNullOrEmpty { "Failed to retrieve new message contents" } + name = Translations.Events.MessageEdit.embedNew.translate() + value = message.trimmedContents().ifNullOrEmpty { Translations.Events.MessageEdit.embedNewFail.translate() } inline = false } attachmentsAndProxiedMessageInfo(guild, message, proxiedMessage) } components { linkButton { - label = "Jump" + label = Translations.Events.MessageEdit.embedButton url = message.getJumpUrl() } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ClearCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ClearCommands.kt index 9ad4cc7c..3961bb5e 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ClearCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ClearCommands.kt @@ -24,6 +24,7 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toSet +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.utils.botHasChannelPerms @@ -37,12 +38,12 @@ class ClearCommands : Extension() { override suspend fun setup() { ephemeralSlashCommand { - name = "clear" - description = "Parent command for clear commands" + name = Translations.Moderation.ClearCommands.Clear.name + description = Translations.Moderation.ClearCommands.Clear.description ephemeralSubCommand(ClearCommandArgs::Count) { - name = "count" - description = "Clear a specific count of messages" + name = Translations.Moderation.ClearCommands.Clear.Count.name + description = Translations.Moderation.ClearCommands.Clear.Count.description requirePermission(Permission.ManageMessages) @@ -58,8 +59,8 @@ class ClearCommands : Extension() { } ephemeralSubCommand(ClearCommandArgs::Before) { - name = "before" - description = "Clear messages before a given message ID" + name = Translations.Moderation.ClearCommands.Clear.Before.name + description = Translations.Moderation.ClearCommands.Clear.Before.description requirePermission(Permission.ManageMessages) @@ -75,8 +76,8 @@ class ClearCommands : Extension() { } ephemeralSubCommand(ClearCommandArgs::After) { - name = "after" - description = "Clear messages before a given message ID" + name = Translations.Moderation.ClearCommands.Clear.After.name + description = Translations.Moderation.ClearCommands.Clear.After.description requirePermission(Permission.ManageMessages) @@ -92,8 +93,8 @@ class ClearCommands : Extension() { } ephemeralSubCommand(ClearCommandArgs::Between) { - name = "between" - description = "Clear messages between 2 message IDs" + name = Translations.Moderation.ClearCommands.Clear.Between.name + description = Translations.Moderation.ClearCommands.Clear.Between.description requirePermission(Permission.ManageMessages) @@ -128,14 +129,14 @@ class ClearCommands : Extension() { internal class Count : Arguments() { /** The number of messages the user wants to remove. */ val count by int { - name = "messages" - description = "Number of messages to delete" + name = Translations.Moderation.ClearCommands.Arguments.Count.name + description = Translations.Moderation.ClearCommands.Arguments.Count.description } /** The author of the messages that need clearing. */ val author by optionalUser { - name = "author" - description = "The author of the messages to clear" + name = Translations.Moderation.ClearCommands.Arguments.Author.name + description = Translations.Moderation.ClearCommands.Arguments.Author.description } } @@ -143,20 +144,20 @@ class ClearCommands : Extension() { internal class After : Arguments() { /** The ID of the message to start clearing from. */ val after by snowflake { - name = "after" - description = "The ID of the message to clear after" + name = Translations.Moderation.ClearCommands.Clear.After.Arguments.After.name + description = Translations.Moderation.ClearCommands.Clear.After.Arguments.After.description } /** The number of messages the user wants to remove. */ val count by optionalInt { - name = "message-count" - description = "The number of messages to clear" + name = Translations.Moderation.ClearCommands.Arguments.Count.name + description = Translations.Moderation.ClearCommands.Arguments.Count.description } /** The author of the messages that need clearing. */ val author by optionalUser { - name = "author" - description = "The author of the messages to clear" + name = Translations.Moderation.ClearCommands.Arguments.Author.name + description = Translations.Moderation.ClearCommands.Arguments.Author.description } } @@ -164,20 +165,20 @@ class ClearCommands : Extension() { internal class Before : Arguments() { /** The ID of the message to start clearing before. */ val before by snowflake { - name = "before" - description = "The ID of the message to clear before" + name = Translations.Moderation.ClearCommands.Clear.Before.Arguments.Before.name + description = Translations.Moderation.ClearCommands.Clear.Before.Arguments.Before.description } /** The number of messages the user wants to remove. */ val count by optionalInt { - name = "message-count" - description = "The number of messages to clear" + name = Translations.Moderation.ClearCommands.Arguments.Count.name + description = Translations.Moderation.ClearCommands.Arguments.Count.description } /** The author of the messages that need clearing. */ val author by optionalUser { - name = "author" - description = "The author of the messages to clear" + name = Translations.Moderation.ClearCommands.Arguments.Author.name + description = Translations.Moderation.ClearCommands.Arguments.Author.description } } @@ -185,20 +186,20 @@ class ClearCommands : Extension() { internal class Between : Arguments() { /** The ID of the message to start clearing from. */ val after by snowflake { - name = "after" - description = "The ID of the message to clear after" + name = Translations.Moderation.ClearCommands.Clear.After.Arguments.After.name + description = Translations.Moderation.ClearCommands.Clear.After.Arguments.After.description } /** The ID of the message to start clearing before. */ val before by snowflake { - name = "before" - description = "The ID of the message to clear before" + name = Translations.Moderation.ClearCommands.Clear.Before.Arguments.Before.name + description = Translations.Moderation.ClearCommands.Clear.Before.Arguments.Before.description } /** The author of the messages that need clearing. */ val author by optionalUser { - name = "author" - description = "The author of the messages to clear" + name = Translations.Moderation.ClearCommands.Arguments.Author.name + description = Translations.Moderation.ClearCommands.Arguments.Author.description } } } @@ -225,14 +226,14 @@ private suspend fun EphemeralSlashCommandContext<*, *>.clearMessages( if (textChannel == null) { respond { - content = "Could not get the channel to clear messages from." + content = Translations.Moderation.ClearCommands.Error.noChannel.translate() } return } if ((before != null && after != null) && (before < after)) { respond { - content = "Before cannot be more recent than after!" + content = Translations.Moderation.ClearCommands.Error.beforeAfter.translate() } return } @@ -262,12 +263,12 @@ private suspend fun EphemeralSlashCommandContext<*, *>.clearMessages( textChannel.bulkDelete(messages) respond { - content = "Messages cleared." + content = Translations.Moderation.ClearCommands.cleared.translate() } if (config.publicLogging != null && config.publicLogging == true) { channel.createEmbed { - title = "$count messages have been cleared." + title = Translations.Moderation.ClearCommands.numberCleared.translate(count) color = DISCORD_BLACK } } @@ -275,10 +276,10 @@ private suspend fun EphemeralSlashCommandContext<*, *>.clearMessages( val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return actionLog.createEmbed { - title = "${count ?: messages.size} messages have been cleared." - description = "Action occurred in ${textChannel.mention}" + title = Translations.Moderation.ClearCommands.numberCleared.translate(count ?: messages.size) + description = Translations.Moderation.ClearCommands.occurredIn.translate() footer { - text = user.asUserOrNull()?.username ?: "Unable to get username" + text = user.asUserOrNull()?.username ?: Translations.Basic.UnableTo.tag.translate() icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } color = DISCORD_BLACK diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/LockingCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/LockingCommands.kt index b7994e75..99dfd4fc 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/LockingCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/LockingCommands.kt @@ -24,6 +24,7 @@ import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.ephemeralSlashCommand import dev.kordex.core.types.EphemeralInteractionContext import kotlinx.datetime.Clock +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.LockedChannelCollection import org.hyacinthbots.lilybot.database.entities.LockedChannelData import org.hyacinthbots.lilybot.extensions.config.ConfigOptions @@ -42,12 +43,12 @@ class LockingCommands : Extension() { * @since 3.1.0 */ ephemeralSlashCommand { - name = "lock" - description = "The parent command for all locking commands" + name = Translations.Moderation.LockingCommands.Lock.name + description = Translations.Moderation.LockingCommands.Lock.description ephemeralSubCommand(::LockChannelArgs) { - name = "channel" - description = "Lock a channel so those with default permissions cannot send messages" + name = Translations.Moderation.LockingCommands.Channel.name + description = Translations.Moderation.LockingCommands.Lock.Channel.description requirePermission(Permission.ModerateMembers) @@ -67,13 +68,13 @@ class LockingCommands : Extension() { val currentChannelPerms = targetChannel?.getPermissionOverwritesForRole(guild!!.id) if (currentChannelPerms == null) { respond { - content = "There was an error getting the permissions for this channel. Please try again." + content = Translations.Moderation.LockingCommands.Lock.Channel.permsError.translate() } return@action } if (LockedChannelCollection().getLockedChannel(guild!!.id, targetChannel.id) != null) { - respond { content = "This channel is already locked" } + respond { content = Translations.Moderation.LockingCommands.Lock.Channel.already.translate() } return@action } @@ -88,16 +89,18 @@ class LockingCommands : Extension() { val everyoneRole = guild!!.getRoleOrNull(guild!!.id) if (everyoneRole == null) { - respond { content = "I was unable to get the `@everyone` role. Please try again." } + respond { content = Translations.Moderation.LockingCommands.unableToEveryone.translate() } return@action } else if (!everyoneRole.permissions.contains(Permission.SendMessages)) { - respond { content = "The server is locked, so I cannot lock this channel." } + respond { + content = Translations.Moderation.LockingCommands.Lock.Channel.serverLocked.translate() + } return@action } targetChannel.createEmbed { - title = "Channel Locked" - description = "This channel has been locked by a moderator." + title = Translations.Moderation.LockingCommands.Lock.Channel.publicEmbedTitle.translate() + description = Translations.Moderation.LockingCommands.Lock.Channel.publicEmbedDesc.translate() color = DISCORD_RED } @@ -113,15 +116,22 @@ class LockingCommands : Extension() { allowed += Permission.SendMessages } - respond { content = "${targetChannel.mention} has been locked." } + respond { + content = Translations.Moderation.LockingCommands.Lock.Channel.lockConfirmation.translate( + targetChannel.mention + ) + } val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action actionLog.createEmbed { - title = "Channel Locked" - description = "${targetChannel.mention} has been locked.\n\n**Reason:** ${arguments.reason}" + title = Translations.Moderation.LockingCommands.Lock.Channel.publicEmbedTitle.translate() + description = Translations.Moderation.LockingCommands.Lock.Channel.embedDesc.translateNamed( + "channel" to targetChannel.mention, + "reason" to arguments.reason + ) footer { - text = user.asUserOrNull()?.username ?: "Unable to get username" + text = user.asUserOrNull()?.username ?: Translations.Basic.UnableTo.tag.translate() icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } timestamp = Clock.System.now() @@ -131,8 +141,8 @@ class LockingCommands : Extension() { } ephemeralSubCommand(::LockServerArgs) { - name = "server" - description = "Lock the server so those with default permissions cannot send messages" + name = Translations.Moderation.LockingCommands.Server.name + description = Translations.Moderation.LockingCommands.Lock.Server.description requirePermission(Permission.ModerateMembers) @@ -147,10 +157,10 @@ class LockingCommands : Extension() { val everyoneRole = guild!!.getRoleOrNull(guild!!.id) if (everyoneRole == null) { - respond { content = "I was unable to get the `@everyone` role. Please try again." } + respond { content = Translations.Moderation.LockingCommands.unableToEveryone.translate() } return@action } else if (!everyoneRole.permissions.contains(Permission.SendMessages)) { - respond { content = "The server is already locked." } + respond { content = Translations.Moderation.LockingCommands.Lock.Server.already.translate() } return@action } @@ -162,15 +172,17 @@ class LockingCommands : Extension() { .minus(Permission.UseApplicationCommands) } - respond { content = "Server locked." } + respond { + content = Translations.Moderation.LockingCommands.Lock.Server.lockConfirmation.translate() + } val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action actionLog.createEmbed { - title = "Server locked" - description = "**Reason:** ${arguments.reason}" + title = Translations.Moderation.LockingCommands.Lock.Server.lockConfirmation.translate() + description = Translations.Moderation.LockingCommands.Lock.Server.embedDesc.translate() footer { - text = user.asUserOrNull()?.username ?: "Unable to get user username" + text = user.asUserOrNull()?.username ?: Translations.Basic.UnableTo.tag.translate() icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } timestamp = Clock.System.now() @@ -187,12 +199,12 @@ class LockingCommands : Extension() { * @since 3.1.0 */ ephemeralSlashCommand { - name = "unlock" - description = "The parent command for all unlocking commands" + name = Translations.Moderation.LockingCommands.Unlock.name + description = Translations.Moderation.LockingCommands.Unlock.description ephemeralSubCommand(::UnlockChannelArgs) { - name = "channel" - description = "Unlock a channel so everyone can send messages again" + name = Translations.Moderation.LockingCommands.Channel.name + description = Translations.Moderation.LockingCommands.Unlock.Channel.description requirePermission(Permission.ModerateMembers) @@ -211,21 +223,28 @@ class LockingCommands : Extension() { val everyoneRole = guild!!.getRoleOrNull(guild!!.id) if (everyoneRole == null) { - respond { content = "Unable to get `@everyone` role. Please try again" } + respond { content = Translations.Moderation.LockingCommands.unableToEveryone.translate() } return@action } else if (!everyoneRole.permissions.contains(Permission.SendMessages)) { - respond { content = "Please unlock the server to unlock this channel." } + respond { + content = + Translations.Moderation.LockingCommands.Unlock.Channel.unlockServerToUnlock.translate() + } return@action } val channelPerms = targetChannel?.getPermissionOverwritesForRole(guild!!.id) if (channelPerms == null) { - respond { content = "This channel is not locked!" } + respond { + content = Translations.Moderation.LockingCommands.Unlock.Channel.notLocked.translate() + } return@action } val lockedChannel = LockedChannelCollection().getLockedChannel(guild!!.id, targetChannel.id) if (lockedChannel == null) { - respond { content = "This channel is not locked!" } + respond { + content = Translations.Moderation.LockingCommands.Unlock.Channel.notLocked.translate() + } return@action } @@ -240,23 +259,28 @@ class LockingCommands : Extension() { } targetChannel.createEmbed { - title = "Channel Unlocked" - description = "This channel has been unlocked by a moderator.\n" + - "Please be aware of the rules when continuing discussion." + title = Translations.Moderation.LockingCommands.Unlock.Channel.publicEmbedTitle.translate() + description = Translations.Moderation.LockingCommands.Unlock.Channel.publicEmbedDesc.translate() color = DISCORD_GREEN } LockedChannelCollection().removeLockedChannel(guild!!.id, targetChannel.id) - respond { content = "${targetChannel.mention} has been unlocked." } + respond { + content = Translations.Moderation.LockingCommands.Unlock.Channel.unlockConfirmation.translate( + targetChannel.mention + ) + } val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action actionLog.createEmbed { - title = "Channel Unlocked" - description = "${targetChannel.mention} has been unlocked." + title = Translations.Moderation.LockingCommands.Unlock.Channel.publicEmbedTitle.translate() + description = Translations.Moderation.LockingCommands.Unlock.Channel.unlockConfirmation.translate( + targetChannel.mention + ) footer { - text = user.asUserOrNull()?.username ?: "Unable to get user username" + text = user.asUserOrNull()?.username ?: Translations.Basic.UnableTo.tag.translate() icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } timestamp = Clock.System.now() @@ -266,8 +290,8 @@ class LockingCommands : Extension() { } ephemeralSubCommand { - name = "server" - description = "Unlock the server so everyone can send messages again" + name = Translations.Moderation.LockingCommands.Server.name + description = Translations.Moderation.LockingCommands.Unlock.Server.description requirePermission(Permission.ModerateMembers) @@ -281,10 +305,10 @@ class LockingCommands : Extension() { action { val everyoneRole = guild!!.getRoleOrNull(guild!!.id) if (everyoneRole == null) { - respond { content = "Unable to get `@everyone` role. Please try again" } + respond { content = Translations.Moderation.LockingCommands.unableToEveryone.translate() } return@action } else if (everyoneRole.permissions.contains(Permission.SendMessages)) { - respond { content = "The server isn't locked!" } + respond { content = Translations.Moderation.LockingCommands.Unlock.Server.notLocked.translate() } return@action } @@ -296,14 +320,14 @@ class LockingCommands : Extension() { .plus(Permission.UseApplicationCommands) } - respond { content = "Server unlocked." } + respond { content = Translations.Moderation.LockingCommands.Unlock.Server.unlockConfirmation.translate() } val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action actionLog.createEmbed { - title = "Server unlocked" + title = Translations.Moderation.LockingCommands.Unlock.Server.unlockConfirmation.translate() footer { - text = user.asUserOrNull()?.username ?: "Unable to get user username" + text = user.asUserOrNull()?.username ?: Translations.Basic.UnableTo.tag.translate() icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } timestamp = Clock.System.now() @@ -349,7 +373,7 @@ class LockingCommands : Extension() { val targetChannel = channelParent ?: channelArg?.asChannelOfOrNull() if (targetChannel == null) { respond { - content = "I can't fetch the targeted channel properly." + content = Translations.Moderation.LockingCommands.unableToChannel.translate() } return null } @@ -360,32 +384,32 @@ class LockingCommands : Extension() { inner class LockChannelArgs : Arguments() { /** The channel that the user wants to lock. */ val channel by optionalChannel { - name = "channel" - description = "Channel to lock. Defaults to current channel" + name = Translations.Moderation.LockingCommands.Channel.name + description = Translations.Moderation.LockingCommands.Lock.Channel.Arguments.Channel.description } /** The reason for the locking. */ val reason by defaultingString { - name = "reason" - description = "Reason for locking the channel" - defaultValue = "No reason provided" + name = Translations.Moderation.LockingCommands.Lock.Channel.Arguments.Reason.name + description = Translations.Moderation.LockingCommands.Lock.Channel.Arguments.Reason.description + defaultValue = Translations.Moderation.LockingCommands.Lock.Channel.Arguments.Reason.default.translate() } } inner class LockServerArgs : Arguments() { /** The reason for the locking. */ val reason by defaultingString { - name = "reason" - description = "Reason for locking the server" - defaultValue = "No reason provided" + name = Translations.Moderation.LockingCommands.Lock.Channel.Arguments.Reason.name + description = Translations.Moderation.LockingCommands.Lock.Channel.Arguments.Reason.description + defaultValue = Translations.Moderation.LockingCommands.Lock.Channel.Arguments.Reason.default.translate() } } inner class UnlockChannelArgs : Arguments() { /** The channel to unlock. */ val channel by optionalChannel { - name = "channel" - description = "Channel to unlock. Defaults to current channel" + name = Translations.Moderation.LockingCommands.Channel.name + description = Translations.Moderation.LockingCommands.Unlock.Channel.Arguments.Channel.description } } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModUtilities.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModUtilities.kt index 1654b12f..f0cd6463 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModUtilities.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModUtilities.kt @@ -42,6 +42,7 @@ import dev.kordex.core.utils.scheduling.Scheduler import dev.kordex.core.utils.scheduling.Task import kotlinx.coroutines.flow.toList import kotlinx.datetime.Clock +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.AutoThreadingCollection import org.hyacinthbots.lilybot.database.collections.GalleryChannelCollection import org.hyacinthbots.lilybot.database.collections.GithubCollection @@ -86,8 +87,8 @@ class ModUtilities : Extension() { * @since 2.0 */ ephemeralSlashCommand(::SayArgs) { - name = "say" - description = "Say something through Lily." + name = Translations.Moderation.ModUtilities.Say.name + description = Translations.Moderation.ModUtilities.Say.description requirePermission(Permission.ModerateMembers) @@ -104,6 +105,7 @@ class ModUtilities : Extension() { channel.asChannelOfOrNull() ?: return@action } val createdMessage: Message + val translations = Translations.Moderation.ModUtilities.Say try { if (arguments.embed) { @@ -119,38 +121,38 @@ class ModUtilities : Extension() { content = arguments.message } } - } catch (e: KtorRequestException) { - respond { content = "Lily does not have permission to send messages in this channel." } + } catch (_: KtorRequestException) { + respond { content = translations.noSendPerms.translate() } return@action } - respond { content = "Message sent." } + respond { content = translations.response.translate() } val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) ?: return@action utilityLog.createMessage { embed { - title = "Say command used" + title = translations.embedTitle.translate() description = "```${arguments.message}```" field { - name = "Channel:" + name = translations.channelField.translate() value = targetChannel.mention inline = true } field { - name = "Type:" - value = if (arguments.embed) "Embed" else "Message" + name = translations.typeField.translate() + value = if (arguments.embed) { translations.embedType } else { translations.messageType }.translate() inline = true } footer { - text = user.asUserOrNull()?.username ?: "Unable to get user username" + text = user.asUserOrNull()?.username ?: Translations.Basic.UnableTo.tag.translate() icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } timestamp = Clock.System.now() if (arguments.embed) { color = arguments.color field { - name = "Color:" + name = translations.colorField.translate() value = arguments.color.toString() inline = true } @@ -160,7 +162,7 @@ class ModUtilities : Extension() { } components { linkButton { - label = "Jump to message" + label = translations.jumpButton url = createdMessage.getJumpUrl() } } @@ -174,8 +176,8 @@ class ModUtilities : Extension() { * @since 3.3.0 */ ephemeralSlashCommand(::SayEditArgs) { - name = "edit-say" - description = "Edit a message created by /say" + name = Translations.Moderation.ModUtilities.EditSay.name + description = Translations.Moderation.ModUtilities.EditSay.description requirePermission(Permission.ModerateMembers) @@ -195,10 +197,10 @@ class ModUtilities : Extension() { } val message = channelOfMessage?.getMessageOrNull(arguments.messageToEdit) + val translations = Translations.Moderation.ModUtilities.EditSay + if (message == null) { - respond { - content = "I was unable to get the target message! Please check the message exists" - } + respond { content = translations.unableTo.translate() } return@action } @@ -207,39 +209,35 @@ class ModUtilities : Extension() { // it's not by LilyBot, it returns if (message.embeds.isEmpty()) { if (message.author!!.id != this@ephemeralSlashCommand.kord.selfId) { - respond { content = "I did not send this message, I cannot edit this!" } + respond { content = translations.notAuthor.translate() } return@action } else if (arguments.newContent == null) { - respond { content = "Please specify a new message content" } + respond { content = translations.missingContent.translate() } return@action } else if (arguments.newContent != null && arguments.newContent!!.length > 1024) { - respond { - content = - "Maximum embed length reached! Your embed character length cannot be more than 1024 " + - "characters, due to Discord limitations" - } + respond { content = translations.maxLength.translate() } return@action } message.edit { content = arguments.newContent } - respond { content = "Message edited" } + respond { content = translations.response.translate() } val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) ?: return@action utilityLog.createMessage { embed { - title = "Say message edited" + title = translations.embedTitle.translate() field { - name = "Original Content" + name = translations.embedOriginal.translate() value = "```${originalContent.trimmedContents(500)}```" } field { - name = "New Content" + name = translations.embedNew.translate() value = "```${arguments.newContent.trimmedContents(500)}```" } footer { - text = "Edited by ${user.asUserOrNull()?.username}" + text = translations.editedBy.translate(user.asUserOrNull()?.username) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } color = DISCORD_WHITE @@ -247,14 +245,14 @@ class ModUtilities : Extension() { } components { linkButton { - label = "Jump to message" + label = Translations.Moderation.ModUtilities.Say.jumpButton url = message.getJumpUrl() } } } } else { if (message.author!!.id != this@ephemeralSlashCommand.kord.selfId) { - respond { content = "I did not send this message, I cannot edit this!" } + respond { content = translations.notAuthor.translate() } return@action } @@ -275,39 +273,39 @@ class ModUtilities : Extension() { } } - respond { content = "Embed updated" } + respond { content = translations.response.translate() } val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) ?: return@action utilityLog.createMessage { embed { - title = "Say message edited" + title = translations.embedTitle.translate() field { - name = "Original content" + name = translations.embedOriginal.translate() // The old content, if null none - value = "```${oldContent ?: "none"}```" + value = "```${oldContent ?: Translations.Basic.none.translate()}```" } field { - name = "New content" + name = translations.embedNew.translate() // The new content, if null the old content, if null none - value = "```${arguments.newContent ?: oldContent ?: "none"}```" + value = "```${arguments.newContent ?: oldContent ?: Translations.Basic.none.translate()}```" } field { - name = "Old color" + name = translations.embedOldColor.translate() value = oldColor.toString() } field { - name = "New color" + name = translations.embedNewColor.translate() value = if (arguments.newColor != null) arguments.newColor.toString() else oldColor.toString() } field { - name = "Has Timestamp" + name = translations.embedHasTime.translate() value = when (arguments.timestamp) { - true -> "True" - false -> "False" - else -> "Original" - } + true -> Translations.Basic.`true` + false -> Translations.Basic.`false` + else -> Translations.Basic.original + }.translate() } footer { text = "Edited by ${user.asUserOrNull()?.username}" @@ -318,7 +316,7 @@ class ModUtilities : Extension() { } components { linkButton { - label = "Jump to message" + label = Translations.Moderation.ModUtilities.Say.jumpButton url = message.getJumpUrl() } } @@ -333,14 +331,14 @@ class ModUtilities : Extension() { * @since 2.0 */ ephemeralSlashCommand(::PresenceArgs) { - name = "status" - description = "Set Lily's current presence/status." + name = Translations.Moderation.ModUtilities.Status.name + description = Translations.Moderation.ModUtilities.Status.description guild(TEST_GUILD_ID) ephemeralSubCommand(::PresenceArgs) { - name = "set" - description = "Set a custom status for Lily." + name = Translations.Moderation.ModUtilities.Status.Set.name + description = Translations.Moderation.ModUtilities.Status.Set.description guild(TEST_GUILD_ID) requirePermission(Permission.Administrator) @@ -354,6 +352,8 @@ class ModUtilities : Extension() { val config = ModerationConfigCollection().getConfig(guildFor(event)!!.id)!! val actionLog = guild!!.getChannelOfOrNull(config.channel!!) + val translations = Translations.Moderation.ModUtilities.Status.Set + // Update the presence in the action this@ephemeralSlashCommand.kord.editPresence { status = PresenceStatus.Online @@ -363,13 +363,13 @@ class ModUtilities : Extension() { // Store the new presence in the database for if there is a restart StatusCollection().setStatus(arguments.presenceArgument) - respond { content = "Presence set to `${arguments.presenceArgument}`" } + respond { content = translations.response.translate(arguments.presenceArgument) } actionLog?.createEmbed { - title = "Presence changed" - description = "Lily's presence has been set to `${arguments.presenceArgument}`" + title = translations.embedTitle.translate() + description = translations.embedDesc.translate(arguments.presenceArgument) footer { - text = user.asUserOrNull()?.username ?: "Unable to get user username" + text = user.asUserOrNull()?.username ?: Translations.Basic.UnableTo.tag.translate() icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } color = DISCORD_BLACK @@ -378,8 +378,8 @@ class ModUtilities : Extension() { } ephemeralSubCommand { - name = "reset" - description = "Reset Lily's presence to the default status." + name = Translations.Moderation.ModUtilities.Status.Reset.name + description = Translations.Moderation.ModUtilities.Status.Reset.description guild(TEST_GUILD_ID) requirePermission(Permission.Administrator) @@ -390,24 +390,26 @@ class ModUtilities : Extension() { } action { + val translations = Translations.Moderation.ModUtilities.Status.Reset + // Store the new presence in the database for if there is a restart StatusCollection().setStatus(null) updateDefaultPresence() val guilds = this@ephemeralSlashCommand.kord.guilds.toList().size - respond { content = "Presence set to default" } + respond { content = translations.response.translate() } val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) ?: return@action utilityLog.createEmbed { - title = "Presence changed" - description = "Lily's presence has been set to default." + title = Translations.Moderation.ModUtilities.Status.Set.embedTitle.translate() + description = translations.embedDesc.translate() field { - value = "Watching over $guilds servers." + value = translations.embedValue.translate(guilds) } footer { - text = user.asUserOrNull()?.username ?: "Unable to get user username" + text = user.asUserOrNull()?.username ?: Translations.Basic.UnableTo.tag.translate() icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } color = DISCORD_BLACK @@ -417,8 +419,8 @@ class ModUtilities : Extension() { } ephemeralSlashCommand(::ResetModal) { - name = "reset" - description = "'Resets' Lily for this guild by deleting all database information relating to this guild" + name = Translations.Moderation.ModUtilities.Reset.name + description = Translations.Moderation.ModUtilities.Reset.description requirePermission(Permission.Administrator) // Hide this command from non-administrators @@ -428,27 +430,25 @@ class ModUtilities : Extension() { } action { modal -> + val translations = Translations.Moderation.ModUtilities.Reset if (modal?.confirmation?.value?.lowercase() != "yes") { - respond { content = "Confirmation failure. Reset cancelled" } + respond { content = translations.failResponse.translate() } return@action } var response: EphemeralFollowupMessage? = null response = respond { - content = - "Are you sure you want to reset the database? This will remove all data associated with " + - "this guild from Lily's database. This includes configs, user-set reminders, usernames and more." + - "This action is **irreversible** and the data **cannot** be recovered." + content = translations.tripleCheck.translate() components { ephemeralButton(0) { - label = "I'm sure" + label = translations.yesButton style = ButtonStyle.Danger action { response?.edit { - content = "Database reset!" + content = translations.resetResponse.translate() components { removeAll() } } @@ -458,8 +458,8 @@ class ModUtilities : Extension() { ?.getSystemChannel()!!.id )?.createMessage { embed { - title = "Database Reset!" - description = "All data associated with this guild has been removed." + title = translations.resetResponse.translate() + description = translations.embedDesc.translate() timestamp = Clock.System.now() color = DISCORD_BLACK } @@ -484,12 +484,12 @@ class ModUtilities : Extension() { } ephemeralButton(0) { - label = "Nevermind" + label = translations.noButton style = ButtonStyle.Secondary action { response?.edit { - content = "Reset cancelled" + content = translations.cancelResponse.translate() components { removeAll() } } } @@ -503,8 +503,8 @@ class ModUtilities : Extension() { inner class SayArgs : Arguments() { /** The message the user wishes to send. */ val message by string { - name = "message" - description = "The text of the message to be sent." + name = Translations.Moderation.ModUtilities.Say.Arguments.Message.name + description = Translations.Moderation.ModUtilities.Say.Arguments.Message.description // Fix newline escape characters mutate { @@ -516,29 +516,28 @@ class ModUtilities : Extension() { /** The channel to aim the message at. */ val channel by optionalChannel { - name = "channel" - description = "The channel the message should be sent in." + name = Translations.Moderation.ModUtilities.Say.Arguments.Channel.name + description = Translations.Moderation.ModUtilities.Say.Arguments.Channel.description } /** Whether to embed the message or not. */ val embed by defaultingBoolean { - name = "embed" - description = "If the message should be sent as an embed." + name = Translations.Moderation.ModUtilities.Say.Arguments.Embed.name + description = Translations.Moderation.ModUtilities.Say.Arguments.Embed.description defaultValue = false } /** If the embed should have a timestamp. */ val timestamp by defaultingBoolean { - name = "timestamp" - description = "If the message should be sent with a timestamp. Only works with embeds." + name = Translations.Moderation.ModUtilities.Say.Arguments.Timestamp.name + description = Translations.Moderation.ModUtilities.Say.Arguments.Timestamp.description defaultValue = true } /** What color the embed should be. */ val color by defaultingColor { - name = "color" - description = "The color of the embed. Can be either a hex code or one of Discord's supported colors. " + - "Embeds only" + name = Translations.Moderation.ModUtilities.Say.Arguments.Color.name + description = Translations.Moderation.ModUtilities.Say.Arguments.Color.description defaultValue = DISCORD_BLURPLE } } @@ -546,14 +545,14 @@ class ModUtilities : Extension() { inner class SayEditArgs : Arguments() { /** The ID of the embed to edit. */ val messageToEdit by snowflake { - name = "message-to-edit" - description = "The ID of the message you'd like to edit" + name = Translations.Moderation.ModUtilities.EditSay.Arguments.MessageToEdit.name + description = Translations.Moderation.ModUtilities.EditSay.Arguments.MessageToEdit.description } /** The new content of the embed. */ val newContent by optionalString { - name = "new-content" - description = "The new content of the message" + name = Translations.Moderation.ModUtilities.EditSay.Arguments.NewContent.name + description = Translations.Moderation.ModUtilities.EditSay.Arguments.NewContent.description mutate { it?.replace("\\n", "\n") @@ -564,37 +563,37 @@ class ModUtilities : Extension() { /** The new color for the embed. */ val newColor by optionalColour { - name = "new-color" - description = "The new color of the embed. Embeds only" + name = Translations.Moderation.ModUtilities.EditSay.Arguments.NewColor.name + description = Translations.Moderation.ModUtilities.EditSay.Arguments.NewColor.description } /** The channel the embed was originally sent in. */ val channelOfMessage by optionalChannel { - name = "channel-of-message" - description = "The channel of the message" + name = Translations.Moderation.ModUtilities.EditSay.Arguments.ChannelOf.name + description = Translations.Moderation.ModUtilities.EditSay.Arguments.ChannelOf.description } /** Whether to add the timestamp of when the message was originally sent or not. */ val timestamp by optionalBoolean { - name = "timestamp" - description = "Whether to timestamp the embed or not. Embeds only" + name = Translations.Moderation.ModUtilities.EditSay.Arguments.Timestamp.name + description = Translations.Moderation.ModUtilities.EditSay.Arguments.Timestamp.description } } inner class PresenceArgs : Arguments() { /** The new presence set by the command user. */ val presenceArgument by string { - name = "presence" - description = "The new value Lily's presence should be set to" + name = Translations.Moderation.ModUtilities.Status.Arguments.Presence.name + description = Translations.Moderation.ModUtilities.Status.Arguments.Presence.description } } inner class ResetModal : ModalForm() { - override var title = "Reset data for this guild" + override var title = Translations.Moderation.ModUtilities.Reset.Modal.title val confirmation = lineText { - label = "Confirm Reset" - placeholder = "Type 'yes' to confirm" + label = Translations.Moderation.ModUtilities.Reset.Modal.Confirmation.label + placeholder = Translations.Moderation.ModUtilities.Reset.Modal.Confirmation.placeholder required = true } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModerationCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModerationCommands.kt index 274285a8..5d1e4928 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModerationCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/ModerationCommands.kt @@ -54,6 +54,7 @@ import kotlinx.datetime.Clock import kotlinx.datetime.DateTimePeriod import kotlinx.datetime.TimeZone import kotlinx.datetime.plus +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.ModerationActionCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.TemporaryBanCollection @@ -75,8 +76,7 @@ import kotlin.time.Duration class ModerationCommands : Extension() { override val name = "moderation" - private val warnSuffix = "Please consider your actions carefully.\n\n" + - "For more information about the warn system, please see [this document]" + + private val warnSuffix = Translations.Moderation.ModCommands.warnSuffix.translate() + "($HYACINTH_GITHUB/LilyBot/blob/main/docs/commands.md#name-warn)" /** The scheduler that will track the time for un-banning in temp bans. */ @@ -89,7 +89,7 @@ class ModerationCommands : Extension() { override suspend fun setup() { tempBanTask = tempBanScheduler.schedule(120, repeat = true, callback = ::removeTempBans) ephemeralMessageCommand { - name = "Moderate" + name = Translations.Moderation.ModCommands.MessageCommand.name locking = true requirePermission(Permission.BanMembers, Permission.KickMembers, Permission.ModerateMembers) @@ -100,13 +100,14 @@ class ModerationCommands : Extension() { } action { + val messageTranslations = Translations.Moderation.ModCommands.MessageCommand val messageEvent = event val loggingChannel = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!) ?: return@action var menuMessage: EphemeralFollowupMessage? = null val targetMessage = messageEvent.interaction.getTargetOrNull() if (targetMessage == null) { respond { - content = "The message this command was run on cannot be found! It may have been deleted." + content = messageTranslations.notFound.translate() } return@action } @@ -117,7 +118,7 @@ class ModerationCommands : Extension() { targetMessage.id ) proxiedMessage ?: run { - respond { content = "Unable to find user" } + respond { content = Translations.Basic.UnableTo.findUser.translate() } return@action } senderId = proxiedMessage.sender @@ -126,56 +127,53 @@ class ModerationCommands : Extension() { } val sender = guild!!.getMemberOrNull(senderId) ?: run { - respond { content = "Unable to find user" } + respond { content = Translations.Basic.UnableTo.findUser.translate() } return@action } isBotOrModerator(event.kord, sender.asUserOrNull(), guild, "moderate") ?: return@action menuMessage = respond { - content = "How would you like to moderate this message?" + content = messageTranslations.howMod.translate() components { ephemeralStringSelectMenu { - placeholder = "Select action..." + val selectTranslations = Translations.Moderation.ModCommands.MessageCommand.SelectMenu + placeholder = selectTranslations.placeholder maximumChoices = 1 // Prevent selecting multiple options at once - option("Ban user", ModerationAction.BAN.name) { - description = "Ban the user that sent this message" - } - - option("Soft-ban", ModerationAction.SOFT_BAN.name) { - description = "Soft-ban the user that sent this message" - } - - option("Kick", ModerationAction.KICK.name) { - description = "Kick the user that sent this message" - } - - option("Timeout", ModerationAction.TIMEOUT.name) { - description = "Timeout the user that sent this message" - } - - option("Warn", ModerationAction.WARN.name) { - description = "Warn the user that sent this message" - } + option(selectTranslations.ban, ModerationAction.BAN.name) + option(selectTranslations.softBan, ModerationAction.SOFT_BAN.name) + option(selectTranslations.kick, ModerationAction.KICK.name) + option(selectTranslations.timeout, ModerationAction.TIMEOUT.name) + option(selectTranslations.warn, ModerationAction.WARN.name) action SelectMenu@{ // Get the first because there can only be one val option = event.interaction.values.firstOrNull() if (option == null) { - respond { content = "You did not select an option!" } + respond { + content = selectTranslations.noOption.translate() + } return@SelectMenu } - val reasonSuffix = "for sending the following message: `${targetMessage.content}`" + val reasonSuffix = + messageTranslations.reasonSuffix.translate( + targetMessage.content + ) val modConfig = ModerationConfigCollection().getConfig(guild!!.id) when (option) { ModerationAction.BAN.name -> { + val banTranslations = Translations.Moderation.ModCommands.Ban + val quickTranslations = Translations.Moderation.ModCommands.Ban.Quick val dm = sender.dm { embed { - title = "You have been banned from ${guild?.asGuildOrNull()?.name}" - description = modConfig?.banDmMessage ?: "Quick banned $reasonSuffix" + title = banTranslations.dmTitle.translate(guild?.asGuildOrNull()?.name) + description = + modConfig?.banDmMessage ?: quickTranslations.defaultDesc.translate( + reasonSuffix + ) color = DISCORD_GREEN } @@ -186,7 +184,7 @@ class ModerationCommands : Extension() { user.id, null, null, - "Quick banned $reasonSuffix", + quickTranslations.defaultDesc.translate(reasonSuffix), dm != null, true, null @@ -194,41 +192,41 @@ class ModerationCommands : Extension() { ) sender.ban { - reason = - "Quick banned $reasonSuffix" + reason = quickTranslations.defaultDesc.translate(reasonSuffix) } if (modConfig?.publicLogging != null && modConfig.publicLogging == true) { try { targetMessage.reply { embed { - title = "Banned." - description = "${sender.mention} user was banned " + - "for sending this message." + title = quickTranslations.embedTitle.translate() + description = + quickTranslations.embedDescMessage.translate(sender.mention) } } } catch (_: KtorRequestException) { channel.createEmbed { - title = "Banned." - description = "${sender.mention} user was banned " + - "for sending a deleted message." + title = quickTranslations.embedTitle.translate() + description = + quickTranslations.embedDescDeleted.translate(sender.mention) } } } menuMessage?.edit { - content = "Banned a user." + content = banTranslations.response.translate() components { removeAll() } } } ModerationAction.SOFT_BAN.name -> { + val softBanTranslations = Translations.Moderation.ModCommands.SoftBan + val quickTranslations = Translations.Moderation.ModCommands.SoftBan.Quick val dm = sender.dm { embed { - title = "You have been soft-banned from ${guild?.asGuildOrNull()?.name}" - description = - "Quick soft-banned $reasonSuffix. This is a soft-ban, you are " + - "free to rejoin at any time" + title = + softBanTranslations.dmTitle.translate(guild?.asGuildOrNull()?.name) + description = softBanTranslations.dmDesc.translate(reasonSuffix) } } @@ -238,7 +236,9 @@ class ModerationCommands : Extension() { user.id, null, null, - "Quick banned $reasonSuffix", + Translations.Moderation.ModCommands.Ban.Quick.defaultDesc.translate( + reasonSuffix + ), dm != null, true, null @@ -247,7 +247,9 @@ class ModerationCommands : Extension() { sender.ban { reason = - "Quick soft-banned $reasonSuffix" + Translations.Moderation.ModCommands.Ban.Quick.defaultDesc.translate( + reasonSuffix + ) } ModerationActionCollection().shouldIgnoreAction( @@ -260,31 +262,33 @@ class ModerationCommands : Extension() { try { targetMessage.reply { embed { - title = "Soft-banned." - description = "${sender.mention} user was soft-banned " + - "for sending this message." + title = quickTranslations.embedTitle.translate() + description = + quickTranslations.embedDescMessage.translate(sender.mention) } } } catch (_: KtorRequestException) { channel.createEmbed { - title = "Soft-Banned." - description = "${sender.mention} user was soft-banned " + - "for sending a deleted message." + title = quickTranslations.embedTitle.translate() + description = + quickTranslations.embedDescDeleted.translate(sender.mention) } } } menuMessage?.edit { - content = "Soft-Banned a user." + content = softBanTranslations.response.translate() components { removeAll() } } } ModerationAction.KICK.name -> { + val kickTranslations = Translations.Moderation.ModCommands.Kick + val quickTranslations = Translations.Moderation.ModCommands.Kick.Quick val dm = sender.dm { embed { - title = "You have been kicked from ${guild?.asGuildOrNull()?.name}" - description = "Quick kicked $reasonSuffix." + title = kickTranslations.dmTitle.translate(guild?.asGuildOrNull()?.name) + description = quickTranslations.dmDesc.translate(reasonSuffix) } } @@ -294,16 +298,16 @@ class ModerationCommands : Extension() { try { targetMessage.reply { embed { - title = "Kicked." - description = "${sender.mention} user was kicked " + - "for sending this message." + title = quickTranslations.embedTitle.translate() + description = + quickTranslations.embedDescMessage.translate(sender.mention) } } } catch (_: KtorRequestException) { channel.createEmbed { - title = "Kicked." - description = "${sender.mention} user was kicked " + - "for sending a deleted message." + title = quickTranslations.embedTitle.translate() + description = + quickTranslations.embedDescDeleted.translate(sender.mention) } } } @@ -314,7 +318,7 @@ class ModerationCommands : Extension() { user.id, null, null, - "Quick kicked via moderate menu $reasonSuffix", + quickTranslations.actionDesc.translate(reasonSuffix), dm != null, true, null @@ -322,18 +326,19 @@ class ModerationCommands : Extension() { ) menuMessage?.edit { - content = "Kicked a user." + content = kickTranslations.response.translate() components { removeAll() } } } ModerationAction.TIMEOUT.name -> { + val timeoutTranslations = Translations.Moderation.ModCommands.Timeout + val quickTranslations = Translations.Moderation.ModCommands.Timeout.Quick val timeoutTime = ModerationConfigCollection().getConfig(guild!!.id)?.quickTimeoutLength if (timeoutTime == null) { menuMessage?.edit { - content = - "No timeout length has been set in the moderation config, please set a length." + content = timeoutTranslations.noLength.translate() components { removeAll() } } return@SelectMenu @@ -341,9 +346,12 @@ class ModerationCommands : Extension() { val dm = sender.dm { embed { - title = "You have been timed-out in ${guild?.asGuildOrNull()?.name}" - description = - "Quick timed out for ${timeoutTime.interval()} $reasonSuffix." + title = + timeoutTranslations.dmTitle.translate(guild?.asGuildOrNull()?.name) + description = quickTranslations.dmDesc.translate( + timeoutTime.interval(), + reasonSuffix + ) } } @@ -353,16 +361,20 @@ class ModerationCommands : Extension() { try { targetMessage.reply { embed { - title = "Timed-out." - description = "${sender.mention} user was timed-out for " + - "${timeoutTime.interval()} for sending this message." + title = quickTranslations.embedTitle.translate() + description = quickTranslations.embedDescMessage.translate( + sender.mention, + timeoutTime.interval() + ) } } } catch (_: KtorRequestException) { channel.createEmbed { - title = "Timed-out." - description = "${sender.mention} user was timed-out for " + - "${timeoutTime.interval()} for sending a deleted message." + title = quickTranslations.embedTitle.translate() + description = quickTranslations.embedDescDeleted.translate( + sender.mention, + timeoutTime.interval() + ) } } } @@ -373,7 +385,7 @@ class ModerationCommands : Extension() { user.id, null, TimeData(timeoutTime, null, null, null), - "Quick timed-out via moderate menu $reasonSuffix", + quickTranslations.actionDesc.translate(reasonSuffix), dm != null, null, null @@ -381,20 +393,23 @@ class ModerationCommands : Extension() { ) menuMessage?.edit { - content = "Timed-out a user." + content = timeoutTranslations.response.translate() components { removeAll() } } } ModerationAction.WARN.name -> { + val warnTranslations = Translations.Moderation.ModCommands.Warning WarnCollection().setWarn(senderId, guild!!.id, false) val strikes = WarnCollection().getWarn(senderId, guild!!.id)?.strikes val dm = sender.dm { embed { - title = "Warning $strikes in ${guild?.asGuildOrNull()?.name}" + title = warnTranslations.dmTitle.translate( + strikes, guild?.asGuildOrNull()?.name + ) description = - "Quick warned $reasonSuffix\n $warnSuffix" + warnTranslations.quickDmDesc.translate(reasonSuffix, warnSuffix) } } @@ -411,16 +426,16 @@ class ModerationCommands : Extension() { loggingChannel.createMessage { embed { - title = "Warning" + title = warnTranslations.embedTitle.translate() baseModerationEmbed( - "Quick warned via moderate menu $reasonSuffix", + warnTranslations.quickWarned.translate(), sender, user ) dmNotificationStatusEmbedField(dm, true) timestamp = Clock.System.now() field { - name = "Total strikes" + name = warnTranslations.embedStrikeTot.translate() value = strikes.toString() } } @@ -430,14 +445,14 @@ class ModerationCommands : Extension() { strikes!!, event.interaction.user.asUserOrNull(), sender.asUserOrNull(), - "Quick warned via moderate menu $reasonSuffix" + warnTranslations.quickWarned.translate() ) } } } menuMessage?.edit { - content = "Warned a user." + content = warnTranslations.response.translate() components { removeAll() } } @@ -445,16 +460,16 @@ class ModerationCommands : Extension() { try { targetMessage.reply { embed { - title = "Warned." - description = "${sender.mention} user was warned " + - "for sending this message." + title = warnTranslations.embedTitle.translate() + description = + warnTranslations.embedDescMessage.translate(sender.mention) } } } catch (_: KtorRequestException) { channel.createEmbed { - title = "Warned." - description = "${sender.mention} user was warned " + - "for sending a deleted message." + title = warnTranslations.embedTitle.translate() + description = + warnTranslations.embedDescDeleted.translate(sender.mention) } } } @@ -468,8 +483,8 @@ class ModerationCommands : Extension() { } ephemeralSlashCommand(::BanArgs) { - name = "ban" - description = "Bans a user." + name = Translations.Moderation.ModCommands.Ban.name + description = Translations.Moderation.ModCommands.Ban.description requirePermission(Permission.BanMembers) @@ -479,11 +494,12 @@ class ModerationCommands : Extension() { } action { + val translations = Translations.Moderation.ModCommands.Ban isBotOrModerator(event.kord, arguments.userArgument, guild, "ban") ?: return@action // The discord limit for deleting days of messages in a ban is 7, so we should catch invalid inputs. if (arguments.messages > 7 || arguments.messages < 0) { - respond { content = "Invalid `messages` parameter! This number must be between 0 and 7!" } + respond { content = translations.invalidMessages.translate() } return@action } @@ -493,18 +509,18 @@ class ModerationCommands : Extension() { if (dmControl) { dmStatus = arguments.userArgument.dm { embed { - title = "You have been banned from ${guild?.asGuildOrNull()?.name}" - description = "**Reason:**\n${ - if (modConfig?.banDmMessage != null && arguments.reason == "No reason provided") { + title = translations.dmTitle.translate(guild?.asGuildOrNull()?.name) + description = "${translations.dmDesc.translate()}\n${ + if (modConfig?.banDmMessage != null && arguments.reason == Translations.Basic.noReason.translate()) { modConfig.banDmMessage - } else if (modConfig?.banDmMessage != null && arguments.reason != "No reason provided") { + } else if (modConfig?.banDmMessage != null && arguments.reason != Translations.Basic.noReason.translate()) { "${arguments.reason}\n${modConfig.banDmMessage}" } else { arguments.reason } }\n${ if (arguments.softBan) { - "You were soft-banned. You are free to rejoin without the need to be unbanned" + translations.wasSoft.translate() } else { "" } @@ -530,11 +546,11 @@ class ModerationCommands : Extension() { if (modConfig?.publicLogging == true) { event.interaction.channel.createEmbed { if (arguments.softBan) { - title = "Soft-Banned a user" - description = "${arguments.userArgument.mention} has been soft-banned!" + title = Translations.Moderation.ModCommands.SoftBan.response.translate() + description = translations.publicSoftDesc.translate() } else { - title = "Banned a user" - description = "${arguments.userArgument.mention} has been banned!" + title = translations.response.translate() + description = translations.publicDesc.translate() } color = DISCORD_BLACK } @@ -557,18 +573,23 @@ class ModerationCommands : Extension() { } respond { - content = if (arguments.softBan) "Soft-banned " else "Banned " + arguments.userArgument.mention + content = + if (arguments.softBan) { + Translations.Moderation.ModCommands.SoftBan.Quick.embedTitle.translate() + } else { + Translations.Moderation.ModCommands.Ban.Quick.embedTitle.translate() + } + " " + arguments.userArgument.mention } } } ephemeralSlashCommand { - name = "temp-ban" - description = "The parent command for temporary ban commands" + name = Translations.Moderation.ModCommands.TempBan.name + description = Translations.Moderation.ModCommands.TempBan.description ephemeralSubCommand(::TempBanArgs) { - name = "add" - description = "Temporarily bans a user" + name = Translations.Moderation.ModCommands.TempBan.Add.name + description = Translations.Moderation.ModCommands.TempBan.Add.description requirePermission(Permission.BanMembers) check { @@ -577,6 +598,7 @@ class ModerationCommands : Extension() { } action { + val translations = Translations.Moderation.ModCommands.TempBan.Add isBotOrModerator(event.kord, arguments.userArgument, guild, "temp-ban add") val now = Clock.System.now() val duration = now.plus(arguments.duration, TimeZone.UTC) @@ -586,9 +608,8 @@ class ModerationCommands : Extension() { if (dmControl) { dmStatus = arguments.userArgument.dm { embed { - title = "You have been temporarily banned from ${guild?.fetchGuild()?.name}" - description = "**Reason:**\n${arguments.reason}\n\n" + - "You are banned until $duration" + title = translations.dmTitle.translate(guild?.asGuildOrNull()?.name) + description = translations.dmDesc.translate(arguments.reason, duration) } } } @@ -608,8 +629,8 @@ class ModerationCommands : Extension() { if (modConfig?.publicLogging == true) { event.interaction.channel.createEmbed { - title = "Temp Banned a user" - description = "${arguments.userArgument.mention} has been Temp Banned!" + title = translations.publicEmbedTitle.translate() + description = translations.publicEmbedDesc.translate(arguments.userArgument.mention) color = DISCORD_BLACK } } @@ -624,14 +645,14 @@ class ModerationCommands : Extension() { } respond { - content = "Temporarily banned ${arguments.userArgument.mention}" + content = translations.response.translate(arguments.userArgument.mention) } } } ephemeralSubCommand { - name = "view-all" - description = "View all temporary bans for this guild" + name = Translations.Moderation.ModCommands.TempBan.View.name + description = Translations.Moderation.ModCommands.TempBan.View.description requirePermission(Permission.BanMembers) @@ -641,12 +662,14 @@ class ModerationCommands : Extension() { } action { + val translations = Translations.Moderation.ModCommands.TempBan.View + val pageTranslations = Translations.Moderation.ModCommands.TempBan.View.Page val pagesObj = Pages() val tempBans = TemporaryBanCollection().getTempBansForGuild(guild!!.id) if (tempBans.isEmpty()) { pagesObj.addPage( Page { - description = "There are no temporary bans in this guild." + description = translations.none.translate() } ) } else { @@ -654,12 +677,12 @@ class ModerationCommands : Extension() { var content = "" tempBan.forEach { content = """ - User: ${this@ephemeralSubCommand.kord.getUser(it.bannedUserId)?.username} - Moderator: ${guild?.getMemberOrNull(it.moderatorUserId)?.username} - Start Time: ${it.startTime.toDiscord(TimestampType.ShortDateTime)} (${ + ${pageTranslations.user.translate(this@ephemeralSubCommand.kord.getUser(it.bannedUserId)?.username)} + ${pageTranslations.mod.translate(guild?.getMemberOrNull(it.moderatorUserId)?.username)} + ${pageTranslations.start.translate(it.startTime.toDiscord(TimestampType.ShortDateTime))} (${ it.startTime.toDiscord(TimestampType.RelativeTime) }) - End Time: ${it.endTime.toDiscord(TimestampType.ShortDateTime)} (${ + ${pageTranslations.end.translate(it.endTime.toDiscord(TimestampType.ShortDateTime))} (${ it.endTime.toDiscord(TimestampType.RelativeTime) }) --- @@ -668,7 +691,7 @@ class ModerationCommands : Extension() { pagesObj.addPage( Page { - title = "Temporary bans for ${guild?.asGuildOrNull()?.name ?: "this guild"}" + title = pageTranslations.title.translate(guild?.asGuildOrNull()?.name) description = content } ) @@ -688,8 +711,8 @@ class ModerationCommands : Extension() { } ephemeralSlashCommand(::UnbanArgs) { - name = "unban" - description = "Unbans a user." + name = Translations.Moderation.ModCommands.Unban.name + description = Translations.Moderation.ModCommands.Unban.description requirePermission(Permission.BanMembers) @@ -719,14 +742,14 @@ class ModerationCommands : Extension() { TemporaryBanCollection().removeTempBan(guild!!.id, arguments.userArgument.id) } respond { - content = "Unbanned user." + content = Translations.Moderation.ModCommands.Unban.response.translate() } } } ephemeralSlashCommand(::KickArgs) { - name = "kick" - description = "Kicks a user." + name = Translations.Moderation.ModCommands.Kick.name + description = Translations.Moderation.ModCommands.Kick.description requirePermission(Permission.KickMembers) @@ -738,14 +761,15 @@ class ModerationCommands : Extension() { action { isBotOrModerator(event.kord, arguments.userArgument, guild, "kick") ?: return@action + val translations = Translations.Moderation.ModCommands.Kick val modConfig = ModerationConfigCollection().getConfig(guild!!.id) var dmStatus: Message? = null val dmControl = if (arguments.dm != null) arguments.dm!! else modConfig?.dmDefault == true if (dmControl) { dmStatus = arguments.userArgument.dm { embed { - title = "You have been kicked from ${guild?.fetchGuild()?.name}" - description = "**Reason:**\n${arguments.reason}" + title = translations.dmTitle.translate(guild?.fetchGuild()?.name) + description = translations.dmDesc.translate(arguments.reason) } } } @@ -758,8 +782,8 @@ class ModerationCommands : Extension() { if (modConfig?.publicLogging == true) { event.interaction.channel.createEmbed { - title = "Kicked a user" - description = "${arguments.userArgument.mention} has been kicked!" + title = translations.response.translate() + description = translations.embedDesc.translate(arguments.userArgument.mention) color = DISCORD_BLACK } } @@ -767,14 +791,14 @@ class ModerationCommands : Extension() { guild?.kick(arguments.userArgument.id, arguments.reason) respond { - content = "Kicked ${arguments.userArgument.mention}" + content = translations.response.translate() } } } ephemeralSlashCommand(::TimeoutArgs) { - name = "timeout" - description = "Times out a user." + name = Translations.Moderation.ModCommands.Timeout.name + description = Translations.Moderation.ModCommands.Timeout.description requirePermission(Permission.ModerateMembers) @@ -784,6 +808,7 @@ class ModerationCommands : Extension() { } action { + val translations = Translations.Moderation.ModCommands.Timeout val modConfig = ModerationConfigCollection().getConfig(guild!!.id) val durationArg = arguments.duration ?: modConfig?.quickTimeoutLength ?: DateTimePeriod(hours = 6) val duration = Clock.System.now().plus(durationArg, TimeZone.UTC) @@ -795,17 +820,19 @@ class ModerationCommands : Extension() { if (dmControl) { dmStatus = arguments.userArgument.dm { embed { - title = "You have been timed out in ${guild?.fetchGuild()?.name}" - description = "**Duration:**\n${ - duration.toDiscord(TimestampType.Default) + " (${durationArg.interval()})" - }\n**Reason:**\n${arguments.reason}" + title = translations.dmTitle.translate(guild?.fetchGuild()?.name) + description = translations.dmDesc.translate( + duration.toDiscord(TimestampType.Default), + durationArg.interval(), + arguments.reason + ) } } } ModerationActionCollection().addAction( ModerationAction.TIMEOUT, guild!!.id, arguments.userArgument.id, - ActionData( + ActionData( user.id, null, TimeData(durationArg, duration, Clock.System.now(), duration), @@ -818,11 +845,11 @@ class ModerationCommands : Extension() { if (modConfig?.publicLogging == true) { event.interaction.channel.createEmbed { - title = "Timeout" - description = "${arguments.userArgument.mention} was timed out by a moderator" + title = Translations.Moderation.ModCommands.Timeout.Quick.embedTitle.translate() + description = translations.embedDesc.translate(arguments.userArgument.mention) color = DISCORD_BLACK field { - name = "Duration:" + name = translations.duration.translate() value = duration.toDiscord(TimestampType.Default) + " (${durationArg.interval()})" inline = false } @@ -834,14 +861,14 @@ class ModerationCommands : Extension() { } respond { - content = "Timed-out ${arguments.userArgument.mention}." + content = translations.response.translate() } } } ephemeralSlashCommand(::RemoveTimeoutArgs) { - name = "remove-timeout" - description = "Removes a timeout from a user" + name = Translations.Moderation.ModCommands.RemoveTimeout.name + description = Translations.Moderation.ModCommands.RemoveTimeout.description requirePermission(Permission.ModerateMembers) @@ -851,21 +878,22 @@ class ModerationCommands : Extension() { } action { + val translations = Translations.Moderation.ModCommands.RemoveTimeout val config = ModerationConfigCollection().getConfig(guild!!.id) var dmStatus: Message? = null val dmControl = if (arguments.dm != null) arguments.dm!! else config?.dmDefault == true if (dmControl) { dmStatus = arguments.userArgument.dm { embed { - title = "Timeout removed in ${guild!!.asGuildOrNull()?.name}" - description = "Your timeout has been manually removed in this guild." + title = translations.dmTitle.translate(guild?.fetchGuild()?.name) + description = translations.dmDesc.translate() } } } ModerationActionCollection().addAction( ModerationAction.REMOVE_TIMEOUT, guild!!.id, arguments.userArgument.id, - ActionData( + ActionData( user.id, null, null, null, dmStatus != null, arguments.dm, null ) ) @@ -875,14 +903,14 @@ class ModerationCommands : Extension() { } respond { - content = "Removed timeout from user." + content = translations.response.translate() } } } ephemeralSlashCommand(::WarnArgs) { - name = "warn" - description = "Warns a user." + name = Translations.Moderation.ModCommands.Warning.name + description = Translations.Moderation.ModCommands.Warning.description requirePermission(Permission.ModerateMembers) @@ -892,6 +920,7 @@ class ModerationCommands : Extension() { } action { + val translations = Translations.Moderation.ModCommands.Warning val config = ModerationConfigCollection().getConfig(guild!!.id)!! val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action @@ -902,30 +931,26 @@ class ModerationCommands : Extension() { WarnCollection().setWarn(arguments.userArgument.id, guild!!.id, false) val strikes = WarnCollection().getWarn(arguments.userArgument.id, guild!!.id)?.strikes - respond { - content = "Warned user." - } - var dmStatus: Message? = null val dmControl = if (arguments.dm != null) arguments.dm!! else config.dmDefault == true if (dmControl) { val warnText = if (config.autoPunishOnWarn == false) { - "No moderation action has been taken.\n $warnSuffix" + "${translations.noAction.translate()}\n $warnSuffix" } else { when (strikes) { - 1 -> "No moderation action has been taken.\n $warnSuffix" - 2 -> "You have been timed out for 3 hours.\n $warnSuffix" - 3 -> "You have been timed out for 12 hours.\n $warnSuffix" - else -> "You have been timed out for 3 days.\n $warnSuffix" + 1 -> "${translations.noAction.translate()}\n $warnSuffix" + 2 -> "${translations.action3h.translate()}\n $warnSuffix" + 3 -> "${translations.action12h.translate()}\n $warnSuffix" + else -> "${translations.action3d.translate()}\n $warnSuffix" } } dmStatus = arguments.userArgument.dm { embed { - title = "Warning $strikes in $guildName" - description = "**Reason:** ${arguments.reason}\n\n$warnText" + title = translations.dmTitle.translate(strikes, guildName) + description = translations.dmDesc.translate(arguments.reason, warnText) } } } @@ -943,13 +968,13 @@ class ModerationCommands : Extension() { actionLog.createMessage { embed { - title = "Warning" + title = translations.embedTitle.translate() image = arguments.image?.url baseModerationEmbed(arguments.reason, arguments.userArgument, user) dmNotificationStatusEmbedField(dmStatus, dmControl) timestamp = Clock.System.now() field { - name = "Total strikes" + name = translations.embedStrikeTot.translate() value = strikes.toString() } color = DISCORD_RED @@ -968,17 +993,21 @@ class ModerationCommands : Extension() { if (config.publicLogging != null && config.publicLogging == true) { channel.createEmbed { - title = "Warning" - description = "${arguments.userArgument.mention} has been warned by a moderator" + title = translations.embedTitle.translate() + description = translations.embedDesc.translate(arguments.userArgument.mention) color = DISCORD_RED } } + + respond { + content = translations.response.translate() + } } } ephemeralSlashCommand(::RemoveWarnArgs) { - name = "remove-warn" - description = "Removes a user's warnings" + name = Translations.Moderation.ModCommands.RemoveWarning.name + description = Translations.Moderation.ModCommands.RemoveWarning.description requirePermission(Permission.ModerateMembers) @@ -988,10 +1017,11 @@ class ModerationCommands : Extension() { } action { + val translations = Translations.Moderation.ModCommands.RemoveWarning val config = ModerationConfigCollection().getConfig(guild!!.id)!! val targetUser = guild?.getMemberOrNull(arguments.userArgument.id) ?: run { respond { - content = "I was unable to find the member in this guild! Please try again!" + content = translations.unableToFind.translate() } return@action } @@ -999,7 +1029,7 @@ class ModerationCommands : Extension() { var userStrikes = WarnCollection().getWarn(targetUser.id, guild!!.id)?.strikes if (userStrikes == 0 || userStrikes == null) { respond { - content = "This user does not have any warning strikes!" + content = translations.noStrikes.translate() } return@action } @@ -1007,16 +1037,12 @@ class ModerationCommands : Extension() { WarnCollection().setWarn(targetUser.id, guild!!.id, true) userStrikes = WarnCollection().getWarn(targetUser.id, guild!!.id)?.strikes - respond { - content = "Removed strike from user" - } - var dmStatus: Message? = null if (arguments.dm) { dmStatus = targetUser.dm { embed { - title = "Warn strike removal in ${guild?.fetchGuild()?.name}" - description = "You have had a warn strike removed. You now have $userStrikes strikes." + title = translations.dmTitle.translate(guild?.fetchGuild()?.name) + description = translations.dmDesc.translate(userStrikes) color = DISCORD_GREEN } } @@ -1025,13 +1051,13 @@ class ModerationCommands : Extension() { val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action actionLog.createEmbed { - title = "Warning Removal" + title = translations.embedTitle.translate() color = DISCORD_GREEN timestamp = Clock.System.now() baseModerationEmbed(null, targetUser, user) dmNotificationStatusEmbedField(dmStatus, arguments.dm) field { - name = "Total Strikes:" + name = Translations.Moderation.ModCommands.Warning.embedStrikeTot.translate() value = userStrikes.toString() inline = false } @@ -1039,11 +1065,15 @@ class ModerationCommands : Extension() { if (config.publicLogging != null && config.publicLogging == true) { channel.createEmbed { - title = "Warning Removal" - description = "${arguments.userArgument.mention} had a warn strike removed by a moderator." + title = translations.embedTitle.translate() + description = translations.embedDesc.translate(arguments.userArgument.mention) color = DISCORD_GREEN } } + + respond { + content = translations.response.translate() + } } } } @@ -1088,209 +1118,209 @@ class ModerationCommands : Extension() { inner class BanArgs : Arguments() { /** The user to ban. */ val userArgument by user { - name = "user" - description = "Person to ban" + name = Translations.Moderation.ModCommands.Arguments.User.name + description = Translations.Moderation.ModCommands.Arguments.User.description } /** The number of days worth of messages to delete. */ val messages by int { - name = "delete-message-days" - description = "The number of days worth of messages to delete" + name = Translations.Moderation.ModCommands.Arguments.Messages.name + description = Translations.Moderation.ModCommands.Arguments.Messages.description } /** The reason for the ban. */ val reason by defaultingString { - name = "reason" - description = "The reason for the ban" - defaultValue = "No reason provided" + name = Translations.Moderation.ModCommands.Arguments.Reason.name + description = Translations.Moderation.ModCommands.Arguments.Reason.description + defaultValue = Translations.Moderation.ModCommands.Arguments.Reason.default.translate() } /** Weather to softban this user or not. */ val softBan by defaultingBoolean { - name = "soft-ban" - description = "Weather to soft-ban this user (unban them once messages are deleted)" + name = Translations.Moderation.ModCommands.Arguments.Soft.name + description = Translations.Moderation.ModCommands.Arguments.Soft.description defaultValue = false } /** Whether to DM the user or not. */ val dm by optionalBoolean { - name = "dm" - description = "Whether to send a direct message to the user about the ban" + name = Translations.Moderation.ModCommands.Arguments.Dm.name + description = Translations.Moderation.ModCommands.Arguments.Dm.description } /** An image that the user wishes to provide for context to the ban. */ val image by optionalAttachment { - name = "image" - description = "An image you'd like to provide as extra context for the action" + name = Translations.Moderation.ModCommands.Arguments.Image.name + description = Translations.Moderation.ModCommands.Arguments.Image.description } } inner class TempBanArgs : Arguments() { /** The user to ban. */ val userArgument by user { - name = "user" - description = "Person to ban" + name = Translations.Moderation.ModCommands.Arguments.User.name + description = Translations.Moderation.ModCommands.Arguments.User.description } /** The number of days worth of messages to delete. */ val messages by int { - name = "delete-message-days" - description = "The number of days worth of messages to delete" + name = Translations.Moderation.ModCommands.Arguments.Messages.name + description = Translations.Moderation.ModCommands.Arguments.Messages.description } /** The duration of the temporary ban. */ val duration by coalescingDuration { - name = "duration" - description = "The duration of the temporary ban." + name = Translations.Moderation.ModCommands.TempBan.Arguments.Duration.name + description = Translations.Moderation.ModCommands.TempBan.Arguments.Duration.description } /** The reason for the ban. */ val reason by defaultingString { - name = "reason" - description = "The reason for the ban" - defaultValue = "No reason provided" + name = Translations.Moderation.ModCommands.Arguments.Reason.name + description = Translations.Moderation.ModCommands.Arguments.Reason.description + defaultValue = Translations.Moderation.ModCommands.Arguments.Reason.default.translate() } /** Whether to DM the user or not. */ val dm by optionalBoolean { - name = "dm" - description = "Whether to send a direct message to the user about the ban" + name = Translations.Moderation.ModCommands.Arguments.Dm.name + description = Translations.Moderation.ModCommands.Arguments.Dm.description } /** An image that the user wishes to provide for context to the ban. */ val image by optionalAttachment { - name = "image" - description = "An image you'd like to provide as extra context for the action" + name = Translations.Moderation.ModCommands.Arguments.Image.name + description = Translations.Moderation.ModCommands.Arguments.Image.description } } inner class UnbanArgs : Arguments() { /** The ID of the user to unban. */ val userArgument by user { - name = "user" - description = "Person to un-ban" + name = Translations.Moderation.ModCommands.Arguments.User.name + description = Translations.Moderation.ModCommands.Arguments.User.description } /** The reason for the un-ban. */ val reason by defaultingString { - name = "reason" - description = "The reason for the un-ban" - defaultValue = "No reason provided" + name = Translations.Moderation.ModCommands.Arguments.Reason.name + description = Translations.Moderation.ModCommands.Arguments.Reason.description + defaultValue = Translations.Moderation.ModCommands.Arguments.Reason.default.translate() } } inner class KickArgs : Arguments() { /** The user to kick. */ val userArgument by user { - name = "user" - description = "Person to kick" + name = Translations.Moderation.ModCommands.Arguments.User.name + description = Translations.Moderation.ModCommands.Arguments.User.description } /** The reason for the kick. */ val reason by defaultingString { - name = "reason" - description = "The reason for the Kick" - defaultValue = "No reason provided" + name = Translations.Moderation.ModCommands.Arguments.Reason.name + description = Translations.Moderation.ModCommands.Arguments.Reason.description + defaultValue = Translations.Moderation.ModCommands.Arguments.Reason.default.translate() } /** Whether to DM the user or not. */ val dm by optionalBoolean { - name = "dm" - description = "Whether to send a direct message to the user about the kick" + name = Translations.Moderation.ModCommands.Arguments.Dm.name + description = Translations.Moderation.ModCommands.Arguments.Dm.description } /** An image that the user wishes to provide for context to the kick. */ val image by optionalAttachment { - name = "image" - description = "An image you'd like to provide as extra context for the action" + name = Translations.Moderation.ModCommands.Arguments.Image.name + description = Translations.Moderation.ModCommands.Arguments.Image.description } } inner class TimeoutArgs : Arguments() { /** The requested user to timeout. */ val userArgument by user { - name = "user" - description = "Person to timeout" + name = Translations.Moderation.ModCommands.Arguments.User.name + description = Translations.Moderation.ModCommands.Arguments.User.description } /** The time the timeout should last for. */ val duration by coalescingOptionalDuration { - name = "duration" - description = "Duration of timeout" + name = Translations.Moderation.ModCommands.TempBan.Arguments.Duration.name + description = Translations.Moderation.ModCommands.Timeout.Arguments.Duration.description } /** The reason for the timeout. */ val reason by defaultingString { - name = "reason" - description = "Reason for timeout" - defaultValue = "No reason provided" + name = Translations.Moderation.ModCommands.Arguments.Reason.name + description = Translations.Moderation.ModCommands.Arguments.Reason.description + defaultValue = Translations.Moderation.ModCommands.Arguments.Reason.default.translate() } /** Whether to DM the user or not. */ val dm by optionalBoolean { - name = "dm" - description = "Whether to send a direct message to the user about the timeout" + name = Translations.Moderation.ModCommands.Arguments.Dm.name + description = Translations.Moderation.ModCommands.Arguments.Dm.description } /** An image that the user wishes to provide for context to the kick. */ val image by optionalAttachment { - name = "image" - description = "An image you'd like to provide as extra context for the action" + name = Translations.Moderation.ModCommands.Arguments.Image.name + description = Translations.Moderation.ModCommands.Arguments.Image.description } } inner class RemoveTimeoutArgs : Arguments() { /** The requested user to remove the timeout from. */ val userArgument by user { - name = "user" - description = "Person to remove timeout from" + name = Translations.Moderation.ModCommands.Arguments.User.name + description = Translations.Moderation.ModCommands.Arguments.User.description } /** Whether to DM the user about the timeout removal or not. */ val dm by optionalBoolean { - name = "dm" - description = "Whether to dm the user about this or not" + name = Translations.Moderation.ModCommands.Arguments.Dm.name + description = Translations.Moderation.ModCommands.Arguments.Dm.description } } inner class WarnArgs : Arguments() { /** The requested user to warn. */ val userArgument by user { - name = "user" - description = "Person to warn" + name = Translations.Moderation.ModCommands.Arguments.User.name + description = Translations.Moderation.ModCommands.Arguments.User.description } /** The reason for the warning. */ val reason by defaultingString { - name = "reason" - description = "Reason for warning" - defaultValue = "No reason provided" + name = Translations.Moderation.ModCommands.Arguments.Reason.name + description = Translations.Moderation.ModCommands.Arguments.Reason.description + defaultValue = Translations.Moderation.ModCommands.Arguments.Reason.default.translate() } /** Whether to DM the user or not. */ val dm by optionalBoolean { - name = "dm" - description = "Whether to send a direct message to the user about the warning" + name = Translations.Moderation.ModCommands.Arguments.Dm.name + description = Translations.Moderation.ModCommands.Arguments.Dm.description } /** An image that the user wishes to provide for context to the kick. */ val image by optionalAttachment { - name = "image" - description = "An image you'd like to provide as extra context for the action" + name = Translations.Moderation.ModCommands.Arguments.Image.name + description = Translations.Moderation.ModCommands.Arguments.Image.description } } inner class RemoveWarnArgs : Arguments() { /** The requested user to remove the warning from. */ val userArgument by user { - name = "user" - description = "Person to remove warn from" + name = Translations.Moderation.ModCommands.Arguments.User.name + description = Translations.Moderation.ModCommands.Arguments.User.description } /** Whether to DM the user or not. */ val dm by defaultingBoolean { - name = "dm" - description = "Whether to send a direct message to the user about the warning" + name = Translations.Moderation.ModCommands.Arguments.Dm.name + description = Translations.Moderation.ModCommands.Arguments.Dm.description defaultValue = true } } @@ -1307,25 +1337,25 @@ class ModerationCommands : Extension() { * @since 4.4.0 */ private fun EmbedBuilder.warnTimeoutLog(warningNumber: Int, moderator: User, targetUser: User, reason: String) { + val translations = Translations.Moderation.ModCommands.Warning.Log when (warningNumber) { 1 -> {} - 2 -> description = "${targetUser.mention} has been timed-out for 3 hours due to 2 warn strikes" + 2 -> description = translations.action3h.translate(targetUser.mention) - 3 -> description = "${targetUser.mention} has been timed-out for 12 hours due to 3 warn strikes" + 3 -> description = translations.action12h.translate(targetUser.mention) else -> - description = "${targetUser.mention} has been timed-out for 3 days due to $warningNumber warn " + - "strikes\nIt might be time to consider other action." + description = translations.action3d.translate(targetUser.mention, warningNumber) } if (warningNumber != 1) { - title = "Timeout" + title = Translations.Moderation.ModCommands.Timeout.Quick.embedTitle.translate() field { - name = "User" + name = Translations.Basic.userField.translate() value = "${targetUser.id} (${targetUser.username})" } field { - name = "Reason" + name = translations.reason.translate() value = reason } footer { diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/Report.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/Report.kt index a78b5fe3..afe04618 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/Report.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/commands/Report.kt @@ -22,11 +22,16 @@ import dev.kordex.core.extensions.ephemeralMessageCommand import dev.kordex.core.extensions.ephemeralSlashCommand import dev.kordex.core.utils.getJumpUrl import kotlinx.datetime.Clock +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms import org.hyacinthbots.lilybot.utils.requiredConfigs +private val noAccess = Translations.Moderation.Report.noAccess.translate() + +private val ownMessage = Translations.Moderation.Report.ownMessage.translate() + /** * The message reporting feature in the bot. * @@ -37,7 +42,7 @@ class Report : Extension() { override suspend fun setup() { ephemeralMessageCommand(::ReportModal) { - name = "Report" + name = Translations.Moderation.Report.MessageCommand.name locking = true // To prevent the command from being run more than once concurrently check { @@ -50,16 +55,12 @@ class Report : Extension() { action { modal -> val reportedMessage = event.interaction.getTargetOrNull() if (reportedMessage == null) { - respond { - content = "Sorry, I can't properly access this message. Please ping the moderators instead." - } + respond { content = noAccess } return@action } if (reportedMessage.author == event.interaction.user) { - respond { - content = "You may not report your own message." - } + respond { content = ownMessage } return@action } @@ -72,15 +73,15 @@ class Report : Extension() { config.role!!, actionLog, reportedMessage, - modal?.reason?.value ?: "No reason provided" + modal?.reason?.value ?: Translations.Basic.noReason.translate() ) } } } ephemeralSlashCommand(::ManualReportArgs, ::ReportModal) { - name = "manual-report" - description = "Report a message, using a link instead of the message command" + name = Translations.Moderation.Report.SlashCommand.name + description = Translations.Moderation.Report.SlashCommand.description locking = true // To prevent the command from being run more than once concurrently check { @@ -96,7 +97,7 @@ class Report : Extension() { if (arguments.message.contains("/").not()) { respond { - content = "The URL provided was malformed and the message could not be found!" + content = Translations.Moderation.Report.SlashCommand.malformedLink.translate() } return@action } @@ -105,25 +106,19 @@ class Report : Extension() { guild!!.getChannelOfOrNull(Snowflake(arguments.message.split("/")[5])) if (channel == null) { - respond { - content = "Sorry, I can't properly access this message. Please ping the moderators instead." - } + respond { content = noAccess } return@action } val reportedMessage = channel.getMessageOrNull(Snowflake(arguments.message.split("/")[6])) if (reportedMessage == null) { - respond { - content = "Sorry, I can't find this message. Please ping the moderators instead." - } + respond { content = noAccess } return@action } if (reportedMessage.author == event.interaction.user) { - respond { - content = "You may not report your own message." - } + respond { content = ownMessage } return@action } @@ -133,7 +128,7 @@ class Report : Extension() { moderationConfig.role!!, modLog, reportedMessage, - modal?.reason?.value ?: "No reason provided" + modal?.reason?.value ?: Translations.Basic.noReason.translate() ) } } @@ -158,45 +153,47 @@ class Report : Extension() { reportedMessage: Message, reason: String ) { - content = "Would you like to report this message? This will ping moderators, and false reporting will be " + - "treated as spam and punished accordingly" + val translations = Translations.Moderation.Report.Confirmation + content = translations.content.translate() components { ephemeralButton(0) { - label = "Yes" + label = Translations.Basic.yes style = ButtonStyle.Success action { this.edit { - content = "Message reported to staff" + content = translations.response.translate() components { removeAll() } modLog?.createMessage { content = "<@&$moderatorRoleId>" } modLog?.createMessage { embed { - title = "Message reported" - description = "A message was reported in ${ - reportedMessage.getChannelOrNull()?.mention ?: "`Unable to get channel`" - }" + title = translations.embedTitle.translate() + description = translations.embedDesc.translate( + reportedMessage.getChannelOrNull()?.mention + ?: Translations.Basic.UnableTo.channel.translate() + ) + field { - name = "Message Content:" + name = translations.embedContentField.translate() value = reportedMessage.content.ifEmpty { - "Failed to get content of message" + Translations.Basic.UnableTo.getContents.translate() } inline = true } field { - name = "Message Author:" + name = translations.embedAuthorField.translate() value = - reportedMessage.author?.mention ?: "Failed to get author of message" + reportedMessage.author?.mention ?: Translations.Basic.UnableTo.author.translate() } field { - name = "Report reason:" + name = translations.embedReasonField.translate() value = reason } footer { - text = "Reported by: ${user.asUserOrNull()?.username}" + text = translations.embedReportedBy.translate(user.asUserOrNull()?.username) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } timestamp = Clock.System.now() @@ -204,7 +201,7 @@ class Report : Extension() { } components { linkButton { - label = "Jump to message" + label = translations.jumpButton url = reportedMessage.getJumpUrl() } } @@ -213,12 +210,12 @@ class Report : Extension() { } } ephemeralButton(0) { - label = "No" + label = Translations.Basic.no style = ButtonStyle.Danger action { this.edit { - content = "Message not reported." + content = translations.notReported.translate() components { removeAll() } } } @@ -229,17 +226,17 @@ class Report : Extension() { inner class ManualReportArgs : Arguments() { /** The link to the message being reported. */ val message by string { - name = "message-link" - description = "Link to the message to report" + name = Translations.Moderation.Report.SlashCommand.Arguments.Message.name + description = Translations.Moderation.Report.SlashCommand.Arguments.Message.description } } inner class ReportModal : ModalForm() { - override var title = "Report a message" + override var title = Translations.Moderation.Report.Modal.title val reason = paragraphText { - label = "Why are you reporting this message" - placeholder = "It violates rule X!" + label = Translations.Moderation.Report.Modal.label + placeholder = Translations.Moderation.Report.Modal.placeholder } } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationArgs.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationArgs.kt index d8a09edd..3af9e6e0 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationArgs.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationArgs.kt @@ -7,55 +7,56 @@ import dev.kordex.core.commands.converters.impl.optionalBoolean import dev.kordex.core.commands.converters.impl.optionalChannel import dev.kordex.core.commands.converters.impl.optionalRole import dev.kordex.core.commands.converters.impl.optionalString +import lilybot.i18n.Translations class ModerationArgs : Arguments() { val enabled by boolean { - name = "enable-moderation" - description = "Whether to enable the moderation system" + name = Translations.Config.Arguments.Moderation.Enabled.name + description = Translations.Config.Arguments.Moderation.Enabled.description } val moderatorRole by optionalRole { - name = "moderator-role" - description = "The role of your moderators, used for pinging in message logs." + name = Translations.Config.Arguments.Moderation.ModeratorRole.name + description = Translations.Config.Arguments.Moderation.ModeratorRole.description } val modActionLog by optionalChannel { - name = "action-log" - description = "The channel used to store moderator actions." + name = Translations.Config.Arguments.Moderation.ModActionLog.name + description = Translations.Config.Arguments.Moderation.ModActionLog.description } val quickTimeoutLength by coalescingOptionalDuration { - name = "quick-timeout-length" - description = "The length of timeouts to use for quick timeouts" + name = Translations.Config.Arguments.Moderation.QuickTimeout.name + description = Translations.Config.Arguments.Moderation.QuickTimeout.description } val warnAutoPunishments by optionalBoolean { - name = "warn-auto-punishments" - description = "Whether to automatically punish users for reach a certain threshold on warns" + name = Translations.Config.Arguments.Moderation.AutoPunish.name + description = Translations.Config.Arguments.Moderation.AutoPunish.description } val logPublicly by optionalBoolean { - name = "log-publicly" - description = "Whether to log moderation publicly or not." + name = Translations.Config.Arguments.Moderation.LogPublicly.name + description = Translations.Config.Arguments.Moderation.LogPublicly.description } val dmDefault by optionalBoolean { - name = "dm-default" - description = "The default value for whether to DM a user in a ban action or not." + name = Translations.Config.Arguments.Moderation.DmDefault.name + description = Translations.Config.Arguments.Moderation.DmDefault.description } val banDmMessage by optionalString { - name = "ban-dm-message" - description = "A custom message to send to users when they are banned." + name = Translations.Config.Arguments.Moderation.BanDm.name + description = Translations.Config.Arguments.Moderation.BanDm.description } val autoInviteModeratorRole by optionalBoolean { - name = "auto-invite-moderator-role" - description = "Silently ping moderators to invite them to new threads." + name = Translations.Config.Arguments.Moderation.InviteMods.name + description = Translations.Config.Arguments.Moderation.InviteMods.description } val logMemberRoleChanges by optionalBoolean { - name = "log-member-role-changes" - description = "Whether to log changes to the roles members have in a guild." + name = Translations.Config.Arguments.Moderation.RoleChanges.name + description = Translations.Config.Arguments.Moderation.RoleChanges.description } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationCommand.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationCommand.kt index 69a57a2d..7290ac03 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationCommand.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/config/ModerationCommand.kt @@ -10,6 +10,7 @@ import dev.kordex.core.checks.hasPermission import dev.kordex.core.commands.application.slash.SlashCommand import dev.kordex.core.commands.application.slash.ephemeralSubCommand import dev.kordex.core.utils.botHasPermissions +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.entities.ModerationConfigData import org.hyacinthbots.lilybot.extensions.config.ConfigOptions @@ -19,8 +20,8 @@ import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms suspend fun SlashCommand<*, *, *>.moderationCommand() = ephemeralSubCommand(::ModerationArgs) { - name = "moderation" - description = "Configure Lily's moderation system" + name = Translations.Config.Moderation.name + description = Translations.Config.Moderation.description requirePermission(Permission.ManageGuild) @@ -33,8 +34,7 @@ suspend fun SlashCommand<*, *, *>.moderationCommand() = val moderationConfig = ModerationConfigCollection().getConfig(guild!!.id) if (moderationConfig != null) { respond { - content = "You already have a moderation configuration set. " + - "Please clear it before attempting to set a new one." + content = Translations.Config.configAlreadyExists.translate("moderation") } return@action } @@ -56,7 +56,7 @@ suspend fun SlashCommand<*, *, *>.moderationCommand() = ) ) respond { - content = "Moderation system disabled." + content = Translations.Config.Moderation.systemDisabled.translate() } return@action } @@ -66,17 +66,14 @@ suspend fun SlashCommand<*, *, *>.moderationCommand() = arguments.moderatorRole == null && arguments.modActionLog != null ) { respond { - content = - "You must set both the moderator role and the action log channel to use the moderation configuration." + content = Translations.Config.Moderation.roleAndChannelRequired.translate() } return@action } if (!canPingRole(arguments.moderatorRole, guild!!.id, this@moderationCommand.kord)) { respond { - content = - "I cannot use the role: ${arguments.moderatorRole!!.mention}, because it is not mentionable by " + - "regular users. Please enable this in the role settings, or use a different role." + content = Translations.Config.Moderation.roleNotPingable.translate(arguments.moderatorRole!!.mention) } return@action } @@ -86,8 +83,7 @@ suspend fun SlashCommand<*, *, *>.moderationCommand() = modActionLog = guild!!.getChannelOfOrNull(arguments.modActionLog!!.id) if (modActionLog?.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) != true) { respond { - content = "The mod action log you've selected is invalid, or I can't view it. " + - "Please attempt to resolve this and try again." + content = Translations.Config.invalidChannel.translate("mod-action log") } return@action } @@ -119,7 +115,7 @@ suspend fun SlashCommand<*, *, *>.moderationCommand() = if (utilityLog == null) { respond { - content = "Consider setting a utility config to log changes to configurations." + content = Translations.Config.considerUtility.translate() } return@action } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/events/ModerationEvents.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/events/ModerationEvents.kt index 22071026..3e5c9716 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/events/ModerationEvents.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/events/ModerationEvents.kt @@ -15,6 +15,7 @@ import dev.kordex.core.time.TimestampType import dev.kordex.core.time.toDiscord import dev.kordex.core.utils.timeoutUntil import kotlinx.datetime.Clock +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.LeftMemberFlagCollection import org.hyacinthbots.lilybot.database.collections.ModerationActionCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection @@ -59,18 +60,18 @@ class ModerationEvents : Extension() { if (existingAction != null) { getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, event.getGuild())?.createEmbed { title = when (existingAction.actionType) { - ModerationAction.BAN -> "Banned a user" - ModerationAction.SOFT_BAN -> "Soft-banned a user" - ModerationAction.TEMP_BAN -> "Temp-banned a user" + ModerationAction.BAN -> Translations.Events.Moderation.Ban.banned.translate() + ModerationAction.SOFT_BAN -> Translations.Events.Moderation.Ban.softBanned.translate() + ModerationAction.TEMP_BAN -> Translations.Events.Moderation.Ban.tempBanned.translate() else -> null // Theoretically this should never occur but the compiler cries otherwise - } + } + Translations.Events.Moderation.Ban.aUser.translate() - description = "${event.user.mention} has been ${ + description = Translations.Events.Moderation.Ban.banDescription.translate(event.user.mention) + if (existingAction.data.reason?.contains("quick ban", true) == false) { when (existingAction.actionType) { - ModerationAction.BAN -> "banned" - ModerationAction.SOFT_BAN -> "soft-banned" - ModerationAction.TEMP_BAN -> "temporarily banned" + ModerationAction.BAN -> Translations.Events.Moderation.Ban.banned.translate() + ModerationAction.SOFT_BAN -> Translations.Events.Moderation.Ban.softBanned.translate() + ModerationAction.TEMP_BAN -> Translations.Events.Moderation.Ban.tempBanned.translate() else -> null // Again should theoretically never occur, but compiler } } else { @@ -80,7 +81,6 @@ class ModerationEvents : Extension() { else -> null } } - }" baseModerationEmbed( existingAction.data.reason, event.user, @@ -91,7 +91,7 @@ class ModerationEvents : Extension() { timestamp = Clock.System.now() if (existingAction.data.deletedMessages != null) { field { - name = "Days of messages deleted" + name = Translations.Events.Moderation.Ban.deleteDays.translate() value = if (existingAction.actionType == ModerationAction.SOFT_BAN && existingAction.data.deletedMessages == 0 @@ -105,7 +105,7 @@ class ModerationEvents : Extension() { } if (existingAction.data.timeData != null) { field { - name = "Duration:" + name = Translations.Events.Moderation.Ban.duration.translate() value = existingAction.data.timeData.durationInst?.toDiscord(TimestampType.Default) + " (${existingAction.data.timeData.durationDtp?.interval()})" @@ -120,8 +120,10 @@ class ModerationEvents : Extension() { ) } else { getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, event.getGuild())?.createEmbed { - title = "Banned a User" - description = "${event.user.mention} has been banned!" + title = + Translations.Events.Moderation.Ban.banned.translate() + Translations.Events.Moderation.Ban.aUser.translate() + description = + Translations.Events.Moderation.Ban.defaultBanDescription.translate(event.user.mention) baseModerationEmbed(event.getBan().reason, event.user, null) timestamp = Clock.System.now() } @@ -152,18 +154,28 @@ class ModerationEvents : Extension() { val isTempUnban = existingAction.data.reason?.contains("**temporary-ban**", true) == true getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, event.getGuild())?.createEmbed { - title = if (isTempUnban) "Temporary ban removed" else "Unbanned a user" - description = "${event.user.mention} has " + - "${if (isTempUnban) "had their temporary ban removed!" else "been unbanned!"}\n" + + title = if (isTempUnban) { + Translations.Events.Moderation.Unban.tempRemoved + } else { + Translations.Events.Moderation.Unban.unbanned + }.translate() + description = Translations.Events.Moderation.Unban.unbannedDesc.translate(event.user.mention) + + "${ + if (isTempUnban) { + Translations.Events.Moderation.Unban.tempRemovedDesc + } else { + Translations.Events.Moderation.Unban.unbanned + }.translate() + }\n" + "${event.user.id} (${event.user.username})" if (existingAction.data.reason?.contains("**temporary-ban-expire**") == false) { field { - name = "Reason:" + name = Translations.Events.Moderation.Unban.reason.translate() value = existingAction.data.reason.toString() } } else { field { - name = "Initial Ban date:" + name = Translations.Events.Moderation.Unban.initialDate.translate() value = existingAction.data.timeData?.start?.toDiscord(TimestampType.ShortDateTime) .toString() } @@ -172,7 +184,7 @@ class ModerationEvents : Extension() { if (existingAction.data.actioner != null) { val user = UserBehavior(existingAction.data.actioner, kord).asUserOrNull() footer { - text = user?.username ?: "Unable to get username" + text = user?.username ?: Translations.Basic.UnableTo.tag.translate() icon = user?.avatar?.cdnUrl?.toUrl() } } @@ -217,27 +229,27 @@ class ModerationEvents : Extension() { val actioner = data.actioner?.let { UserBehavior(it, kord) }?.asUserOrNull() channel?.createEmbed { if (isRemove) { - title = "Timeout removed" + title = Translations.Events.Moderation.MemberUpdate.timeoutRemoved.translate() dmNotificationStatusEmbedField(data.dmOutcome, data.dmOverride) field { - name = "User:" + name = Translations.Basic.userField.translate() value = "${user?.username} (${user?.id})" } footer { - text = "Requested by ${actioner?.username}" + text = Translations.Basic.requestedBy.translate(actioner?.username) icon = user?.avatar?.cdnUrl?.toUrl() } timestamp = Clock.System.now() color = DISCORD_GREEN } else { - title = "Timeout" + title = Translations.Events.Moderation.MemberUpdate.timeoutAdded.translate() image = data.imageUrl baseModerationEmbed(data.reason, event.member, user) dmNotificationStatusEmbedField(data.dmOutcome, data.dmOverride) timestamp = data.timeData?.start color = DISCORD_RED field { - name = "Duration" + name = Translations.Events.Moderation.Ban.duration.translate() value = data.timeData?.durationInst?.toDiscord(TimestampType.Default) + " (${data.timeData?.durationDtp.interval()})" } @@ -252,21 +264,21 @@ class ModerationEvents : Extension() { } else { channel?.createEmbed { if (event.member.timeoutUntil != null) { - title = "Timeout" + title = Translations.Events.Moderation.MemberUpdate.timeoutAdded.translate() color = DISCORD_RED field { - name = "Duration" + name = Translations.Events.Moderation.Ban.duration.translate() value = event.member.timeoutUntil?.toDiscord(TimestampType.Default) ?: "0" } } else { - title = "Removed timeout" + title = Translations.Events.Moderation.MemberUpdate.timeoutRemoved.translate() color = DISCORD_GREEN } field { - name = "User" + name = Translations.Basic.userField.translate() value = event.member.mention + "(${event.member.id})" } - description = "via Discord menus" + description = Translations.Events.Moderation.MemberUpdate.viaMenu.translate() timestamp = Clock.System.now() } } @@ -275,36 +287,41 @@ class ModerationEvents : Extension() { if (event.member.nickname == event.old?.nickname && event.member.roleBehaviors == event.old?.roleBehaviors && ( - event.member.data != event.old?.data || event.member.avatar != event.old?.avatar || - event.member.premiumSince != event.old?.premiumSince || - event.member.isPending != event.old?.isPending || event.member.flags != event.old?.flags || - event.member.avatarDecoration != event.old?.avatarDecoration - ) + event.member.data != event.old?.data || event.member.avatar != event.old?.avatar || + event.member.premiumSince != event.old?.premiumSince || + event.member.isPending != event.old?.isPending || event.member.flags != event.old?.flags || + event.member.avatarDecoration != event.old?.avatarDecoration + ) ) { - return@action - } + return@action + } val newRoles = mutableListOf() val oldRoles = mutableListOf() event.member.roleBehaviors.forEach { newRoles.add(it.mention) } event.old?.roleBehaviors?.forEach { oldRoles.add(it.mention) } channel?.createEmbed { - title = "Member updated" - description = "${event.member.username} has been updated" + title = Translations.Events.Moderation.MemberUpdate.updatedTitle.translate() + description = + Translations.Events.Moderation.MemberUpdate.updatedDesc.translate(event.member.username) if (event.member.nickname != event.old?.nickname) { field { - name = "Nickname change" - value = "Old: ${event.old?.nickname}\nNew: ${event.member.nickname}" + name = Translations.Events.Moderation.MemberUpdate.nickChange.translate() + value = Translations.Events.Moderation.MemberUpdate.nickChangeVal.translateNamed( + "old" to event.old?.nickname, "new" to event.member.nickname + ) } } if (event.member.roleIds != event.old?.roleIds) { field { - name = "New Roles" - value = newRoles.joinToString(", ").ifNullOrEmpty { "None" } + name = Translations.Events.Moderation.MemberUpdate.newRoles.translate() + value = + newRoles.joinToString(", ").ifNullOrEmpty { Translations.Basic.none.translate() } inline = true } field { - name = "Old roles" - value = oldRoles.joinToString(", ").ifNullOrEmpty { "None" } + name = Translations.Events.Moderation.MemberUpdate.oldRoles.translate() + value = + oldRoles.joinToString(", ").ifNullOrEmpty { Translations.Basic.none.translate() } inline = true } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/threads/AutoThreading.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/threads/AutoThreading.kt index 40ca5fd5..7619cb52 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/threads/AutoThreading.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/threads/AutoThreading.kt @@ -43,6 +43,7 @@ import dev.kordex.modules.pluralkit.events.PKMessageCreateEvent import dev.kordex.modules.pluralkit.events.ProxiedMessageCreateEvent import dev.kordex.modules.pluralkit.events.UnProxiedMessageCreateEvent import kotlinx.datetime.Clock +import lilybot.i18n.Translations import org.hyacinthbots.docgenerator.subCommandAdditionalDocumentation import org.hyacinthbots.lilybot.database.collections.AutoThreadingCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection @@ -58,13 +59,13 @@ class AutoThreading : Extension() { override suspend fun setup() { ephemeralSlashCommand { - name = "auto-threading" - description = "The parent command for auto-threading management." + name = Translations.Threads.AutoThreading.name + description = Translations.Threads.AutoThreading.description @OptIn(UnsafeAPI::class) unsafeSubCommand(::AutoThreadingArgs) { - name = "enable" - description = "Automatically create a thread for each message sent in this channel." + name = Translations.Threads.AutoThreading.Enable.name + description = Translations.Threads.AutoThreading.Enable.description initialResponse = InitialSlashCommandResponse.None @@ -82,7 +83,7 @@ class AutoThreading : Extension() { if (AutoThreadingCollection().getSingleAutoThread(channel.id) != null) { ackEphemeral() respondEphemeral { - content = "Auto-threading is already enabled for this channel." + content = Translations.Threads.AutoThreading.alreadyOn.translate() } return@action } @@ -91,7 +92,7 @@ class AutoThreading : Extension() { if (!canPingRole(arguments.role, guild!!.id, this@unsafeSubCommand.kord)) { ackEphemeral() respondEphemeral { - content = "Lily cannot mention this role. Please fix the role's permissions and try again." + content = Translations.Threads.AutoThreading.Enable.noMention.translate() } return@action } @@ -104,10 +105,10 @@ class AutoThreading : Extension() { this@unsafeSubCommand.componentRegistry.register(modalObj) event.interaction.modal( - modalObj.title, + modalObj.title.translate(), modalObj.id ) { - modalObj.applyToBuilder(this, getLocale(), null) + modalObj.applyToBuilder(this, getLocale()) } modalObj.awaitCompletion { modalSubmitInteraction -> @@ -120,7 +121,7 @@ class AutoThreading : Extension() { } respondEphemeral { - content = "Auto-threading has been **enabled** in this channel." + content = Translations.Threads.AutoThreading.Enable.enabled.translate() } // Add the channel to the database as auto-threaded @@ -143,41 +144,42 @@ class AutoThreading : Extension() { val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild!!) ?: return@action utilityLog.createEmbed { - title = "Auto-threading Enabled" + val obj = Translations.Threads.AutoThreading.Enable.Embed + title = obj.title.translate() description = null field { - name = "Channel:" + name = obj.channel.translate() value = channel.mention inline = true } field { - name = "Role:" - value = arguments.role?.mention ?: "null" + name = obj.role.translate() + value = arguments.role?.mention ?: Translations.Basic.`null`.translate() inline = true } field { - name = "Prevent Duplicates:" + name = obj.preventDuplicates.translate() value = arguments.preventDuplicates.toString() inline = true } field { - name = "Begin Archived:" + name = obj.beginArchived.translate() value = arguments.archive.toString() inline = true } field { - name = "Smart Naming Enabled:" + name = obj.smartNaming.translate() value = arguments.contentAwareNaming.toString() inline = true } field { - name = "Mention:" + name = obj.mention.translate() value = arguments.mention.toString() inline = true } field { - name = "Initial Message:" - value = if (message != null) "```$message```" else "null" + name = obj.initialMessage.translate() + value = if (message != null) "```$message```" else Translations.Basic.`null`.translate() inline = message == null } footer { @@ -191,8 +193,8 @@ class AutoThreading : Extension() { } ephemeralSubCommand { - name = "disable" - description = "Stop automatically creating threads in this channel." + name = Translations.Threads.AutoThreading.Disable.name + description = Translations.Threads.AutoThreading.Disable.description requirePermission(Permission.ManageChannels) @@ -207,7 +209,7 @@ class AutoThreading : Extension() { // Check if auto-threading is enabled if (AutoThreadingCollection().getSingleAutoThread(channel.id) == null) { respond { - content = "Auto-threading is not enabled for this channel." + content = Translations.Threads.AutoThreading.alreadyOff.translate() } return@action } @@ -217,18 +219,18 @@ class AutoThreading : Extension() { // Respond to the user respond { - content = "Auto-threading has been **disabled** in this channel." + content = Translations.Threads.AutoThreading.Disable.disabled.translate() } // Log the change val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild!!) ?: return@action utilityLog.createEmbed { - title = "Auto-threading Disabled" + title = Translations.Threads.AutoThreading.Disable.Embed.title.translate() description = null field { - name = "Channel:" + name = Translations.Threads.AutoThreading.Disable.Embed.channel.translate() value = channel.mention inline = true } @@ -243,8 +245,8 @@ class AutoThreading : Extension() { } ephemeralSubCommand { - name = "list" - description = "List all the auto-threaded channels in this server, if any." + name = Translations.Threads.AutoThreading.List.name + description = Translations.Threads.AutoThreading.List.description check { anyGuild() @@ -258,7 +260,7 @@ class AutoThreading : Extension() { autoThreads.forEach { responseContent += "\n<#${it.channelId}>" if (responseContent.length > 4080) { - responseContent += "(List trimmed.)" + responseContent += Translations.Threads.AutoThreading.List.trimmed.translate() return@forEach } } @@ -266,10 +268,10 @@ class AutoThreading : Extension() { respond { embed { if (responseContent == null) { - title = "There are no auto-threaded channels in this guild." - description = "Add new ones by using `/auto-threading enable`" + title = Translations.Threads.AutoThreading.List.noChannels.translate() + description = Translations.Threads.AutoThreading.List.addNewChannels.translate() } else { - title = "Auto-threaded channels in this guild:" + title = Translations.Threads.AutoThreading.List.channelList.translate() description = responseContent.replace("null", "") } } @@ -278,8 +280,8 @@ class AutoThreading : Extension() { } ephemeralSubCommand(::AutoThreadingViewArgs) { - name = "view" - description = "View the settings of an auto-threaded channel" + name = Translations.Threads.AutoThreading.View.name + description = Translations.Threads.AutoThreading.View.description requirePermission(Permission.ManageChannels) @@ -294,21 +296,22 @@ class AutoThreading : Extension() { val autoThread = AutoThreadingCollection().getSingleAutoThread(arguments.channel.id) if (autoThread == null) { respond { - content = "**Error:** This is not an auto-threaded channel!" + content = Translations.Threads.AutoThreading.View.noThreaded.translate() } return@action } respond { + val obj = Translations.Threads.AutoThreading.View.Embed embed { - title = "Auto-Threaded channel settings" - description = "These are the settings for channel: ${arguments.channel.mention}" + title = obj.title.translate() + description = obj.description.translate(arguments.channel.mention) field { - name = "Ping role" + name = obj.role.translate() value = if (autoThread.roleId != null) { - guild!!.getRoleOrNull(autoThread.roleId)?.mention ?: "Unable to get role!" + guild!!.getRoleOrNull(autoThread.roleId)?.mention ?: obj.unable.translate() } else { - "None" + Translations.Basic.none.translate() } } if (autoThread.extraRoleIds.isNotEmpty()) { @@ -317,32 +320,32 @@ class AutoThreading : Extension() { mentions += "${guild!!.getRoleOrNull(it)?.mention} " } field { - name = "Extra Ping Roles" + name = obj.extraRoles.translate() value = mentions } } field { - name = "Prevent duplicates" + name = obj.preventDuplicates.translate() value = autoThread.preventDuplicates.toString() } field { - name = "Archive on creation" + name = obj.archiveOnStart.translate() value = autoThread.archive.toString() } field { - name = "Content aware naming" + name = obj.contentAwareNaming.translate() value = autoThread.contentAwareNaming.toString() } field { - name = "Mention creator on creation" + name = obj.mentionCreator.translate() value = autoThread.mention.toString() } field { - name = "Creation message" - value = autoThread.creationMessage ?: "Default" + name = obj.creationMessage.translate() + value = autoThread.creationMessage ?: Translations.Basic.default.translate() } field { - name = "Add mods and ping role" + name = obj.addMods.translate() value = autoThread.addModsAndRole.toString() } } @@ -351,12 +354,11 @@ class AutoThreading : Extension() { } ephemeralSubCommand(::ExtraRolesArgs) { - name = "add-roles" - description = "Add extra to threads in auto-threaded channels" + name = Translations.Threads.AutoThreading.AddRoles.name + description = Translations.Threads.AutoThreading.AddRoles.description subCommandAdditionalDocumentation { - extraInformation = "This command will add roles to be pinged alongside the default ping role " + - "for this auto-threaded channel" + extraInformation = Translations.Threads.AutoThreading.AddRoles.extraInfo.translate() } requirePermission(Permission.ManageChannels) @@ -372,21 +374,21 @@ class AutoThreading : Extension() { val autoThread = AutoThreadingCollection().getSingleAutoThread(event.interaction.channelId) if (autoThread == null) { respond { - content = "**Error:** This is not an auto-threaded channel!" + content = Translations.Threads.AutoThreading.AddRoles.noThreaded.translate() } return@action } if (!canPingRole(arguments.role, guild!!.id, this@ephemeralSubCommand.kord)) { respond { - content = "Lily cannot mention this role. Please fix the role's permissions and try again." + content = Translations.Threads.AutoThreading.Enable.noMention.translate() } return@action } if (autoThread.extraRoleIds.contains(arguments.role!!.id)) { respond { - content = "This role has already been added." + content = Translations.Threads.AutoThreading.AddRoles.alreadyAdded.translate() } return@action } @@ -397,18 +399,17 @@ class AutoThreading : Extension() { AutoThreadingCollection().updateExtraRoles(event.interaction.channelId, updatedRoles) respond { - content = "Role (${arguments.role!!.mention}) added" + content = Translations.Threads.AutoThreading.AddRoles.added.translate(arguments.role!!.mention) } } } ephemeralSubCommand(::ExtraRolesArgs) { - name = "remove-roles" - description = "Remove extra from threads in auto-threaded channels" + name = Translations.Threads.AutoThreading.RemoveRoles.name + description = Translations.Threads.AutoThreading.RemoveRoles.description subCommandAdditionalDocumentation { - extraInformation = "This command will remove roles that have been added to be pinged alongside " + - "the default ping role for this auto-threaded channel" + extraInformation = Translations.Threads.AutoThreading.RemoveRoles.extraInfo.translate() } requirePermission(Permission.ManageChannels) @@ -424,14 +425,14 @@ class AutoThreading : Extension() { val autoThread = AutoThreadingCollection().getSingleAutoThread(event.interaction.channelId) if (autoThread == null) { respond { - content = "**Error:** This is not an auto-threaded channel!" + content = Translations.Threads.AutoThreading.AddRoles.noThreaded.translate() } return@action } if (!autoThread.extraRoleIds.contains(arguments.role!!.id)) { respond { - content = "This role has not been added." + content = Translations.Threads.AutoThreading.RemoveRoles.notAdded.translate() } return@action } @@ -442,7 +443,8 @@ class AutoThreading : Extension() { AutoThreadingCollection().updateExtraRoles(event.interaction.channelId, updatedRoles) respond { - content = "Role (${arguments.role!!.mention}) removed" + content = + Translations.Threads.AutoThreading.RemoveRoles.removed.translate(arguments.role!!.mention) } } } @@ -522,67 +524,67 @@ class AutoThreading : Extension() { inner class AutoThreadingArgs : Arguments() { val role by optionalRole { - name = "role" - description = "The role, if any, to invite to threads created in this channel." + name = Translations.Threads.AutoThreading.Arguments.Role.name + description = Translations.Threads.AutoThreading.Arguments.Role.description } val addModsAndRole by defaultingBoolean { - name = "add-mods-and-role" - description = "Whether to add moderators to the thread alongside the role" + name = Translations.Threads.AutoThreading.Arguments.AddMods.name + description = Translations.Threads.AutoThreading.Arguments.AddMods.description defaultValue = false } val preventDuplicates by defaultingBoolean { - name = "prevent-duplicates" - description = "If users should be stopped from having multiple open threads in this channel. Default false." + name = Translations.Threads.AutoThreading.Arguments.PreventDuplicates.name + description = Translations.Threads.AutoThreading.Arguments.PreventDuplicates.description defaultValue = false } val archive by defaultingBoolean { - name = "archive" - description = "If threads should be archived on creation to avoid filling the sidebar. Default false." + name = Translations.Threads.AutoThreading.Arguments.Archive.name + description = Translations.Threads.AutoThreading.Arguments.Archive.description defaultValue = false } val contentAwareNaming by defaultingBoolean { - name = "content-aware-naming" - description = "If Lily should use content-aware thread titles. Default false" + name = Translations.Threads.AutoThreading.Arguments.ContentAware.name + description = Translations.Threads.AutoThreading.Arguments.ContentAware.description defaultValue = false } val mention by defaultingBoolean { - name = "mention" - description = "If the creator should be mentioned in new threads in this channel. Default false." + name = Translations.Threads.AutoThreading.Arguments.Mention.name + description = Translations.Threads.AutoThreading.Arguments.Mention.description defaultValue = false } val message by defaultingBoolean { - name = "message" - description = "Whether to send a custom message on thread creation or not. Default false." + name = Translations.Threads.AutoThreading.Arguments.Message.name + description = Translations.Threads.AutoThreading.Arguments.Message.description defaultValue = false } } inner class ExtraRolesArgs : Arguments() { val role by optionalRole { - name = "role" - description = "A role to invite to threads in this channel" + name = Translations.Threads.AutoThreading.Arguments.Role.name + description = Translations.Threads.AutoThreading.Arguments.ExtraRole.description } } inner class AutoThreadingViewArgs : Arguments() { val channel by channel { - name = "channel" - description = "The channel to view the auto-threading settings for." + name = Translations.Threads.AutoThreading.Arguments.Channel.name + description = Translations.Threads.AutoThreading.Arguments.Channel.description } } inner class MessageModal : ModalForm() { - override var title = "Thread Creation Message Configuration" + override var title = Translations.Threads.AutoThreading.Modal.title val msgInput = paragraphText { - label = "Thread Creation Message" - placeholder = "Input the content of the message to send when a thread is created in this channel" + label = Translations.Threads.AutoThreading.Modal.MsgInput.name + placeholder = Translations.Threads.AutoThreading.Modal.MsgInput.placeholder required = true } } @@ -622,9 +624,9 @@ class AutoThreading : Extension() { ) if (!options.contentAwareNaming || threadName.isNullOrEmpty()) { - threadName = "Thread for ${ - message?.author?.asUser()?.username ?: proxiedMessage?.member?.name - }".take(75) + threadName = Translations.Threads.AutoThreading.threadFor.translate( + message?.author?.asUser()?.username ?: proxiedMessage?.member?.name?.take(75) + ) } if (options.preventDuplicates) { @@ -649,7 +651,7 @@ class AutoThreading : Extension() { if (previousThreadExists) { val response = event.message.respond { // There is a not-null call because the compiler knows it's not null if the boolean is true. - content = "Please use your existing thread in this channel ${previousUserThread!!.mention}" + content = Translations.Threads.AutoThreading.existingThread.translate(previousUserThread!!.mention) } event.message.delete("User already has a thread") response.delete(10000L, false) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/threads/ThreadControl.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/threads/ThreadControl.kt index 64aff562..32b807d7 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/threads/ThreadControl.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/threads/ThreadControl.kt @@ -37,6 +37,7 @@ import dev.kordex.core.extensions.ephemeralSlashCommand import dev.kordex.core.extensions.event import dev.kordex.core.utils.hasPermission import kotlinx.datetime.Clock +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.ThreadsCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions @@ -49,12 +50,12 @@ class ThreadControl : Extension() { override suspend fun setup() { ephemeralSlashCommand { - name = "thread" - description = "The parent command for all /thread commands" + name = Translations.Threads.ThreadControl.name + description = Translations.Threads.ThreadControl.description ephemeralSubCommand(::ThreadRenameArgs) { - name = "rename" - description = "Rename a thread!" + name = Translations.Threads.ThreadControl.Rename.name + description = Translations.Threads.ThreadControl.Rename.description check { isInThread() @@ -70,18 +71,18 @@ class ThreadControl : Extension() { threadChannel.edit { name = arguments.newThreadName - reason = "Renamed by ${member.username}" + reason = Translations.Threads.ThreadControl.Rename.renamedBy.translate(member.username) } respond { - content = "Thread Renamed!" + content = Translations.Threads.ThreadControl.Rename.renamed.translate() } } } ephemeralSubCommand(::ThreadArchiveArgs) { - name = "archive" - description = "Archive this thread" + name = Translations.Threads.ThreadControl.Archive.name + description = Translations.Threads.ThreadControl.Archive.description check { isInThread() @@ -104,17 +105,19 @@ class ThreadControl : Extension() { guild!!.getChannelOfOrNull( ModerationConfigCollection().getConfig(guild!!.id)!!.channel!! )?.createEmbed { - title = "Thread archive prevention disabled" + title = + Translations.Threads.ThreadControl.Archive.PreventionDisabled.title.translate() description = - "Archive prevention has been disabled, as `/thread archive` was used." + Translations.Threads.ThreadControl.Archive.PreventionDisabled.description.translate() color = DISCORD_FUCHSIA field { - name = "User" - value = user.asUserOrNull()?.username ?: "Unable to get user tag" + name = Translations.Threads.ThreadControl.Archive.user.translate() + value = user.asUserOrNull()?.username + ?: Translations.Threads.ThreadControl.Archive.user.translate() } field { - name = "Thread" + name = Translations.Threads.ThreadControl.Archive.thread.translate() value = "${threadChannel.mention} ${threadChannel.name}" } } @@ -123,7 +126,7 @@ class ThreadControl : Extension() { } if (threadChannel.isArchived) { - edit { content = "**Error:** This channel is already archived!" } + edit { content = Translations.Threads.ThreadControl.Archive.already.translate() } return@action } @@ -131,13 +134,14 @@ class ThreadControl : Extension() { this.archived = true this.locked = arguments.lock && member.hasPermission(Permission.ModerateMembers) - reason = "Archived by ${user.asUserOrNull()?.username}" + reason = + Translations.Threads.ThreadControl.Archive.archivedBy.translate(user.asUserOrNull()?.username) } respond { - content = "Thread archived" + content = Translations.Threads.ThreadControl.Archive.response.translate() if (arguments.lock && member.hasPermission(Permission.ModerateMembers)) { - content += " and locked" + content += Translations.Threads.ThreadControl.Archive.responseLock.translate() } content += "!" } @@ -145,8 +149,8 @@ class ThreadControl : Extension() { } ephemeralSubCommand(::ThreadTransferArgs) { - name = "transfer" - description = "Transfer ownership of this thread" + name = Translations.Threads.ThreadControl.Transfer.name + description = Translations.Threads.ThreadControl.Transfer.description check { isInThread() @@ -164,42 +168,56 @@ class ThreadControl : Extension() { if (!ownsThreadOrModerator(threadChannel, member)) return@action if (arguments.newOwner.id == oldOwnerId) { - respond { content = "That person already owns the thread!" } + respond { content = Translations.Threads.ThreadControl.Transfer.alreadyOwns.translate() } return@action } if (arguments.newOwner.isBot) { - respond { content = "You cannot transfer ownership of a thread to a bot." } + respond { content = Translations.Threads.ThreadControl.Transfer.cannotBot.translate() } return@action } - ThreadsCollection().setThreadOwner(guild!!.id, threadChannel.id, arguments.newOwner.id, threadChannel.parentId) + ThreadsCollection().setThreadOwner( + guild!!.id, + threadChannel.id, + arguments.newOwner.id, + threadChannel.parentId + ) - respond { content = "Ownership transferred." } + respond { content = Translations.Threads.ThreadControl.Transfer.success.translate() } - var content = "Thread ownership transferred from ${oldOwner?.mention} " + - "to ${arguments.newOwner.mention}." + var content = Translations.Threads.ThreadControl.Transfer.fromTo.translateNamed( + "old" to oldOwner?.mention, + "new" to arguments.newOwner.mention + ) - if (member != oldOwner) content += " Transferred by ${member.mention}" + if (member != oldOwner) { + content += Translations.Threads.ThreadControl.Transfer.transferredBy.translate( + member.mention + ) + } threadChannel.createMessage(content) val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) ?: return@action utilityLog.createMessage { + val obj = Translations.Threads.ThreadControl.Transfer.Embed embed { - title = "Thread ownership transferred" + title = obj.title.translate() field { - name = "Previous owner" - value = "${oldOwner?.mention ?: "Unable to find previous owner"} ${oldOwner?.username ?: ""}" + name = obj.prevOwner.translate() + value = + "${oldOwner?.mention ?: obj.cannotFind.translate()} ${oldOwner?.username ?: ""}" } field { - name = "New owner" + name = obj.newOwner.translate() value = "${arguments.newOwner.mention} ${arguments.newOwner.username}" } if (member != oldOwner) { footer { - text = "Transferred by ${member.mention}" + text = + Translations.Threads.ThreadControl.Transfer.transferredBy.translate(member.mention) icon = member.avatar?.cdnUrl?.toUrl() } } @@ -211,8 +229,8 @@ class ThreadControl : Extension() { } ephemeralSubCommand { - name = "prevent-archiving" - description = "Stop a thread from being archived" + name = Translations.Threads.ThreadControl.PreventArchiving.name + description = Translations.Threads.ThreadControl.PreventArchiving.description check { isInThread() @@ -246,28 +264,39 @@ class ThreadControl : Extension() { } if (thread?.preventArchiving == true) { message = edit { - content = "Thread archiving is already being prevented, would you like to remove this?" + content = + Translations.Threads.ThreadControl.PreventArchiving.alreadyPreventedWarning.translate() }.edit { components { ephemeralButton { - label = "Yes" + label = Translations.Basic.yes style = ButtonStyle.Primary action button@{ - ThreadsCollection().setThreadOwner(thread.guildId, thread.threadId, thread.ownerId, threadChannel.parentId) - edit { content = "Thread archiving will no longer be prevented" } + ThreadsCollection().setThreadOwner( + thread.guildId, + thread.threadId, + thread.ownerId, + threadChannel.parentId + ) + edit { + content = + Translations.Threads.ThreadControl.PreventArchiving.noLongerPrevented.translate() + } val utilityLog = getLoggingChannelWithPerms( ConfigOptions.UTILITY_LOG, this.getGuild()!! ) ?: return@button utilityLog.createMessage { embed { - title = "Thread archive prevention disabled" + title = + Translations.Threads.ThreadControl.Archive.PreventionDisabled.title.translate() color = DISCORD_FUCHSIA field { - name = "User" - value = user.asUserOrNull()?.username ?: "Unable to get user tag" + name = Translations.Threads.ThreadControl.Archive.user.translate() + value = user.asUserOrNull()?.username + ?: Translations.Basic.UnableTo.tag.translate() } field { name = "Thread" @@ -279,11 +308,14 @@ class ThreadControl : Extension() { } } ephemeralButton { - label = "No" + label = Translations.Basic.no style = ButtonStyle.Secondary action { - edit { content = "Thread archiving will remain prevented" } + edit { + content = + Translations.Threads.ThreadControl.PreventArchiving.stillPrevented.translate() + } message!!.edit { components { removeAll() } } } } @@ -291,31 +323,38 @@ class ThreadControl : Extension() { } return@action } else if (thread?.preventArchiving == false) { - ThreadsCollection().setThreadOwner(thread.guildId, thread.threadId, thread.ownerId, threadChannel.parentId) + ThreadsCollection().setThreadOwner( + thread.guildId, + thread.threadId, + thread.ownerId, + threadChannel.parentId + ) try { val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) ?: return@action utilityLog.createMessage { embed { - title = "Thread archive prevention enabled" + title = Translations.Threads.ThreadControl.PreventArchiving.Embed.title.translate() color = DISCORD_FUCHSIA field { - name = "User" - value = user.asUserOrNull()?.username ?: "Unable to get user tag" + name = Translations.Threads.ThreadControl.Archive.user.translate() + value = + user.asUserOrNull()?.username ?: Translations.Basic.UnableTo.tag.translate() } field { - name = "Thread" + name = Translations.Threads.ThreadControl.Archive.thread.translate() value = threadChannel.mention } } } - edit { content = "Thread archiving will now be prevented" } - } catch (e: EntityNotFoundException) { edit { - content = "Thread archiving will now be prevented\nNote: Failed to send a log" + - "to your specified mod action log. Please check the channel exists and " + - "permissions are right" + content = Translations.Threads.ThreadControl.PreventArchiving.nowPrevented.translate() + } + } catch (_: EntityNotFoundException) { + edit { + content = + Translations.Threads.ThreadControl.PreventArchiving.nowPreventedNoLog.translate() } } } @@ -350,7 +389,7 @@ class ThreadControl : Extension() { val threadChannel = channel.asChannelOfOrNull() if (threadChannel == null) { respond { - content = "Are you sure this channel is a thread? If it is, I can't fetch it properly." + content = Translations.Threads.ThreadControl.fetchIssue.translate() } return null } @@ -361,16 +400,16 @@ class ThreadControl : Extension() { inner class ThreadRenameArgs : Arguments() { /** The new name for the thread. */ val newThreadName by string { - name = "new-name" - description = "The new name to give to the thread" + name = Translations.Threads.ThreadControl.Arguments.Rename.NewName.name + description = Translations.Threads.ThreadControl.Arguments.Rename.NewName.description } } inner class ThreadArchiveArgs : Arguments() { /** Whether to lock the thread or not. */ val lock by defaultingBoolean { - name = "lock" - description = "Whether to lock the thread if you are a moderator. Default is false" + name = Translations.Threads.ThreadControl.Arguments.Archive.Lock.name + description = Translations.Threads.ThreadControl.Arguments.Archive.Lock.description defaultValue = false } } @@ -378,8 +417,8 @@ class ThreadControl : Extension() { inner class ThreadTransferArgs : Arguments() { /** The new thread owner. */ val newOwner by member { - name = "new-owner" - description = "The user you want to transfer ownership of the thread to" + name = Translations.Threads.ThreadControl.Arguments.Transfer.NewOwner.name + description = Translations.Threads.ThreadControl.Arguments.Transfer.NewOwner.name } } @@ -402,7 +441,7 @@ class ThreadControl : Extension() { return true } - respond { content = "**Error:** This is not your thread!" } + respond { content = Translations.Threads.ThreadControl.notYours.translate() } return false } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/GalleryChannel.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/GalleryChannel.kt index b4c9c903..8ab0d920 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/GalleryChannel.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/GalleryChannel.kt @@ -23,6 +23,7 @@ import dev.kordex.core.utils.delete import dev.kordex.core.utils.permissionsForMember import dev.kordex.core.utils.respond import kotlinx.coroutines.delay +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.GalleryChannelCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.utils.botHasChannelPerms @@ -44,15 +45,15 @@ class GalleryChannel : Extension() { * @since 3.3.0 */ ephemeralSlashCommand { - name = "gallery-channel" - description = "The parent command for image channel setting" + name = Translations.Utility.GalleryChannel.name + description = Translations.Utility.GalleryChannel.description /** * The command that sets the gallery channel. */ ephemeralSubCommand { - name = "set" - description = "Set a channel as a gallery channel" + name = Translations.Utility.GalleryChannel.Set.name + description = Translations.Utility.GalleryChannel.Set.description requirePermission(Permission.ManageGuild) @@ -64,10 +65,11 @@ class GalleryChannel : Extension() { } action { + val translations = Translations.Utility.GalleryChannel.Set GalleryChannelCollection().getChannels(guildFor(event)!!.id).forEach { if (channel.asChannelOrNull()?.id == it.channelId) { respond { - content = "This channel is already a gallery channel!" + content = translations.already.translate() } return@action } @@ -76,16 +78,16 @@ class GalleryChannel : Extension() { GalleryChannelCollection().setChannel(guild!!.id, channel.asChannelOrNull()!!.id) respond { - content = "Set channel as gallery channel." + content = translations.response.translate() } val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) ?: return@action utilityLog.createEmbed { - title = "New Gallery channel" - description = "${channel.mention} was added as a Gallery channel" + title = translations.embedTitle.translate() + description = translations.embedDesc.translate(channel.mention) footer { - text = "Requested by ${user.asUserOrNull()?.username}" + text = translations.embedRequested.translate(user.asUserOrNull()?.username) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } color = DISCORD_GREEN @@ -97,8 +99,8 @@ class GalleryChannel : Extension() { * The command that unsets the gallery channel. */ ephemeralSubCommand { - name = "unset" - description = "Unset a channel as a gallery channel." + name = Translations.Utility.GalleryChannel.Unset.name + description = Translations.Utility.GalleryChannel.Unset.description requirePermission(Permission.ManageGuild) @@ -110,6 +112,7 @@ class GalleryChannel : Extension() { } action { + val translations = Translations.Utility.GalleryChannel.Unset var channelFound = false GalleryChannelCollection().getChannels(guildFor(event)!!.id).forEach { @@ -121,23 +124,23 @@ class GalleryChannel : Extension() { if (channelFound) { respond { - content = "Unset channel as gallery channel." + content = translations.response.translate() } val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) ?: return@action utilityLog.createEmbed { - title = "Removed Gallery channel" - description = "${channel.mention} was removed as a Gallery channel" + title = translations.embedTitle.translate() + description = translations.embedDesc.translate(channel.mention) footer { - text = "Requested by ${user.asUserOrNull()?.username}" + text = translations.embedRequested.translate(user.asUserOrNull()?.username) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } color = DISCORD_RED } } else { respond { - content = "This channel is not a gallery channel!" + content = translations.notGallery.translate() } } } @@ -147,8 +150,8 @@ class GalleryChannel : Extension() { * The command that returns a list of all image channels for a particular guild. */ ephemeralSubCommand { - name = "list" - description = "List all gallery channels in the guild" + name = Translations.Utility.GalleryChannel.List.name + description = Translations.Utility.GalleryChannel.List.description check { anyGuild() @@ -159,6 +162,7 @@ class GalleryChannel : Extension() { } action { + val translations = Translations.Utility.GalleryChannel.List var channels = "" GalleryChannelCollection().getChannels(guildFor(event)!!.id).forEach { @@ -167,11 +171,11 @@ class GalleryChannel : Extension() { respond { embed { - title = "Gallery channels" - description = "Here are the gallery channels in this guild." + title = translations.embedTitle.translate() + description = translations.embedDesc.translate() field { - name = "Channels:" - value = if (channels != "") channels.replace(" ", "\n") else "No channels found!" + name = translations.embedChannelsField.translate() + value = if (channels != "") channels.replace(" ", "\n") else translations.noneFound.translate() } } } @@ -197,9 +201,7 @@ class GalleryChannel : Extension() { .contains(Permission.ManageMessages) ) { event.message.channel.createMessage { - "Hi! This is a gallery channel, but I don't have Manage Messages for this " + - "channel, therefore I cannot delete messages that don't contain images! Could " + - "someone ask staff fix it please? Thanks!" + content = Translations.Utility.GalleryChannel.noPerms.translate() } return@forEach } @@ -213,7 +215,7 @@ class GalleryChannel : Extension() { } val response = event.message.respond { - content = "This channel is for images only!" + content = Translations.Utility.GalleryChannel.images.translate() } event.message.delete() diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Github.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Github.kt index 9171d11d..f9881e8c 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Github.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Github.kt @@ -13,8 +13,8 @@ import dev.kordex.core.commands.converters.impl.optionalString import dev.kordex.core.commands.converters.impl.string import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.publicSlashCommand -import dev.kordex.core.sentry.BreadcrumbType import kotlinx.datetime.Clock +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.GithubCollection import org.hyacinthbots.lilybot.github import org.kohsuke.github.GHDirection @@ -50,20 +50,19 @@ class Github : Extension() { * @since 2.0 */ publicSlashCommand { - name = "github" - description = "The parent command for all /github commands" + name = Translations.Utility.Github.name + description = Translations.Utility.Github.description publicSubCommand(::IssueArgs) { - name = "issue" - description = "Look up an issue on a specific repository" + name = Translations.Utility.Github.Issue.name + description = Translations.Utility.Github.Issue.description action { + val translations = Translations.Utility.Github.Issue val repository = arguments.repository ?: GithubCollection().getDefaultRepo(guild!!.id) if (repository == null) { respond { - content = - "There is no default repository set. Please specify a repository to search or set" + - "a default." + content = Translations.Utility.Github.noDefault.translate() } return@action } @@ -71,8 +70,8 @@ class Github : Extension() { if (!repository.contains("/")) { respond { embed { - title = "Make sure your repository input is formatted like this:" - description = "Format: `User/Repo` or `Org/Repo` \nFor example: `HyacinthBots/LilyBot`" + title = Translations.Utility.Github.badFormatEmbedTitle.translate() + description = Translations.Utility.Github.badFormatEmbedDesc.translate() } } return@action @@ -84,7 +83,7 @@ class Github : Extension() { issue = if (repository.contains("http", true)) { github.getRepository( "${repository.split("/")[3]}/" + - repository.split("/")[4] + repository.split("/")[4] )?.getIssue(arguments.issue) } else { try { @@ -92,7 +91,7 @@ class Github : Extension() { } catch (_: GHFileNotFoundException) { respond { embed { - title = "Unable to find issue number! Make sure this issue exists" + title = translations.unableToFind.translate() } } return@action @@ -111,7 +110,7 @@ class Github : Extension() { } catch (_: GHException) { respond { embed { - title = "Unable to access repository, make sure this repository exists!" + title = Translations.Utility.Github.unableToRepo.translate() } } return@action @@ -122,7 +121,7 @@ class Github : Extension() { } else { respond { embed { - title = "Invalid issue number. Make sure this issue exists!" + title = translations.unableToFind.translate() } } return@action @@ -134,7 +133,7 @@ class Github : Extension() { } catch (_: GHFileNotFoundException) { respond { embed { - title = "Unable to find issue number! Make sure this issue exists" + title = translations.unableToFind.translate() } } return@action @@ -150,8 +149,9 @@ class Github : Extension() { if (issue!!.isPullRequest) { title = issue.title url = issue.htmlUrl.toString() - description = - "**Information for Pull request #${issue.number} in ${issue.repository.fullName}**" + description = translations.embedDescPr.translateNamed( + "number" to issue.number, "repo" to issue.repository.fullName + ) try { val pull: GHPullRequest = issue.repository.getPullRequest(issue.number) @@ -159,16 +159,17 @@ class Github : Extension() { draft = pull.isDraft } catch (ioException: IOException) { ioException.printStackTrace() - title = "Error!" - description = "Error occurred initializing Pull Request. How did this happen?" + title = translations.errorTitle.translate() + description = translations.errorDesc.translate() color = DISCORD_RED return@respond } } else { title = issue.title url = issue.htmlUrl.toString() - description = - "**Information for issue #${issue.number} in ${issue.repository.fullName}**" + description = translations.embedDescIs.translateNamed( + "number" to issue.number, "repo" to issue.repository.fullName + ) } field { @@ -179,7 +180,7 @@ class Github : Extension() { } else if (issue.body.isNotEmpty() && issue.body.length <= 399) { issue.body } else { - "No description Provided" + translations.noDesc.translate() } } } @@ -188,29 +189,29 @@ class Github : Extension() { if (merged) { color = dev.kord.common.Color(111, 66, 193) field { - name = "Status:" - value = "Merged" + name = translations.statusField.translate() + value = translations.merged.translate() inline = false } } else if (!open) { color = dev.kord.common.Color(203, 36, 49) field { - name = "Status:" - value = "Closed" + name = translations.statusField.translate() + value = translations.closed.translate() inline = false } } else if (draft) { color = dev.kord.common.Color(255, 255, 255) field { - name = "Status:" - value = "Draft" + name = translations.statusField.translate() + value = translations.draft.translate() inline = false } } else { color = dev.kord.common.Color(44, 185, 78) field { - name = "Status:" - value = "Open" + name = translations.statusField.translate() + value = translations.open.translate() inline = false } } @@ -219,31 +220,31 @@ class Github : Extension() { val author: GHUser = issue.user if (author.name != null) { field { - name = "Author:" + name = translations.authorField.translate() value = "[" + author.login + " (" + author.name + ")](" + - "https://github.com/" + author.login + ")" + "https://github.com/" + author.login + ")" inline = false } } else { field { - name = "Author:" + name = translations.authorField.translate() value = "[" + author.login + "](https://github.com/" + author.login + ")" inline = false } } } catch (_: IOException) { field { - name = "Author:" - value = "Unknown Author" + name = translations.authorField.translate() + value = translations.unknownAuthor.translate() inline = false } } try { field { - name = "Opened on:" - value = "${issue.createdAt}" + name = translations.openedField.translate() + value = issue.createdAt.toString() inline = false } @@ -255,7 +256,7 @@ class Github : Extension() { if (labels.isNotEmpty()) { field { - name = "Labels:" + name = translations.labelsField.translate() value = labels.joinToString(", ") inline = false } @@ -269,16 +270,15 @@ class Github : Extension() { } publicSubCommand(::RepoArgs) { - name = "repo" - description = "Search GitHub for a specific repository" + name = Translations.Utility.Github.Repo.name + description = Translations.Utility.Github.Repo.description action { + val translations = Translations.Utility.Github.Repo val repository = arguments.repository ?: GithubCollection().getDefaultRepo(guild!!.id) if (repository == null) { respond { - content = - "There is no default repository set. Please specify a repository to search or set" + - "a default." + content = Translations.Utility.Github.unableToRepo.translate() } return@action } @@ -286,8 +286,8 @@ class Github : Extension() { if (!repository.contains("/")) { respond { embed { - title = "Make sure your input is formatted like this:" - description = "Format: `User/Repo` or `Org/Repo`\nFor example: `HyacinthBots/LilyBot`" + title = Translations.Utility.Github.badFormatEmbedTitle.translate() + description = Translations.Utility.Github.badFormatEmbedDesc.translate() } } return@action @@ -299,19 +299,15 @@ class Github : Extension() { repo = if (repository.contains("http", true)) { github.getRepository( "${repository.split("/")[3]}/" + - repository.split("/")[4] + repository.split("/")[4] ) } else { github.getRepository(repository) } } catch (_: IOException) { - sentry.breadcrumb(BreadcrumbType.Error) { - category = "extensions.util.Github.repository.getRepository" - message = "Repository not found" - } respond { embed { - title = "Invalid repository name. Make sure this repository exists" + title = Translations.Utility.Github.unableToRepo.translate() } } repo = null @@ -321,41 +317,41 @@ class Github : Extension() { respond { embed { try { - title = "GitHub Repository Info for " + repo?.fullName + title = translations.embedTitle.translate(repo?.fullName) url = repo?.htmlUrl.toString() description = repo?.description if (repo!!.license != null) { field { - name = "License:" + name = translations.licenceField.translate() value = repo.license.name inline = false } } field { - name = "Open Issues and PRs:" + name = translations.openIssues.translate() value = repo.openIssueCount.toString() inline = false } field { - name = "Forks:" + name = translations.forks.translate() value = repo.forksCount.toString() inline = false } field { - name = "Stars:" + name = translations.stars.translate() value = repo.stargazersCount.toString() inline = false } field { - name = "Size:" + name = translations.size.translate() value = bytesToFriendly(repo.size) inline = false } if (repo.language != null) { field { - name = "Language:" + name = translations.language.translate() value = repo.language.toString() inline = false } @@ -369,10 +365,11 @@ class Github : Extension() { } publicSubCommand(::UserArgs) { - name = "user" - description = "Search GitHub for a User/Organisation" + name = Translations.Utility.Github.User.name + description = Translations.Utility.Github.User.description action { + val translations = Translations.Utility.Github.User val ghUser: GHUser? try { @@ -384,7 +381,7 @@ class Github : Extension() { } catch (_: IOException) { respond { embed { - title = "Invalid Username. Make sure this user exists!" + title = translations.invalidName.translate() } } return@action @@ -394,32 +391,33 @@ class Github : Extension() { val isOrg: Boolean = ghUser?.type.equals("Organization") if (!isOrg) { -// sentry.breadcrumb(BreadcrumbType.Info) { -// category = "extensions.util.Github.user.isOrg" -// message = "User is not Organisation" -// data["isNotOrg"] = ghUser?.login -// } respond { embed { - title = "GitHub profile for " + ghUser?.login + title = translations.embedTitle.translate(ghUser?.login) url = "https://github.com/" + ghUser?.login description = ghUser?.bio field { - name = "Followers:" + name = translations.repositories.translate() + value = ghUser?.publicRepoCount.toString() + inline = false + } + + field { + name = translations.followers.translate() value = ghUser?.followersCount.toString() inline = false } field { - name = "Following:" + name = translations.following.translate() value = ghUser?.followingCount.toString() inline = false } if (ghUser!!.company != null) { field { - name = "Company" + name = translations.company.translate() value = ghUser.company inline = false } @@ -427,7 +425,7 @@ class Github : Extension() { if (!ghUser.blog.equals("")) { field { - name = "Website:" + name = translations.website.translate() value = ghUser.blog inline = false } @@ -435,10 +433,10 @@ class Github : Extension() { if (ghUser.twitterUsername != null) { field { - name = "Twitter:" + name = translations.twitter.translate() value = "[@" + ghUser.twitterUsername + "](" + - "https://twitter.com/" + ghUser.twitterUsername + ")" + "https://twitter.com/" + ghUser.twitterUsername + ")" inline = false } } @@ -450,32 +448,27 @@ class Github : Extension() { } } } else { -// sentry.breadcrumb(BreadcrumbType.Info) { -// category = "extensions.util.Github.user.isOrg" -// message = "User is Organisation" -// data["isOrg"] = ghUser?.login -// } val org: GHOrganization? = github.getOrganization(ghUser?.login) respond { embed { - title = "GitHub profile for " + ghUser?.login - url = "https://github.com/" + ghUser?.login + title = translations.embedTitle.translate(org?.login) + url = "https://github.com/" + org?.login field { - name = "Public Members:" + name = translations.publicMembers.translate() value = org!!.listMembers().toArray().size.toString() inline = false } field { - name = "Repositories:" - value = ghUser?.publicRepoCount.toString() + name = translations.repositories.translate() + value = org?.publicRepoCount.toString() inline = false } footer { - text = ghUser?.login.toString() - icon = ghUser?.avatarUrl + text = org?.login.toString() + icon = org?.avatarUrl } timestamp = Clock.System.now() @@ -489,8 +482,8 @@ class Github : Extension() { } ephemeralSubCommand(::DefaultArgs) { - name = "default-repo" - description = "Set the default repo to look up issues in." + name = Translations.Utility.Github.DefaultRepo.name + description = Translations.Utility.Github.DefaultRepo.description requirePermission(Permission.ModerateMembers) @@ -504,8 +497,8 @@ class Github : Extension() { if (!arguments.defaultRepo.contains("/")) { respond { embed { - title = "Make sure your input is formatted like this:" - description = "Format: `User/Repo` or `Org/Repo`\nFor example: `HyacinthBots/LilyBot`" + title = Translations.Utility.Github.badFormatEmbedTitle.translate() + description = Translations.Utility.Github.badFormatEmbedDesc.translate() } } return@action @@ -514,7 +507,7 @@ class Github : Extension() { if (arguments.defaultRepo.contains("http", true)) { val urlParts = arguments.defaultRepo.split("/") if (urlParts.size <= 4) { - respond { content = "Invalid repo URL, please try again" } + respond { content = Translations.Utility.Github.DefaultRepo.invalidUrl.translate() } return@action } github.getRepository("${urlParts[3]}/${urlParts[4]}") @@ -523,19 +516,19 @@ class Github : Extension() { } } catch (_: IOException) { respond { - content = "GitHub repository not found! Please make sure this repository exists" + content = Translations.Utility.Github.unableToRepo.translate() } return@action } GithubCollection().setDefaultRepo(guild!!.id, arguments.defaultRepo) - respond { content = "Default repo set." } + respond { content = Translations.Utility.Github.DefaultRepo.response.translate() } } } ephemeralSubCommand { - name = "remove-default-repo" - description = "Removes the default repo for this guild" + name = Translations.Utility.Github.RemoveDefaultRepo.name + description = Translations.Utility.Github.RemoveDefaultRepo.description requirePermission(Permission.ModerateMembers) @@ -547,14 +540,12 @@ class Github : Extension() { action { if (GithubCollection().getDefaultRepo(guild!!.id) == null) { - respond { content = "There is no default repo for this guild!" } + respond { content = Translations.Utility.Github.RemoveDefaultRepo.noDefault.translate() } return@action } GithubCollection().removeDefaultRepo(guild!!.id) - respond { - content = "Default repo removed" - } + respond { content = Translations.Utility.Github.RemoveDefaultRepo.response.translate() } } } } @@ -587,38 +578,38 @@ class Github : Extension() { inner class IssueArgs : Arguments() { /** The issue number being searched for. */ val issue by int { - name = "issue-number" - description = "The issue number you would like to search for" + name = Translations.Utility.Github.Issue.Arguments.Issue.name + description = Translations.Utility.Github.Issue.Arguments.Issue.description } /** The repository being searched for, must contain a `/`. */ val repository by optionalString { - name = "repository" - description = "The GitHub repository you would like to search if no default is set" + name = Translations.Utility.Github.Issue.Arguments.Repo.name + description = Translations.Utility.Github.Issue.Arguments.Repo.description } } inner class RepoArgs : Arguments() { /** The repository being searched for, must contain a `/`. */ val repository by optionalString { - name = "repository" - description = "The GitHub repository you would like to search if no default is set" + name = Translations.Utility.Github.Issue.Arguments.Repo.name + description = Translations.Utility.Github.Issue.Arguments.Repo.description } } inner class UserArgs : Arguments() { /** The name of the User/Organisation being searched for. */ val username by string { - name = "username" - description = "The name of the User/Organisation you wish to search for" + name = Translations.Utility.Github.User.Arguments.Username.name + description = Translations.Utility.Github.User.Arguments.Username.description } } inner class DefaultArgs : Arguments() { /** The default repo for the GitHub commands. */ val defaultRepo by string { - name = "default-repo" - description = "The default repo to look up issues in" + name = Translations.Utility.Github.DefaultRepo.name + description = Translations.Utility.Github.DefaultRepo.description } } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/GuildAnnouncements.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/GuildAnnouncements.kt index 08e5e67b..e20509cc 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/GuildAnnouncements.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/GuildAnnouncements.kt @@ -16,6 +16,7 @@ import dev.kordex.core.components.forms.ModalForm import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.ephemeralSlashCommand import kotlinx.coroutines.flow.toList +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.utils.TEST_GUILD_ID import org.hyacinthbots.lilybot.utils.getFirstUsableChannel @@ -27,8 +28,8 @@ class GuildAnnouncements : Extension() { override suspend fun setup() { ephemeralSlashCommand(::GuildAnnouncementArgs, ::GuildAnnouncementModal) { - name = "announcement" - description = "Send an announcement to all guilds Lily is in" + name = Translations.Utility.GuildAnnouncements.name + description = Translations.Utility.GuildAnnouncements.description guild(TEST_GUILD_ID) requirePermission(Permission.Administrator) @@ -38,29 +39,30 @@ class GuildAnnouncements : Extension() { } action { modal -> + val translations = Translations.Utility.GuildAnnouncements var response: EphemeralFollowupMessage? = null response = respond { - content = "Would you like to send this message? " + + content = translations.sendConfirm.translate() + if (arguments.targetGuild == null) { - "It will be delivered to all servers this bot is in." + translations.deliverAll } else { - "It will be delivered to your specified target guild: ${arguments.targetGuild}" - } + translations.deliverSpecific + }.translate(arguments.targetGuild) components { ephemeralButton { - label = "Yes" + label = Translations.Basic.yes style = ButtonStyle.Success action ButtonAction@{ response?.edit { - content = "Message sent!" + content = translations.sent.translate() components { removeAll() } } if (arguments.targetGuild != null) { val guild = event.kord.getGuildOrNull(arguments.targetGuild!!) if (guild == null) { - respond { content = "Target guild not found" } + respond { content = translations.targetNotFound.translate() } return@ButtonAction } @@ -70,7 +72,7 @@ class GuildAnnouncements : Extension() { ?: getFirstUsableChannel(guild) if (channel == null) { - respond { content = "Couldn't find an available channel to send a message in" } + respond { content = translations.noAvailableChannel.translate() } return@ButtonAction } @@ -79,7 +81,7 @@ class GuildAnnouncements : Extension() { description = modal?.body?.value color = Color(0x7B52AE) footer { - text = "This message was delivered to this server alone" + text = translations.deliveredToOne.translate() } } } else { @@ -98,7 +100,7 @@ class GuildAnnouncements : Extension() { description = modal?.body?.value color = Color(0x7B52AE) footer { - text = "Sent by ${user.asUserOrNull()?.username}" + text = translations.sentBy.translate(user.asUserOrNull()?.username) } } } catch (_: KtorRequestException) { @@ -111,12 +113,12 @@ class GuildAnnouncements : Extension() { } ephemeralButton { - label = "No" + label = Translations.Basic.no style = ButtonStyle.Danger action { response?.edit { - content = "Message not sent." + content = translations.notSent.translate() components { removeAll() } } } @@ -129,24 +131,24 @@ class GuildAnnouncements : Extension() { inner class GuildAnnouncementArgs : Arguments() { val targetGuild by optionalSnowflake { - name = "target-guild" - description = "The guild to send the announcement too" + name = Translations.Utility.GuildAnnouncements.Arguments.Target.name + description = Translations.Utility.GuildAnnouncements.Arguments.Target.description } } inner class GuildAnnouncementModal : ModalForm() { - override var title = "Send an announcement" + override var title = Translations.Utility.GuildAnnouncements.Modal.title val header = lineText { - label = "Announcement Header" - placeholder = "Version 7.6.5!" + label = Translations.Utility.GuildAnnouncements.Modal.Header.label + placeholder = Translations.Utility.GuildAnnouncements.Modal.Header.placeholder maxLength = 250 required = false } val body = paragraphText { - label = "Announcement Body" - placeholder = "We're happy to announce Lily is now written in Rust! It turns out we really like crabs" + label = Translations.Utility.GuildAnnouncements.Modal.Body.label + placeholder = Translations.Utility.GuildAnnouncements.Modal.Body.placeholder maxLength = 1750 required = true } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/InfoCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/InfoCommands.kt index 35a61b85..5c359ebf 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/InfoCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/InfoCommands.kt @@ -7,6 +7,7 @@ import dev.kordex.core.components.components import dev.kordex.core.components.linkButton import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.publicSlashCommand +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.utils.HYACINTH_GITHUB /** @@ -26,51 +27,38 @@ class InfoCommands : Extension() { * @since 3.3.0 */ publicSlashCommand { - name = "help" - description = "Get help with using Lily!" + name = Translations.Utility.InfoCommands.Help.name + description = Translations.Utility.InfoCommands.Help.description action { + val translations = Translations.Utility.InfoCommands.Help respond { embed { thumbnail { url = event.kord.getSelf().avatar?.cdnUrl!!.toUrl() } - title = "What is LilyBot?" - description = "Lily is a FOSS multi-purpose bot for Discord created by " + - "the HyacinthBots organization. " + - "Use `/about` to learn more, or `/invite` to get an invite link." + title = translations.embedTitle.translate() + description = translations.embedDesc.translate() field { - name = "How do I configure Lily?" - value = "Run the `/config set` command and provided the requested values. " + - "You may need to run the command multiple times to set a config for each " + - "section of the bot you wish to use. For more information, use the " + - "`/command-list` command and navigate to the relevant page." + name = translations.configFieldName.translate() + value = translations.configFieldValue.translate() } field { - name = "What commands are there?" - value = "Lots! Too many to list here. You can read about the commands " + - "using the `/command-list` command, or visiting the [commands list on GitHub]" + - "($HYACINTH_GITHUB/LilyBot/blob/main/docs/commands.md)." + name = translations.whatFieldName.translate() + value = + translations.whatFieldValue.translate() + "($HYACINTH_GITHUB/LilyBot/blob/main/docs/commands.md)." } field { - name = "How do I get more help or learn more?" - value = "To get additional support, discuss Lily, suggest features, " + - "or even lend a hand with development join our Discord at " + - "https://discord.gg/hy2329fcTZ" + name = translations.supportFieldName.translate() + value = translations.supportFieldValue.translate() } field { - name = "Useful links" - value = - "Website: Coming Soon™️\n" + - "GitHub: ${HYACINTH_GITHUB}\n" + - "Buy Me a Coffee: https://buymeacoffee.com/HyacinthBots\n" + - "Twitter: https://twitter.com/HyacinthBots\n" + - "Email: `hyacinthbots@outlook.com`\n" + - "Discord: https://discord.gg/hy2329fcTZ" + name = translations.usefulFieldName.translate() + value = translations.usefulFieldValue.translate(HYACINTH_GITHUB) } color = DISCORD_BLURPLE } @@ -87,14 +75,12 @@ class InfoCommands : Extension() { * @since 4.4.0 */ publicSlashCommand { - name = "invite" - description = "Get an invitation link for Lily!" + name = Translations.Utility.InfoCommands.Invite.name + description = Translations.Utility.InfoCommands.Invite.description action { respond { - content = "Use this link to add Lily to your server:" + - "https://discord.com/api/oauth2/authorize?client_id=876278900836139008" + - "&permissions=1151990787078&scope=bot%20applications.commands" + content = Translations.Utility.InfoCommands.Invite.value.translate() } } } @@ -108,19 +94,20 @@ class InfoCommands : Extension() { * @since 4.4.0 */ suspend fun MessageCreateBuilder.buttons() { + val translations = Translations.Utility.InfoCommands.Help.Button components { linkButton { - label = "Invite Link" + label = translations.invite url = "https://discord.com/api/oauth2/authorize?client_id=876278900836139008" + - "&permissions=1151990787078&scope=bot%20applications.commands" + "&permissions=1151990787078&scope=bot%20applications.commands" } linkButton { - label = "Privacy Policy" + label = translations.privacy url = "$HYACINTH_GITHUB/LilyBot/blob/main/docs/privacy-policy.md" } linkButton { - label = "Terms of Service" + label = translations.tos url = "$HYACINTH_GITHUB/.github/blob/main/terms-of-service.md" } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/NewsChannelPublishing.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/NewsChannelPublishing.kt index 7f79935d..86077727 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/NewsChannelPublishing.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/NewsChannelPublishing.kt @@ -24,6 +24,7 @@ import dev.kordex.core.pagination.EphemeralResponsePaginator import dev.kordex.core.pagination.pages.Page import dev.kordex.core.pagination.pages.Pages import kotlinx.datetime.Clock +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.NewsChannelPublishingCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms @@ -54,11 +55,10 @@ class NewsChannelPublishing : Extension() { val channel = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, event.getGuildOrNull()!!) ?: return@action channel.createEmbed { - title = "Unable to Auto-publish news channel!" - description = - "Please ensure I have the `Send Messages`, `Manage Channel` or `Manage Messages` permission" + title = Translations.Utility.NewsChannel.NewsPublishing.errorTitle.translate() + description = Translations.Utility.NewsChannel.NewsPublishing.missingPerms.translate() field { - name = "Channel:" + name = Translations.Utility.NewsChannel.NewsPublishing.embedChannelField.translate() value = event.message.channel.mention } color = DISCORD_RED @@ -72,12 +72,12 @@ class NewsChannelPublishing : Extension() { } ephemeralSlashCommand { - name = "news-publishing" - description = "The parent command for news publishing channels" + name = Translations.Utility.NewsChannel.NewsPublishing.name + description = Translations.Utility.NewsChannel.NewsPublishing.description ephemeralSubCommand(::PublishingSetArgs) { - name = "set" - description = "Set this channel to automatically publish messages." + name = Translations.Utility.NewsChannel.NewsPublishing.Set.name + description = Translations.Utility.NewsChannel.NewsPublishing.Set.description requirePermission(Permission.ManageGuild) @@ -87,12 +87,13 @@ class NewsChannelPublishing : Extension() { } action { + val translations = Translations.Utility.NewsChannel.NewsPublishing.Set if (channel.asChannelOfOrNull()?.getEffectivePermissions(event.kord.selfId) ?.contains(Permissions(Permission.SendMessages, Permission.ManageChannels)) == false ) { respond { - content = "I don't have permission for this channel; Please ensure I have the " + - "`Send Messages` or `Manage Channel` permission" + content = + translations.noPerms.translate() + Translations.Utility.NewsChannel.NewsPublishing.missingPerms.translate() } return@action } @@ -100,17 +101,17 @@ class NewsChannelPublishing : Extension() { NewsChannelPublishingCollection().addAutoPublishingChannel(guild!!.id, arguments.channel.id) respond { - content = "${arguments.channel.mention} has been set to automatically publish messages!" + content = translations.success.translate(arguments.channel.mention) } getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, getGuild()!!)?.createEmbed { - title = "News Channel set to Auto-Publish" + title = translations.embedTitle.translate() field { - name = "Channel:" + name = Translations.Utility.NewsChannel.NewsPublishing.embedChannelField.translate() value = arguments.channel.mention } footer { - text = "Set by ${user.asUserOrNull()?.username}" + text = translations.setBy.translate(user.asUserOrNull()?.username) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } timestamp = Clock.System.now() @@ -120,8 +121,8 @@ class NewsChannelPublishing : Extension() { } ephemeralSubCommand(::PublishingRemoveArgs) { - name = "remove" - description = "Stop a news channel from auto-publishing messages" + name = Translations.Utility.NewsChannel.NewsPublishing.Remove.name + description = Translations.Utility.NewsChannel.NewsPublishing.Remove.description requirePermission(Permission.ManageGuild) @@ -131,13 +132,14 @@ class NewsChannelPublishing : Extension() { } action { + val translations = Translations.Utility.NewsChannel.NewsPublishing.Remove if (NewsChannelPublishingCollection().getAutoPublishingChannel( guild!!.id, arguments.channel.id ) == null ) { respond { - content = "**Error:** ${arguments.channel.mention} does not automatically publish messages!" + content = translations.noAuto.translate(arguments.channel.mention) } return@action } @@ -145,17 +147,18 @@ class NewsChannelPublishing : Extension() { NewsChannelPublishingCollection().removeAutoPublishingChannel(guild!!.id, arguments.channel.id) respond { - content = "${arguments.channel.mention} will no longer automatically publish messages!" + content = translations.success.translate(arguments.channel.mention) } getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, getGuild()!!)?.createEmbed { - title = "News Channel will no longer Auto-Publish" + title = translations.embedTitle.translate() field { - name = "Channel:" + name = Translations.Utility.NewsChannel.NewsPublishing.embedChannelField.translate() value = arguments.channel.mention } footer { - text = "Removed by ${user.asUserOrNull()?.username}" + text = translations.removedBy.translate(user.asUserOrNull()?.username) + "Removed by ${user.asUserOrNull()?.username}" icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } timestamp = Clock.System.now() @@ -165,8 +168,8 @@ class NewsChannelPublishing : Extension() { } ephemeralSubCommand { - name = "list" - description = "List Auto-publishing channels" + name = Translations.Utility.NewsChannel.NewsPublishing.List.name + description = Translations.Utility.NewsChannel.NewsPublishing.List.description requirePermission(Permission.ManageGuild) @@ -179,10 +182,12 @@ class NewsChannelPublishing : Extension() { val pagesObj = Pages() val channelsData = NewsChannelPublishingCollection().getAutoPublishingChannels(guild!!.id) + val translations = Translations.Utility.NewsChannel.NewsPublishing.List + if (channelsData.isEmpty()) { pagesObj.addPage( Page { - description = "There are no news channels set for this guild." + description = translations.none.translate() } ) } else { @@ -190,12 +195,13 @@ class NewsChannelPublishing : Extension() { var response = "" channelDataChunk.forEach { data -> val channel = guild!!.getChannelOrNull(data.channelId) - response += "${channel?.mention ?: "Unable to get channel!"} (${channel?.name ?: ""}" + response += + "${channel?.mention ?: Translations.Basic.UnableTo.channel.translate()} (${channel?.name ?: ""}" } pagesObj.addPage( Page { - title = "Auto-publishing channels for this guild" - description = "These are all news channels that automatically publish messages" + title = translations.title.translate() + description = translations.desc.translate() field { value = response } @@ -215,8 +221,8 @@ class NewsChannelPublishing : Extension() { } ephemeralSubCommand { - name = "remove-all" - description = "Remove all auto-publishing channels for this guild" + name = Translations.Utility.NewsChannel.NewsPublishing.RemoveAll.name + description = Translations.Utility.NewsChannel.NewsPublishing.RemoveAll.description requirePermission(Permission.ManageGuild) @@ -228,12 +234,12 @@ class NewsChannelPublishing : Extension() { action { if (NewsChannelPublishingCollection().getAutoPublishingChannels(guild!!.id).isEmpty()) { respond { - content = "**Error**: There are no auto-publishing channels for this guild!" + content = Translations.Utility.NewsChannel.NewsPublishing.RemoveAll.noChannels.translate() } } else { NewsChannelPublishingCollection().clearAutoPublishingForGuild(guild!!.id) respond { - content = "Cleared all auto-publishing channels from this guild!" + content = Translations.Utility.NewsChannel.NewsPublishing.RemoveAll.success.translate() } } } @@ -243,16 +249,16 @@ class NewsChannelPublishing : Extension() { inner class PublishingSetArgs : Arguments() { val channel by channel { - name = "channel" - description = "The channel to set auto-publishing for" + name = Translations.Utility.NewsChannel.NewsPublishing.Arguments.Channel.name + description = Translations.Utility.NewsChannel.NewsPublishing.Arguments.Channel.description requiredChannelTypes = mutableSetOf(ChannelType.GuildNews) } } inner class PublishingRemoveArgs : Arguments() { val channel by channel { - name = "channel" - description = "The channel to stop auto-publishing for" + name = Translations.Utility.NewsChannel.NewsPublishing.Arguments.Channel.name + description = Translations.Utility.NewsChannel.NewsPublishing.Arguments.Channel.description requiredChannelTypes = mutableSetOf(ChannelType.GuildNews) } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/PublicUtilities.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/PublicUtilities.kt index 9b51cc7e..c6c73057 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/PublicUtilities.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/PublicUtilities.kt @@ -27,6 +27,7 @@ import dev.kordex.core.utils.dm import dev.kordex.core.utils.getTopRole import dev.kordex.core.utils.hasPermission import kotlinx.datetime.Clock +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.utils.requiredConfigs @@ -47,8 +48,8 @@ class PublicUtilities : Extension() { * @since 2.0 */ publicSlashCommand { - name = "ping" - description = "Am I alive?" + name = Translations.Utility.PublicUtilities.Ping.name + description = Translations.Utility.PublicUtilities.Ping.description action { val averagePing = this@PublicUtilities.kord.gateway.averagePing @@ -56,12 +57,12 @@ class PublicUtilities : Extension() { respond { embed { color = DISCORD_YELLOW - title = "Pong!" + title = Translations.Utility.PublicUtilities.Ping.title.translate() timestamp = Clock.System.now() field { - name = "Lily's Ping to Discord is:" + name = Translations.Utility.PublicUtilities.Ping.pingValue.translate() value = "**$averagePing**" inline = true } @@ -76,12 +77,12 @@ class PublicUtilities : Extension() { * @since 3.1.0 */ ephemeralSlashCommand { - name = "nickname" - description = "The parent command for all nickname commands" + name = Translations.Utility.PublicUtilities.Nickname.name + description = Translations.Utility.PublicUtilities.Nickname.description ephemeralSubCommand(::NickRequestArgs) { - name = "request" - description = "Request a new nickname for the server!" + name = Translations.Utility.PublicUtilities.Nickname.Request.name + description = Translations.Utility.PublicUtilities.Nickname.Request.description check { anyGuild() @@ -96,18 +97,21 @@ class PublicUtilities : Extension() { val requesterAsMember = requester?.asMemberOrNull(guild!!.id) val self = this@PublicUtilities.kord.getSelf().asMemberOrNull(guild!!.id) + val translations = Translations.Utility.PublicUtilities.Nickname.Request + val embedTranslations = Translations.Utility.PublicUtilities.Nickname.Request.LogEmbed + if (requesterAsMember?.getTopRole()?.getPosition() != null && self?.getTopRole()?.getPosition() == null ) { respond { - content = "You have a role and Lily does not, so she cannot change your nickname." + content = translations.lilyNoRolePublic.translate() } return@action } else if ((requesterAsMember?.getTopRole()?.getPosition() ?: 0) > (self?.getTopRole()?.getPosition() ?: 0) ) { respond { - content = "Your highest role is above Lily's, so she cannot change your nickname." + content = translations.highestRolePublic.translate() } return@action } @@ -115,7 +119,7 @@ class PublicUtilities : Extension() { if (requesterAsMember?.hasPermission(Permission.ChangeNickname) == true) { requesterAsMember.edit { nickname = arguments.newNick } respond { - content = "You have permission to change your own nickname, so I've just made the change." + content = translations.hasPermission.translate() } return@action } @@ -123,38 +127,38 @@ class PublicUtilities : Extension() { // Declare the embed outside the action to allow us to reference it inside the action var actionLogEmbed: Message? = null - respond { content = "Nickname request sent!" } + respond { content = translations.sent.translate() } try { actionLogEmbed = utilityLog?.createMessage { embed { color = DISCORD_YELLOW - title = "Nickname Request" + title = translations.embedTitle.translate() timestamp = Clock.System.now() field { - name = "User:" + name = Translations.Utility.PublicUtilities.Nickname.userField.translate() value = "${requester?.mention}\n${requester?.asUserOrNull()?.username}\n${requester?.id}" inline = false } field { - name = "Current Nickname:" + name = translations.embedCurrentNick.translate() value = "`${requesterAsMember?.nickname}`" inline = false } field { - name = "Requested Nickname:" + name = translations.embedRequestedNick.translate() value = "`${arguments.newNick}`" inline = false } } components { ephemeralButton(row = 0) { - label = "Accept" + label = Translations.Utility.PublicUtilities.Nickname.Request.Button.accept style = ButtonStyle.Success action button@{ @@ -162,18 +166,14 @@ class PublicUtilities : Extension() { self?.getTopRole()?.getPosition() == null ) { respond { - content = "This user has a role and Lily does not, " + - "so she cannot change their nickname. " + - "Please fix Lily's permissions and try again" + content = translations.lilyNoRolePrivate.translate() } return@button } else if ((requesterAsMember?.getTopRole()?.getPosition() ?: 0) > (self?.getTopRole()?.getPosition() ?: 0) ) { respond { - content = "This user's highest role is above Lily's, " + - "so she cannot change their nickname. " + - "Please fix Lily's permissions and try again." + content = translations.highestRolePrivate.translate() } return@button } @@ -182,11 +182,13 @@ class PublicUtilities : Extension() { requester?.dm { embed { - title = - "Nickname Change Accepted in ${guild!!.asGuildOrNull()?.name}" - description = - "Nickname updated from `${requesterAsMember?.nickname}` to " + - "`${arguments.newNick}`" + title = translations.dmAcceptTitle.translate( + guild!!.asGuildOrNull()?.name + ) + description = translations.dmAcceptDescription.translate( + requesterAsMember?.nickname, + arguments.newNick + ) color = DISCORD_GREEN } } @@ -196,31 +198,34 @@ class PublicUtilities : Extension() { embed { color = DISCORD_GREEN - title = "Nickname Request Accepted" + title = embedTranslations.acceptTitle.translate() field { - name = "User:" + name = + Translations.Utility.PublicUtilities.Nickname.userField.translate() value = "${requester?.mention}\n${requester?.asUserOrNull()?.username}\n" + - "${requester?.id}" + "${requester?.id}" inline = false } // these two fields should be the same and exist as a sanity check field { - name = "Previous Nickname:" + name = embedTranslations.previousNick.translate() value = "`${requesterAsMember?.nickname}`" inline = false } field { - name = "Accepted Nickname:" + name = embedTranslations.acceptedNick.translate() value = "`${arguments.newNick}`" inline = false } footer { - text = "Nickname accepted by ${user.asUserOrNull()?.username}" + text = embedTranslations.acceptedBy.translate( + user.asUserOrNull()?.username + ) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } @@ -231,45 +236,48 @@ class PublicUtilities : Extension() { } ephemeralButton(row = 0) { - label = "Deny" + label = Translations.Utility.PublicUtilities.Nickname.Request.Button.deny style = ButtonStyle.Danger action { requester?.dm { embed { - title = "Nickname Request Denied" - description = "Moderators have reviewed your nickname request (`${ + title = translations.dmDenyTitle.translate() + description = translations.dmDenyDescription.translate( arguments.newNick - }`) and rejected it.\nPlease try a different nickname" + ) } } actionLogEmbed!!.edit { components { removeAll() } embed { - title = "Nickname Request Denied" + title = embedTranslations.denyTitle.translate() field { - name = "User:" + name = + Translations.Utility.PublicUtilities.Nickname.userField.translate() value = "${requester?.mention}\n" + - "${requester?.asUserOrNull()?.username}\n${requester?.id}" + "${requester?.asUserOrNull()?.username}\n${requester?.id}" inline = false } field { - name = "Current Nickname:" + name = embedTranslations.currentNick.translate() value = "`${requesterAsMember?.nickname}`" inline = false } field { - name = "Rejected Nickname:" + name = embedTranslations.rejectedNick.translate() value = "`${arguments.newNick}`" inline = false } footer { - text = "Nickname denied by ${user.asUserOrNull()?.username}" + text = embedTranslations.deniedBy.translate( + user.asUserOrNull()?.username + ) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } @@ -284,8 +292,7 @@ class PublicUtilities : Extension() { } catch (_: KtorRequestException) { // Avoid hard failing on permission error, since the public won't know what it means respond { - content = "Error sending message to moderators. Please ask the moderators to check" + - "the `UTILITY` config." + content = Translations.Utility.PublicUtilities.Nickname.failToSend.translate() } return@action } @@ -293,8 +300,8 @@ class PublicUtilities : Extension() { } ephemeralSubCommand { - name = "clear" - description = "Clear your current nickname" + name = Translations.Utility.PublicUtilities.Nickname.Clear.name + description = Translations.Utility.PublicUtilities.Nickname.Clear.description check { anyGuild() @@ -304,38 +311,43 @@ class PublicUtilities : Extension() { action { val config = UtilityConfigCollection().getConfig(guild!!.id)!! val utilityLog = guild?.getChannelOfOrNull(config.utilityLogChannel!!) + val translations = Translations.Utility.PublicUtilities.Nickname.Clear // Check the user has a nickname to clear, avoiding errors and useless action-log notifications if (user.fetchMember(guild!!.id).nickname == null) { - respond { content = "You have no nickname to clear!" } + respond { + content = translations.nothingToClear.translate() + } return@action } - respond { content = "Nickname cleared" } + respond { content = translations.cleared.translate() } try { utilityLog?.createEmbed { - title = "Nickname Cleared" + val embedTranslations = Translations.Utility.PublicUtilities.Nickname.Clear.LogEmbed + title = embedTranslations.title.translate() color = DISCORD_YELLOW timestamp = Clock.System.now() field { - name = "User:" + name = Translations.Utility.PublicUtilities.Nickname.userField.translate() value = "${user.mention}\n${user.asUserOrNull()?.username}\n${user.id}" inline = false } field { - name = "New Nickname:" - value = "Nickname changed from `${user.asMemberOrNull(guild!!.id)?.nickname}` to `null`" + name = embedTranslations.newNickTitle.translate() + value = embedTranslations.newNickValue.translate( + user.asMemberOrNull(guild!!.id)?.nickname + ) inline = false } } } catch (_: KtorRequestException) { // Avoid hard failing on permission error, since the public won't know what it means respond { - content = "Error sending message to moderators. Please " + - "ask the moderators to check the `UTILITY` config." + content = Translations.Utility.PublicUtilities.Nickname.failToSend.translate() } return@action } @@ -348,8 +360,8 @@ class PublicUtilities : Extension() { inner class NickRequestArgs : Arguments() { /** The new nickname that the command user requested. */ val newNick by string { - name = "nickname" - description = "The new nickname you would like" + name = Translations.Utility.PublicUtilities.Nickname.Request.Args.NewNick.name + description = Translations.Utility.PublicUtilities.Nickname.Request.Args.NewNick.description minLength = 1 maxLength = 32 diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Reminders.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Reminders.kt index deca47d4..e2465589 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Reminders.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Reminders.kt @@ -44,6 +44,7 @@ import io.ktor.client.request.post import kotlinx.datetime.Clock import kotlinx.datetime.DateTimePeriod import kotlinx.datetime.TimeZone +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.ReminderCollection import org.hyacinthbots.lilybot.database.entities.ReminderData import org.hyacinthbots.lilybot.utils.botHasChannelPerms @@ -70,15 +71,15 @@ class Reminders : Extension() { reminderTask = reminderScheduler.schedule(30, repeat = true, callback = ::postReminders) publicSlashCommand { - name = "reminder" - description = "The parent command for all reminder commands" + name = Translations.Utility.Reminders.Reminder.name + description = Translations.Utility.Reminders.Reminder.description /* Reminder set */ publicSubCommand(::ReminderSetArgs) { - name = "set" - description = "Set a reminder for some time in the future!" + name = Translations.Utility.Reminders.Reminder.Set.name + description = Translations.Utility.Reminders.Reminder.Set.description check { anyGuild() @@ -92,85 +93,70 @@ class Reminders : Extension() { } action { + val translations = Translations.Utility.Reminders.Reminder.Set val setTime = Clock.System.now() val remindTime = Clock.System.now().plus(arguments.time.toDuration(TimeZone.UTC)) if (arguments.customMessage != null && arguments.customMessage.fitsEmbedField() == false) { - respond { content = "Custom Message is too long. Message must be 1024 characters or fewer." } + respond { content = translations.messageTooLong.translate() } return@action } if (arguments.repeating && arguments.repeatingInterval == null) { - respond { - content = "You must specify a repeating interval if you are setting a repeating reminder." - } + respond { content = translations.noRepeatingInt.translate() } return@action } if (arguments.repeatingInterval != null && arguments.repeatingInterval!!.toDuration(TimeZone.UTC) < DateTimePeriod(hours = 1).toDuration(TimeZone.UTC) ) { - respond { - content = "The Repeating interval cannot be less than one hour!\n\n" + - "This is to prevent spam and/or abuse of reminders." - } + respond { content = translations.tooShort.translate() } return@action } val reminderEmbed = respond { embed { if (arguments.customMessage.isNullOrEmpty() && !arguments.repeating) { - title = "Reminder Set!" - description = - "I will remind you at ${remindTime.toDiscord(TimestampType.LongDateTime)} (${ - remindTime.toDiscord(TimestampType.RelativeTime) - })" + title = translations.embedTitle.translate() } else if (arguments.customMessage.isNullOrEmpty() && arguments.repeating) { - title = "Repeating Reminder Set!" - description = - "I will remind you at ${remindTime.toDiscord(TimestampType.LongDateTime)} (${ - remindTime.toDiscord(TimestampType.RelativeTime) - })" + title = translations.repeatingEmbedTitle.translate() field { - name = "Repeating Interval" + name = translations.embedRepeatingInt.translate() value = arguments.repeatingInterval.toString().lowercase() .replace("pt", "") .replace("p", "") } } else if (arguments.customMessage != null && !arguments.repeating) { - title = "Reminder Set!" - description = - "I will remind you at ${remindTime.toDiscord(TimestampType.LongDateTime)} (${ - remindTime.toDiscord(TimestampType.RelativeTime) - })" + title = translations.embedTitle.translate() field { - name = "Custom Message" + name = translations.embedCustomMessage.translate() value = arguments.customMessage!! } } else if (arguments.customMessage != null && arguments.repeating) { - title = "Repeating Reminder Set!" - description = - "I will remind you at ${remindTime.toDiscord(TimestampType.LongDateTime)} (${ - remindTime.toDiscord(TimestampType.RelativeTime) - })" + title = translations.repeatingEmbedTitle.translate() field { - name = "Custom Message" + name = translations.embedCustomMessage.translate() value = arguments.customMessage!! } field { - name = "Repeating Interval" + name = translations.embedRepeatingInt.translate() value = arguments.repeatingInterval.interval()!! } } + description = translations.embedDesc.translateNamed( + "long" to remindTime.toDiscord(TimestampType.LongDateTime), + "relative" to remindTime.toDiscord(TimestampType.RelativeTime) + ) + if (arguments.dm) { field { - value = "Reminder will be send via DM to all participants" + value = translations.reminderToAll.translate() } } footer { - text = "Use `/reminder remove` to cancel" + text = Translations.Utility.Reminders.Reminder.Embed.footer.translate() } } } @@ -199,8 +185,8 @@ class Reminders : Extension() { Reminder List */ ephemeralSubCommand { - name = "list" - description = "List your reminders for this guild" + name = Translations.Utility.Reminders.Reminder.List.name + description = Translations.Utility.Reminders.Reminder.List.description check { anyGuild() @@ -224,31 +210,31 @@ class Reminders : Extension() { Reminder Remove */ ephemeralSubCommand(::ReminderRemoveArgs) { - name = "remove" - description = "Remove a reminder you have set from this guild" + name = Translations.Utility.Reminders.Reminder.Remove.name + description = Translations.Utility.Reminders.Reminder.description check { anyGuild() } action { + val translations = Translations.Utility.Reminders.Reminder.Remove val reminders = ReminderCollection().getRemindersForUser(user.id) val reminder = reminders.find { it.id == arguments.reminder } if (reminder == null) { respond { - content = "Reminder not found. Please use `/reminder list` to find out the correct " + - "reminder number" + content = translations.notFound.translate() } return@action } respond { embed { - title = "Reminder cancelled" + title = translations.embedTitle.translate() field { - name = "Reminder" + name = translations.reminderField.translate() value = reminder.getContent()!! } } @@ -263,25 +249,26 @@ class Reminders : Extension() { Reminder Remove all */ ephemeralSubCommand(::ReminderRemoveAllArgs) { - name = "remove-all" - description = "Remove all a specific type of reminder from this guild" + name = Translations.Utility.Reminders.Reminder.RemoveAll.name + description = Translations.Utility.Reminders.Reminder.RemoveAll.description check { anyGuild() } action { + val translations = Translations.Utility.Reminders.Reminder.RemoveAll val reminders = ReminderCollection().getRemindersForUserInGuild(user.id, guild!!.id) if (reminders.isEmpty()) { respond { content = when (arguments.type) { - "all" -> "You do not have any reminders for this guild!" - "repeating" -> "You do not have any repeating reminders for this guild" - "non-repeating" -> "You do not have any regular reminders for this guild" + "all" -> translations.noReminders + "repeating" -> translations.noRepeating + "non-repeating" -> translations.noNonRepeating // This is impossible but the compiler complains otherwise - else -> "You do not have any reminders for this guild" - } + else -> translations.noReminders + }.translate() } return@action } @@ -294,7 +281,7 @@ class Reminders : Extension() { } respond { - content = "Removed all reminders for this guild." + content = translations.removedAll.translate() } } @@ -307,7 +294,7 @@ class Reminders : Extension() { } respond { - content = "Removed all repeating reminders for this guild." + content = translations.removedRepeating.translate() } } @@ -320,7 +307,7 @@ class Reminders : Extension() { } respond { - content = "Removed all non-repeating reminders for this guild." + content = translations.removedNonRepeating.translate() } } } @@ -331,8 +318,8 @@ class Reminders : Extension() { Reminder Mod List */ ephemeralSubCommand(::ReminderModListArgs) { - name = "mod-list" - description = "List all reminders for a user, if you're a moderator" + name = Translations.Utility.Reminders.Reminder.ModList.name + description = Translations.Utility.Reminders.Reminder.ModList.description requirePermission(Permission.ModerateMembers) @@ -360,8 +347,8 @@ class Reminders : Extension() { Reminder Mod Remove */ ephemeralSubCommand(::ReminderModRemoveArgs) { - name = "mod-remove" - description = "Remove a reminder for a user, if you're a moderator" + name = Translations.Utility.Reminders.Reminder.ModRemove.name + description = Translations.Utility.Reminders.Reminder.ModRemove.description requirePermission(Permission.ModerateMembers) @@ -372,23 +359,21 @@ class Reminders : Extension() { } action { + val translations = Translations.Utility.Reminders.Reminder.ModRemove val reminders = ReminderCollection().getRemindersForUser(arguments.user.id) val reminder = reminders.find { it.id == arguments.reminder } if (reminder == null) { - respond { - content = "Reminder not found. Please use `/reminder mod-list` to find out the correct " + - "reminder number" - } + respond { content = translations.notFound.translate() } return@action } respond { embed { - title = "Reminder cancelled by moderator" + title = translations.embedTitle.translate() field { - name = "Reminder" + name = Translations.Utility.Reminders.Reminder.Remove.reminderField.translate() value = reminder.getContent()!! } } @@ -407,8 +392,8 @@ class Reminders : Extension() { Reminder Mod Remove All */ ephemeralSubCommand(::ReminderModRemoveAllArgs) { - name = "mod-remove-all" - description = "Remove all a specific type of reminder for a user, if you're a moderator" + name = Translations.Utility.Reminders.Reminder.ModRemoveAll.name + description = Translations.Utility.Reminders.Reminder.ModRemoveAll.description requirePermission(Permission.ModerateMembers) @@ -419,17 +404,19 @@ class Reminders : Extension() { } action { + val translations = Translations.Utility.Reminders.Reminder.ModRemoveAll val reminders = ReminderCollection().getRemindersForUserInGuild(arguments.user.id, guild!!.id) + val target = guild!!.getMemberOrNull(arguments.user.id)?.username if (reminders.isEmpty()) { respond { content = when (arguments.type) { - "all" -> "${user.asUserOrNull()?.username} does not have any reminders for this guild!" - "repeating" -> "${user.asUserOrNull()?.username} does not have any repeating reminders for this guild" - "non-repeating" -> "${user.asUserOrNull()?.username} does not have any regular reminders for this guild" + "all" -> translations.noReminders + "repeating" -> translations.noRepeating + "non-repeating" -> translations.noNonRepeating // This is impossible but the compiler complains otherwise - else -> "You do not have any reminders for this guild" - } + else -> translations.noReminders + }.translate(target) } return@action } @@ -445,11 +432,7 @@ class Reminders : Extension() { ) } - respond { - content = "Removed all ${ - guild!!.getMemberOrNull(arguments.user.id)?.mention - }'s reminders for this guild." - } + respond { content = translations.removedAll.translate(target) } } "repeating" -> { @@ -464,11 +447,7 @@ class Reminders : Extension() { } } - respond { - content = "Removed all ${ - guild!!.getMemberOrNull(arguments.user.id)?.mention - }'s repeating reminders for this guild." - } + respond { content = translations.removedRepeating.translate(target) } } "non-repeating" -> { @@ -483,11 +462,7 @@ class Reminders : Extension() { } } - respond { - content = "Removed all ${ - guild!!.getMemberOrNull(arguments.user.id)?.mention - }'s non-repeating reminders for this guild." - } + respond { content = translations.removedNonRepeating.translate(target) } } } } @@ -539,9 +514,7 @@ class Reminders : Extension() { if (it.dm || !hasPerms) { guild.getMemberOrNull(it.userId)?.dm { if (!it.dm) { - content = - "I was unable to find/access the channel from $guild that this" + - "reminder was set in." + content = Translations.Utility.Reminders.Reminder.unableToAccess.translate(guild) } reminderEmbed(it) } @@ -570,21 +543,22 @@ class Reminders : Extension() { * @since 4.2.0 */ private fun MessageCreateBuilder.reminderEmbed(data: ReminderData) { + val translations = Translations.Utility.Reminders.Reminder.Embed embed { - title = "Reminder" + title = translations.title.translate() if (data.customMessage != null) description = data.customMessage field { - name = "Set time" + name = translations.set.translate() value = data.setTime.toDiscord(TimestampType.LongDateTime) } if (data.repeating) { field { - name = "Repeating Interval" - value = "This reminder repeats every ${data.repeatingInterval.interval()}" + name = translations.repeatingTitle.translate() + value = translations.repeatingValue.translate(data.repeatingInterval.interval()) } footer { - text = "Use `/reminder remove` to cancel this reminder" + text = translations.footer.translate() } } } @@ -617,14 +591,18 @@ class Reminders : Extension() { val channel = guild.getChannelOfOrNull(channelId) ?: return val message = channel.getMessageOrNull(messageId) ?: return message.edit { - content = "${message.content} **Reminder " + - "${ - if (wasCancelled) { - "cancelled${if (byModerator) " by moderator" else ""}." + content = "${message.content} **${Translations.Utility.Reminders.Reminder.Embed.title.translate()} " + + if (wasCancelled) { + "${Translations.Utility.Reminders.Reminder.OriginalMessage.canc.translate()}${ + if (byModerator) { + " ${Translations.Utility.Reminders.Reminder.OriginalMessage.byMod.translate()}" } else { - "completed." + "" } - }**" + }." + } else { + Translations.Utility.Reminders.Reminder.OriginalMessage.complete.translate() + } + "**" } } @@ -642,6 +620,7 @@ class Reminders : Extension() { event: ChatInputCommandInteractionCreateEvent, userId: Snowflake? = null ): Pages { + val translations = Translations.Utility.Reminders.Reminder.ListPage val pagesObj = Pages() val userReminders = ReminderCollection().getRemindersForUserInGuild( userId ?: event.interaction.user.id, @@ -652,10 +631,10 @@ class Reminders : Extension() { pagesObj.addPage( Page { description = if (userId == null) { - "You have no reminders set for this guild." + translations.desc } else { - "<@$userId> has no reminders set for this guild." - } + translations.modDesc + }.translate(userId) } ) } else { @@ -667,7 +646,7 @@ class Reminders : Extension() { pagesObj.addPage( Page { - title = "Reminders for ${guildFor(event)?.asGuildOrNull()?.name ?: "this guild"}" + title = translations.title.translate(guildFor(event)?.asGuildOrNull()?.name) description = response } ) @@ -685,68 +664,71 @@ class Reminders : Extension() { * @since 4.2.0 */ private fun ReminderData?.getContent(): String? { + val translations = Translations.Utility.Reminders.Reminder.Content this ?: return null - return "Reminder ID: ${this.id}\nTime set: ${this.setTime.toDiscord(TimestampType.ShortDateTime)},\n" + - "Time until reminder: ${this.remindTime.toDiscord(TimestampType.RelativeTime)} (${ - this.remindTime.toDiscord(TimestampType.ShortDateTime) - }),\nCustom Message: ${ - if (this.customMessage != null && this.customMessage.length >= 150) { - this.customMessage.substring(0..150) - } else { - this.customMessage ?: "none" - } - }\n---\n" + return "${translations.line1.translate()}: ${this.id}\n" + + "${translations.line2.translate()}: ${this.setTime.toDiscord(TimestampType.ShortDateTime)},\n" + + "${translations.line3.translate()}: ${this.remindTime.toDiscord(TimestampType.RelativeTime)} (${ + this.remindTime.toDiscord(TimestampType.ShortDateTime) + }),\n" + + "${translations.line4.translate()}: ${ + if (this.customMessage != null && this.customMessage.length >= 150) { + this.customMessage.substring(0..150) + } else { + this.customMessage ?: Translations.Basic.none + } + }\n---\n" } inner class ReminderSetArgs : Arguments() { /** The time until the reminding should happen. */ val time by coalescingDuration { - name = "time" - description = "How long until reminding? Format: 1d12h30m / 3d / 26m30s" + name = Translations.Utility.Reminders.Reminder.Set.Arguments.Time.name + description = Translations.Utility.Reminders.Reminder.Set.Arguments.Time.description } val dm by boolean { - name = "remind-in-dm" - description = "Whether to remind in DMs or not" + name = Translations.Utility.Reminders.Reminder.Set.Arguments.Dm.name + description = Translations.Utility.Reminders.Reminder.Set.Arguments.Dm.description } /** An optional message to attach to the reminder. */ val customMessage by optionalString { - name = "custom-message" - description = "A message to attach to your reminder" + name = Translations.Utility.Reminders.Reminder.Set.Arguments.Message.name + description = Translations.Utility.Reminders.Reminder.Set.Arguments.Message.description } /** Whether to repeat the reminder or have it run once. */ val repeating by defaultingBoolean { - name = "repeat" - description = "Whether to repeat the reminder or not" + name = Translations.Utility.Reminders.Reminder.Set.Arguments.Repeating.name + description = Translations.Utility.Reminders.Reminder.Set.Arguments.Repeating.description defaultValue = false } /** The interval for the repeating reminder to run at. */ val repeatingInterval by coalescingOptionalDuration { - name = "repeat-interval" - description = "The interval to repeat the reminder at. Format: 1d / 1h / 5d" + name = Translations.Utility.Reminders.Reminder.Set.Arguments.RepeatingInt.name + description = Translations.Utility.Reminders.Reminder.Set.Arguments.RepeatingInt.description } } inner class ReminderRemoveArgs : Arguments() { /** The number of the reminder to remove. */ val reminder by long { - name = "reminder-number" - description = "The number of the reminder to remove. Use '/reminder list' to get this" + name = Translations.Utility.Reminders.Reminder.Remove.Arguments.Reminder.name + description = Translations.Utility.Reminders.Reminder.Remove.Arguments.Reminder.description } } inner class ReminderRemoveAllArgs : Arguments() { /** The type of reminder to remove. */ val type by stringChoice { - name = "reminder-type" - description = "The type of reminder to remove" + name = Translations.Utility.Reminders.Reminder.RemoveAll.Arguments.Type.name + description = Translations.Utility.Reminders.Reminder.RemoveAll.Arguments.Type.description choices = mutableMapOf( - "repeating" to "repeating", - "non-repeating" to "non-repeating", - "all" to "all" + Translations.Utility.Reminders.Reminder.RemoveAll.Arguments.Type.Choices.repeating to "repeating", + Translations.Utility.Reminders.Reminder.RemoveAll.Arguments.Type.Choices.nonRepeating to "non-repeating", + Translations.Utility.Reminders.Reminder.RemoveAll.Arguments.Type.Choices.all to "all" ) } } @@ -754,40 +736,40 @@ class Reminders : Extension() { inner class ReminderModListArgs : Arguments() { /** The user whose reminders are being viewed. */ val user by user { - name = "user" - description = "The user to view reminders for" + name = Translations.Utility.Reminders.Reminder.ModList.Arguments.User.name + description = Translations.Utility.Reminders.Reminder.ModList.Arguments.User.description } } inner class ReminderModRemoveArgs : Arguments() { /** The user whose reminders need removing. */ val user by user { - name = "user" - description = "The user to remove the reminder for" + name = Translations.Utility.Reminders.Reminder.ModRemove.Arguments.User.name + description = Translations.Utility.Reminders.Reminder.ModRemove.Arguments.User.description } /** The number of the reminder to remove. */ val reminder by long { - name = "reminder-number" - description = "The number of the reminder to remove. Use '/reminder mod-list' to get this" + name = Translations.Utility.Reminders.Reminder.ModRemove.Arguments.Reminder.name + description = Translations.Utility.Reminders.Reminder.ModRemove.Arguments.Reminder.description } } inner class ReminderModRemoveAllArgs : Arguments() { /** The user whose reminders need removing. */ val user by user { - name = "user" - description = "The user to remove the reminders for" + name = Translations.Utility.Reminders.Reminder.ModRemoveAll.Arguments.User.name + description = Translations.Utility.Reminders.Reminder.ModRemoveAll.Arguments.User.description } /** The type of reminder to remove. */ val type by stringChoice { - name = "reminder-type" - description = "The type of reminder to remove" + name = Translations.Utility.Reminders.Reminder.RemoveAll.Arguments.Type.name + description = Translations.Utility.Reminders.Reminder.RemoveAll.Arguments.Type.description choices = mutableMapOf( - "repeating" to "repeating", - "non-repeating" to "non-repeating", - "all" to "all" + Translations.Utility.Reminders.Reminder.RemoveAll.Arguments.Type.Choices.repeating to "repeating", + Translations.Utility.Reminders.Reminder.RemoveAll.Arguments.Type.Choices.nonRepeating to "non-repeating", + Translations.Utility.Reminders.Reminder.RemoveAll.Arguments.Type.Choices.all to "all" ) } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/RoleMenu.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/RoleMenu.kt index 5ce716c2..2ea55c4a 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/RoleMenu.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/RoleMenu.kt @@ -33,11 +33,13 @@ import dev.kordex.core.components.linkButton import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.ephemeralSlashCommand import dev.kordex.core.extensions.event +import dev.kordex.core.i18n.toKey import dev.kordex.core.utils.getJumpUrl import dev.kordex.core.utils.getTopRole import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.toList +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.RoleMenuCollection import org.hyacinthbots.lilybot.database.collections.RoleSubscriptionCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions @@ -61,15 +63,15 @@ class RoleMenu : Extension() { * @since 3.4.0 */ ephemeralSlashCommand { - name = "role-menu" - description = "The parent command for managing role menus." + name = Translations.Utility.RoleMenu.name + description = Translations.Utility.RoleMenu.description /** * The command to create a new role menu. */ ephemeralSubCommand(::RoleMenuCreateArgs) { - name = "create" - description = "Create a new role menu in this channel. A channel can have any number of role menus." + name = Translations.Utility.RoleMenu.Create.name + description = Translations.Utility.RoleMenu.Create.description requirePermission(Permission.ManageRoles) @@ -84,6 +86,7 @@ class RoleMenu : Extension() { var menuMessage: Message? action { + val translations = Translations.Utility.RoleMenu.Create val kord = this@ephemeralSlashCommand.kord if (!botCanAssignRole(kord, arguments.initialRole)) return@action @@ -103,7 +106,7 @@ class RoleMenu : Extension() { menuMessage.edit { val components = components { ephemeralButton { - label = "Select roles" + label = translations.selectButton style = ButtonStyle.Primary id = "role-menu${menuMessage.id}" @@ -127,40 +130,40 @@ class RoleMenu : Extension() { utilityLog.createMessage { embed { - title = "Role Menu Created" - description = "A role menu for the ${arguments.initialRole.mention} role was created in " + - "${channel.mention}." + title = translations.embedTitle.translate() + description = + translations.embedDesc.translate(arguments.initialRole.mention, channel.mention) field { - name = "Content:" + name = translations.embedContent.translate() value = "```${arguments.content}```" inline = false } field { - name = "Color:" + name = translations.embedColor.translate() value = arguments.color.toString() inline = true } field { - name = "Embed:" + name = translations.embedEmbed.translate() value = arguments.embed.toString() inline = true } footer { - text = "Created by ${user.asUserOrNull()?.username}" + text = translations.createdBy.translate(user.asUserOrNull()?.username) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } } components { linkButton { - label = "Jump to role menu" + label = Translations.Utility.RoleMenu.jumpButton url = menuMessage.getJumpUrl() } } } respond { - content = "Role menu created. You can add more roles using the `/role-menu add` command." + content = translations.response.translate() } } } @@ -169,8 +172,8 @@ class RoleMenu : Extension() { * The command to add a role to an existing role menu. */ ephemeralSubCommand(::RoleMenuAddArgs) { - name = "add" - description = "Add a role to the existing role menu in this channel." + name = Translations.Utility.RoleMenu.Add.name + description = Translations.Utility.RoleMenu.Add.description requirePermission(Permission.ManageRoles) @@ -186,6 +189,8 @@ class RoleMenu : Extension() { action { val kord = this@ephemeralSlashCommand.kord + val translations = Translations.Utility.RoleMenu.Add + if (!botCanAssignRole(kord, arguments.role)) return@action val message = channel.getMessageOrNull(arguments.messageId) @@ -194,16 +199,12 @@ class RoleMenu : Extension() { val data = RoleMenuCollection().getRoleData(arguments.messageId)!! if (arguments.role.id in data.roles) { - respond { - content = "This menu already contains that role." - } + respond { content = translations.alreadyGot.translate() } return@action } if (data.roles.size == 24) { - respond { - content = "You can't have more than 24 roles in a role menu. This is a Discord limitation." - } + respond { content = translations.max24.translate() } return@action } @@ -219,24 +220,23 @@ class RoleMenu : Extension() { ?: return@action utilityLog.createMessage { embed { - title = "Role Added to Role Menu" - description = "The ${arguments.role.mention} role was added to a role menu in " + - "${channel.mention}." + title = translations.embedTitle.translate() + description = translations.embedDesc.translate(arguments.role.mention, channel.mention) footer { - text = "Added by ${user.asUserOrNull()?.username}" + text = translations.addedBy.translate(user.asUserOrNull()?.username) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } } components { linkButton { - label = "Jump to role menu" + label = Translations.Utility.RoleMenu.jumpButton url = message!!.getJumpUrl() } } } respond { - content = "Added the ${arguments.role.mention} role to the specified role menu." + content = translations.response.translate(arguments.role.mention) } } } @@ -245,8 +245,8 @@ class RoleMenu : Extension() { * The command to remove a role from an existing role menu. */ ephemeralSubCommand(::RoleMenuRemoveArgs) { - name = "remove" - description = "Remove a role from the existing role menu in this channel." + name = Translations.Utility.RoleMenu.Remove.name + description = Translations.Utility.RoleMenu.Remove.description requirePermission(Permission.ManageMessages) @@ -263,19 +263,17 @@ class RoleMenu : Extension() { val menuMessage = channel.getMessageOrNull(arguments.messageId) if (!roleMenuExists(menuMessage, arguments.messageId)) return@action + val translations = Translations.Utility.RoleMenu.Remove + val data = RoleMenuCollection().getRoleData(arguments.messageId)!! if (arguments.role.id !in data.roles) { - respond { - content = "You can't remove a role from a menu it's not in." - } + respond { content = translations.cantRemove.translate() } return@action } if (data.roles.size == 1) { - respond { - content = "You can't remove the last role from a role menu." - } + respond { content = translations.cantRemoveLast.translate() } return@action } @@ -285,24 +283,23 @@ class RoleMenu : Extension() { ?: return@action utilityLog.createMessage { embed { - title = "Role Removed from Role Menu" - description = "The ${arguments.role.mention} role was removed from a role menu in " + - "${channel.mention}." + title = translations.embedTitle.translate() + description = translations.embedDesc.translate(arguments.role.mention, channel.mention) footer { - text = "Removed by ${user.asUserOrNull()?.username}" + text = translations.removedBy.translate(user.asUserOrNull()?.username) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } } components { linkButton { - label = "Jump to role menu" + label = Translations.Utility.RoleMenu.jumpButton url = menuMessage.getJumpUrl() } } } respond { - content = "Removed the ${arguments.role.mention} role from the specified role menu." + content = translations.response.translate(arguments.role.mention) } } } @@ -311,8 +308,8 @@ class RoleMenu : Extension() { * A command that creates a new role menu specifically for selecting pronouns. */ ephemeralSubCommand { - name = "pronouns" - description = "Create a pronoun selection role menu and the roles to go with it." + name = Translations.Utility.RoleMenu.Pronouns.name + description = Translations.Utility.RoleMenu.Pronouns.description requirePermission(Permission.ManageMessages) @@ -326,19 +323,20 @@ class RoleMenu : Extension() { } action { + val translations = Translations.Utility.RoleMenu.Pronouns respond { - content = "Pronoun role menu created." + content = translations.response.translate() } val menuMessage = channel.createMessage { - content = "Select pronoun roles from the menu below!" + content = translations.message.translate() } // While we don't normally edit in components, in this case we need the message ID. menuMessage.edit { val components = components { ephemeralButton { - label = "Select roles" + label = Translations.Utility.RoleMenu.Create.selectButton style = ButtonStyle.Primary this.id = "role-menu${menuMessage.id}" @@ -397,16 +395,16 @@ class RoleMenu : Extension() { ?: return@action utilityLog.createMessage { embed { - title = "Pronoun Role Menu Created" - description = "A pronoun role menu was created in ${channel.mention}." + title = translations.embedTitle.translate() + description = translations.embedDesc.translate(channel.mention) footer { - text = "Created by ${user.asUserOrNull()?.username}" + text = translations.createdBy.translate(user.asUserOrNull()?.username) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } } components { linkButton { - label = "Jump to role menu" + label = Translations.Utility.RoleMenu.jumpButton url = menuMessage.getJumpUrl() } } @@ -428,21 +426,18 @@ class RoleMenu : Extension() { action Button@{ val data = RoleMenuCollection().getRoleData(event.interaction.message.id) + val translations = Translations.Utility.RoleMenu.Interaction if (data == null) { event.interaction.respondEphemeral { - content = "This role menu seems to be broken, please ask staff to recreate it. " + - "If this isn't a role menu, or if the issue persists, open a report at " + - "<$HYACINTH_GITHUB/LilyBot/issues>" + content = translations.broken.translate(HYACINTH_GITHUB) } return@Button } if (data.roles.isEmpty()) { event.interaction.respondEphemeral { - content = "Could not find any roles associated with this menu. Please ask staff to add some. " + - "If this isn't a role menu, or if the issue persists, open a report at " + - "<$HYACINTH_GITHUB/LilyBot/issues>" + content = translations.noRoles.translate(HYACINTH_GITHUB) } return@Button } @@ -450,8 +445,7 @@ class RoleMenu : Extension() { val guild = kord.getGuildOrNull(data.guildId) if (guild == null) { event.interaction.respondEphemeral { - content = "An error occurred getting when trying to get the server, please try again! If the " + - "problem persists, open a report at <$HYACINTH_GITHUB/LilyBot/issues>" + content = translations.serverError.translate(HYACINTH_GITHUB) } return@Button } @@ -468,9 +462,7 @@ class RoleMenu : Extension() { if (roles.isEmpty()) { event.interaction.respondEphemeral { - content = "Could not find any roles associated with this menu. Please ask staff to add some. " + - "If this isn't a role menu, or if the issue persists, open a report at " + - "<$HYACINTH_GITHUB/LilyBot/issues>" + content = translations.noRoles.translate(HYACINTH_GITHUB) } return@Button } @@ -483,17 +475,17 @@ class RoleMenu : Extension() { val userRoles = member.roleIds.filter { it in guildRoles.keys } event.interaction.respondEphemeral { - content = "Use the menu below to select roles." + content = translations.menuMessage.translate() components { // TODO Update to ephemeralRoleSelectMenu ephemeralStringSelectMenu { - placeholder = "Select roles..." + placeholder = translations.placeholder maximumChoices = roles.size minimumChoices = 0 roles.forEach { option( - label = "@${it.name}", + label = "@${it.name}".toKey(), value = it.id.toString() ) { default = it.id in userRoles @@ -510,7 +502,7 @@ class RoleMenu : Extension() { member.removeRole(it.id) } } - respond { content = "Your roles have been adjusted" } + respond { content = translations.response.translate() } return@SelectMenu } @@ -518,9 +510,7 @@ class RoleMenu : Extension() { val rolesToRemove = userRoles.filterNot { it in selectedRoles } if (rolesToAdd.isEmpty() && rolesToRemove.isEmpty()) { - respond { - content = "You didn't select any different roles, so no changes were made." - } + respond { content = translations.noChanges.translate() } return@SelectMenu } @@ -531,7 +521,7 @@ class RoleMenu : Extension() { this@edit.roles!!.addAll(rolesToAdd.toSet()) this@edit.roles!!.removeAll(rolesToRemove.toSet()) } - respond { content = "Your roles have been adjusted." } + respond { content = translations.response.translate() } } } } @@ -540,12 +530,12 @@ class RoleMenu : Extension() { } ephemeralSlashCommand { - name = "role-subscription" - description = "The parent command for role-subscription commands" + name = Translations.Utility.RoleMenu.RoleSubscription.name + description = Translations.Utility.RoleMenu.RoleSubscription.description ephemeralSubCommand { - name = "update" - description = "Update your role subscription" + name = Translations.Utility.RoleMenu.RoleSubscription.Update.name + description = Translations.Utility.RoleMenu.RoleSubscription.Update.description check { anyGuild() @@ -553,11 +543,12 @@ class RoleMenu : Extension() { action { val guild = guild ?: return@action + val translations = Translations.Utility.RoleMenu.RoleSubscription.Update val data = RoleSubscriptionCollection().getSubscribableRoles(guild.id) if (data == null) { respond { - content = "This guild does not have any subscribable roles." + content = translations.noSubs.translate() } return@action } @@ -580,17 +571,17 @@ class RoleMenu : Extension() { val userRoles = member?.roleIds?.filter { it in guildRoles.keys } respond { - content = "Use the menu below to subscribe to roles." + content = translations.useMenu.translate() components { // TODO Update to ephemeralRoleSelectMenu ephemeralStringSelectMenu { - placeholder = "Select roles to subscribe to..." + placeholder = translations.placeholder minimumChoices = 0 maximumChoices = subscribableRoles.size subscribableRoles.forEach { option( - label = "@${it.name}", + label = "@${it.name}".toKey(), value = it.id.toString() ) { if (userRoles != null) { @@ -609,7 +600,7 @@ class RoleMenu : Extension() { member.removeRole(it.id) } } - respond { content = "Your role subscription has been adjusted" } + respond { content = translations.adjusted.translate() } return@SelectMenu } @@ -623,7 +614,7 @@ class RoleMenu : Extension() { if (rolesToAdd.isEmpty() && rolesToRemove?.isEmpty() == true) { respond { - content = "You didn't select any different roles, so no changes were made." + content = Translations.Utility.RoleMenu.Interaction.noChanges.translate() } return@SelectMenu } @@ -635,7 +626,7 @@ class RoleMenu : Extension() { this@edit.roles!!.addAll(rolesToAdd.toSet()) rolesToRemove?.toSet()?.let { this@edit.roles!!.removeAll(it) } } - respond { content = "Your role subscription has been adjusted." } + respond { content = translations.adjusted.translate() } } } } @@ -644,8 +635,8 @@ class RoleMenu : Extension() { } ephemeralSubCommand(::RoleSubscriptionRoleArgs) { - name = "add-role" - description = "Add a role that can be added through role subscription commands" + name = Translations.Utility.RoleMenu.RoleSubscription.Add.name + description = Translations.Utility.RoleMenu.RoleSubscription.Add.description requirePermission(Permission.ManageRoles, Permission.ManageGuild) @@ -667,18 +658,20 @@ class RoleMenu : Extension() { val formattedRoleList = config.subscribableRoles.map { guild.getRoleOrNull(it)?.mention } + val translations = Translations.Utility.RoleMenu.RoleSubscription.Add + respond { - content = - "${arguments.role.mention} was added as a subscribable role. Current subscribable roles are:\n${ - formattedRoleList.joinToString("\n") - }" + content = translations.response.translate( + arguments.role.mention, + formattedRoleList.joinToString("\n") + ) } utilityConfig?.createEmbed { - title = "Subscribable Role added" - description = "${arguments.role.mention} was added as a subscribable role" + title = translations.embedTitle.translate() + description = translations.embedDesc.translate(arguments.role.mention) footer { - text = "Added by ${user.asUserOrNull()?.username}" + text = translations.addedBy.translate(user.asUserOrNull()?.username) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } } @@ -686,8 +679,8 @@ class RoleMenu : Extension() { } ephemeralSubCommand(::RoleSubscriptionRoleArgs) { - name = "remove-role" - description = "Remove a role that can be added through role subscription commands" + name = Translations.Utility.RoleMenu.RoleSubscription.Remove.name + description = Translations.Utility.RoleMenu.RoleSubscription.Remove.description requirePermission(Permission.ManageRoles, Permission.ManageGuild) @@ -700,16 +693,19 @@ class RoleMenu : Extension() { val guild = guild ?: return@action var config = RoleSubscriptionCollection().getSubscribableRoles(guild.id) val utilityConfig = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild) + + val translations = Translations.Utility.RoleMenu.RoleSubscription.Remove + if (config == null) { respond { - content = "There are no subscribable roles for this guild." + content = Translations.Utility.RoleMenu.RoleSubscription.Update.noSubs.translate() } return@action } if (!config.subscribableRoles.contains(arguments.role.id)) { respond { - content = "That is not a subscribable role." + content = translations.notSubable.translate() } return@action } @@ -720,21 +716,21 @@ class RoleMenu : Extension() { val formattedRoleList = config!!.subscribableRoles.map { guild.getRoleOrNull(it)?.mention } respond { - content = - "${arguments.role.mention} was removed as a subscribable role. Current subscribable roles are:\n${ - if (formattedRoleList.isNotEmpty()) { - formattedRoleList.joinToString("\n") - } else { - "None" - } - }" + content = translations.response.translate( + arguments.role.mention, + if (formattedRoleList.isNotEmpty()) { + formattedRoleList.joinToString("\n") + } else { + Translations.Basic.none.translate() + } + ) } utilityConfig?.createEmbed { - title = "Subscribable Role Removed" - description = "${arguments.role.mention} was removed as a subscribable role" + title = translations.embedTitle.translate() + description = translations.embedDesc.translate(arguments.role.mention) footer { - text = "Removed by ${user.asUserOrNull()?.username}" + text = translations.removedBy.translate(user.asUserOrNull()?.username) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } } @@ -759,7 +755,7 @@ class RoleMenu : Extension() { ): Boolean { if (inputMessage == null) { respond { - content = "I couldn't find that message in this channel. Make sure it exists." + content = Translations.Utility.RoleMenu.Check.cantFind.translate() } return false } @@ -767,7 +763,7 @@ class RoleMenu : Extension() { val data = RoleMenuCollection().getRoleData(argumentMessageId) if (data == null) { respond { - content = "That message doesn't seem to be a role menu." + content = Translations.Utility.RoleMenu.Check.notRole.translate() } return false } @@ -789,8 +785,7 @@ class RoleMenu : Extension() { val self = guild?.getMemberOrNull(kord.selfId)!! if (self.getTopRole()!! < role) { respond { - content = "The selected role is higher than me in the role hierarchy. " + - "Please move it and try again." + content = Translations.Utility.RoleMenu.Check.higherRole.translate() } return false } @@ -800,14 +795,14 @@ class RoleMenu : Extension() { inner class RoleMenuCreateArgs : Arguments() { /** The initial role for a new role menu. */ val initialRole by role { - name = "role" - description = "The first role to start the menu with. Add more via `/role-menu add`" + name = Translations.Utility.RoleMenu.Create.Arguments.Role.name + description = Translations.Utility.RoleMenu.Create.Arguments.Role.description } /** The content of the embed or message to attach the role menu to. */ val content by string { - name = "content" - description = "The content of the embed or message." + name = Translations.Utility.RoleMenu.Create.Arguments.Content.name + description = Translations.Utility.RoleMenu.Create.Arguments.Content.description // Fix newline escape characters mutate { @@ -818,15 +813,15 @@ class RoleMenu : Extension() { /** If the message the role menu is attached to should be an embed. */ val embed by defaultingBoolean { - name = "embed" - description = "If the message containing the role menu should be sent as an embed." + name = Translations.Utility.RoleMenu.Create.Arguments.Embed.name + description = Translations.Utility.RoleMenu.Create.Arguments.Embed.description defaultValue = true } /** If the message the role menu is attached to is an embed, the color that embed should be. */ val color by defaultingColor { - name = "color" - description = "The color for the message to be. Embed only." + name = Translations.Utility.RoleMenu.Create.Arguments.Color.name + description = Translations.Utility.RoleMenu.Create.Arguments.Color.description defaultValue = DISCORD_BLACK } } @@ -834,35 +829,35 @@ class RoleMenu : Extension() { inner class RoleMenuAddArgs : Arguments() { /** The message ID of the role menu being edited. */ val messageId by snowflake { - name = "menu-id" - description = "The message ID of the role menu you'd like to edit." + name = Translations.Utility.RoleMenu.Add.Arguments.Id.name + description = Translations.Utility.RoleMenu.Add.Arguments.Id.description } /** The role to add to the role menu. */ val role by role { - name = "role" - description = "The role you'd like to add to the selected role menu." + name = Translations.Utility.RoleMenu.Add.Arguments.Role.name + description = Translations.Utility.RoleMenu.Add.Arguments.Role.description } } inner class RoleMenuRemoveArgs : Arguments() { /** The message ID of the role menu being edited. */ val messageId by snowflake { - name = "menu-id" - description = "The message ID of the menu you'd like to edit." + name = Translations.Utility.RoleMenu.Add.Arguments.Id.name + description = Translations.Utility.RoleMenu.Add.Arguments.Id.description } /** The role to remove from the role menu. */ val role by role { - name = "role" - description = "The role you'd like to remove from the selected role menu." + name = Translations.Utility.RoleMenu.Add.Arguments.Role.name + description = Translations.Utility.RoleMenu.Remove.Arguments.Role.description } } inner class RoleSubscriptionRoleArgs : Arguments() { val role by role { - name = "role" - description = "A role to add or remove from the subscribable roles" + name = Translations.Utility.RoleMenu.Add.Arguments.Role.name + description = Translations.Utility.RoleMenu.RoleSubscription.Arguments.Role.description } } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/StartupHooks.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/StartupHooks.kt index 3fc602b7..3e030e9d 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/StartupHooks.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/StartupHooks.kt @@ -12,6 +12,7 @@ import dev.kordex.core.time.toDiscord import dev.kordex.core.utils.scheduling.Scheduler import dev.kordex.core.utils.scheduling.Task import kotlinx.datetime.Clock +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.Cleanups import org.hyacinthbots.lilybot.database.collections.UptimeCollection import org.hyacinthbots.lilybot.utils.ONLINE_STATUS_CHANNEL @@ -51,7 +52,7 @@ class StartupHooks : Extension() { val homeGuild = kord.getGuildOrNull(TEST_GUILD_ID) ?: return@action val onlineLog = homeGuild.getChannelOfOrNull(ONLINE_STATUS_CHANNEL) ?: return@action onlineLog.createEmbed { - title = "Lily is now online!" + title = Translations.Utility.StartupHooks.onlineTitle.translate() description = "${now.toDiscord(TimestampType.LongDateTime)} (${now.toDiscord(TimestampType.RelativeTime)})" color = DISCORD_GREEN diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Tags.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Tags.kt index 20c1a808..f90a6804 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Tags.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/commands/Tags.kt @@ -31,6 +31,7 @@ import dev.kordex.core.pagination.pages.Page import dev.kordex.core.pagination.pages.Pages import dev.kordex.core.utils.suggestStringMap import kotlinx.datetime.Clock +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.TagsCollection import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions @@ -46,6 +47,7 @@ import org.hyacinthbots.lilybot.utils.trimmedContents class Tags : Extension() { override val name = "tags" + // TODO Rewrite to use modals? override suspend fun setup() { /** * The command that allows users to preview tag contents before sending it in public @@ -54,8 +56,8 @@ class Tags : Extension() { * @since 3.4.3 */ ephemeralSlashCommand(::DeleteTagArgs) { - name = "tag-preview" - description = "Preview a tag's contents without sending it publicly." + name = Translations.Utility.Tags.Preview.name + description = Translations.Utility.Tags.Preview.description check { requireBotPermissions(Permission.SendMessages, Permission.EmbedLinks) @@ -64,19 +66,12 @@ class Tags : Extension() { action { val tagFromDatabase = TagsCollection().getTag(guild!!.id, arguments.tagName) ?: run { - respond { - content = "Unable to find tag `${arguments.tagName}` for preview. " + - "Be sure it exists and you've typed it correctly." - } + respond { content = Translations.Utility.Tags.unableToFind.translate(arguments.tagName) } return@action } if (tagFromDatabase.tagValue.length > 4096) { - respond { - content = - "The body of this tag is too long! Somehow this tag has a body of 1024 characters or" + - "more, which is above the Discord limit. Please re-create this tag!" - } + respond { content = Translations.Utility.Tags.bodyTooLongSomehow.translate() } return@action } @@ -86,7 +81,7 @@ class Tags : Extension() { title = tagFromDatabase.tagTitle description = tagFromDatabase.tagValue footer { - text = "Tag preview" + text = Translations.Utility.Tags.Preview.footer.translate() } color = DISCORD_BLURPLE } @@ -104,8 +99,8 @@ class Tags : Extension() { * @since 3.1.0 */ ephemeralSlashCommand(::CallTagArgs) { - name = "tag" - description = "Call a tag from this guild! Use /tag-help for more info." + name = Translations.Utility.Tags.Tag.name + description = Translations.Utility.Tags.Tag.description check { anyGuild() @@ -114,24 +109,18 @@ class Tags : Extension() { } action { + val translations = Translations.Utility.Tags.Tag val tagFromDatabase = TagsCollection().getTag(guild!!.id, arguments.tagName) ?: run { - respond { - content = "Unable to find tag `${arguments.tagName}`. " + - "Be sure it exists and you've typed it correctly." - } + respond { content = Translations.Utility.Tags.unableToFind.translate(arguments.tagName) } return@action } if (tagFromDatabase.tagValue.length > 4096) { - respond { - content = - "The body of this tag is too long! Somehow this tag has a body of 4096 characters or" + - "more, which is above the Discord limit. Please re-create this tag!" - } + respond { content = Translations.Utility.Tags.bodyTooLongSomehow.translate() } return@action } - respond { content = "Tag sent" } + respond { content = translations.response.translate() } // This is not the best way to do this. Ideally the ping would be in the same message as the embed in // a `respond` builder. A Discord limitation makes this not possible. @@ -142,7 +131,7 @@ class Tags : Extension() { title = tagFromDatabase.tagTitle description = tagFromDatabase.tagValue footer { - text = "Tag requested by ${user.asUserOrNull()?.username}" + text = translations.footer.translate(user.asUserOrNull()?.username) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } color = DISCORD_BLURPLE @@ -158,21 +147,21 @@ class Tags : Extension() { val utilityLog = UtilityConfigCollection().getConfig(guild!!.id)?.utilityLogChannel ?: return@action guild!!.getChannelOfOrNull(utilityLog)?.createMessage { embed { - title = "Message Tag used" + title = translations.embedTitle.translate() field { - name = "User" + name = Translations.Basic.userField.translate() value = "${user.asUserOrNull()?.mention} (${user.asUserOrNull()?.username})" } field { - name = "Tag name" + name = translations.embedName.translate() value = "`${arguments.tagName}`" } field { - name = "Location" + name = translations.embedLocation.translate() value = "${channel.mention} ${channel.asChannelOrNull()?.data?.name?.value}" } footer { - text = "User ID: ${user.asUserOrNull()?.id}" + text = translations.embedFooter.translate(user.asUserOrNull()?.id) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } timestamp = Clock.System.now() @@ -189,8 +178,8 @@ class Tags : Extension() { * @since 3.1.0 */ publicSlashCommand { - name = "tag-help" - description = "Explains how the tag command works!" + name = Translations.Utility.Tags.TagHelp.name + description = Translations.Utility.Tags.TagHelp.description check { requireBotPermissions(Permission.SendMessages, Permission.EmbedLinks) @@ -200,27 +189,8 @@ class Tags : Extension() { action { respond { embed { - title = "How does the tag system work?" - description = - "The tag command allows users to add guild specific 'tag' commands at runtime to their " + - "guild. **Tags are like custom commands**, they can do say what ever you want " + - "them to say.\n\n**To create a tag**, if you have the Moderate Members " + - "permission, run the following command:\n`/tag-create <value>`\n " + - "You will be prompted to enter a name for the tag, a title for the tag, and the " + - "value for the tag. This is what will appear in the embed of your tag. You can " + - "enter any character you like into all of these inputs.\n\n**To use a tag**, " + - "run the following command:\n`/tag <name>`\nYou will be prompted to enter a " + - "tag name, but will have an autocomplete window to aid you. The window will " + - "list all the tags that the guild has.\n\n**To delete a tag**, if you have " + - "the Moderate Members permission, run the following command:\n" + - "`/tag-delete <name>`\nYou will be prompted to enter the name of the tag, " + - "again aided by autocomplete.\n`/tag-edit`\nYou will be prompted to enter a " + - "tag name, but will have an autocomplete window to aid you. The window will " + - "list all the tags that the guild has. From there you can enter a new name, title " + - "or value. None of these are mandatory.\n`/tag-list`\nDisplays a paginated list " + - "of all tags for this guild. There are 10 tags on each page.\n\n**Guilds can " + - "have any number of tags they like.** The limit on `tagValue` for tags is 1024 " + - "characters, which is the embed description limit enforced by Discord." + title = Translations.Utility.Tags.TagHelp.title.translate() + description = Translations.Utility.Tags.TagHelp.content.translate() color = DISCORD_BLURPLE timestamp = Clock.System.now() } @@ -235,8 +205,8 @@ class Tags : Extension() { * @since 3.1.0 */ ephemeralSlashCommand(::CreateTagArgs) { - name = "tag-create" - description = "Create a tag for your guild! Use /tag-help for more info." + name = Translations.Utility.Tags.Create.name + description = Translations.Utility.Tags.Create.description requirePermission(Permission.ModerateMembers) @@ -248,23 +218,17 @@ class Tags : Extension() { } action { + val translations = Translations.Utility.Tags.Create if (TagsCollection().getTag(guild!!.id, arguments.tagName) != null) { - respond { content = "A tag with that name already exists in this guild." } + respond { content = translations.already.translate() } return@action } if (arguments.tagValue.length > 4096) { - respond { - content = - "That tag's body is too long! Due to Discord limitations tag bodies can only be " + - "4096 characters or less!" - } + respond { content = Translations.Utility.Tags.bodyTooLong.translate() } return@action } else if (arguments.tagTitle.length > 256) { - respond { - content = "That tag's title is too long! Due to Discord limitations tag titles can only be " + - "256 characters or less" - } + respond { content = Translations.Utility.Tags.tooLongTitle.translate() } } TagsCollection().setTag( @@ -277,17 +241,18 @@ class Tags : Extension() { val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) ?: return@action + // TODO Do if inside builder to reduce duplication, not a translations PR thing if (arguments.tagValue.length <= 1024) { utilityLog.createEmbed { - title = "Tag created!" - description = "The tag `${arguments.tagName}` has been created" + title = translations.embedTitle.translate() + description = translations.embedDesc.translate(arguments.tagName) field { - name = "Tag title:" + name = translations.embedTagTitle.translate() value = "`${arguments.tagTitle}`" inline = false } field { - name = "Tag value:" + name = translations.embedTagValue.translate() value = "```${arguments.tagValue}```" inline = false } @@ -296,15 +261,15 @@ class Tags : Extension() { } else { utilityLog.createMessage { embed { - title = "Tag created!" - description = "The tag `${arguments.tagName}` has been created" + title = translations.embedTitle.translate() + description = translations.embedDesc.translate(arguments.tagName) field { - name = "Tag title:" + name = translations.embedTagTitle.translate() value = "`${arguments.tagTitle}`" inline = false } field { - name = "Tag value:" + name = translations.embedTagValue.translate() value = "${arguments.tagValue.trimmedContents(1024)}" inline = false } @@ -318,7 +283,7 @@ class Tags : Extension() { } respond { - content = "Tag: `${arguments.tagName}` created" + content = translations.response.translate(arguments.tagName) } } } @@ -330,8 +295,8 @@ class Tags : Extension() { * @since 3.1.0 */ ephemeralSlashCommand(::DeleteTagArgs) { - name = "tag-delete" - description = "Delete a tag from your guild. Use /tag-help for more info." + name = Translations.Utility.Tags.Delete.name + description = Translations.Utility.Tags.Delete.description requirePermission(Permission.ModerateMembers) @@ -343,10 +308,11 @@ class Tags : Extension() { } action { + val translations = Translations.Utility.Tags.Delete // Check to make sure the tag exists in the database if (TagsCollection().getTag(guild!!.id, arguments.tagName)?.name == null) { respond { - content = "Unable to find tag `${arguments.tagName}`! Does this tag exist?" + content = Translations.Utility.Tags.unableToFind.translate(arguments.tagName) } return@action } @@ -356,23 +322,23 @@ class Tags : Extension() { val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) ?: return@action utilityLog.createEmbed { - title = "Tag deleted!" - description = "The tag ${arguments.tagName} was deleted" + title = translations.embedTitle.translate() + description = translations.embedDesc.translate() footer { - text = user.asUserOrNull()?.username ?: "Unable to get user tag" + text = user.asUserOrNull()?.username ?: Translations.Basic.UnableTo.tag.translate() icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } color = DISCORD_RED } respond { - content = "Tag: `${arguments.tagName}` deleted" + content = translations.response.translate(arguments.tagName) } } } ephemeralSlashCommand(::EditTagArgs) { - name = "tag-edit" - description = "Edit a tag in your guild. Use /tag-help for more info." + name = Translations.Utility.Tags.Edit.name + description = Translations.Utility.Tags.Edit.description requirePermission(Permission.ModerateMembers) @@ -384,29 +350,24 @@ class Tags : Extension() { } action { + val translations = Translations.Utility.Tags.Edit val originalTag = TagsCollection().getTag(guild!!.id, arguments.tagName) if (originalTag == null) { - respond { content = "Unable to find tag `${arguments.tagName}`! Does this tag exist?" } + respond { content = Translations.Utility.Tags.unableToFind.translate() } return@action } + // TODO Why was this ever done, this can probably be yeeted val originalName = originalTag.name val originalTitle = originalTag.tagTitle val originalValue = originalTag.tagValue val originalAppearance = originalTag.tagAppearance if (arguments.newValue != null && arguments.newValue!!.length > 4096) { - respond { - content = - "That tag's body is too long! Due to Discord limitations tag bodies can only be " + - "4096 characters or less!" - } + respond { content = Translations.Utility.Tags.bodyTooLong.translate() } return@action } else if (arguments.newTitle != null && arguments.newTitle!!.length > 256) { - respond { - content = "That tag's title is too long! Due to Discord limitations tag titles can only be " + - "256 characters or less" - } + respond { content = Translations.Utility.Tags.tooLongTitle.translate() } } TagsCollection().removeTag(guild!!.id, arguments.tagName) @@ -420,16 +381,16 @@ class Tags : Extension() { ) respond { - content = "Tag edited!" + content = translations.response.translate() } val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) ?: return@action utilityLog.createEmbed { - title = "Tag Edited" - description = "The tag `${arguments.tagName}` was edited" + title = translations.response.translate() + description = translations.embedDesc.translate(arguments.tagName) field { - name = "Name" + name = translations.embedName.translate() value = if (arguments.newName.isNullOrEmpty()) { originalName } else { @@ -437,7 +398,7 @@ class Tags : Extension() { } } field { - name = "Title" + name = translations.embedTitleField.translate() value = if (arguments.newTitle.isNullOrEmpty()) { originalTitle } else { @@ -445,7 +406,7 @@ class Tags : Extension() { } } field { - name = "Tag appearance" + name = Translations.Utility.Tags.tagAppearance.translate() value = if (arguments.newAppearance.isNullOrEmpty()) { originalAppearance } else { @@ -456,10 +417,10 @@ class Tags : Extension() { } if (arguments.newValue.isNullOrEmpty()) { utilityLog.createEmbed { - title = "Value" + title = translations.embedValue.translate() description = originalValue footer { - text = "Edited by ${user.asUserOrNull()?.username}" + text = translations.editedBy.translate(user.asUserOrNull()?.username) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } timestamp = Clock.System.now() @@ -467,15 +428,15 @@ class Tags : Extension() { } } else { utilityLog.createEmbed { - title = "Old value" + title = translations.oldValue.translate() description = originalValue color = DISCORD_YELLOW } utilityLog.createEmbed { - title = "New value" + title = translations.newValue.translate() description = arguments.newValue footer { - text = "Edited by ${user.asUserOrNull()?.username}" + text = translations.editedBy.translate(user.asUserOrNull()?.username) icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } timestamp = Clock.System.now() @@ -486,8 +447,8 @@ class Tags : Extension() { } ephemeralSlashCommand { - name = "tag-list" - description = "List all tags for this guild" + name = Translations.Utility.Tags.List.name + description = Translations.Utility.Tags.List.description check { anyGuild() @@ -499,10 +460,12 @@ class Tags : Extension() { val pagesObj = Pages() val tags = TagsCollection().getAllTags(guild!!.id) + val translations = Translations.Utility.Tags.List + if (tags.isEmpty()) { pagesObj.addPage( Page { - description = "There are no tags for this guild." + description = translations.noTags.translate() } ) } else { @@ -522,10 +485,10 @@ class Tags : Extension() { } pagesObj.addPage( Page { - title = "Tags for this guild" - description = "Here are all the tags for this guild" + title = translations.pageTitle.translate() + description = translations.pageDesc.translate() field { - name = "Name | Title" + name = translations.pageValue.translate() value = response } } @@ -548,12 +511,12 @@ class Tags : Extension() { private suspend fun EmbedBuilder.appearanceFooter(tagAppearance: String, user: UserBehavior) { field { - name = "Tag appearance" + name = Translations.Utility.Tags.tagAppearance.translate() value = tagAppearance } footer { icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() - text = "Requested by ${user.asUserOrNull()?.username}" + text = Translations.Basic.requestedBy.translate(user.asUserOrNull()?.username) } timestamp = Clock.System.now() color = DISCORD_GREEN @@ -562,8 +525,8 @@ class Tags : Extension() { inner class CallTagArgs : Arguments() { /** The named identifier of the tag the user would like. */ val tagName by string { - name = "name" - description = "The name of the tag you want to call" + name = Translations.Utility.Tags.Tag.Arguments.Name.name + description = Translations.Utility.Tags.Tag.Arguments.Name.description autoComplete { val tags = TagsCollection().getAllTags(data.guildId.value!!) @@ -578,15 +541,15 @@ class Tags : Extension() { } val user by optionalUser { - name = "user" - description = "The user to mention with the tag (optional)" + name = Translations.Utility.Tags.Tag.Arguments.User.name + description = Translations.Utility.Tags.Tag.Arguments.User.description } } inner class DeleteTagArgs : Arguments() { val tagName by string { - name = "name" - description = "The name of the tag" + name = Translations.Utility.Tags.Tag.Arguments.Name.name + description = Translations.Utility.Tags.Tag.Arguments.Name.description autoComplete { val tags = TagsCollection().getAllTags(data.guildId.value!!) @@ -606,20 +569,20 @@ class Tags : Extension() { inner class CreateTagArgs : Arguments() { /** The named identifier of the tag being created. */ val tagName by string { - name = "name" - description = "The name of the tag you're making" + name = Translations.Utility.Tags.Tag.Arguments.Name.name + description = Translations.Utility.Tags.Tag.Arguments.Name.description } /** The title of the tag being created. */ val tagTitle by string { - name = "title" - description = "The title of the tag embed you're making" + name = Translations.Utility.Tags.Create.Arguments.Title.name + description = Translations.Utility.Tags.Create.Arguments.Title.description } /** The value of the tag being created. */ val tagValue by string { - name = "value" - description = "The content of the tag embed you're making" + name = Translations.Utility.Tags.Create.Arguments.Content.name + description = Translations.Utility.Tags.Create.Arguments.Content.description // Fix newline escape characters mutate { @@ -630,17 +593,20 @@ class Tags : Extension() { } val tagAppearance by stringChoice { - name = "appearance" - description = "The appearance of the tag embed you're making" - choices = mutableMapOf("embed" to "embed", "message" to "message") + name = Translations.Utility.Tags.Create.Arguments.Appearance.name + description = Translations.Utility.Tags.Create.Arguments.Appearance.description + choices = mutableMapOf( + Translations.Utility.Tags.Create.Arguments.Appearance.Choice.embed to "embed", + Translations.Utility.Tags.Create.Arguments.Appearance.Choice.message to "message" + ) } } inner class EditTagArgs : Arguments() { /** The named identifier of the tag being edited. */ val tagName by string { - name = "name" - description = "The name of the tag you're editing" + name = Translations.Utility.Tags.Tag.Arguments.Name.name + description = Translations.Utility.Tags.Tag.Arguments.Name.description autoComplete { val tags = TagsCollection().getAllTags(data.guildId.value!!) @@ -658,20 +624,20 @@ class Tags : Extension() { /** The new name for the tag being edited. */ val newName by optionalString { - name = "new-name" - description = "The new name for the tag you're editing" + name = Translations.Utility.Tags.Edit.Arguments.NewName.name + description = Translations.Utility.Tags.Edit.Arguments.NewName.description } /** The new title for the tag being edited. */ val newTitle by optionalString { - name = "new-title" - description = "The new title for the tag you're editing" + name = Translations.Utility.Tags.Edit.Arguments.NewTitle.name + description = Translations.Utility.Tags.Edit.Arguments.NewTitle.description } /** The new value for the tag being edited. */ val newValue by optionalString { - name = "new-value" - description = "The new value for the tag you're editing" + name = Translations.Utility.Tags.Edit.Arguments.NewValue.name + description = Translations.Utility.Tags.Edit.Arguments.NewValue.description mutate { it?.replace("\\n", "\n") @@ -682,9 +648,12 @@ class Tags : Extension() { /** The new appearance for the tag being edited. */ val newAppearance by optionalStringChoice { - name = "new-appearance" - description = "The new appearance for the tag you're editing" - choices = mutableMapOf("embed" to "embed", "message" to "message") + name = Translations.Utility.Tags.Edit.Arguments.NewAppearance.name + description = Translations.Utility.Tags.Edit.Arguments.NewAppearance.description + choices = mutableMapOf( + Translations.Utility.Tags.Create.Arguments.Appearance.Choice.embed to "embed", + Translations.Utility.Tags.Create.Arguments.Appearance.Choice.message to "message" + ) } } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/config/UtilityArgs.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/config/UtilityArgs.kt index f150193a..72a56d98 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/config/UtilityArgs.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/config/UtilityArgs.kt @@ -3,30 +3,31 @@ package org.hyacinthbots.lilybot.extensions.utility.config import dev.kordex.core.commands.Arguments import dev.kordex.core.commands.converters.impl.defaultingBoolean import dev.kordex.core.commands.converters.impl.optionalChannel +import lilybot.i18n.Translations class UtilityArgs : Arguments() { val utilityLogChannel by optionalChannel { - name = "utility-log" - description = "The channel to log various utility actions too." + name = Translations.Config.Arguments.Utility.UtilityLog.name + description = Translations.Config.Arguments.Utility.UtilityLog.description } val logChannelUpdates by defaultingBoolean { - name = "log-channel-updates" - description = "Whether to log changes made to channels in this guild." + name = Translations.Config.Arguments.Utility.ChannelUpdates.name + description = Translations.Config.Arguments.Utility.ChannelUpdates.description defaultValue = false } val logEventUpdates by defaultingBoolean { - name = "log-event-updates" - description = "Whether to log changes made to scheduled events in this guild." + name = Translations.Config.Arguments.Utility.EventUpdates.name + description = Translations.Config.Arguments.Utility.EventUpdates.description defaultValue = false } val logInviteUpdates by defaultingBoolean { - name = "log-invite-updates" - description = "Whether to log changes made to invites in this guild." + name = Translations.Config.Arguments.Utility.InviteUpdates.name + description = Translations.Config.Arguments.Utility.InviteUpdates.description defaultValue = false } val logRoleUpdates by defaultingBoolean { - name = "log-role-updates" - description = "Whether to log changes made to roles in this guild." + name = Translations.Config.Arguments.Utility.RoleUpdates.name + description = Translations.Config.Arguments.Utility.RoleUpdates.description defaultValue = false } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/config/UtilityCommand.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/config/UtilityCommand.kt index f3f77feb..44a52e97 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/config/UtilityCommand.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/config/UtilityCommand.kt @@ -10,13 +10,14 @@ import dev.kordex.core.checks.hasPermission import dev.kordex.core.commands.application.slash.SlashCommand import dev.kordex.core.commands.application.slash.ephemeralSubCommand import dev.kordex.core.utils.botHasPermissions +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection import org.hyacinthbots.lilybot.database.entities.UtilityConfigData import org.hyacinthbots.lilybot.extensions.config.utils.utilityEmbed suspend fun SlashCommand<*, *, *>.utilityCommand() = ephemeralSubCommand(::UtilityArgs) { - name = "utility" - description = "Configure Lily's utility settings" + name = Translations.Config.Utility.name + description = Translations.Config.Utility.description requirePermission(Permission.ManageGuild) @@ -30,8 +31,7 @@ suspend fun SlashCommand<*, *, *>.utilityCommand() = ephemeralSubCommand(::Utili if (utilityConfig != null) { respond { - content = "You already have a utility configuration set. " + - "Please clear it before attempting to set a new one." + content = Translations.Config.configAlreadyExists.translate("utility") } return@action } @@ -41,8 +41,7 @@ suspend fun SlashCommand<*, *, *>.utilityCommand() = ephemeralSubCommand(::Utili utilityLog = guild!!.getChannelOfOrNull(arguments.utilityLogChannel!!.id) if (utilityLog?.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) != true) { respond { - content = "The utility log you've selected is invalid, or I can't view it. " + - "Please attempt to resolve this and try again." + content = Translations.Config.invalidChannel.translate("utility") } return@action } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/events/UtilityEvents.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/events/UtilityEvents.kt index 59538182..8e9914a5 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/events/UtilityEvents.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/utility/events/UtilityEvents.kt @@ -33,9 +33,11 @@ import dev.kordex.core.DISCORD_YELLOW import dev.kordex.core.checks.anyGuild import dev.kordex.core.extensions.Extension import dev.kordex.core.extensions.event +import dev.kordex.core.i18n.types.Key import dev.kordex.core.time.TimestampType import dev.kordex.core.time.toDiscord import kotlinx.datetime.Clock +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.utils.afterDot @@ -54,6 +56,8 @@ class UtilityEvents : Extension() { override val name: String = "utility-events" override suspend fun setup() { + val translations = Translations.Utility.UtilityEvents + event<ChannelCreateEvent> { check { failIf { @@ -70,19 +74,20 @@ class UtilityEvents : Extension() { var allowed = perms.getValue(ALLOWED) var denied = perms.getValue(DENIED) - if (allowed.isBlank()) allowed = "None overrides set" - if (denied.isBlank()) denied = "None overrides set" + if (allowed.isBlank()) allowed = translations.noOverrides.translate() + if (denied.isBlank()) denied = translations.noOverrides.translate() getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild!!)?.createEmbed { - title = "${writeChannelType(event.channel.type)} Created" - description = "${event.channel.mention} (${event.channel.data.name.value}) was created." + title = translations.createTitle.translate(writeChannelType(event.channel.type)) + description = + translations.createDesc.translate(event.channel.mention, event.channel.data.name.value) field { - name = "Allowed Permissions" + name = translations.allowed.translate() value = allowed inline = true } field { - name = "Denied Permissions" + name = translations.denied.translate() value = denied inline = true } @@ -98,8 +103,8 @@ class UtilityEvents : Extension() { if (guildId?.let { UtilityConfigCollection().getConfig(it)?.logChannelUpdates } == false) return@action val guild = guildId?.let { GuildBehavior(it, event.kord) } getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild!!)?.createEmbed { - title = "${writeChannelType(event.channel.type)} Deleted" - description = "`${event.channel.data.name.value}` was deleted." + title = translations.deleteTitle.translate(writeChannelType(event.channel.type)) + description = translations.deleteDesc.translate(event.channel.data.name.value) timestamp = Clock.System.now() color = DISCORD_RED } @@ -137,96 +142,100 @@ class UtilityEvents : Extension() { } getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild!!)?.createEmbed { - title = "${writeChannelType(event.channel.type)} Updated" + title = translations.updateTitle.translate(writeChannelType(event.channel.type)) oldNewEmbedField( - "Type Change", writeChannelType(event.old?.type), writeChannelType(event.channel.type) + translations.typeChange, writeChannelType(event.old?.type), writeChannelType(event.channel.type) ) - oldNewEmbedField("Name Change", oldData?.name?.value, newData.name.value) - oldNewEmbedField("Topic Changed", oldData?.topic?.value, newData.topic.value) + oldNewEmbedField(translations.nameChange, oldData?.name?.value, newData.name.value) + oldNewEmbedField(translations.topicChange, oldData?.topic?.value, newData.topic.value) oldNewEmbedField( - "Parent Category Change", + translations.parentChange, kord.getChannelOf<Category>(oldData?.parentId.value!!)?.mention, kord.getChannelOf<Category>(newData.parentId.value!!)?.mention ) if (event.channel.data.nsfw != event.old?.data?.nsfw) { field { - name = "NSFW Setting" + name = translations.nsfwChange.translate() value = event.channel.data.nsfw.discordBoolean.toString() } } - oldNewEmbedField("Position Changed", oldData?.position.value, newData.position.value) + oldNewEmbedField(translations.positionChanged, oldData?.position.value, newData.position.value) oldNewEmbedField( - "Slowmode time changed", + translations.slowmodeChange, oldData?.rateLimitPerUser?.value?.toString() ?: "0", newData.rateLimitPerUser.value?.toString() ?: "0" ) - oldNewEmbedField("Bitrate changed", oldData?.bitrate.value, newData.bitrate.value) - oldNewEmbedField("User limit changed", oldData?.userLimit.value ?: 0, newData.userLimit.value ?: 0) + oldNewEmbedField(translations.bitrateChange, oldData?.bitrate.value, newData.bitrate.value) + oldNewEmbedField( + translations.userlimitChange, + oldData?.userLimit.value ?: 0, + newData.userLimit.value ?: 0 + ) oldNewEmbedField( - "Region Changed", + translations.regionChanged, oldData?.rtcRegion?.value ?: "Automatic", newData.rtcRegion.value ?: "Automatic" ) oldNewEmbedField( - "Video Quality Changed", + translations.videoQualityChanged, oldData?.videoQualityMode?.value.afterDot(), newData.videoQualityMode.value.afterDot() ) oldNewEmbedField( - "Default Auto-Archive Duration", + translations.defaultAutoChanged, oldData?.defaultAutoArchiveDuration?.value?.duration.toString(), newData.defaultAutoArchiveDuration.value?.duration.toString() ) oldNewEmbedField( - "Default Sort Changed", + translations.defaultSortChanged, oldData?.defaultSortOrder?.value.afterDot(), newData.defaultSortOrder.value.afterDot() ) oldNewEmbedField( - "Default Layout Changed", + translations.defaultLayoutChanged, oldData?.defaultForumLayout?.value.afterDot(), newData.defaultForumLayout.value.afterDot() ) oldNewEmbedField( - "Available tags Changed", + translations.availableTagsChanged, formatAvailableTags(oldData?.availableTags?.value), formatAvailableTags(newData.availableTags.value) ) oldNewEmbedField( - "Applied tags Changed", + translations.appliedTagsChanged, oldAppliedTags.joinToString(", "), newAppliedTags.joinToString(", ") ) oldNewEmbedField( - "Default Reaction Emoji Changed", + translations.defaultReactionChanged, oldData?.defaultReactionEmoji?.value?.emojiName, newData.defaultReactionEmoji.value?.emojiName ) oldNewEmbedField( - "Default Thread Slowmode Changed", + translations.defaultThreadChanged, oldData?.defaultThreadRateLimitPerUser?.value.toString(), newData.defaultThreadRateLimitPerUser.value.toString() ) if (oldAllowed != newAllowed) { field { - name = "New Allowed Permissions" + name = translations.newAllowed.translate() value = newAllowed inline = true } field { - name = "Old Allowed Permissions" + name = translations.oldAllowed.translate() value = oldAllowed inline = true } } if (oldDenied != newDenied) { field { - name = "New Denied Permissions" + name = translations.newDenied.translate() value = newDenied inline = false } field { - name = "Old Denied Permissions" + name = translations.oldDenied.translate() value = oldDenied inline = true } @@ -243,8 +252,8 @@ class UtilityEvents : Extension() { if (UtilityConfigCollection().getConfig(event.guildId)?.logEventUpdates == false) return@action val guild = GuildBehavior(event.guildId, kord) getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild)?.createEmbed { - title = "Scheduled Event Created!" - description = "An event has been created!" + title = translations.scheduledCreateTitle.translate() + description = translations.scheduledCreateDesc.translate() guildEventEmbed(event, guild) // This appears to exist in the front end but the api doesn't have it anywhere, api payloads contain // a recurrence field that would fill this but the api doesn't mention it @@ -253,12 +262,12 @@ class UtilityEvents : Extension() { // value = event.scheduledEvent. // } field { - name = "Guild Members only" + name = translations.guildMembersOnly.translate() value = if (event.scheduledEvent.privacyLevel == GuildScheduledEventPrivacyLevel.GuildOnly) { - "True" + Translations.Basic.`true` } else { - "False" - } + Translations.Basic.`false` + }.translate() } color = DISCORD_GREEN footer { @@ -277,8 +286,8 @@ class UtilityEvents : Extension() { if (UtilityConfigCollection().getConfig(event.guildId)?.logEventUpdates == false) return@action val guild = GuildBehavior(event.guildId, kord) getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild)?.createEmbed { - title = "Scheduled Event Deleted!" - description = "An event has been deleted" + title = translations.scheduledDeleteTitle.translate() + description = translations.scheduledDeleteDesc.translate() guildEventEmbed(event, guild) color = DISCORD_RED footer { @@ -299,17 +308,19 @@ class UtilityEvents : Extension() { val oldEvent = event.oldEvent val newEvent = event.scheduledEvent getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild)?.createEmbed { - title = "Scheduled Event Updated!" - description = "An event has been updated" - oldNewEmbedField("Name Changed", oldEvent?.name, newEvent.name) - oldNewEmbedField("Description Changed", oldEvent?.description, newEvent.description) + title = translations.scheduledUpdateTitle.translate() + description = translations.scheduledUpdateDesc.translate() + oldNewEmbedField(translations.nameChange, oldEvent?.name, newEvent.name) + oldNewEmbedField(translations.descriptionChange, oldEvent?.description, newEvent.description) oldNewEmbedField( - "Location Changed", - oldEvent?.channelId?.let { guild.getChannelOrNull(it) }?.mention ?: "Unable to get channel", - newEvent.channelId?.let { guild.getChannelOrNull(it) }?.mention ?: "Unable to get channel" + translations.locationChange, + oldEvent?.channelId?.let { guild.getChannelOrNull(it) }?.mention + ?: Translations.Basic.UnableTo.channel.translate(), + newEvent.channelId?.let { guild.getChannelOrNull(it) }?.mention + ?: Translations.Basic.UnableTo.channel.translate() ) oldNewEmbedField( - "Start time changed", + translations.startChanged, oldEvent?.scheduledStartTime?.toDiscord(TimestampType.ShortDateTime), newEvent.scheduledStartTime.toDiscord(TimestampType.ShortDateTime) ) @@ -324,30 +335,30 @@ class UtilityEvents : Extension() { if (event.guildId?.let { UtilityConfigCollection().getConfig(it) }?.logInviteUpdates == false) return@action val guild = event.guildId?.let { GuildBehavior(it, kord) } ?: return@action getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild)?.createEmbed { - title = "Invite link created" - description = "An invite has been created" + title = translations.inviteCreateTitle.translate() + description = translations.inviteCreateDesc.translate() field { - name = "Code" + name = translations.code.translate() value = event.code } field { - name = "Target Channel" + name = translations.targetChannel.translate() value = event.channel.mention } field { - name = "Max uses" + name = translations.maxUses.translate() value = event.maxUses.toString() } field { - name = "Duration of Invite" + name = translations.duration.translate() value = event.maxAge.toIsoString() } field { - name = "Temporary Membership invite" + name = translations.tempMembershipInvite.translate() value = event.isTemporary.toString() } footer { - text = "Created by ${event.getInviterAsMemberOrNull()?.mention}" + text = Translations.Basic.createdBy.translate(event.getInviterAsMemberOrNull()?.mention) icon = event.getInviterAsMemberOrNull()?.avatar?.cdnUrl?.toUrl() } timestamp = Clock.System.now() @@ -362,14 +373,14 @@ class UtilityEvents : Extension() { if (event.guildId?.let { UtilityConfigCollection().getConfig(it) }?.logInviteUpdates == false) return@action val guild = event.guildId?.let { GuildBehavior(it, kord) } ?: return@action getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild)?.createEmbed { - title = "Invite link deleted" - description = "An invite has been deleted" + title = translations.inviteDeleteTitle.translate() + description = translations.inviteDeleteDesc.translate() field { - name = "Code" + name = translations.code.translate() value = event.code } field { - name = "Target Channel" + name = translations.targetChannel.translate() value = event.channel.mention } timestamp = Clock.System.now() @@ -383,50 +394,50 @@ class UtilityEvents : Extension() { if (UtilityConfigCollection().getConfig(event.guildId)?.logRoleUpdates == false) return@action val guild = GuildBehavior(event.guildId, kord) getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild)?.createEmbed { - title = "Created a Role" - description = "A new role has been created" + title = translations.createdRoleTitle.translate() + description = translations.createdRoleDesc.translate() field { - name = "Role name" + name = translations.roleName.translate() value = event.role.name inline = true } field { - name = "Role Color" + name = translations.roleColor.translate() value = event.role.color.rgb.toString() inline = true } field { - name = "Position" + name = translations.rolePosition.translate() value = event.role.rawPosition.toString() inline = true } field { - name = "Display separately?" + name = translations.roleDisplaySep.translate() value = event.role.hoisted.toString() inline = true } field { - name = "Mentionable" + name = translations.roleMention.translate() value = event.role.mentionable.toString() inline = true } field { - name = "Icon" - value = event.role.icon?.cdnUrl?.toUrl() ?: "No icon" + name = translations.roleIcon.translate() + value = event.role.icon?.cdnUrl?.toUrl() ?: translations.noRoleIcon.translate() inline = true } field { - name = "Emoji" - value = event.role.unicodeEmoji ?: "No emoji" + name = translations.emoji.translate() + value = event.role.unicodeEmoji ?: translations.noEmoji.translate() inline = true } field { - name = "Managed by integration?" + name = translations.managed.translate() value = event.role.managed.toString() inline = true } field { - name = "Permissions" + name = translations.permissions.translate() value = formatPermissionSet(event.role.permissions) } color = DISCORD_GREEN @@ -440,11 +451,11 @@ class UtilityEvents : Extension() { if (UtilityConfigCollection().getConfig(event.guildId)?.logRoleUpdates == false) return@action val guild = GuildBehavior(event.guildId, kord) getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild)?.createEmbed { - title = "Role deleted" - description = "A role has been deleted" + title = translations.deletedRoleTitle.translate() + description = translations.deletedRoleDesc.translate() field { - name = "Role name" - value = event.role?.name ?: "Unable to get name" + name = translations.roleName.translate() + value = event.role?.name ?: translations.unableToName.translate() } color = DISCORD_RED timestamp = Clock.System.now() @@ -462,32 +473,38 @@ class UtilityEvents : Extension() { event.old?.icon == event.role.icon && event.old?.unicodeEmoji == event.role.unicodeEmoji && event.old?.permissions == event.role.permissions && event.old?.color == event.role.color && ( - event.old?.managed != event.role.managed || event.old?.tags != event.role.tags || - event.old?.flags != event.role.flags - ) + event.old?.managed != event.role.managed || event.old?.tags != event.role.tags || + event.old?.flags != event.role.flags + ) ) { - return@action - } + return@action + } val guild = GuildBehavior(event.guildId, kord) val channel = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, guild) channel?.createMessage { embed { - title = "Updated a Role" - description = event.role.mention + "has been updated" - oldNewEmbedField("Name changed", event.old?.name, event.role.name) - oldNewEmbedField("Display separately setting changed", event.old?.hoisted, event.role.hoisted) - oldNewEmbedField("Mentionable setting changed", event.old?.mentionable, event.role.mentionable) - oldNewEmbedField("Position changed", event.old?.getPosition(), event.role.getPosition()) + title = translations.updatedRoleTitle.translate() + description = event.role.mention + translations.updatedRoleDesc.translate() + oldNewEmbedField(translations.nameChange, event.old?.name, event.role.name) + oldNewEmbedField(translations.displaySepChanged, event.old?.hoisted, event.role.hoisted) + oldNewEmbedField(translations.mentionChanged, event.old?.mentionable, event.role.mentionable) oldNewEmbedField( - "Icon changed", - event.old?.icon?.cdnUrl?.toUrl() ?: "No icon", - event.role.icon?.cdnUrl?.toUrl() ?: "No icon" + translations.positionChanged, + event.old?.getPosition(), + event.role.getPosition() ) oldNewEmbedField( - "Emoji changed", event.old?.unicodeEmoji ?: "No icon", event.role.unicodeEmoji ?: "No icon" + translations.iconChanged, + event.old?.icon?.cdnUrl?.toUrl() ?: translations.noRoleIcon.translate(), + event.role.icon?.cdnUrl?.toUrl() ?: translations.noRoleIcon.translate() ) oldNewEmbedField( - "Permissions changed", + translations.emojiChanged, + event.old?.unicodeEmoji ?: translations.noRoleIcon.translate(), + event.role.unicodeEmoji ?: translations.noRoleIcon.translate() + ) + oldNewEmbedField( + translations.permissionsChanged, event.old?.permissions?.let { formatPermissionSet(it) } ?: "Unable to get permissions", formatPermissionSet(event.role.permissions) ) @@ -496,11 +513,11 @@ class UtilityEvents : Extension() { } if (event.old?.color != event.role.color) { embed { - description = "Old color" + description = translations.oldColor.translate() color = if (event.old?.color?.rgb != 0) event.old?.color else null } embed { - description = "New color" + description = translations.newColor.translate() color = event.role.color } } @@ -519,19 +536,20 @@ class UtilityEvents : Extension() { } getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, event.channel.guild)?.createEmbed { - title = "${writeChannelType(event.channel.type)} Created" - description = "${event.channel.mention} (${event.channel.data.name.value}) was created." + title = translations.createTitle.translate(writeChannelType(event.channel.type)) + description = + translations.createDesc.translate(event.channel.mention, event.channel.data.name.value) field { - name = "Parent Channel" + name = translations.parentChannel.translate() value = "${event.channel.parent.mention} (`${event.channel.parent.asChannelOrNull()?.name}`)" } field { - name = "Archive duration" + name = translations.archiveDuration.translate() value = event.channel.autoArchiveDuration.duration.toString() } if (appliedTags.isNotEmpty()) { field { - name = "Applied tags" + name = translations.appliedTags.translate() value = appliedTags.joinToString(", ") } } @@ -546,8 +564,8 @@ class UtilityEvents : Extension() { // Do not log if the channel logging option is false if (UtilityConfigCollection().getConfig(event.channel.guild.id)?.logChannelUpdates == false) return@action getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, event.channel.guild)?.createEmbed { - title = "${writeChannelType(event.channel.type)} Deleted" - description = "`${event.channel.data.name.value}` was deleted." + title = translations.deleteTitle.translate(writeChannelType(event.channel.type)) + description = translations.deleteDesc.translate(event.channel.data.name.value) timestamp = Clock.System.now() color = DISCORD_RED } @@ -567,11 +585,12 @@ class UtilityEvents : Extension() { * @author NoComment1105 * @since 5.0.0 */ - private fun EmbedBuilder.oldNewEmbedField(detailName: String, oldValue: String?, newValue: String?) { + private fun EmbedBuilder.oldNewEmbedField(detailName: Key, oldValue: String?, newValue: String?) { if (newValue != oldValue) { field { - name = detailName - value = "Old: $oldValue\nNew: $newValue" + name = detailName.translate() + value = + "${Translations.Basic.old.translate()}: $oldValue\n${Translations.Basic.new.translate()}: $newValue" } } } @@ -583,7 +602,7 @@ class UtilityEvents : Extension() { * @author NoComment1105 * @since 5.0.0 */ - private fun EmbedBuilder.oldNewEmbedField(detailName: String, oldValue: Int?, newValue: Int?) = + private fun EmbedBuilder.oldNewEmbedField(detailName: Key, oldValue: Int?, newValue: Int?) = oldNewEmbedField(detailName, oldValue.toString(), newValue.toString()) /** @@ -593,7 +612,7 @@ class UtilityEvents : Extension() { * @author NoComment1105 * @since 5.0.0 */ - private fun EmbedBuilder.oldNewEmbedField(detailName: String, oldValue: Boolean?, newValue: Boolean?) = + private fun EmbedBuilder.oldNewEmbedField(detailName: Key, oldValue: Boolean?, newValue: Boolean?) = oldNewEmbedField(detailName, oldValue.toString(), newValue.toString()) /** @@ -605,16 +624,16 @@ class UtilityEvents : Extension() { * @since 5.0.0 */ private fun writeChannelType(type: ChannelType?): String? = when (type) { - ChannelType.GuildCategory -> "Category" - ChannelType.GuildNews -> "Announcement Channel" - ChannelType.GuildForum -> "Forum Channel" - ChannelType.GuildStageVoice -> "Stage Channel" - ChannelType.GuildText -> "Text Channel" - ChannelType.GuildVoice -> "Voice Channel" - ChannelType.PublicGuildThread -> "Thread" - ChannelType.PrivateThread -> "Private Thread" + ChannelType.GuildCategory -> Translations.Utility.UtilityEvents.Channel.category + ChannelType.GuildNews -> Translations.Utility.UtilityEvents.Channel.announcement + ChannelType.GuildForum -> Translations.Utility.UtilityEvents.Channel.forum + ChannelType.GuildStageVoice -> Translations.Utility.UtilityEvents.Channel.stage + ChannelType.GuildText -> Translations.Utility.UtilityEvents.Channel.text + ChannelType.GuildVoice -> Translations.Utility.UtilityEvents.Channel.voice + ChannelType.PublicGuildThread -> Translations.Utility.UtilityEvents.Channel.thread + ChannelType.PrivateThread -> Translations.Utility.UtilityEvents.Channel.privateThread else -> null - } + }?.translate() /** * Formats the permission overwrites for a [channel] to a string map of allowed and denied permissions. @@ -650,10 +669,12 @@ class UtilityEvents : Extension() { private fun formatAvailableTags(tagList: List<ForumTag>?): String { var tagString = "" tagList?.forEach { - tagString += "\n* Name: ${it.name}\n* Moderated: ${it.moderated}\n" + - "* Emoji: ${if (it.emojiId != null) "<!${it.emojiId}>" else it.emojiName}\n---" + tagString += "\n* ${Translations.Utility.UtilityEvents.name.translate()}: ${it.name}\n" + + "* ${Translations.Utility.UtilityEvents.moderated.translate()}: ${it.moderated}\n" + + "* ${Translations.Utility.UtilityEvents.emoji.translate()}: " + + "${if (it.emojiId != null) "<!${it.emojiId}>" else it.emojiName}\n---" } - return tagString.ifNullOrEmpty { "None" } + return tagString.ifNullOrEmpty { Translations.Basic.none.translate() } } /** @@ -665,26 +686,27 @@ class UtilityEvents : Extension() { * @since 5.0.0 */ private suspend fun EmbedBuilder.guildEventEmbed(event: GuildScheduledEventEvent, guild: GuildBehavior) { + val translations = Translations.Utility.UtilityEvents field { - name = "Event Name" + name = translations.eventName.translate() value = event.scheduledEvent.name } field { - name = "Event Description" - value = event.scheduledEvent.description ?: "No description provided" + name = translations.eventDesc.translate() + value = event.scheduledEvent.description ?: translations.eventDescNone.translate() } field { - name = "Event location" + name = translations.eventLocation.translate() value = if (event.scheduledEvent.channelId != null) { - guild.getChannelOrNull(event.scheduledEvent.channelId!!)?.mention ?: "Unable to get channel" + guild.getChannelOrNull(event.scheduledEvent.channelId!!)?.mention ?: Translations.Basic.UnableTo.channel.translate() } else { - "External event, no channel." + translations.externalChannel.translate() } } field { - name = "Start time" + name = translations.startTime.translate() value = event.scheduledEvent.scheduledStartTime.toDiscord(TimestampType.ShortDateTime) } - image = event.scheduledEvent.image?.cdnUrl?.toUrl().ifNullOrEmpty { "No image" } + image = event.scheduledEvent.image?.cdnUrl?.toUrl().ifNullOrEmpty { translations.noImage.translate() } } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/ResponseHelper.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/ResponseHelper.kt index 5c86d07b..dd36aa2b 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/ResponseHelper.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/ResponseHelper.kt @@ -6,6 +6,7 @@ import dev.kord.core.entity.Message import dev.kord.core.entity.User import dev.kord.rest.builder.message.EmbedBuilder import dev.kordex.modules.pluralkit.api.PKMessage +import lilybot.i18n.Translations /** * This is the base moderation embed for all moderation actions. This should be posted to the action log of a guild. @@ -19,18 +20,18 @@ import dev.kordex.modules.pluralkit.api.PKMessage */ suspend inline fun EmbedBuilder.baseModerationEmbed(reason: String?, targetUser: User?, commandUser: UserBehavior?) { field { - name = "User:" - value = "${targetUser?.username ?: "Unable to get user"}\n${targetUser?.id ?: ""}" + name = Translations.Basic.userField.translate() + value = "${targetUser?.username ?: Translations.Basic.UnableTo.findUser}\n${targetUser?.id ?: ""}" inline = false } field { - name = "Reason:" - value = reason ?: "No reason provided" + name = Translations.Moderation.ModCommands.Arguments.Reason.name.translate() + value = reason ?: Translations.Basic.noReason.translate() inline = false } if (commandUser != null) { footer { - text = "Requested by ${commandUser.asUserOrNull()?.username}" + text = Translations.Basic.requestedBy.translate(commandUser.asUserOrNull()?.username) icon = commandUser.asUserOrNull()?.avatar?.cdnUrl?.toUrl() } } @@ -47,13 +48,13 @@ suspend inline fun EmbedBuilder.baseModerationEmbed(reason: String?, targetUser: */ fun EmbedBuilder.dmNotificationStatusEmbedField(dm: Message?, override: Boolean) { field { - name = "User Notification:" + name = Translations.Utils.DmField.userNotif.translate() value = if (dm != null) { - "User notified with direct message" + Translations.Utils.DmField.success.translate() } else if (!override) { - "DM Notification Disabled" + Translations.Utils.DmField.disabled.translate() } else { - "Failed to notify user with direct message" + Translations.Utils.DmField.failure.translate() } inline = false } @@ -69,13 +70,13 @@ fun EmbedBuilder.dmNotificationStatusEmbedField(dm: Message?, override: Boolean) */ fun EmbedBuilder.dmNotificationStatusEmbedField(success: Boolean?, override: Boolean?) { field { - name = "User Notification:" + name = Translations.Utils.DmField.userNotif.translate() value = if (success != null && success) { - "User notified with direct message" + Translations.Utils.DmField.success.translate() } else if (override != null && !override) { - "DM Notification Disabled" + Translations.Utils.DmField.disabled.translate() } else { - "Failed to notify user with direct message" + Translations.Utils.DmField.failure.translate() } inline = false } @@ -94,33 +95,37 @@ suspend inline fun EmbedBuilder.attachmentsAndProxiedMessageInfo( ) { if (message.attachments.isNotEmpty()) { field { - name = "Attachments" + name = Translations.Utils.Attachments.attachments.translate() value = message.attachments.joinToString(separator = "\n") { it.url } inline = false } } if (proxiedMessage != null) { field { - name = "Message Author:" - value = "System Member: ${proxiedMessage.member?.name}\n" + - "Account: ${guild.getMemberOrNull(proxiedMessage.sender)?.username ?: "Unable to get account"} " + - guild.getMemberOrNull(proxiedMessage.sender)?.mention + name = Translations.Moderation.Report.Confirmation.embedAuthorField.translate() + value = "${Translations.Utils.Attachments.systemMember.translate()}: ${proxiedMessage.member?.name}\n" + + "${Translations.Utils.Attachments.account.translate()}: ${ + guild.getMemberOrNull(proxiedMessage.sender)?.username + ?: Translations.Utils.Attachments.unableToAccount.translate() + } " + + guild.getMemberOrNull(proxiedMessage.sender)?.mention inline = true } field { - name = "Author ID:" + name = Translations.Utils.Attachments.authorId.translate() value = proxiedMessage.sender.toString() } } else { field { - name = "Message Author:" - value = "${message.author?.username ?: "Failed to get author of message"} ${message.author?.mention ?: ""}" + name = Translations.Moderation.Report.Confirmation.embedAuthorField.translate() + value = + "${message.author?.username ?: Translations.Basic.UnableTo.findUser.translate()} ${message.author?.mention ?: ""}" inline = true } field { - name = "Author ID:" + name = Translations.Utils.Attachments.authorId.translate() value = message.author?.id.toString() } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_ConfigUtils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_ConfigUtils.kt index 2ea5b0e6..dd183fc2 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_ConfigUtils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_ConfigUtils.kt @@ -3,6 +3,7 @@ package org.hyacinthbots.lilybot.utils import dev.kord.common.entity.Snowflake import dev.kordex.core.checks.guildFor import dev.kordex.core.checks.types.CheckContext +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection @@ -21,28 +22,30 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO return } + val translations = Translations.Checks.RequiredConfigs + // Prevent commands being run in DMs, although [anyGuild] should still be used as backup - guildFor(event) ?: fail("Must be in a server") + guildFor(event) ?: fail(translations.inServer) if (configOptions.isEmpty()) { - fail("There are no config options provided in the code. Please inform the developers immediately!") + fail(translations.noConfigInCode) } val moderationConfig = ModerationConfigCollection().getConfig(guildFor(event)!!.id) if (moderationConfig == null) { - fail("Unable to access moderation config for this guild! Please inform a member of staff.") + fail(translations.cantModConfig) return } val loggingConfig = LoggingConfigCollection().getConfig(guildFor(event)!!.id) if (loggingConfig == null) { - fail("Unable to access logging config for this guild! Please inform a member of staff.") + fail(translations.cantLoggingConfig) return } val utilityConfig = UtilityConfigCollection().getConfig(guildFor(event)!!.id) if (utilityConfig == null) { - fail("Unable to access logging config for this guild! Please inform a member of staff.") + fail(translations.cantUtilityConfig) return } @@ -51,7 +54,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO when (option) { ConfigOptions.MODERATION_ENABLED -> { if (!moderationConfig.enabled) { - fail("Moderation is disabled for this guild!") + fail(translations.modDisabled) break } else { pass() @@ -60,7 +63,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO ConfigOptions.MODERATOR_ROLE -> { if (moderationConfig.role == null) { - fail("A moderator role has not been set for this guild!") + fail(translations.noModRole) break } else { pass() @@ -69,7 +72,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO ConfigOptions.ACTION_LOG -> { if (moderationConfig.channel == null) { - fail("An action log has not been set for this guild!") + fail(translations.noModLog) break } else { pass() @@ -78,7 +81,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO ConfigOptions.LOG_PUBLICLY -> { if (moderationConfig.publicLogging == null) { - fail("Public logging has not been enabled for this guild!") + fail(translations.logPubliclyOff) break } else { pass() @@ -87,7 +90,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO ConfigOptions.MESSAGE_DELETE_LOGGING_ENABLED -> { if (!loggingConfig.enableMessageDeleteLogs) { - fail("Message delete logging is disabled for this guild!") + fail(translations.noDeleteLogging) break } else { pass() @@ -96,7 +99,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO ConfigOptions.MESSAGE_EDIT_LOGGING_ENABLED -> { if (!loggingConfig.enableMessageEditLogs) { - fail("Message edit logging is disabled for this guild!") + fail(translations.noEditLogging) break } else { pass() @@ -105,7 +108,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO ConfigOptions.MESSAGE_LOG -> { if (loggingConfig.messageChannel == null) { - fail("A message log has not been set for this guild!") + fail(translations.noMessageLogging) break } else { pass() @@ -114,7 +117,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO ConfigOptions.MEMBER_LOGGING_ENABLED -> { if (!loggingConfig.enableMemberLogs) { - fail("Member logging is disabled for this guild!") + fail(translations.noMemberLogging) break } else { pass() @@ -123,7 +126,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO ConfigOptions.MEMBER_LOG -> { if (loggingConfig.memberLog == null) { - fail("A member log has not been set for this guild") + fail(translations.noMemberLogSet) break } else { pass() @@ -132,7 +135,7 @@ suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigO ConfigOptions.UTILITY_LOG -> { if (utilityConfig.utilityLogChannel == null) { - fail("A utility log has not been set for this guild") + fail(translations.noUtilityLog) break } else { pass() diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt index bbb4e912..a007255d 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_PermissionUtils.kt @@ -21,6 +21,7 @@ import dev.kordex.core.types.EphemeralInteractionContext import dev.kordex.core.utils.botHasPermissions import dev.kordex.core.utils.getTopRole import kotlinx.coroutines.flow.toList +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection @@ -66,11 +67,7 @@ suspend inline fun getLoggingChannelWithPerms( } val informChannel = getSystemChannelWithPerms(guild as Guild) ?: getFirstUsableChannel(guild) informChannel?.createMessage( - "Lily is unable to send messages in the configured " + - "${channelType.toString().lowercase()} for this guild. " + - "As a result, the corresponding config has been reset. \n\n" + - "*Note:* this channel has been used to send this message because it's the first channel " + - "in the guild Lily could use. Please inform this guild's staff about this message." + Translations.Checks.LoggingChannelPerms.cannotGet.translate(channelType.toString().lowercase()) ) } return null @@ -140,9 +137,7 @@ suspend inline fun CheckContext<*>.botHasChannelPerms(permissions: Permissions) ) { pass() } else { - fail( - "Incorrect permissions!\nI do not have the $permissionsSet permissions for ${eventChannel.mention}" - ) + fail(Translations.Checks.ChannelPerms.incorrectPerms.withOrdinalPlaceholders(permissionsSet, eventChannel.mention)) } } else if (eventChannel is NewsChannel) { if (eventChannel.asChannelOfOrNull<NewsChannel>()?.getEffectivePermissions(event.kord.selfId) @@ -150,9 +145,7 @@ suspend inline fun CheckContext<*>.botHasChannelPerms(permissions: Permissions) ) { pass() } else { - fail( - "Incorrect permissions!\nI do not have the $permissionsSet permissions for ${eventChannel.mention}" - ) + fail(Translations.Checks.ChannelPerms.incorrectPerms.withOrdinalPlaceholders(permissionsSet, eventChannel.mention)) } } else if (eventChannel is ThreadChannel) { if (eventChannel.asChannelOfOrNull<ThreadChannel>()?.getParent()?.getEffectivePermissions(event.kord.selfId) @@ -160,12 +153,10 @@ suspend inline fun CheckContext<*>.botHasChannelPerms(permissions: Permissions) ) { pass() } else { - fail( - "Incorrect permissions!\nI do not have the $permissionsSet permissions for ${eventChannel.mention}" - ) + fail(Translations.Checks.ChannelPerms.incorrectPerms.withOrdinalPlaceholders(permissionsSet, eventChannel.mention)) } } else { - fail("Unable to get permissions for channel! Please report this to the developers!") + fail(Translations.Checks.ChannelPerms.unableToPerms) } } @@ -190,19 +181,16 @@ suspend inline fun EphemeralInteractionContext.isBotOrModerator( guild: GuildBehavior?, commandName: String ): String? { + val translations = Translations.Checks.BotOrMod if (guild == null) { - respond { - content = "**Error:** Unable to access this guild. Please try again" - } + respond { content = translations.noGuild.translate() } return null } val moderationConfig = ModerationConfigCollection().getConfig(guild.id) moderationConfig ?: run { - respond { - content = "**Error:** Unable to access configuration for this guild! Is your configuration set?" - } + respond { content = translations.unableToAccess.translate() } return null } @@ -211,34 +199,24 @@ suspend inline fun EphemeralInteractionContext.isBotOrModerator( return "skip" } val self = kord.getSelf().asMemberOrNull(guild.id) ?: run { - respond { - content = "There was an error getting Lily as a member of this server, please try again!" - } + respond { content = translations.lilyError.translate() } return null } // Get the users roles into a List of Snowflakes val roles = member.roles.toList().map { it.id } // If the user is a bot, return if (member.isBot) { - respond { - content = "You cannot $commandName bot users!" - } + respond { content = translations.cantBot.translate(commandName) } return null // If the moderator ping role is in roles, return } else if (moderationConfig.role in roles) { - respond { - content = "You cannot $commandName moderators!" - } + respond { content = translations.cantMod.translate(commandName) } return null } else if (member.getTopRole()?.getPosition() != null && self.getTopRole()?.getPosition() == null) { - respond { - content = "This user has a role and Lily does not, therefore she cannot $commandName them." - } + respond { content = translations.lilyNoRole.translate(commandName) } return null } else if ((member.getTopRole()?.getPosition() ?: 0) > (self.getTopRole()?.getPosition() ?: 0)) { - respond { - content = "This users highest role is above Lily's, therefore she cannot $commandName them." - } + respond { content = translations.userHigher.translate(commandName) } return null } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt index 220efda9..a46358fa 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.runBlocking import kotlinx.datetime.DateTimePeriod import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime +import lilybot.i18n.Translations import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.collections.AutoThreadingCollection import org.hyacinthbots.lilybot.database.collections.ConfigMetaCollection @@ -210,7 +211,8 @@ suspend inline fun Extension.updateDefaultPresence() { */ fun generateBulkDeleteFile(messages: Set<Message>): String? = if (messages.isNotEmpty()) { - "# Messages\n\n**Total:** ${messages.size}\n\n" + + "# ${Translations.Utils.GenerateBulkDelete.message.translate()}:\n\n" + + "**${Translations.Utils.GenerateBulkDelete.total.translate()}:** ${messages.size}\n\n" + messages.reversed().joinToString("\n") { // Reversed for chronology "* [${ it.timestamp.toLocalDateTime(TimeZone.UTC).toString().replace("T", " @ ") diff --git a/src/main/resources/translations/lilybot/strings.properties b/src/main/resources/translations/lilybot/strings.properties new file mode 100644 index 00000000..1a36eb42 --- /dev/null +++ b/src/main/resources/translations/lilybot/strings.properties @@ -0,0 +1,1174 @@ +basic.enabled=Enabled +basic.disabled=Disabled +basic.true=True +basic.false=False +basic.none=None +basic.yes=Yes +basic.no=No +basic.null=Null +basic.default=Default +basic.original=Original +basic.old=Old +basic.new=New +basic.requestedBy=Requested by {0} +basic.userField=User: +basic.noReason=No reason provided +basic.createdBy=Created by {0} +basic.unableTo.tag=Unable to get username +basic.unableTo.mention=Unable to get user mention +basic.unableTo.findUser=Unable to find user +basic.unableTo.channel=Unable to get channel +basic.unableTo.getContents=Unable to get contents of message +basic.unableTo.author=Unable to get author of message + +# Naming format. package.class.command(or purpose).(subGroup.)name + +about.embedTitle=Info about LilyBot +about.embedDesc=Lily is a FOSS multipurpose bot for Discord created by the HyacinthBots organization. Use `/help` for support or `/invite` to get an invitation link. +about.howSupportTitle=How can I support the continued development of Lily? +about.howSupportValue=Lily is developed primarily by NoComment#6411 in their free time. Hyacinth doesn't have the resources to invest in dedicated hosting, so financial donations via [Buy Me a Coffee](https://buymeacoffee.com/Hyacinthbots) help keep Lily afloat. Currently, we run lily on a Hetzner cloud server, which we can afford in our current situation. We also have domain costs for our website.\n\nContributions of code & documentation are also incredibly appreciated, and you can read our [contributing guide]({0}/LilyBot/blob/main/CONTRIBUTING.md) or [development guide]({0}/LilyBot/blob/main/docs/development-guide.md) to get started. +about.version=Version +about.upSince=Up since + +checks.requiredConfigs.inServer=Must be in a server +checks.requiredConfigs.noConfigInCode=There are no config options provided in the code. Please inform the developers immediately! +checks.requiredConfigs.cantModConfig=Unable to access moderation config for this guild! Please inform a member of staff. +checks.requiredConfigs.cantLoggingConfig=Unable to access logging config for this guild! Please inform a member of staff. +checks.requiredConfigs.cantUtilityConfig=Unable to access utility config for this guild! Please inform a member of staff. +checks.requiredConfigs.modDisabled=Moderation is disabled for this guild! +checks.requiredConfigs.noModRole=A moderator role has not been set for this guild! +checks.requiredConfigs.noModLog=An action log has not been set for this guild! +checks.requiredConfigs.logPubliclyOff=Public logging has not been enabled for this guild! +checks.requiredConfigs.noDeleteLogging=Message delete logging is disabled for this guild! +checks.requiredConfigs.noEditLogging=Message edit logging is disabled for this guild! +checks.requiredConfigs.noMessageLogging=A message log has not been set for this guild! +checks.requiredConfigs.noMemberLogging=Member logging is disabled for this guild! +checks.requiredConfigs.noMemberLogSet=A member log has not been set for this guild! +checks.requiredConfigs.noUtilityLog=A utility log has not been set for this guild! +checks.loggingChannelPerms.cannotGet=Lily is unable to send messages in the configured {0} for this guild. As a result, the corresponding config has been reset. \n\n*Note:* this channel has been used to send this message because it's the first channel in the guild Lily could use. Please inform this guild's staff about this message. +checks.channelPerms.incorrectPerms=Incorrect permissions!\nI do not have the {0} permissions for {1} +checks.channelPerms.unableToPerms=Unable to get permissions for channel! Please report this to the developers! +checks.botOrMod.noGuild=**Error:** Unable to access this guild. Please try again +checks.botOrMod.unableToAccess=**Error:** Unable to access configuration for this guild! Is your configuration set? +checks.botOrMod.lilyError=There was an error getting Lily as a member of this server, please try again! +checks.botOrMod.cantBot=You cannot {0} bot users! +checks.botOrMod.cantMod=You cannot {0} moderators! +checks.botOrMod.lilyNoRole=This user has a role and Lily does not, therefore she cannot {0} them. +checks.botOrMod.userHigher=This users highest role is above Lily's, therefore she cannot {0} them. + +config.name=config +config.description=Configure Lily's Settings +config.configuredBy=Configured by {0} +config.considerUtility=Consider setting a utility config to log changes to configurations +config.configAlreadyExists=You already have a {0} configuration set. Please clear it before attempting to set a new one. +config.invalidChannel=The {0} you've selected is invalid, or I can't view it. Please attempt to resolve this and try again. +config.unableTo.mention=Unable to get channel mention +config.unableTo.name=Unable to get channel name +config.moderation.name=moderation +config.moderation.description=Configure Lily's moderation system +config.moderation.systemDisabled=Moderation system disabled +config.moderation.roleAndChannelRequired=You must set both the moderator role and the action log channel to use the moderation configuration. +config.moderation.roleNotPingable=I cannot use the role: {0}, because it is not mentionable by regular users. Please enable this in the role settings, or use a different role. +config.moderation.embed.title=Configuration: Moderation +config.moderation.embed.moderatorsFieldName=Moderators +config.moderation.embed.actionLogFieldName=Action Log +config.moderation.embed.logPubliclyFieldName=Log Publicly +config.moderation.embed.quickTimeoutLength.name=Quick timeout length +config.moderation.embed.quickTimeoutLength.disabled=No quick timeout message set +config.moderation.embed.warningAutoPunishmentsName=Warning Auto-punishments +config.moderation.embed.banDmMessage.name=Ban DM Message +config.moderation.embed.banDmMessage.disabled=No custom Ban DM message set +config.moderation.embed.dmDefault.name=DM Default +config.moderation.embed.dmDefault.true=DM argument will default to true +config.moderation.embed.dmDefault.false=DM argument will default to false +config.moderation.embed.autoInviteRoleName=Auto-invite Moderator Role +config.moderation.embed.memberRoleChangesName=Log member role changes +config.logging.name=logging +config.logging.description=Configure Lily's logging system +config.logging.memberMissing=You must specify a channel to log members joining and leaving too! +config.logging.editMissing=You must specify a channel to log deleted/edited messages too! +config.logging.publicMemberMissing=You must specify a channel to publicly log members joining and leaving too! +config.logging.embed.title=Configuration: Logging +config.logging.embed.messageDeleteFieldName=Message Delete Logs +config.logging.embed.messageEditFieldName=Message Edit Logs +config.logging.embed.memberFieldName=Member Logs +config.logging.embed.publicMemberField.name=Public Member logs +config.logging.embed.publicMemberField.joinMessage=Join Message +config.logging.embed.publicMemberField.leaveMessage=Leave Message +config.logging.embed.publicMemberField.pingOnJoin=Ping on Join +config.logging.modal.title=Public logging configuration +config.logging.modal.joinMessage.label=What would you like sent when a user joins +config.logging.modal.joinMessage.placeholder=Welcome to the server! +config.logging.modal.leaveMessage.label=What would you like sent when a user leaves +config.logging.modal.leaveMessage.placeholder=Adiós amigo! +config.logging.modal.ping.label=Type `yes` to ping new users when they join +config.logging.modal.ping.placeholder=Defaults to false if input is invalid or not `yes` +config.utility.name=utility +config.utility.description=Configure Lily's utility settings +config.utility.embed.title=Configuration: Utility +config.utility.embed.utilityFieldName=Utility Log +config.utility.embed.channelUpdates=Log Channel updates +config.utility.embed.eventUpdates=Log Event updates +config.utility.embed.inviteUpdates=Log Invite updates +config.utility.embed.roleUpdates=Log Role updates +config.clear.name=clear +config.clear.description=Clear a config type +config.clear.embed.title=Configuration Cleared: {0} +config.clear.noConfigMod=No moderation configuration exists to clear +config.clear.noConfigLogging=No logging configuration exists to clear +config.clear.noConfigUtility=No utility configuration exists to clear +config.clear.all=All configs cleared +config.clear.footer=Config cleared by {0} +config.view.name=view +config.view.description=View the current config you have set +config.view.noModConfig=There is no moderation config for this guild +config.view.noLoggingConfig=There is no logging config for this guild +config.view.noUtilityConfig=There is no utility config for this guild +config.view.currentConfig.modTitle=Current moderation config +config.view.currentConfig.loggingTitle=Current logging config +config.view.currentConfig.utilityTitle=Current utility config +config.view.currentConfig.modDescription=This is the current moderation config for this guild +config.view.currentConfig.loggingDescription=This is the current logging config for this guild +config.view.currentConfig.utilityDescription=This is the current utility config for this guild +config.arguments.moderation.enabled.name=enable-moderation +config.arguments.moderation.enabled.description=Whether to enable the moderation system +config.arguments.moderation.moderatorRole.name=moderator-role +config.arguments.moderation.moderatorRole.description=The role of your moderators, used for pinging in message logs +config.arguments.moderation.modActionLog.name=action-log +config.arguments.moderation.modActionLog.description=The channel used to store moderator actions +config.arguments.moderation.quickTimeout.name=quick-timeout-length +config.arguments.moderation.quickTimeout.description=The length of timeouts to use for quick timeouts +config.arguments.moderation.autoPunish.name=warn-auto-punishments +config.arguments.moderation.autoPunish.description=Whether to automatically punish users for reaching a certain threshold on warns +config.arguments.moderation.dmDefault.name=dm-default +config.arguments.moderation.dmDefault.description=The default value for whether to DM a user in a moderation action +config.arguments.moderation.logPublicly.name=log-publicly +config.arguments.moderation.logPublicly.description=Whether to log moderation publicly or not +config.arguments.moderation.banDm.name=ban-dm-message +config.arguments.moderation.banDm.description=A custom message to send to users when they are banned. +config.arguments.moderation.inviteMods.name=auto-invite-moderator-role +config.arguments.moderation.inviteMods.description=Silent ping moderators to invite them to new threads. +config.arguments.moderation.roleChanges.name=log-member-role-changes +config.arguments.moderation.roleChanges.description=Whether to log changes to the roles members have in a guild. +config.arguments.logging.enableDelete.name=enable-delete-logs +config.arguments.logging.enableDelete.description=Enable logging of message deletions +config.arguments.logging.enableEdit.name=enable-edit-logs +config.arguments.logging.enableEdit.description=Enable logging of message edits +config.arguments.logging.enableMember.name=enable-member-logging +config.arguments.logging.enableMember.description=Enable logging of members joining and leaving the guild +config.arguments.logging.enablePublicMember.name=enable-public-member-logging +config.arguments.logging.enablePublicMember.description=Enable logging of members joining and leaving the guild with a public message +config.arguments.logging.messageLog.name=message-logs +config.arguments.logging.messageLog.description=The channel for logging message deletions +config.arguments.logging.memberLog.name=member-log +config.arguments.logging.memberLog.description=The channel for logging members joining and leaving the guild +config.arguments.logging.publicMemberLog.name=public-member-log +config.arguments.logging.publicMemberLog.description=The channel for the public logging of members joining and leaving the guild +config.arguments.utility.utilityLog.name=utility-log +config.arguments.utility.utilityLog.description=The channel to log various utility actions too. +config.arguments.utility.channelUpdates.name=log-channel-updates +config.arguments.utility.channelUpdates.description=Whether to log changes made to channels in this guild. +config.arguments.utility.eventUpdates.name=log-event-updates +config.arguments.utility.eventUpdates.description=Whether to log changes made to events in this guild. +config.arguments.utility.inviteUpdates.name=log-invite-updates +config.arguments.utility.inviteUpdates.description=Whether to log changes made to invites in this guild. +config.arguments.utility.roleUpdates.name=log-role-updates +config.arguments.utility.roleUpdates.description=Whether to log changes made to roles in this guild. +config.arguments.clear.name=config-type +config.arguments.clear.description=The type of config to clear +config.arguments.clear.choice.moderation=moderation +config.arguments.clear.choice.logging=logging +config.arguments.clear.choice.utility=utility +config.arguments.clear.choice.all=all +config.arguments.view.description=The type of config to view + +moderation.clearCommands.clear.name=clear +moderation.clearCommands.clear.description=Parent command for clear commands +moderation.clearCommands.clear.count.name=count +moderation.clearCommands.clear.count.description=Clear a specific count of messages +moderation.clearCommands.clear.before.name=before +moderation.clearCommands.clear.before.description=Clear messages before a given message ID +moderation.clearCommands.clear.before.arguments.before.name=before +moderation.clearCommands.clear.before.arguments.before.description=The ID of the message to clear before +moderation.clearCommands.clear.after.name=after +moderation.clearCommands.clear.after.description=clear messages after a given message ID +moderation.clearCommands.clear.after.arguments.after.name=after +moderation.clearCommands.clear.after.arguments.after.description=The ID of the message to clear after +moderation.clearCommands.clear.between.name=between +moderation.clearCommands.clear.between.description=Clear messages between 2 message ID's +moderation.clearCommands.arguments.author.name=author +moderation.clearCommands.arguments.author.description=The author of the messages to clear +moderation.clearCommands.arguments.count.name=message-count +moderation.clearCommands.arguments.count.description=The number of messages to clear +moderation.clearCommands.error.noChannel=Could not get the channel to clear messages from +moderation.clearCommands.error.beforeAfter=Before cannot be more recent than after +moderation.clearCommands.cleared=Messages cleared. +moderation.clearCommands.numberCleared={0} messages have been cleared +moderation.clearCommands.occurredIn=Action occurred in {0} + +moderation.lockingCommands.lock.name=lock +moderation.lockingCommands.lock.description=The parent command for all locking commands +moderation.lockingCommands.channel.name=channel +moderation.lockingCommands.lock.channel.description=Lock a channel so those with default permissions cannot send messages +moderation.lockingCommands.lock.channel.permsError=There was an error getting the permissions for this channel. Please try again. +moderation.lockingCommands.lock.channel.already=This channel is already locked +moderation.lockingCommands.lock.channel.serverLocked=The server is locked, I cannot unlock this channel +moderation.lockingCommands.lock.channel.publicEmbedTitle=Channel Locked +moderation.lockingCommands.lock.channel.publicEmbedDesc=This channel has been locked by a moderator. +moderation.lockingCommands.lock.channel.lockConfirmation={0} has been locked. +moderation.lockingCommands.lock.channel.embedDesc={channel} has been locked.\n\n**Reason:** {reason} +moderation.lockingCommands.lock.channel.arguments.channel.description=Channel to lock. Defaults to current channel +moderation.lockingCommands.lock.channel.arguments.reason.name=reason +moderation.lockingCommands.lock.channel.arguments.reason.description=Reason for locking the channel +moderation.lockingCommands.lock.channel.arguments.reason.default=No reason provided +moderation.lockingCommands.server.name=server +moderation.lockingCommands.lock.server.description=Lock the server so those with default permissions cannot send messages +moderation.lockingCommands.lock.server.already=The server is already locked. +moderation.lockingCommands.lock.server.lockConfirmation=Server locked. +moderation.lockingCommands.lock.server.embedDesc=**Reason:** {0} +moderation.lockingCommands.unlock.name=unlock +moderation.lockingCommands.unlock.description=The parent command for all unlocking commands +moderation.lockingCommands.unlock.channel.description=Unlock a channel so everyone can type messages again +moderation.lockingCommands.unlock.channel.unlockServerToUnlock=Please unlock the server to unlock this channel +moderation.lockingCommands.unlock.channel.notLocked=This channel is not locked! +moderation.lockingCommands.unlock.channel.publicEmbedTitle=Channel Unlocked +moderation.lockingCommands.unlock.channel.publicEmbedDesc=This channel has been unlocked by a moderator.\nPlease be aware of the rules when continuing discussion. +moderation.lockingCommands.unlock.channel.unlockConfirmation={0} has been unlocked. +moderation.lockingCommands.unlock.channel.arguments.channel.description=Channel to unlock. Defaults to current channel +moderation.lockingCommands.unlock.server.description=Unlock the server so everyone can send messages again +moderation.lockingCommands.unlock.server.notLocked=The server isn't locked! +moderation.lockingCommands.unlock.server.unlockConfirmation=Server unlocked. +moderation.lockingCommands.unableToChannel=I can't fetch the targeted channel properly +moderation.lockingCommands.unableToEveryone=I was unable to get the `@everyone` role. Please try again. + +moderation.modCommands.messageCommand.name=Moderate +moderation.modCommands.warnSuffix=Please consider your actions carefully.\n\nFor more information about the warning system, please see [this document] +moderation.modCommands.arguments.user.name=user +moderation.modCommands.arguments.user.description=Person to action +moderation.modCommands.arguments.messages.name=delete-message-days +moderation.modCommands.arguments.messages.description=The number of days worth of messages to delete +moderation.modCommands.arguments.reason.name=reason +moderation.modCommands.arguments.reason.description=The reason for the action +moderation.modCommands.arguments.reason.default=No reason provided +moderation.modCommands.arguments.soft.name=soft-ban +moderation.modCommands.arguments.soft.description=Whether to soft-ban this user (unban them once messages are deleted) +moderation.modCommands.arguments.dm.name=dm +moderation.modCommands.arguments.dm.description=Whether to send the user a direct message about the action +moderation.modCommands.arguments.image.name=image +moderation.modCommands.arguments.image.description=An image you'd like to provide as extra context for the action +moderation.modCommands.messageCommand.notFound=The message this command was run on cannot be found! It may have been deleted +moderation.modCommands.messageCommand.howMod=How would you like to moderate this message? +moderation.modCommands.messageCommand.selectMenu.placeholder=Select action... +moderation.modCommands.messageCommand.selectMenu.ban=Ban +moderation.modCommands.messageCommand.selectMenu.softBan=Soft-ban +moderation.modCommands.messageCommand.selectMenu.kick=Kick +moderation.modCommands.messageCommand.selectMenu.timeout=Timeout +moderation.modCommands.messageCommand.selectMenu.warn=Warn +moderation.modCommands.messageCommand.selectMenu.noOption=You did not select an option! +moderation.modCommands.messageCommand.reasonSuffix=for sending the following message: `{0}` +moderation.modCommands.ban.name=ban +moderation.modCommands.ban.description=Bans a user. +moderation.modCommands.ban.invalidMessages=Invalid `messages` parameter! This must be between 0 and 7! +moderation.modCommands.ban.dmTitle=You have been banned from {0} +moderation.modCommands.ban.dmDesc=**Reason:** +moderation.modCommands.ban.wasSoft=You were soft-banned. You are free to rejoin without the need to be unbanned. +moderation.modCommands.ban.publicSoftDesc={0} has been soft-banned! +moderation.modCommands.ban.publicDesc={0} has been banned! +moderation.modCommands.ban.quick.defaultDesc=Quick banned {0} +moderation.modCommands.ban.quick.embedTitle=Banned +moderation.modCommands.ban.quick.embedDescMessage={0} was banned for sending this message. +moderation.modCommands.ban.quick.embedDescDeleted={0} was banned for sending a deleted message. +moderation.modCommands.ban.response=Banned a user. +moderation.modCommands.tempBan.name=temp-ban +moderation.modCommands.tempBan.description=The parent command for temporary ban commands +moderation.modCommands.tempBan.add.name=add +moderation.modCommands.tempBan.add.description=Temporarily bans a user +moderation.modCommands.tempBan.add.dmTitle=You have been temporarily banned from {0} +moderation.modCommands.tempBan.add.dmDesc=**Reason:**\n{0}\n\nYou are banned until {1} +moderation.modCommands.tempBan.add.publicEmbedTitle=Temp Banned a user +moderation.modCommands.tempBan.add.publicEmbedDesc={0} has been Temp Banned! +moderation.modCommands.tempBan.add.response=Temporarily banned {0} +moderation.modCommands.tempBan.view.name=view-all +moderation.modCommands.tempBan.view.description=View all temporary bans for this guild +moderation.modCommands.tempBan.view.none=There are no temporary bans for this guild +moderation.modCommands.tempBan.view.page.user=User: {0} +moderation.modCommands.tempBan.view.page.mod=Moderator: {0} +moderation.modCommands.tempBan.view.page.start=Start Time: {0} +moderation.modCommands.tempBan.view.page.end=End time: {0} +moderation.modCommands.tempBan.view.page.title=Temporary bans for {0} +moderation.modCommands.tempBan.arguments.duration.name=duration +moderation.modCommands.tempBan.arguments.duration.description=The duration of the action +moderation.modCommands.softBan.dmTitle=You have been soft-banned from {0} +moderation.modCommands.softBan.dmDesc=Quick soft-banned {0}. This is a soft-ban, you are free to rejoin at any time +moderation.modCommands.softBan.quick.embedTitle=Soft-Banned +moderation.modCommands.softBan.quick.embedDescMessage={0} was soft-banned for sending this message. +moderation.modCommands.softBan.quick.embedDescDeleted={0} was soft-banned for sending a deleted message. +moderation.modCommands.softBan.response=Soft-Banned a user. +moderation.modCommands.unban.name=unban +moderation.modCommands.unban.description=Unbans a user. +moderation.modCommands.unban.response=Unbanned user. +moderation.modCommands.kick.name=kick +moderation.modCommands.kick.description=Kicks a user. +moderation.modCommands.kick.dmTitle=You have been kicked from {0} +moderation.modCommands.kick.dmDesc=**Reason:**\n{0} +moderation.modCommands.kick.quick.dmDesc=Quick kicked {0} +moderation.modCommands.kick.quick.embedTitle=Kicked. +moderation.modCommands.kick.quick.embedDescMessage={0} was kicked for sending this message. +moderation.modCommands.kick.quick.embedDescDeleted={0} was kicked for sending a deleted message. +moderation.modCommands.kick.quick.actionDesc=Quick kicked via moderate menu {0} +moderation.modCommands.kick.embedDesc={0} has been kicked! +moderation.modCommands.kick.response=Kicked a user. +moderation.modCommands.timeout.name=timeout +moderation.modCommands.timeout.description=Times out a user. +moderation.modCommands.timeout.noLength=No timeout length has been set in the moderation config, please set a length. +moderation.modCommands.timeout.dmDesc=**Duration:**\n{0} ({1})\n**Reason:**\n${2} +moderation.modCommands.timeout.dmTitle=You have been timed-out in {0} +moderation.modCommands.timeout.embedDesc={0} was timed out by a moderator +moderation.modCommands.timeout.duration=Duration: +moderation.modCommands.timeout.quick.dmDesc=You have been timed out for {0} {1} +moderation.modCommands.timeout.quick.embedTitle=Timeout +moderation.modCommands.timeout.quick.embedDescMessage={0} was timed-out for {1} for sending this message. +moderation.modCommands.timeout.quick.embedDescDeleted={0} was timed-out for {1} for sending a deleted message. +moderation.modCommands.timeout.quick.actionDesc=Quick timeout via moderate menu {0} +moderation.modCommands.timeout.response=Timed-out a user. +moderation.modCommands.timeout.arguments.duration.description=Duration of timeout +moderation.modCommands.removeTimeout.name=remove-timeout +moderation.modCommands.removeTimeout.description=Removes a timeout from a user +moderation.modCommands.removeTimeout.dmTitle=Timeout removed in {0} +moderation.modCommands.removeTimeout.dmDesc=Your timeout has been manually removed in this guild. +moderation.modCommands.removeTimeout.response=Removed timeout from user. +moderation.modCommands.warning.name=warn +moderation.modCommands.warning.description=Warns a user. +moderation.modCommands.warning.noAction=No moderation action has been taken. +moderation.modCommands.warning.action3h=You have been timed out for 3 hours. +moderation.modCommands.warning.action12h=You have been timed out for 12 hours. +moderation.modCommands.warning.action3d=You have been timed out for 3 days. +moderation.modCommands.warning.dmDesc=**Reason:** {0}\n\n{1} +moderation.modCommands.warning.embedDesc={0} has been warned by a moderator +moderation.modCommands.warning.dmTitle=Warning {0} in {1} +moderation.modCommands.warning.quickDmDesc=Quick warned {0}\n {1} +moderation.modCommands.warning.embedTitle=Warning +moderation.modCommands.warning.quickWarned=Quick warned via moderate menu {0} +moderation.modCommands.warning.embedStrikeTot=Total strikes +moderation.modCommands.warning.response=Warned a user. +moderation.modCommands.warning.embedDescMessage={0} was warned for sending this message. +moderation.modCommands.warning.embedDescDeleted={0} was warned for sending a deleted message. +moderation.modCommands.warning.log.action3h={0} has been timed-out for 3 hours due to 2 warn strikes +moderation.modCommands.warning.log.action12h={0} has been timed-out for 12 hours due to 3 warn strikes +moderation.modCommands.warning.log.action3d={0} has been timed-out for 3 days due to {1} warn strikes\nIt might be time to consider other action. +moderation.modCommands.warning.log.reason=Reason +moderation.modCommands.removeWarning.name=remove-warning +moderation.modCommands.removeWarning.description=Removes a user's warnings +moderation.modCommands.removeWarning.unableToFind=I was unable to find the member in this guild! Please try again! +moderation.modCommands.removeWarning.noStrikes=This user does not have any warning strikes +moderation.modCommands.removeWarning.response=Removed strike from user +moderation.modCommands.removeWarning.dmTitle=Warning strike removal in {0} +moderation.modCommands.removeWarning.dmDesc=You have had a warning strike remove. You now have {0} strikes. +moderation.modCommands.removeWarning.embedTitle=Warning Removal +moderation.modCommands.removeWarning.embedDesc={0} had a warning strike removed by a moderator + +moderation.modUtilities.say.name=say +moderation.modUtilities.say.description=Say something through Lily. +moderation.modUtilities.say.noSendPerms=Lily does not have permission to send messages in this channel. +moderation.modUtilities.say.response=Message sent. +moderation.modUtilities.say.embedTitle=Say command used +moderation.modUtilities.say.channelField=Channel: +moderation.modUtilities.say.typeField=Type +moderation.modUtilities.say.embedType=Embed +moderation.modUtilities.say.messageType=Message +moderation.modUtilities.say.colorField=Color: +moderation.modUtilities.say.jumpButton=Jump to message +moderation.modUtilities.say.arguments.message.name=message +moderation.modUtilities.say.arguments.message.description=The text of the message to be sent +moderation.modUtilities.say.arguments.channel.name=channel +moderation.modUtilities.say.arguments.channel.description=The channel the message should be sent in +moderation.modUtilities.say.arguments.embed.name=embed +moderation.modUtilities.say.arguments.embed.description=If the message should be sent as an embed +moderation.modUtilities.say.arguments.timestamp.name=timestamp +moderation.modUtilities.say.arguments.timestamp.description=If the message should be sent with a timestamp. Only works with embeds. +moderation.modUtilities.say.arguments.color.name=color +moderation.modUtilities.say.arguments.color.description=The color of the embed. Can be either a hex code or one of Discord's support colors. Embeds only +moderation.modUtilities.editSay.name=edit-say +moderation.modUtilities.editSay.description=Edit a message created by /say +moderation.modUtilities.editSay.unableTo=I was unable to get the target message! Please check the message exists +moderation.modUtilities.editSay.notAuthor=I did not send this message, I cannot edit it! +moderation.modUtilities.editSay.missingContent=Please specify new message contents +moderation.modUtilities.editSay.maxLength=Maximum embed length reached! Your embed length cannot be more than 1024 characters, due to Discord limitations. +moderation.modUtilities.editSay.response=Message edited +moderation.modUtilities.editSay.embedTitle=Say message edited +moderation.modUtilities.editSay.embedOriginal=Original Content +moderation.modUtilities.editSay.embedNew=New Content +moderation.modUtilities.editSay.editedBy=Edited by {0} +moderation.modUtilities.editSay.embedOldColor=Old color +moderation.modUtilities.editSay.embedNewColor=New Color +moderation.modUtilities.editSay.embedHasTime=Has Timestamp +moderation.modUtilities.editSay.arguments.messageToEdit.name=message-to-edit +moderation.modUtilities.editSay.arguments.messageToEdit.description=The ID of the message you'd like to edit +moderation.modUtilities.editSay.arguments.newContent.name=new-content +moderation.modUtilities.editSay.arguments.newContent.description=The new content of the message +moderation.modUtilities.editSay.arguments.newColor.name=new-color +moderation.modUtilities.editSay.arguments.newColor.description=The new color of the embed. Can be either a hex code or one of Discord's support colors. Embeds only +moderation.modUtilities.editSay.arguments.channelOf.name=channel-of-message +moderation.modUtilities.editSay.arguments.channelOf.description=The channel of the message +moderation.modUtilities.editSay.arguments.timestamp.name=timestamp +moderation.modUtilities.editSay.arguments.timestamp.description=Whether to timestamp the embed or not. Embeds only +moderation.modUtilities.status.name=status +moderation.modUtilities.status.description=Set Lily's current presence/status. +moderation.modUtilities.status.set.name=set +moderation.modUtilities.status.set.description=Set a custom status for Lily. +moderation.modUtilities.status.set.response=Presence set to `{0}` +moderation.modUtilities.status.set.embedTitle=Presence changed +moderation.modUtilities.status.set.embedDesc=Lily's presence has been set to `{0}` +moderation.modUtilities.status.reset.name=reset +moderation.modUtilities.status.reset.description=Reset Lily's presence to the default status. +moderation.modUtilities.status.reset.response=Presence set to default +moderation.modUtilities.status.reset.embedDesc=Lily's presence has been set to default +moderation.modUtilities.status.reset.embedValue=Watching {0} servers. +moderation.modUtilities.status.arguments.presence.name=presence +moderation.modUtilities.status.arguments.presence.description=The new value Lily's presence should be set to +moderation.modUtilities.reset.name=reset +moderation.modUtilities.reset.description='Resets' Lily for this guild by deleting all database information relating to this guild +moderation.modUtilities.reset.failResponse=Confirmation failure. Reset cancelled +moderation.modUtilities.reset.tripleCheck=Are you sure you want to reset the database? This will remove all data associated with this guild from Lily's database. This includes configs, user-set reminders, usernames and more. This action is **irreversible** and the data **cannot** be recovered. +moderation.modUtilities.reset.yesButton=I'm sure +moderation.modUtilities.reset.resetResponse=Database reset! +moderation.modUtilities.reset.embedDesc=All data associated with this guild has been removed. +moderation.modUtilities.reset.noButton=Nevermind +moderation.modUtilities.reset.cancelResponse=Reset Cancelled +moderation.modUtilities.reset.modal.title=Reset data for this guild +moderation.modUtilities.reset.modal.confirmation.label=Confirm Reset +moderation.modUtilities.reset.modal.confirmation.placeholder=Type `yes` to confirm + +moderation.report.noAccess=Sorry, I can't properly access this message. Please ping the moderators instead. +moderation.report.ownMessage=You may not report your own message. +moderation.report.messageCommand.name=Report +moderation.report.slashCommand.name=manual-report +moderation.report.slashCommand.description=Report a message, using a link instead of the message command +moderation.report.slashCommand.malformedLink=The URL provided was malformed and the message could not be found! +moderation.report.slashCommand.arguments.message.name=message-link +moderation.report.slashCommand.arguments.message.description=Link to the message to report +moderation.report.confirmation.content=Would you like to report this message? This will ping moderators, and false reporting will be treated as spam and punished accordingly +moderation.report.confirmation.response=Message reported to staff +moderation.report.confirmation.embedTitle=Message reported +moderation.report.confirmation.embedDesc=A message was reported in {0} +moderation.report.confirmation.embedContentField=Message Content: +moderation.report.confirmation.embedAuthorField=Message Author: +moderation.report.confirmation.embedReasonField=Report Reason: +moderation.report.confirmation.embedReportedBy=Reported by: {0} +moderation.report.confirmation.jumpButton=Jump to message +moderation.report.confirmation.notReported=Message not reported +moderation.report.modal.title=Report a message +moderation.report.modal.label=Why are you reporting this message +moderation.report.modal.placeholder=It violates rule X! + +threads.autoThreading.name=auto-threading +threads.autoThreading.description=The parent command for auto-threading management. +threads.autoThreading.alreadyOn=Auto-threading is already enabled for this channel. +threads.autoThreading.alreadyOff=Auto-threading is not enabled for this channel. +threads.autoThreading.enable.name=enable +threads.autoThreading.enable.description=Automatically create a thread for each message sent in this channel. +threads.autoThreading.enable.noMention=Lily cannot mention this role. Please fix the role's permissions and try again. +threads.autoThreading.enable.enabled=Auto-threading has been **enabled** in this channel. +threads.autoThreading.enable.embed.title=Auto-threading Enabled +threads.autoThreading.enable.embed.channel=Channel: +threads.autoThreading.enable.embed.role=Role: +threads.autoThreading.enable.embed.preventDuplicates=Prevent Duplicates: +threads.autoThreading.enable.embed.beginArchived=Begin Archived: +threads.autoThreading.enable.embed.smartNaming=Smart Naming Enabled: +threads.autoThreading.enable.embed.mention=Mention: +threads.autoThreading.enable.embed.initialMessage=Initial Message: +threads.autoThreading.disable.name=disable +threads.autoThreading.disable.description=Stop automatically creating threads in this channel. +threads.autoThreading.disable.disabled=Auto-threading has been **disabled** in this channel. +threads.autoThreading.disable.embed.title=Auto-threading Disabled +threads.autoThreading.disable.embed.channel=Channel: +threads.autoThreading.list.name=list +threads.autoThreading.list.description=List all the auto-threaded channels in this server, if any. +threads.autoThreading.list.noChannels=There are no auto-threaded channels in this guild. +threads.autoThreading.list.addNewChannels=Add new ones by using `/auto-threading enable` +threads.autoThreading.list.channelList=Auto-threaded channels in this guild: +threads.autoThreading.list.trimmed=(List trimmed.) +threads.autoThreading.view.name=view +threads.autoThreading.view.description=View the settings of an auto-threaded channel +threads.autoThreading.view.noThreaded=**Error:** This is not an auto-threaded channel! +threads.autoThreading.view.embed.title=Auto-Threaded channel settings +threads.autoThreading.view.embed.description=These are the settings for channel: {0} +threads.autoThreading.view.embed.role=Ping role +threads.autoThreading.view.embed.unable=Unable to get role! +threads.autoThreading.view.embed.extraRoles=Extra Ping Roles +threads.autoThreading.view.embed.preventDuplicates=Prevent Duplicates +threads.autoThreading.view.embed.archiveOnStart=Archive on Creation +threads.autoThreading.view.embed.contentAwareNaming=Content Aware Naming +threads.autoThreading.view.embed.mentionCreator=Mention creator on creation +threads.autoThreading.view.embed.creationMessage=Creation Message +threads.autoThreading.view.embed.addMods=Add mods and ping role +threads.autoThreading.addRoles.name=add-roles +threads.autoThreading.addRoles.description=Add extra roles to threads in auto-threaded channels +threads.autoThreading.addRoles.extraInfo=This command will add roles to be pinged alongside the default ping role for this auto-threaded channel +threads.autoThreading.addRoles.noThreaded=**Error:** This is not an auto-threaded channel! +threads.autoThreading.addRoles.alreadyAdded=This role has already been added. +threads.autoThreading.addRoles.added=Role ({0}) added +threads.autoThreading.removeRoles.name=remove-roles +threads.autoThreading.removeRoles.description=Remove extra roles from threads in auto-threaded channels +threads.autoThreading.removeRoles.extraInfo=This command will remove roles that have been added to be pinged alongside the default ping role for this auto-threaded channel +threads.autoThreading.removeRoles.notAdded=This role has not been added +threads.autoThreading.removeRoles.removed=Role ({0}) removed +threads.autoThreading.arguments.role.name=role +threads.autoThreading.arguments.role.description=The role, if any, to invite to threads created in this channel. +threads.autoThreading.arguments.addMods.name=add-mods-and-role +threads.autoThreading.arguments.addMods.description=Whether to add moderators to the thread alongside the role +threads.autoThreading.arguments.preventDuplicates.name=prevent-duplicates +threads.autoThreading.arguments.preventDuplicates.description=If users should be stopped from having multiple open threads in this channel. Default false. +threads.autoThreading.arguments.archive.name=archive +threads.autoThreading.arguments.archive.description=If threads should be archived on creation to avoid filling the sidebar. Default false. +threads.autoThreading.arguments.contentAware.name=content-aware-naming +threads.autoThreading.arguments.contentAware.description=If Lily should use content-aware thread titles. Default false +threads.autoThreading.arguments.mention.name=mention +threads.autoThreading.arguments.mention.description=If the creator should be mentioned in new threads in this channel. Default false. +threads.autoThreading.arguments.message.name=message +threads.autoThreading.arguments.message.description=Whether to send a custom message on thread creation or not. Default false. +threads.autoThreading.arguments.extraRole.description=A role to invite to threads in this channel +threads.autoThreading.arguments.channel.name=channel +threads.autoThreading.arguments.channel.description=The channel to view the auto-threading settings for. +threads.autoThreading.modal.title=Thread Creation Message Configuration +threads.autoThreading.modal.msgInput.name=Thread Creation Message +threads.autoThreading.modal.msgInput.placeholder=Input the content of the message to send when a thread is created in this channel +threads.autoThreading.threadFor=Thread for {0} +threads.autoThreading.existingThread=Please use your existing thread in this channel {0} + +threads.threadControl.name=thread +threads.threadControl.description=The parent command for all /thread commands +threads.threadControl.rename.name=rename +threads.threadControl.rename.description=Rename a thread! +threads.threadControl.rename.renamedBy=Renamed by {0} +threads.threadControl.rename.renamed=Thread renamed! +threads.threadControl.archive.name=archive +threads.threadControl.archive.description=Archive this thread +threads.threadControl.archive.preventionDisabled.title=Thread archive prevention disabled +threads.threadControl.archive.preventionDisabled.description=Archive prevention has been disabled, as `/thread archive` was used. +threads.threadControl.archive.user=User +threads.threadControl.archive.thread=Thread +threads.threadControl.archive.already=**Error:** This channel is already archived! +threads.threadControl.archive.archivedBy=Archived by {0} +threads.threadControl.archive.response=Thread archived +threads.threadControl.archive.responseLock= and locked +threads.threadControl.transfer.name=transfer +threads.threadControl.transfer.description=Transfer ownership of this thread +threads.threadControl.transfer.alreadyOwns=That person already owns the thread! +threads.threadControl.transfer.cannotBot=You cannot transfer ownership of a thread to a bot +threads.threadControl.transfer.success=Ownership transferred. +threads.threadControl.transfer.fromTo=Thread ownership transferred from {old} to {new} +threads.threadControl.transfer.transferredBy=Transferred by {0} +threads.threadControl.transfer.embed.title=Thread ownership transferred +threads.threadControl.transfer.embed.prevOwner=Previous owner +threads.threadControl.transfer.embed.cannotFind=Unable to find previous owner +threads.threadControl.transfer.embed.newOwner=New owner +threads.threadControl.preventArchiving.name=prevent-archived +threads.threadControl.preventArchiving.description=Stop a thread from being archived +threads.threadControl.preventArchiving.alreadyPreventedWarning=Thread archiving is already being prevented, would you like to remove this? +threads.threadControl.preventArchiving.noLongerPrevented=Thread archiving will no longer be prevented +threads.threadControl.preventArchiving.stillPrevented=Thread archiving will remain prevented +threads.threadControl.preventArchiving.embed.title=Thread archive prevent enabled +threads.threadControl.preventArchiving.nowPrevented=Thread archiving will now be prevented +threads.threadControl.preventArchiving.nowPreventedNoLog=Thread archiving will now be prevented\nNote: Failed to send a log to your specified mod action log. Please check the channel exists and permissions are correct +threads.threadControl.fetchIssue=Are you sure this channel is a thread? If it is, I can't fetch it properly. +threads.threadControl.arguments.rename.newName.name=new-name +threads.threadControl.arguments.rename.newName.description=The new name to give to the thread +threads.threadControl.arguments.archive.lock.name=lock +threads.threadControl.arguments.archive.lock.description=Whether to lock the thread if you are a moderator. Default is false. +threads.threadControl.arguments.transfer.newOwner.name=new-owner +threads.threadControl.arguments.transfer.newOwner.description=The user you want to transfer ownership of the thread to +threads.threadControl.notYours=**Error:** This is not your thread! + +events.memberLogging.memberEvent.footer=Member count {0} +events.memberLogging.memberEvent.embedId=ID: +events.memberLogging.memberJoin.embedAuthor=User joined the server! +events.memberLogging.memberJoin.embedWelcome=Welcome: +events.memberLogging.memberJoin.publicEmbedAuthor=Welcome {0} +events.memberLogging.memberJoin.publicEmbedWelcomeMessage=Welcome to the server +events.memberLogging.memberLeave.embedAuthor=User left the server! +events.memberLogging.memberLeave.embedGoodbye=Goodbye: +events.memberLogging.memberLeave.publicEmbedAuthor=Goodbye {0} +events.memberLogging.memberLeave.publicEmbedGoodbyeMessage=Farewell! + +events.messageEvent.location=Location: {0} ({1}) +events.messageEvent.failedContents=Failed to retrieve previous message contents + +events.messageDelete.noChannelName=Could not get channel name +events.messageDelete.single.embedAuthor=Message deleted +events.messageDelete.single.embedContents=Message Contents +events.messageDelete.bulk.embedTitle=Bulk Message Delete +events.messageDelete.bulk.embedDescription=A Bulk delete of message occurred +events.messageDelete.bulk.embedLocation=Location +events.messageDelete.bulk.embedNumber=Number of Messages +events.messageDelete.bulk.embedFailedContent=The messages from this event could not be gathered and logged + +events.messageEdit.embedAuthor=Message edited +events.messageEdit.embedPrevious=Previous Contents +events.messageEdit.embedNew=New Contents +events.messageEdit.embedNewFail=Failed to retrieve new message contents +events.messageEdit.embedButton=Jump + +events.moderation.ban.banned=Banned +events.moderation.ban.softBanned=Soft-Banned +events.moderation.ban.tempBanned=Temporarily Banned +events.moderation.ban.aUser=a user! +events.moderation.ban.banDescription={0} has been +events.moderation.ban.defaultBanDescription={0} has been banned! +events.moderation.ban.deleteDays=Days of messages deleted +events.moderation.ban.duration=Duration: +events.moderation.unban.tempRemoved=Temporary ban removed +events.moderation.unban.unbanned=Unbanned a user +events.moderation.unban.description={0} has +events.moderation.unban.tempRemovedDesc=had their temporary ban removed! +events.moderation.unban.unbannedDesc=been unbanned! +events.moderation.unban.reason=Reason: +events.moderation.unban.initialDate=Initial Ban date: +events.moderation.memberUpdate.timeoutRemoved=Timeout removed +events.moderation.memberUpdate.timeoutAdded=Timeout +events.moderation.memberUpdate.viaMenu=via Discord menus +events.moderation.memberUpdate.updatedTitle=Member updates +events.moderation.memberUpdate.updatedDesc={0} has been updated +events.moderation.memberUpdate.nickChange=Nickname changed +events.moderation.memberUpdate.nickChangeVal=Old: {old}\nNew: {new} +events.moderation.memberUpdate.newRoles=New Roles +events.moderation.memberUpdate.oldRoles=Old Roles + +utility.galleryChannel.name=gallery-channel +utility.galleryChannel.description=The parent command for image channel setting +utility.galleryChannel.set.name=set +utility.galleryChannel.set.description=Set a channel as a gallery channel +utility.galleryChannel.set.already=This channel is already a gallery channel +utility.galleryChannel.set.response=Set channel as a gallery channel. +utility.galleryChannel.set.embedTitle=New Gallery channel +utility.galleryChannel.set.embedDesc={0} was added as a gallery channel +utility.galleryChannel.set.embedRequested=Requested by {0} +utility.galleryChannel.unset.name=unset +utility.galleryChannel.unset.description=Unset a channel as a gallery channel. +utility.galleryChannel.unset.response=Channel no-longer gallery channel. +utility.galleryChannel.unset.embedTitle=Removed Gallery channel +utility.galleryChannel.unset.embedDesc={0} was removed as a Gallery channel +utility.galleryChannel.unset.embedRequested=Requested by {0} +utility.galleryChannel.unset.notGallery=This channel is not a gallery channel! +utility.galleryChannel.list.name=list +utility.galleryChannel.list.description=List all gallery channels in the guild +utility.galleryChannel.list.embedTitle=Gallery Channels +utility.galleryChannel.list.embedDesc=Here are the gallery channels in this guild. +utility.galleryChannel.list.embedChannelsField=Channels: +utility.galleryChannel.list.noneFound=No channels found! +utility.galleryChannel.noPerms=Hi! This is a gallery channel, but I don't have Manage Messages for this channel, therefore I cannot delete messages that don't contain images! Could someone ask staff fix it please? Thanks! +utility.galleryChannel.images=This channel is for images only! + +utility.github.name=github +utility.github.description=THe parent command for all /github commands +utility.github.noDefault=There is no default repository set. Please specify a repository to search, or set a default. +utility.github.badFormatEmbedTitle=Make sure your repository input is formatted like this: +utility.github.badFormatEmbedDesc=Format: `User/Repo` or `Org/Repo`\nFor example: `HyacinthBots/LilyBot` +utility.github.unableToRepo=Unable to access repository, make sure this repository exists +utility.github.issue.name=issue +utility.github.issue.description=Look up an issue on a specific repository +utility.github.issue.unableToFind=Unable to find issue number! Make sure this issue exists +utility.github.issue.embedDescPr=**Information for Pull request #{number} in {repo}** +utility.github.issue.embedDescIs=**Information for Issue #{number} in {repo}** +utility.github.issue.noDesc=No description provided +utility.github.issue.errorTitle=Error! +utility.github.issue.errorDesc=Error occurred initializing Pull Request. How did this happen? +utility.github.issue.statusField=Status: +utility.github.issue.merged=Merged +utility.github.issue.closed=Closed +utility.github.issue.draft=Draft +utility.github.issue.open=Open +utility.github.issue.authorField=Author: +utility.github.issue.unknownAuthor=Unknown Author +utility.github.issue.openedField=Opened on: +utility.github.issue.labelsField=Labels: +utility.github.issue.arguments.issue.name=issue-number +utility.github.issue.arguments.issue.description=The issue number you would like to search for +utility.github.issue.arguments.repo.name=repository +utility.github.issue.arguments.repo.description=The GitHub repository you would like to search, if no default is set. +utility.github.repo.name=repo +utility.github.repo.description=Search GitHub for a specific repository +utility.github.repo.embedTitle=GitHub Repository Info for {0} +utility.github.repo.licenceField=License: +utility.github.repo.openIssues=Open Issues and PRs: +utility.github.repo.forks=Forks: +utility.github.repo.stars=Stars: +utility.github.repo.size=Size: +utility.github.repo.language=Language: +utility.github.user.name=user +utility.github.user.description=Search GitHub for a User/Organization +utility.github.user.invalidName=Invalid Username. Make sure this user exists! +utility.github.user.embedTitle=GitHub profile for {0} +utility.github.user.repositories=Repository: +utility.github.user.followers=Followers: +utility.github.user.following=Following: +utility.github.user.company=Company +utility.github.user.website=Website: +utility.github.user.twitter=X: +utility.github.user.publicMembers=Public Members +utility.github.user.arguments.username.name=username +utility.github.user.arguments.username.description=The name of the User/Organisation you wissh to search for +utility.github.defaultRepo.name=default-repo +utility.github.defaultRepo.description=Set the default repo to look up issues in +utility.github.defaultRepo.invalidUrl=Invalid repo URL, please try again +utility.github.defaultRepo.response=Default repo set. +utility.github.removeDefaultRepo.name=remove-default-repo +utility.github.removeDefaultRepo.description=Removes the default repo for this guild +utility.github.removeDefaultRepo.noDefault=There is no default repo for this guild! +utility.github.removeDefaultRepo.response=Default repo removed + +utility.guildAnnouncements.name=announcement +utility.guildAnnouncements.description=Send an announcement to all guilds Lily is in +utility.guildAnnouncements.sendConfirm=Would you like to send this message +utility.guildAnnouncements.deliverAll=It will be delivered to all servers this bot is in. +utility.guildAnnouncements.deliverSpecific=It will be delivered to your specified target guild: {0} +utility.guildAnnouncements.sent=Message sent! +utility.guildAnnouncements.targetNotFound=Target guild not found +utility.guildAnnouncements.noAvailableChannel=Couldn't find an available channel to send a message in +utility.guildAnnouncements.deliveredToOne=This message was delivered to this server alone +utility.guildAnnouncements.sentBy=Sent by {0} +utility.guildAnnouncements.notSent=Message not sent. +utility.guildAnnouncements.arguments.target.name=target-guild +utility.guildAnnouncements.arguments.target.description=The ID of the target guild to send the announcement too. +utility.guildAnnouncements.modal.title=Send an announcement +utility.guildAnnouncements.modal.header.label=Announcement Header +utility.guildAnnouncements.modal.header.placeholder=Version 7.6.5! +utility.guildAnnouncements.modal.body.label=Announcement Body +utility.guildAnnouncements.modal.body.placeholder=We're happy to announce Lily is now written in Rust! It turns out we really like crabs + +utility.infoCommands.help.name=help +utility.infoCommands.help.description=Get help with using Lily! +utility.infoCommands.help.embedTitle=What is LilyBot? +utility.infoCommands.help.embedDesc=Lily is a FOSS multipurpose bot for Discord created by the HyacinthBots organization. Use `/about` to learn more, or `/invite` to get an invitation link. +utility.infoCommands.help.configFieldName=How do I configure Lily? +utility.infoCommands.help.configFieldValue=Run the `/config X` commands and provide the requested values. For more information, use the `/command-list` command and navigate to the relevant page +utility.infoCommands.help.whatFieldName=What commands are there? +utility.infoCommands.help.whatFieldValue=Lots! Too many to list here. You can read about the commands using the `/command-list` command, or visiting the [commands list on GitHub] +utility.infoCommands.help.supportFieldName=How do I get more help and/or learn more? +utility.infoCommands.help.supportFieldValue=To get additional support, discuss Lily, suggest features, or even lend a hand with development join our Discord at https://discord.gg/hy2329fcTZ +utility.infoCommands.help.usefulFieldName=Useful links +utility.infoCommands.help.usefulFieldValue=Website: https://hyacinthbots.org\nGitHub: {0}\nBuy Me a Coffee: https://buymeacoffee.com/HyacinthBots\nTwitter: https://twitter.com/HyacinthBots\nEmail: `hyacinthbots@outlook.com`\nDiscord: https://discord.gg/hy2329fcTZ +utility.infoCommands.help.button.invite=Invite Link +utility.infoCommands.help.button.privacy=Privacy Policy +utility.infoCommands.help.button.tos=Terms of Service +utility.infoCommands.invite.name=invite +utility.infoCommands.invite.description=Get an invitation link for Lily! +utility.infoCommands.invite.value=Use this link to add Lily to your server: https://discord.com/api/oauth2/authorize?client_id=876278900836139008&permissions=1151990787078&scope=bot%20applications.commands + +utility.newsChannel.newsPublishing.name=news-publishing +utility.newsChannel.newsPublishing.description=The parent command for news publishing channels +utility.newsChannel.newsPublishing.arguments.channel.name=channel +utility.newsChannel.newsPublishing.arguments.channel.description=The channel to set auto-publishing for +utility.newsChannel.newsPublishing.errorTitle=Unable to Auto-publish news channel! +utility.newsChannel.newsPublishing.missingPerms=Please ensure I have the `Send Messages` or `Manage Channel` permissions +utility.newsChannel.newsPublishing.embedChannelField=Channel: +utility.newsChannel.newsPublishing.set.name=set +utility.newsChannel.newsPublishing.set.description=Set this channel to automatically publish messages. +utility.newsChannel.newsPublishing.set.noPerms=I don't have permission for this channel; +utility.newsChannel.newsPublishing.set.success={0} has been set to automatically publish messages! +utility.newsChannel.newsPublishing.set.embedTitle=News Channel set to Auto-publish +utility.newsChannel.newsPublishing.set.setBy=Set by {0} +utility.newsChannel.newsPublishing.remove.name=remove +utility.newsChannel.newsPublishing.remove.description=Stop a news channel from auto-publishing messages +utility.newsChannel.newsPublishing.remove.noAuto=**Error:** {0} does not automatically publish messages! +utility.newsChannel.newsPublishing.remove.success={0} will no longer automatically publish +utility.newsChannel.newsPublishing.remove.embedTitle=News Channel will no longer Auto-publish +utility.newsChannel.newsPublishing.remove.removedBy=Removed by {0} +utility.newsChannel.newsPublishing.list.name=list +utility.newsChannel.newsPublishing.list.description=List auto-publishing channels +utility.newsChannel.newsPublishing.list.none=There are no news channels set for this guild. +utility.newsChannel.newsPublishing.list.title=Auto-publishing channels for this guild +utility.newsChannel.newsPublishing.list.desc=These are all the news channels that automatically publish messages +utility.newsChannel.newsPublishing.removeAll.name=remove-al +utility.newsChannel.newsPublishing.removeAll.description=Remove all auto-publishing channels for this guild +utility.newsChannel.newsPublishing.removeAll.noChannels=**Error:** There are no auto-publishing channels for this guild! +utility.newsChannel.newsPublishing.removeAll.success=Cleared all auto-publishing channels from this guild! + +utility.reminders.reminder.name=reminder +utility.reminders.reminder.description=The parent command for all reminder commands +utility.reminders.reminder.set.name=set +utility.reminders.reminder.set.description=Set a reminder for some time in the future! +utility.reminders.reminder.set.messageTooLong=Custom Message is too long. Message must be 1024 characters or fewer +utility.reminders.reminder.set.noRepeatingInt=You must specify a repeating interval if you are setting a repeating reminder +utility.reminders.reminder.set.tooShort=The Repeating interval cannot be less than one hour!\n\nThis is to prevent spam and/or abuse of reminders +utility.reminders.reminder.set.embedTitle=Reminder set! +utility.reminders.reminder.set.repeatingEmbedTitle=Repeating Reminder set! +utility.reminders.reminder.set.embedDesc=I will remind you at {long} ({relative}) +utility.reminders.reminder.set.embedRepeatingInt=Repeating interval +utility.reminders.reminder.set.embedCustomMessage=Custom Message +utility.reminders.reminder.set.reminderToAll=Reminder will be sent via DM to all participants +utility.reminders.reminder.set.arguments.time.name=time +utility.reminders.reminder.set.arguments.time.description=How long until you need reminding? Format: 1d12h30m / 3d / 26m30s +utility.reminders.reminder.set.arguments.dm.name=remind-in-dm +utility.reminders.reminder.set.arguments.dm.description=Whether to remind in DMs or not +utility.reminders.reminder.set.arguments.message.name=custom-message +utility.reminders.reminder.set.arguments.message.description=A message to attach to your reminder +utility.reminders.reminder.set.arguments.repeating.name=repeat +utility.reminders.reminder.set.arguments.repeating.description=Whether to repeat the number or not +utility.reminders.reminder.set.arguments.repeatingInt.name=repeat-interval +utility.reminders.reminder.set.arguments.repeatingInt.description=The interval to repeating the reminder at. Format: 1d / 4h / 5d +utility.reminders.reminder.list.name=list +utility.reminders.reminder.list.description=List your reminders for this guild +utility.reminders.reminder.remove.name=remove +utility.reminders.reminder.remove.description=Remove a reminder you have set in this guild +utility.reminders.reminder.remove.notFound=Reminder not found. Please use `/reminder list` to find out the correct reminder number! +utility.reminders.reminder.remove.embedTitle=Reminder cancelled +utility.reminders.reminder.remove.reminderField=Reminder +utility.reminders.reminder.remove.arguments.reminder.name=reminder-number +utility.reminders.reminder.remove.arguments.reminder.description=The number of the reminder to remove. Use `/reminder list` to get this +utility.reminders.reminder.removeAll.name=remove-all +utility.reminders.reminder.removeAll.description=Remove all of a specific reminder type from this guild +utility.reminders.reminder.removeAll.noReminders=You do not have any reminders for this guild! +utility.reminders.reminder.removeAll.noRepeating=You do not have any repeating reminders for this guild! +utility.reminders.reminder.removeAll.noNonRepeating=You do not have any non-repeating reminders for this guild! +utility.reminders.reminder.removeAll.removedAll=Removed all reminders for this guild +utility.reminders.reminder.removeAll.removedRepeating=Removed all repeating reminders for this guild +utility.reminders.reminder.removeAll.removedNonRepeating=Removed all non-repeating reminders for this guild +utility.reminders.reminder.removeAll.arguments.type.name=reminder-type +utility.reminders.reminder.removeAll.arguments.type.description=The type of reminder to remove +utility.reminders.reminder.removeAll.arguments.type.choices.repeating=repeating +utility.reminders.reminder.removeAll.arguments.type.choices.nonRepeating=non-repeating +utility.reminders.reminder.removeAll.arguments.type.choices.all=all +utility.reminders.reminder.modList.name=mod-list +utility.reminders.reminder.modList.description=List all reminders for a user, if you're a moderator +utility.reminders.reminder.modList.arguments.user.name=user +utility.reminders.reminder.modList.arguments.user.description=The user to view reminders for +utility.reminders.reminder.modRemove.name=mod-remove +utility.reminders.reminder.modRemove.description=Remove a reminder for a user, if you're a moderator +utility.reminders.reminder.modRemove.notFound=Reminder not found. Please use `/reminder mod-list` to find out the correct reminder number +utility.reminders.reminder.modRemove.embedTitle=Reminder cancelled by moderator +utility.reminders.reminder.modRemove.arguments.user.name=user +utility.reminders.reminder.modRemove.arguments.user.description=The user to remove the reminder for +utility.reminders.reminder.modRemove.arguments.reminder.name=reminder-number +utility.reminders.reminder.modRemove.arguments.reminder.description=The number of the reminder to remove. Use `/reminder mod-list` to get this. +utility.reminders.reminder.modRemoveAll.name=mod-remove-all +utility.reminders.reminder.modRemoveAll.description=Remove all of a specific reminder type for a user, if you're a moderator +utility.reminders.reminder.modRemoveAll.noReminders={0} does not have any reminders for this guild! +utility.reminders.reminder.modRemoveAll.noRepeating={0} does not have any repeating reminders for this guild! +utility.reminders.reminder.modRemoveAll.noNonRepeating={0} does not have any non-repeating reminders for this guild! +utility.reminders.reminder.modRemoveAll.removedAll=Removed all {0} reminders for this guild +utility.reminders.reminder.modRemoveAll.removedRepeating=Removed all {0} repeating reminders for this guild +utility.reminders.reminder.modRemoveAll.removedNonRepeating=Removed all {0} non-repeating reminders for this guild +utility.reminders.reminder.modRemoveAll.arguments.user.name=user +utility.reminders.reminder.modRemoveAll.arguments.user.description=The user to remove the reminders for +utility.reminders.reminder.unableToAccess=I was unable to find/access the channel from {guild} that this reminder was set in. +utility.reminders.reminder.embed.title=Reminder +utility.reminders.reminder.embed.set=Set time +utility.reminders.reminder.embed.repeatingTitle=Repeating Interval +utility.reminders.reminder.embed.repeatingValue=This reminder repeats every {0} +utility.reminders.reminder.embed.footer=Use `/reminder remove` to cancel this reminder +utility.reminders.reminder.originalMessage.canc=cancelled +utility.reminders.reminder.originalMessage.byMod= by moderator +utility.reminders.reminder.originalMessage.complete=completed. +utility.reminders.reminder.listPage.desc=You have no reminders set for this guild. +utility.reminders.reminder.listPage.modDesc=<@{0}> has no reminders set for this guild. +utility.reminders.reminder.listPage.title=Reminders for {0} +utility.reminders.reminder.content.line1=Reminder ID: +utility.reminders.reminder.content.line2=Time set: +utility.reminders.reminder.content.line3=Time until reminder: +utility.reminders.reminder.content.line4=Custom Message + +utility.roleMenu.name=role-menu +utility.roleMenu.description=The parent command for managing role menus +utility.roleMenu.jumpButton=Jump to menu +utility.roleMenu.create.name=create +utility.roleMenu.create.description=Create a new role menu in this channel. A channel can have any number of role menus +utility.roleMenu.create.selectButton=Select roles +utility.roleMenu.create.embedTitle=Role Menu Created +utility.roleMenu.create.embedDesc=A role menu for the {0} role was created in {1} +utility.roleMenu.create.embedContent=Content: +utility.roleMenu.create.embedColor=Color: +utility.roleMenu.create.embedEmbed=Embed: +utility.roleMenu.create.createdBy=Created by {0} +utility.roleMenu.create.response=Role menu created. You can add more roles using the `/role-menu add` command. +utility.roleMenu.create.arguments.role.name=role +utility.roleMenu.create.arguments.role.description=The first role to start the menu with. Add more via `/role-menu add` +utility.roleMenu.create.arguments.content.name=content +utility.roleMenu.create.arguments.content.description=The content of the embed or message. +utility.roleMenu.create.arguments.embed.name=embed +utility.roleMenu.create.arguments.embed.description=If the message containing the role menu should be sent as an embed +utility.roleMenu.create.arguments.color.name=color +utility.roleMenu.create.arguments.color.description=The color for the message to be. Embed only. +utility.roleMenu.add.name=add +utility.roleMenu.add.description=Add a role to the existing role menu in this channel. +utility.roleMenu.add.alreadyGot=This menu already contains that role +utility.roleMenu.add.max24=You can't have more than 24 roles in a role menu. This is a Discord Limitation +utility.roleMenu.add.embedTitle=Role Added to Role Menu +utility.roleMenu.add.embedDesc=The {0} role was added to a role menu in {1} +utility.roleMenu.add.addedBy=Added by {0} +utility.roleMenu.add.response=Added the {0} role to the specified role menu. +utility.roleMenu.add.arguments.id.name=menu-id +utility.roleMenu.add.arguments.id.description=The message ID of the role menu you'd like to edit. +utility.roleMenu.add.arguments.role.name=role +utility.roleMenu.add.arguments.role.description=The role you'd like to add to the selected role menu. +utility.roleMenu.remove.name=remove +utility.roleMenu.remove.description=Remove a role from the existing role menu in this channel. +utility.roleMenu.remove.cantRemove=You can't remove a role from a menu it isn't in. +utility.roleMenu.remove.cantRemoveLast=You can't remove the last role from a role menu. +utility.roleMenu.remove.embedTitle=Role removed from Role Menu +utility.roleMenu.remove.embedDesc=The {0} role was removed from a role menu in {0} +utility.roleMenu.remove.removedBy=Removed by {0} +utility.roleMenu.remove.response=Removed the {0} role from the specified role menu. +utility.roleMenu.remove.arguments.role.description=The role you'd like to remove from the selected role menu, +utility.roleMenu.pronouns.name=pronouns +utility.roleMenu.pronouns.description=Create a pronoun selection role menu and the roles to go with it. +utility.roleMenu.pronouns.response=Pronoun role menu created +utility.roleMenu.pronouns.message=Select pronoun roles from the menu below! +utility.roleMenu.pronouns.embedTitle=Pronoun Role Menu Created +utility.roleMenu.pronouns.embedDesc=A pronoun role menu was created in {0} +utility.roleMenu.pronouns.createdBy=Created by {0} +utility.roleMenu.interaction.broken=This role menu seems to be broken, please ask staff to recreate it. If this isn't a role menu, or if the issue persists, open a report at <{0}/LilyBot/issues> +utility.roleMenu.interaction.noRoles=Could not find any roles associated with this menu. PLease ask staff to add some. If this isn't a role menu, or if the issue persists, open a report at <{0}/LilyBot/issues> +utility.roleMenu.interaction.serverError=An error occurred when trying to get the server, please try again! If the issue persists, open a report at <{0}/LilyBot/issues> +utility.roleMenu.interaction.menuMessage=Use the menu below to select roles. +utility.roleMenu.interaction.placeholder=Select roles... +utility.roleMenu.interaction.response=Your roles have been adjusted +utility.roleMenu.interaction.noChanges=You didn't select any different roles, so no changes were made. +utility.roleMenu.check.cantFind=I couldn't find that message in this channel. Make sure it exists. +utility.roleMenu.check.notRole=That message doesn't seem to be a role menu. +utility.roleMenu.check.higherRole=The selected role is higher than me in the role hierarchy. Please move it and try again. +utility.roleMenu.roleSubscription.name=role-subscription +utility.roleMenu.roleSubscription.description=The parent command for role-subscription commands +utility.roleMenu.roleSubscription.update.name=update +utility.roleMenu.roleSubscription.update.description=Update your role subscription +utility.roleMenu.roleSubscription.update.noSubs=This guild does not have any subscribable roles. +utility.roleMenu.roleSubscription.update.useMenu=Use the menu below to subscribe to roles. +utility.roleMenu.roleSubscription.update.placeholder=Select roles to subscribe to... +utility.roleMenu.roleSubscription.update.adjusted=Your role subscription has been adjusted +utility.roleMenu.roleSubscription.add.name=add-role +utility.roleMenu.roleSubscription.add.description=Add a role that can be added through role subscription commands +utility.roleMenu.roleSubscription.add.response={0} was added as a subscribable role. Current subscribable roles are:\n{0} +utility.roleMenu.roleSubscription.add.embedTitle=Subscribable Role added +utility.roleMenu.roleSubscription.add.embedDesc={0} was added as a subscribable role +utility.roleMenu.roleSubscription.add.addedBy=Added by {0} +utility.roleMenu.roleSubscription.remove.name=remove-role +utility.roleMenu.roleSubscription.remove.description=Remove a role that can be added through role subscription commands +utility.roleMenu.roleSubscription.remove.notSubable=This is not a subscribable role. +utility.roleMenu.roleSubscription.remove.response={0} was removed as a subscribable role. Current subscribable roles are:\n{0} +utility.roleMenu.roleSubscription.remove.embedTitle=Subscribable Role removed +utility.roleMenu.roleSubscription.remove.embedDesc={0} was removed as a subscribable role +utility.roleMenu.roleSubscription.remove.removedBy=Removed by {0} +utility.roleMenu.roleSubscription.arguments.role.description=A role to add or remove from the subscribable roles + +utility.startupHooks.onlineTitle=Lily is now online! + +utility.tags.unableToFind=Unable to find tag `{0}`. Be sure it exists and is spelt correctly +utility.tags.bodyTooLongSomehow=The body of this tag is too long! Somehow this tag has a body of 4096 characters or more, which is above the Discord limit. Please re-create this tag! +utility.tags.bodyTooLong=That tag's body is too long! Due to Discord limitations tag bodies can only be 4096 characters or fewer! +utility.tags.tooLongTitle=That tag's title is too long! Due to Discord limitations tag titles can only be 256 characters or fewer! +utility.tags.tagAppearance=Tag appearance +utility.tags.preview.name=tag-preview +utility.tags.preview.description=Preview a tag''s contents without sending it publicly. +utility.tags.preview.footer=Tag preview +utility.tags.tag.name=tag +utility.tags.tag.description=Call a tag from this guild! Use /tag-help for more info. +utility.tags.tag.response=Tag sent +utility.tags.tag.footer=Tag requested by {0} +utility.tags.tag.embedTitle=Message Tag used +utility.tags.tag.embedName=Tag name: +utility.tags.tag.embedLocation=Location +utility.tags.tag.embedFooter=User ID: {0} +utility.tags.tag.arguments.name.name=name +utility.tags.tag.arguments.name.description=The name of the tag +utility.tags.tag.arguments.user.name=user +utility.tags.tag.arguments.user.description=The user to mention with the tag (optional) +utility.tags.tagHelp.name=tag-help +utility.tags.tagHelp.description=Explains how the tag command works +utility.tags.tagHelp.title=How does the tag system work? +utility.tags.tagHelp.content=The tag command allows users to add guild specific 'tag' commands at runtime to their guild. **Tags are like custom commands**, they can do say what ever you want them to say.\n\n**To create a tag**, if you have the Moderate Members permission, run the following command:\n`/tag-create <name> <title> <value>`\n You will be prompted to enter a name for the tag, a title for the tag, and the value for the tag. This is what will appear in the embed of your tag. You can enter any character you like into all of these inputs.\n\n**To use a tag**, run the following command:\n`/tag <name>`\nYou will be prompted to enter a tag name, but will have an autocomplete window to aid you. The window will list all the tags that the guild has.\n\n**To delete a tag**, if you have the Moderate Members permission, run the following command:\n`/tag-delete <name>`\nYou will be prompted to enter the name of the tag, again aided by autocomplete.\n`/tag-edit`\nYou will be prompted to enter a tag name, but will have an autocomplete window to aid you. The window will list all the tags that the guild has. From there you can enter a new name, title or value. None of these are mandatory.\n`/tag-list`\nDisplays a paginated list of all tags for this guild. There are 10 tags on each page.\n\n**Guilds can have any number of tags they like.** The limit on `tagValue` for tags is 4096 characters, which is the embed description limit enforced by Discord. +utility.tags.create.name=tag-create +utility.tags.create.description=Create a tag for your guild! Use /tag-help for more info. +utility.tags.create.already=A tag with that name already exists in this guild +utility.tags.create.embedTitle=Tag created! +utility.tags.create.embedDesc=The tag `{0}` has been created +utility.tags.create.embedTagTitle=Tag title: +utility.tags.create.embedTagValue=Tag value: +utility.tags.create.response=Tag: `{0}` created +utility.tags.create.arguments.title.name=title +utility.tags.create.arguments.title.description=The title of the tag embed +utility.tags.create.arguments.content.name=value +utility.tags.create.arguments.content.description=The content of the tag embed you're making +utility.tags.create.arguments.appearance.name=appearance +utility.tags.create.arguments.appearance.description=The appearance of the tag embed you're making +utility.tags.create.arguments.appearance.choice.embed=embed +utility.tags.create.arguments.appearance.choice.message=message +utility.tags.delete.name=tag-delete +utility.tags.delete.description=Delete a tag from your guild. Use /tag-help for more info. +utility.tags.delete.embedTitle=Tag deleted! +utility.tags.delete.embedDesc=The tag {0} was deleted +utility.tags.delete.response=Tag: `{0}` deleted +utility.tags.edit.name=tag-edit +utility.tags.edit.description=Edit a tag in your guild. Use /tag-help for more info. +utility.tags.edit.response=Tag edited! +utility.tags.edit.embedDesc=The tag, `{0}`, was edited +utility.tags.edit.embedName=Name: +utility.tags.edit.embedTitleField=Title +utility.tags.edit.embedValue=Value +utility.tags.edit.editedBy=Edited by {0} +utility.tags.edit.oldValue=Old value +utility.tags.edit.newValue=New value +utility.tags.edit.arguments.newName.name=new-name +utility.tags.edit.arguments.newName.description=The new name for the tag +utility.tags.edit.arguments.newTitle.name=new-title +utility.tags.edit.arguments.newTitle.description=The new title for the tag +utility.tags.edit.arguments.newValue.name=new-value +utility.tags.edit.arguments.newValue.description=The new value for the tag +utility.tags.edit.arguments.newAppearance.name=new-appearance +utility.tags.edit.arguments.newAppearance.description=The new appearance for the tag you're editing +utility.tags.list.name=tag-list +utility.tags.list.description=List all tags for this guild +utility.tags.list.noTags=There are no tags for this guild +utility.tags.list.pageTitle=Tags for this guild +utility.tags.list.pageDesc=Here are all the tags for this guild +utility.tags.list.pageValue=Name | Title + +utility.publicUtilities.ping.name=ping +utility.publicUtilities.ping.description=Am I alive? +utility.publicUtilities.ping.title=Pong! +utility.publicUtilities.ping.pingValue="Lily's Ping to Discord is:" +utility.publicUtilities.nickname.name=nickname +utility.publicUtilities.nickname.description=The parent command for all nickname commands +utility.publicUtilities.nickname.request.name=request +utility.publicUtilities.nickname.request.description=Request a new nickname for this server! +utility.publicUtilities.nickname.request.lilyNoRolePublic=You have a role and Lily does not, so she cannot change your nickname +utility.publicUtilities.nickname.request.lilyNoRolePrivate="This user has a role and Lily does not, so she cannot change their nickname. Please fix Lily's roles and then try again" +utility.publicUtilities.nickname.request.highestRolePublic="Your highest role is above Lily's, so she cannot change your nickname" +utility.publicUtilities.nickname.request.highestRolePrivate="This user's highest role is above Lily's, so she cannot change their nickname. Please fix Lily's permissions and try again" +utility.publicUtilities.nickname.request.hasPermission="You have permission to change your own nickname, so I've just made the change" +utility.publicUtilities.nickname.request.sent=Nickname request sent! +utility.publicUtilities.nickname.request.embedTitle=Nickname Request +utility.publicUtilities.nickname.userField=User: +utility.publicUtilities.nickname.request.embedCurrentNick=Current Nickname: +utility.publicUtilities.nickname.request.embedRequestedNick=Requested Nickname: +utility.publicUtilities.nickname.request.logEmbed.acceptTitle=Nickname Request Accepted +utility.publicUtilities.nickname.request.logEmbed.previousNick=Previous Nickname: +utility.publicUtilities.nickname.request.logEmbed.acceptedNick=Accepted Nickname: +utility.publicUtilities.nickname.request.logEmbed.acceptedBy=Nickname accepted by {0} +utility.publicUtilities.nickname.request.logEmbed.denyTitle=Nickname Request Denied +utility.publicUtilities.nickname.request.logEmbed.currentNick=Current Nickname: +utility.publicUtilities.nickname.request.logEmbed.rejectedNick=Rejected Nickname: +utility.publicUtilities.nickname.request.logEmbed.deniedBy=Nickname denied by {0} +utility.publicUtilities.nickname.request.dmAcceptTitle=Nickname Change Accepted in {0} +utility.publicUtilities.nickname.request.dmAcceptDescription=Nickname update from `{0}` to `{1}` +utility.publicUtilities.nickname.request.dmDenyTitle=Nickname Request Denied +utility.publicUtilities.nickname.request.dmDenyDescription=Moderators have reviewed your nickname request (`{0}`) and rejected it.\nPlease try a different nickname +utility.publicUtilities.nickname.request.button.accept=Accept +utility.publicUtilities.nickname.request.button.deny=Deny +utility.publicUtilities.nickname.request.args.newNick.name=nickname +utility.publicUtilities.nickname.request.args.newNick.description=The new nickname you would like +utility.publicUtilities.nickname.clear.name=clear +utility.publicUtilities.nickname.clear.description=Clear your current nickname +utility.publicUtilities.nickname.clear.nothingToClear=You have no nickname to clear! +utility.publicUtilities.nickname.clear.cleared=Nickname Cleared! +utility.publicUtilities.nickname.clear.logEmbed.title=Nickname Cleared +utility.publicUtilities.nickname.clear.logEmbed.newNickTitle=New Nickname: +utility.publicUtilities.nickname.clear.logEmbed.newNickValue=Nickname changed from `{0}` to `null` +utility.publicUtilities.nickname.failToSend=Error sending message to moderators. Please ask the moderators to check the `UTILITY` config. + +utility.utilityEvents.noOverrides=No overrides set +utility.utilityEvents.createTitle={0} Created +utility.utilityEvents.createDesc={0} ({1}) was created +utility.utilityEvents.allowed=Allowed Permissions +utility.utilityEvents.denied=Denied Permissions +utility.utilityEvents.deleteTitle={0} Deleted +utility.utilityEvents.deleteDesc=`{0}` was deleted. +utility.utilityEvents.updateTitle={0} Updated +utility.utilityEvents.typeChange=Type Change +utility.utilityEvents.nameChange=Name Change +utility.utilityEvents.topicChange=Topic Change +utility.utilityEvents.parentChange=Parent Category Change +utility.utilityEvents.nsfwChange=NSFW Setting +utility.utilityEvents.positionChanged=Position Changed +utility.utilityEvents.slowmodeChange=Slowmode time changed +utility.utilityEvents.bitrateChange=Bitrate changed +utility.utilityEvents.userlimitChange=User Limit Changed +utility.utilityEvents.regionChanged=Region Changed +utility.utilityEvents.videoQualityChanged=Video Quality Changed +utility.utilityEvents.defaultAutoChanged=Default Auto-Archive Changed +utility.utilityEvents.defaultSortChanged=Default Sort Changed +utility.utilityEvents.defaultLayoutChanged=Default Layout Changed +utility.utilityEvents.availableTagsChanged=Available tags changed +utility.utilityEvents.appliedTagsChanged=Applied tags Changed +utility.utilityEvents.defaultReactionChanged=Default Reaction Emoji Changed +utility.utilityEvents.defaultThreadChanged=Default Thread Slowmode Changed +utility.utilityEvents.newAllowed=New Allowed Permissions +utility.utilityEvents.oldAllowed=Old Allowed Permissions +utility.utilityEvents.newDenied=New Denied Permissions +utility.utilityEvents.oldDenied=Old Denied Permissions +utility.utilityEvents.scheduledCreateTitle=Scheduled Event Created +utility.utilityEvents.scheduledCreateDesc=An event has been created +utility.utilityEvents.guildMembersOnly=Guild Members only +utility.utilityEvents.scheduledDeleteTitle=Scheduled Event Deleted! +utility.utilityEvents.scheduledDeleteDesc=An event has been deleted +utility.utilityEvents.scheduledUpdateTitle=Scheduled Event Updated! +utility.utilityEvents.scheduledUpdateDesc=An event has been updated +utility.utilityEvents.descriptionChange=Description Changed +utility.utilityEvents.locationChange=Location Changed +utility.utilityEvents.startChanged=Start time changed +utility.utilityEvents.inviteCreateTitle=Invite link created +utility.utilityEvents.inviteCreateDesc=An invitation has been created +utility.utilityEvents.code=Code +utility.utilityEvents.targetChannel=Target Channel +utility.utilityEvents.maxUses=Max uses +utility.utilityEvents.duration=Duration of Invite +utility.utilityEvents.tempMembershipInvite=Temporary Membership invite +utility.utilityEvents.inviteDeleteTitle=Invite link deleted +utility.utilityEvents.inviteDeleteDesc=An invitation has been deleted +utility.utilityEvents.createdRoleTitle=Created a Role +utility.utilityEvents.createdRoleDesc=A new role has been created +utility.utilityEvents.roleName=Role name +utility.utilityEvents.roleColor=Role Color +utility.utilityEvents.rolePosition=Position +utility.utilityEvents.roleDisplaySep=Display separately? +utility.utilityEvents.roleMention=Mentionable +utility.utilityEvents.roleIcon=Icon +utility.utilityEvents.noRoleIcon=No icon +utility.utilityEvents.emoji=Emoji +utility.utilityEvents.noEmoji=No emoji +utility.utilityEvents.managed=Managed by integration? +utility.utilityEvents.permissions=Permissions +utility.utilityEvents.deletedRoleTitle=Role deleted +utility.utilityEvents.deletedRoleDesc=A role has been deleted +utility.utilityEvents.unableToName=Unable to get name +utility.utilityEvents.updatedRoleTitle=Updated a Role +utility.utilityEvents.updatedRoleDesc=has been updated +utility.utilityEvents.displaySepChanged=Display separately setting changed +utility.utilityEvents.mentionChanged=Mentionable setting changed +utility.utilityEvents.iconChanged=Icon changed +utility.utilityEvents.emojiChanged=Emoji changed +utility.utilityEvents.permissionsChanged=Permissions changed +utility.utilityEvents.oldColor=Old Color +utility.utilityEvents.newColor=New Color +utility.utilityEvents.parentChannel=Parent Channel +utility.utilityEvents.archiveDuration=Archive duration +utility.utilityEvents.appliedTags=Applied tags +utility.utilityEvents.channel.category=Category +utility.utilityEvents.channel.announcement=Announcement Channel +utility.utilityEvents.channel.forum=Forum Channel +utility.utilityEvents.channel.stage=Stage Channel +utility.utilityEvents.channel.text=Text Channel +utility.utilityEvents.channel.voice=Voice Channel +utility.utilityEvents.channel.thread=Thread +utility.utilityEvents.channel.privateThread=Private Thread +utility.utilityEvents.name=Name +utility.utilityEvents.moderated=Moderated +utility.utilityEvents.eventName=Event Name +utility.utilityEvents.eventDesc=Event Description +utility.utilityEvents.eventDescNone=No description provided +utility.utilityEvents.eventLocation=Event location +utility.utilityEvents.externalChannel=External event, no channel. +utility.utilityEvents.startTime=Start time +utility.utilityEvents.noImage=No image + +utils.generateBulkDelete.message=Message +utils.generateBulkDelete.total=Total + +utils.dmField.userNotif=User Notification: +utils.dmField.success=User notified with direct message +utils.dmField.disabled=DM Notification Disabled +utils.dmField.failure=Failed to notify user with direct message +utils.attachments.attachments=Attachments +utils.attachments.systemMember=System Member: +utils.attachments.account=Account +utils.attachments.unableToAccount=Unable to get account +utils.attachments.authorId=Author ID: