diff --git a/Cargo.lock b/Cargo.lock index 80945505..3328391e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -330,6 +330,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation" version = "0.9.3" @@ -1824,6 +1830,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "rosetta-build" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24191a1fed7ae7a5d89f816366f5c03ebce70a7a4d7158534afdf8dd719749fe" +dependencies = [ + "convert_case", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "tinyjson", +] + +[[package]] +name = "rosetta-i18n" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f8c01b9158de3aa5a7ac041a41c0e854d7adc3e473e7d7e2143eb5432bc5ba2" + [[package]] name = "rust-fuzzy-search" version = "0.1.1" @@ -2405,6 +2431,8 @@ dependencies = [ "psutil", "regex", "reqwest", + "rosetta-build", + "rosetta-i18n", "rust-fuzzy-search", "sentry", "serde", @@ -2552,6 +2580,12 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinyjson" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab95735ea2c8fd51154d01e39cf13912a78071c2d89abc49a7ef102a7dd725a" + [[package]] name = "tinyvec" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index 1163379e..e3706da0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,3 +39,7 @@ psutil = "3.2.2" rust-fuzzy-search = "0.1.1" moka = { version = "0.11.2", features = ["future"] } cached = "0.44.0" +rosetta-i18n = "0.1.3" + +[build-dependencies] +rosetta-build = "0.1.3" diff --git a/build.rs b/build.rs index d5068697..0fb2d3fe 100644 --- a/build.rs +++ b/build.rs @@ -1,5 +1,11 @@ -// generated by `sqlx migrate build-script` fn main() { - // trigger recompilation when a new migration is added println!("cargo:rerun-if-changed=migrations"); + println!("cargo:rerun-if-changed=locales"); + + rosetta_build::config() + .source("en", "locales/en.json") + .source("pt", "locales/pt.json") + .fallback("en") + .generate() + .unwrap(); } diff --git a/locales/en.json b/locales/en.json new file mode 100644 index 00000000..5770e8d7 --- /dev/null +++ b/locales/en.json @@ -0,0 +1,428 @@ +{ + "disabled": "disabled", + "disable": "disable", + "default": "default", + "none": "none", + "kw_true": "true", + "kw_false": "false", + + "filters_title": "Filters", + "filter_title": "Filter {position}", + "filter_group_title": "Filter Group '{name}'", + "override_title": "Override '{name}'", + "unknown_server": "Unknown Server {id}", + "deleted_role": "Deleted Role {id}", + + "premium_locked_aside": "(premium-locked)", + "premium_command": "Only premium servers can use this command.", + "premium_locked_autostar_info": "This autostar channel is locked because it exceeds the non-premium limit.\n\n", + + "starboard_missing": "Starboard '{name}' does not exist.", + "override_missing": "Override '{name}' does not exist.", + "override_already_exists": "An override named '{name}' already exists.", + "autostar_channel_missing": "Autostar channel '{name}' does not exist.", + "autostar_channel_already_exists": "An autostar channel named '{name}' already exists.", + "exclusive_group_missing": "Exclusive group '{name}' does not exist.", + "exclusive_group_already_exists": "An exclusive group named '{name}' already exists.", + "filter_group_missing": "Filter group '{name}' does not exist.", + "filter_group_already_exists": "A filter group named '{name}' already exists.", + "filter_missing": "No filter exists at {position} in group '{group}'.", + + "autostar": "Manage autostar channels.", + + "autostar_filters": "Manage filter groups for an autostar channel.", + + "autostar_filters_add": "Add a filter group to an autostar channel.", + "autostar_filters_add_option_autostar_channel": "The autostar channel to add the filter to.", + "autostar_filters_add_option_filter_group": "The filter group to add to the autostar channel.", + "autostar_filters_add_success": "Added filter group '{filter_group}' to autostar channel '{autostar_channel}'.", + "autostar_filters_add_already_added": "Filter group '{filter_group}' is already applied to autostar channel '{autostar_channel}'", + + "autostar_filters_remove": "Remove a filter group from an autostar channel.", + "autostar_filters_remove_option_autostar_channel": "The autostar channel to remove the filter group from.", + "autostar_filters_remove_option_filter_group": "The filter group to remove from the autostar channel.", + "autostar_filters_remove_success": "Removed filter group '{filter_group}' from autostar channel '{autostar_channel}'.", + "autostar_filters_remove_fail": "Filter group '{filter_group}' was not applied to autostar channel '{autostar_channel}'.", + + "autostar_create": "Create an autostar channel.", + "autostar_create_option_name": "The name of the autostar channel.", + "autostar_create_option_channel": "The channel to create the autostar channel in.", + "autostar_create_limit_reached": "You can only have up to {limit} autostar channels. The premium limit is {premium_limit}.", + "autostar_create_success": "Created autostar channel '{name}' in {channel}.", + + "autostar_delete": "Delete an autostar channel.", + "autostar_delete_option_name": "The name of the autostar channel to delete.", + "autostar_delete_confirm": "Are you sure you want to delete the autostar channel '{name}'?", + "autostar_delete_done": "Deleted autostar channel '{name}'.", + + "autostar_edit": "Edit the settings for an autostar channel.", + "autostar_edit_option_name": "The name of the autostar channel to edit.", + "autostar_edit_option_emojis": "The emojis to use. Use \"none\" to remove all.", + "autostar_edit_option_min_chars": "The minimum number of characters a message needs.", + "autostar_edit_option_max_chars": "The maximum number of characters a message can have. Set to -1 to disable.", + "autostar_edit_option_require_image": "Whether a message must include an image.", + "autostar_edit_option_delete_invalid": "Whether to delete messages that don't meet requirements.", + "autostar_edit_done": "Updated the settings for autostar channel '{name}'.", + + "autostar_rename": "Rename an autostar channel.", + "autostar_rename_option_current_name": "The current name of the autostar channel.", + "autostar_rename_option_new_name": "The new name of the autostar channel.", + "autostar_rename_done": "Renamed autostar channel '{old_name}' to '{new_name}'.", + + "autostar_view": "View the autostar channels for this server.", + "autostar_view_option_name": "The name of the autostar channel to view.", + "autostar_view_no_autostar_channels": "This server has no autostar channels.", + "autostar_view_title": "Autostar Channel '{name}'", + "autostar_view_channel": "This autostar channel is in <#{channel}>.", + "autostar_view_filters_info": "These are the filters that must pass for a message to be considered valid:\n\n{filters}\n\nYou can change view these filters with `/filters view`, and change which ones apply with `/autostar filters [add|remove|`.", + "autostar_view_filters_none": "No filters set.", + + "exclusive_groups": "Manage exclusive groups for starboards.", + + "exclusive_groups_create": "Create an exclusive group for starboards.", + "exclusive_groups_create_option_name": "The name for the exclusive group.", + "exclusive_groups_create_limit_reached": "You can only have up to {limit} exclusive groups.", + "exclusive_groups_create_done": "Created exclusive group '{name}'.", + + "exclusive_groups_delete": "Delete an exclusive group.", + "exclusive_groups_delete_option_name": "The name of the exclusive group to delete.", + "exclusive_groups_delete_done": "Deleted exclusive group '{name}'.", + + "exclusive_groups_rename": "Rename an exclusive group.", + "exclusive_groups_rename_option_original_name": "The original name for the exclusive group.", + "exclusive_groups_rename_option_new_name": "The new name for the exclusive group.", + "exclusive_groups_rename_done": "Exclusive group '{old_name}' renamed to '{new_name}'.", + + "filters": "Manage filters.", + + "filters_create": "Create a filter in a filter group.", + "filters_create_option_group": "The filter group to create this filter in.", + "filters_create_option_position": "The position to put the filter in. Use 1 for the start (top), or leave blank for the end.", + "filters_create_limit_reached": "You can only have up to {limit} filters per group.", + "filters_create_done": "Created filter at '{position}' in group '{name}'.", + + "filters_create_group": "Create a filter group.", + "filters_create_group_option_name": "The name of the filter group.", + "filters_create_group_limit_reached": "You can only have up to {limit} filter groups.", + "filters_create_group_done": "Created filter group '{name}'.", + + "filters_delete": "Delete a filter from a filter group.", + "filters_delete_option_group": "The name of the filter group to delete the filter from.", + "filters_delete_option_position": "The position of the filter to delete.", + "filters_delete_confirm": "This will delete the filter at {position} in the filter group '{group}'. Continue?", + "filters_delete_done": "Filter at {position} in group '{group}' deleted.", + + "filters_delete_group": "Delete a filter group.", + "filters_delete_group_option_name": "The name of the filter group to delete.", + "filters_delete_group_confirm": "This will delete the filter group '{name}'. Continue?", + "filters_delete_group_done": "Deleted filter group '{name}'.", + + "filters_edit": "Edit a filter's conditions.", + "filters_edit_option_group": "The name of the filter group containing the filter to be edited.", + "filters_edit_option_position": "The position of the filter within the filter group.", + "filters_edit_option_instant_pass": "If true and this filter passes, the entire filter group passes.", + "filters_edit_option_instant_fail": "If true and this filter fails, the entire filter group fails.", + "filters_edit_option_user_has_all_of": "Require that the user/author has all of these roles.", + "filters_edit_option_user_has_some_of": "Require that the user/author has at least one of these roles.", + "filters_edit_option_user_missing_all_of": "Require that the user/author is missing all of these roles.", + "filters_edit_option_user_missing_some_of": "Require that the user/author is missing at least one of these roles.", + "filters_edit_option_user_is_bot": "Require that the user/author is or is not a bot.", + "filters_edit_option_in_channel": "Require that the message was sent in one of these channels.", + "filters_edit_option_not_in_channel": "Require that the message was not sent in any of these channels.", + "filters_edit_option_in_channel_or_sub_channels": "Require that the message was sent in one of these channels or their sub-channels.", + "filters_edit_option_not_in_channel_or_sub_channels": "Require that the message was not sent in one of these channels or their sub-channels.", + "filters_edit_option_min_attachments": "Require that the message has at least this many attachments. Use 0 to disable.", + "filters_edit_option_max_attachments": "Require that the message has at most this many attachments. Use -1 to disable.", + "filters_edit_option_min_length": "Require that the message be at least this many characters long. Use 0 to disable.", + "filters_edit_option_max_length": "Require that the message be at most this many characters long. Use -1 to disable.", + "filters_edit_option_matches": "(Premium) Require that the message match this regex. Use `.*` to disable.", + "filters_edit_option_not_matches": "(Premium) Require that the message not match this regex. Use `.*` to disable.", + "filters_edit_option_voter_has_all_of": "Require that the voter has all of these roles.", + "filters_edit_option_voter_has_some_of": "Require that the voter has at least one of these roles.", + "filters_edit_option_voter_missing_all_of": "Require that the voter is missing all of these roles.", + "filters_edit_option_voter_missing_some_of": "Require that the voter is missing at least one of these roles.", + "filters_edit_option_older_than": "Require that the message being voted on is over a certain age. Use \"disable\" to disable.", + "filters_edit_option_newer_than": "Require that the message being voted on is under a certain age. Use \"disable\" to disable.", + "filters_edit_done": "Updated the conditions for filter at {position} in group '{group}'.", + "filters_bot_req_must_be_bot": "User must be a bot", + "filters_bot_req_must_be_human": "User must not be a bot", + "filters_bot_req_disabled": "Disabled", + "filters_max_roles": "You can only have up to {max} roles in a condition.", + "filters_max_channels": "You can only have up to {max} channels in a condition.", + "filters_max_min_attachments": "`min-attachments` cannot be greater than {max}.", + "filters_min_min_attachments": "`min-attachments` must be at least 0.", + "filters_max_max_attachments": "`max-attachments` cannot be greater than {max}.", + "filters_min_max_attachments": "`max-attachments` must be at least -1.", + "filters_max_min_length": "`min-length` cannot be longer than {max}.", + "filters_min_min_length": "`min-length` must be at least 0.", + "filters_max_max_length": "`max-length` cannot be longer than {max}.", + "filters_min_max_length": "`max-length` must be at least -1.", + "filters_max_matches": "`matches` cannot be longer than {max} characters.", + "filters_max_not_matches": "`not-matches` cannot be longer than {max} characters.", + "filters_matches_premium": "Only premium servers can use the `matches` and `not-matches` conditions.", + + "filters_view": "View the filter groups and filters for this server.", + "filters_view_option_group": "The filter group to view.", + "filters_view_no_groups": "This server does not have any filter groups. Read the [filter docs](<{filter_docs_link}>) to get started.", + "filters_view_read_docs": "Read the [filter_docs](<{filter_docs_link}>) to get started.", + "filters_view_group_no_filters": "This filter group has no filters.", + "filters_view_condition_no_roles": "None set. This condition always passes.", + "filters_view_condition_no_channels": "None set. This condition always passes.", + "filters_view_user_has_all_of": "User must have all of these roles:\n{v}", + "filters_view_user_has_some_of": "User must have at least one of these roles:\n{v}", + "filters_view_user_missing_all_of": "User must be missing all of these roles:\n{v}", + "filters_view_user_missing_some_of": "User must be missing at least one of these roles:\n{v}", + "filters_view_user_must_be_bot": "User must be a bot.", + "filters_view_user_must_be_human": "User must not be a bot.", + "filters_view_in_channel": "Message must be in one of these channels:\n{v}", + "filters_view_not_in_channel": "Message must not be in any of these channels:\n{v}", + "filters_view_in_channel_or_sub_channels": "Message must be in one of these channels or one of their sub-channels:\n{v}", + "filters_view_not_in_channel_or_sub_channels": "Message must not be in any of these channels or any of their sub-channels:\n{v}", + "filters_view_min_attachments": "Message must have at least {v} attachments.", + "filters_view_max_attachments": "Message cannot have more than {v} attachments.", + "filters_view_min_length": "Message must be at least {v} characters long.", + "filters_view_max_length": "Mesage cannot be longer than {v} characters.", + "filters_view_matches": "Message must match the following regex:\n```re\n{v}\n```", + "filters_view_not_matches": "Message must not match the following regex:\n```re\n{v}\n```", + "filters_view_regex_not_applied": ":warning: This setting is ignored because this server doesn't have premium.", + "filters_view_voter_has_all_of": "Voter must have all of these roles:\n{v}", + "filters_view_voter_has_some_of": "Voter must have at least one of these roles:\n{v}", + "filters_view_voter_missing_all_of": "Voter must be missing all of these roles:\n{v}", + "filters_view_voter_missing_some_of": "Voter must be missing at least one of these roles:\n{v}", + "filters_view_older_than": "Message must be older than {v}.", + "filters_view_newer_than": "Message must be newer than {v}", + "filters_default_context_title": "Default Context", + "filters_message_context_title": "Message Context", + "filters_vote_context_title": "Vote Context", + "filters_view_no_conditions": "\n\nThis filter has no conditions, so it always passes.", + + "filters_move": "Change the position of a filter.", + "filters_move_option_group": "The filter group containing the filter to be moved.", + "filters_move_option_current_position": "The current position of the filter.", + "filters_move_option_new_position": "The position to move the filter to.", + "filters_move_done": "Filter at {old_position} moved to {new_position}.", + + "filters_rename_group": "Rename a filter group.", + "filters_rename_group_option_current_name": "The current name of the filter group.", + "filters_rename_group_option_new_name": "The new name for the filter group.", + "filters_rename_group_done": "Filter group '{old_name}' renamed to '{new_name}'.", + + "overrides": "Manage overrides.", + + "overrides_create": "Create an override.", + "overrides_create_option_name": "The name of the override.", + "overrides_create_option_starboard": "The name of the starboard this override belongs to.", + "overrides_create_limit": "You can only have up to {limit} overrides per starboard.", + "overrides_create_done": "Created override '{override_name}' for starboard '{starboard}'.", + + "overrides_delete": "Delete an override.", + "overrides_delete_option_name": "The name of the override to delete.", + "overrides_delete_confirm": "Are you sure you want to delete override '{name}'.", + "overrides_delete_done": "Deleted override '{name}'.", + + "overrides_rename": "Rename an override", + "overrides_rename_option_current_name": "The current name of the override.", + "overrides_rename_option_new_name": "The new name of the override.", + "overrides_rename_done": "Renamed override '{current_name}' to '{new_name}'.", + + "overrides_view": "View the overrides for this server.", + "overrides_view_option_name": "The name of the override to view.", + "overrides_view_none": "This server has no overrides.", + "overrides_view_slug": "{overwritten} overwritten settings for starboard '{starboard}' in {channels} channel(s)", + "overrides_view_description": "This override belongs to starboard '{starboard}'.\n\nThis override applies to the following channels: {channels}", + + "overrides_channels": "Manage the channels an override affects.", + "overrides_channels_done": "Updated the channels for overrides '{name}'.", + + "overrides_channels_add": "Add channels to an override.", + "overrides_channels_add_option_name": "The name of the override to add channels to.", + "overrides_channels_add_option_channels": "The channels to add (as a list of channel mentions).", + + "overrides_channels_remove": "Remove channels from an override.", + "overrides_channels_remove_option_name": "The name of the override to remove channels from.", + "overrides_channels_remove_option_channels": "The channels to remove (as a list of channel mentions).", + + "overrides_channels_set": "Set the channels that an override affects.", + "overrides_channels_set_option_name": "The name of the override to set the channels for.", + "overrides_channels_set_option_channels": "A list of channels (as channel mentions) that the override should affect. Use \"none\" to remove all.", + + "overrides_edit": "Edit an override.", + "overrides_edit_done": "Updated the settings for override '{name}'.", + "overrides_edit_option_name": "The name of the override to edit.", + "overrides_edit_behavior": "Override how the starboard should behave.", + "overrides_edit_embed": "Override the style of the embeds sent to the starboard.", + "overrides_edit_requirements": "Override the requirements for messages to appear on the starboard.", + "overrides_edit_style": "Override the general style of the starboard.", + "overrides_edit_reset": "Reset override settings to the defaults used by the starboard.", + "overrides_edit_reset_option_reset": "The names of the settings to reset, seperated by spaces.", + "overrides_edit_reset_done": "Reset {count} setting(s) for override '{name}'.", + + "permroles": "View and manage PermRoles.", + "permroles_title": "PermRoles", + "permroles_option_vote": "Whether a user can vote on messages.", + "permroles_option_receive_votes": "Whether a user's messages can be voted on.", + "permroles_option_xproles": "Whether a user can gain XPRoles.", + "permrole_missing": "{role} is not a PermRole.", + + "permroles_create": "Create a PermRole.", + "permroles_create_option_role": "The role to use as a PermRole.", + "permroles_create_limit": "You can only have up to {limit} PermRoles.", + "permroles_create_already_exists": "{role} is already a PermRole.", + "permroles_create_done": "{role} is now a PermRole.", + + "permroles_delete": "Delete a PermRole.", + "permroles_delete_option_role": "The PermRole to delete.", + "permroles_delete_done": "{role} is no longer a PermRole.", + + "permroles_clear_deleted": "Delete all PermRoles where the Discord role has been deleted.", + "permroles_clear_deleted_nothing": "Nothing to delete.", + "permroles_clear_deleted_confirm": "This will delete the following PermRoles:\n{to_delete}\nContinue?", + "permroles_clear_deleted_done": "Done.", + + "permroles_edit": "Edit the global permissions for a PermRole.", + "permroles_edit_option_role": "The PermRole to edit.", + "permroles_edit_done": "Updated settings for {role}.", + + "permroles_edit_sb": "Edit the settings for a PermRole for a starboard.", + "permroles_edit_sb_option_starboard": "The starboard to edit the PermRole for.", + "permroles_edit_sb_done": "Updated the settings for {role} in starboard '{starboard}'.", + + "permroles_view": "View the PermRoles for this server.", + "permroles_view_option_role": "The PermRole to view settings for.", + "permroles_view_none": "This server has no PermRoles.", + "permroles_view_title": "Settings for <@&{role}>:\n", + "permroles_view_sb_title": "Settings for '{name}' in <#{channel}>:\n", + + "award_role_managed": "You can't use managed roles as award roles.", + + "posroles": "View and manage position-based award roles.", + "posroles_title": "Position-based Award Roles", + "posrole_description": "<@&{role_id}> - `{members}` members", + "posrole_missing": "{role} is not a PosRole.", + + "posroles_delete": "Delete a position-based award role.", + "posroles_delete_option_posrole": "The PosRole to delete.", + "posroles_delete_done": "{role} is no longer a PosRole.", + + "posroles_clear_deleted": "Delete all PosRoles where the Discord role has been deleted.", + "posroles_clear_deleted_nothing": "Nothing to delete.", + "posroles_clear_deleted_confirm": "This will delete the following PosRoles:\n{to_delete}\nContinue?", + "posroles_clear_deleted_done": "Done.", + + "posroles_refresh": "Refresh the PosRoles for the server.", + "posroles_refresh_done": "Finished updating.\nAdded {added}, {added_fail} failed.\nRemoved {removed}, {removed_fail} failed.", + "posroles_refresh_already_refreshing": "PosRoles are already being refreshed.", + + "posroles_edit": "Create or modify a position-based award role.", + "posroles_edit_option_role": "The role to use as a position-based award role.", + "posroles_edit_option_max_members": "How many members can have this award role.", + "posroles_edit_limit": "You can only have up to {limit} position-based award roles.", + "posroles_edit_edited": "Max members for {role} set to {value}.", + "posroles_edit_created": "{role} is now a PosRole, with a max membership of {max}.", + + "posroles_view": "View the position-based award roles for this server.", + "posroles_view_none": "This server has no PosRoles.", + + "xproles": "View and managed XP-based award roles.", + "xproles_title": "XP-based Award Roles", + "xprole_description": "<@&{role_id}> - `{xp}` XP", + "xprole_missing": "{role} is not an XPRole.", + + "xproles_delete": "Delete an XP-based award role.", + "xproles_delete_option_xprole": "The XPRole to delete.", + "xproles_delete_done": "{role} is no longer an XPRole.", + + "xproles_clear_deleted": "Delete all XPRoles where the Discord role has been deleted.", + "xproles_clear_deleted_none": "Nothing to clear.", + "xproles_clear_deleted_confirm": "This will delete the following XPRoles:\n{to_delete}\nContinue?", + "xproles_clear_deleted_done": "Done.", + + "xproles_setxp": "Create or modify an XP-based award role.", + "xproles_setxp_role": "The role to use as an XP-based award role.", + "xproles_setxp_required_xp": "How much XP is required to obtain this award role.", + "xproles_setxp_limit": "You can only have up to {limit} XP-based award roles.", + "xproles_setxp_edit": "Required XP for {role} set to {xp}.", + "xproles_setxp_create": "{role} is now an XP-based award role requiring {xp} XP.", + + "xproles_view": "View all this XP-based award roles for this server.", + "xproles_view_none": "This server has no XPRoles.", + + "premium": "Premium-related commands. See `/premium-locks` for premium locks.", + "premium_end_no_premium": "This server does not have premium.", + "premium_end_premium_until": "This server has premium until .", + + "premium_info": "Get premium info.", + "premium_emb_title": "Starboard Premium", + "premium_emb_desc": "Starboard uses a credit system for premium. For each USD you donate (currently only Patreon is supported), you receive one premium credit. Three premium credits can be redeemed for one month of premium in any server of your choice.\n\nThis means that premium for one server is $3/month, two servers is $6/month, and so on. To get premium, visit my [Patreon]({patreon}).", + "premium_emb_status": "Status", + "premium_emb_status_value": "You current have {credits} credits.", + "premium_emb_ar": "Auto-redeem", + "premium_emb_ar_top": "Auto-redeem is enabled for the following servers:\n", + "premium_emb_ar_desc": "\nAutoredeem will automatically take credits from your account when the server runs out of premium. This will only occur if Starboard is still un that server and you are still in that server.\n\n Disable it at any time by using `/premium autoredeem disable`.", + "premium_emb_server": "Server Premium", + + "premium_redeem": "Redeem your premium credits.", + "premium_redeem_option_months": "The number of months of premium to redeem.", + "premium_redeem_dm": "Please run this command in the server you want premium in.", + "premium_redeem_confirm": "Doing this will add {months} month(s) of premium to this server, and cost you {credits} credits. Continue?", + "premium_redeem_done": "Done.\n\nTip: Use `/premium autoredeem enable` to enable autoredeem for this server, so you don't have to repeatedly redeem credits.", + "premium_redeem_state_mismatch": "This server's premium status changed while you were running the command. Please try again.", + "premium_redeem_too_few_credits": "You don't have enough premium credits.", + + "autoredeem": "Manage auto-redeem.", + + "autoredeem_disable": "Disable auto-redeem for a server.", + "autoredeem_disable_option_server": "The server to disable auto-redeem for.", + "autoredeem_disable_invalid_guild": "Please enter a server ID, or select a server from the options.", + "autoredeem_disable_done": "Auto-redeem disabled.", + + "autoredeem_enable": "Enable auto-redeem for a server.", + "autoredeem_enable_dm": "Please run this command inside a server.", + "autoredeem_enable_done": "Auto-redeem enabled.", + + "sb_option_category_requirements": "Requirements", + "sb_option_required": "The number of upvotes a message needs. Use \"disable\" to disable.", + "sb_option_required_remove": "How view points the message can have before the starboard post is removed. Use \"disable\" to disable.", + "sb_option_upvote_emojis": "The emojis that can be used to upvote a post. Use \"none\" to remove all.", + "sb_option_downvote_emojis": "The emojis that can be used to downvote a post. Use \"none\" to remove all.", + "sb_option_self_vote": "whether to allow users to vote on their own posts.", + "sb_option_allow_bots": "Whether to allow bot messages to be sent to the starboard.", + "sb_option_require_image": "Whether to require posts to have an image in order to appear on the starboard.", + "sb_option_older_than": "How old a post must be in order for it to be voted on (e.x. \"1 hour\"). Use 0 to disable.", + "sb_option_newer_than": "How new a post must be in order for it to be voted on (e.x. \"1 hour\"). Use 0 to disable.", + "sb_option_matches": "(Premium) Regex that the messages must match in order to be voted on. Use \".*\" to disable.", + "sb_option_not_matches": "(Premium) Regex that messages must not match in order to be voted on. Use \".*\" to disbale.", + + "sb_option_category_behavior": "Behavior", + "sb_option_enabled": "Whether the starboard is enabled.", + "sb_option_autoreact_upvote": "Whether to automatically react to starboard messages with the upvote emojis.", + "sb_option_autoreact_downvote": "Whether to automatically react to starboard messages with the downvote emojis.", + "sb_option_remove_invalid_reactions": "Whether to remove reactions that don't meet requirements.", + "sb_option_link_deletes": "If the original message is deleted, whether to also delete the starboard message.", + "sb_option_link_edits": "If the original message is edited, whether to also update the content of the starboard message.", + "sb_option_on_delete": "What to do if a moderator removes a post from the starboard manually.", + "sb_option_cooldown_enabled": "Whether to enable the per-user vote cooldown.", + "sb_option_cooldown": "The size of the cooldown (e.x. \"5/6\" means 5 votes per 6 seconds).", + "sb_option_exclusive_group": "Set the exclusive group this starboard belongs to.", + "sb_option_remove_exclusive_group": "Remove this starboard from the exclusive group.", + "sb_option_exclusive_group_priority": "Set the priority of this starboard within the exclusive group.", + + "sb_option_category_embed": "Embed Style", + "sb_option_color": "The color of the embeds. Use \"default\" to use the default.", + "sb_option_attachments_list": "Whether to include a list of attachments.", + "sb_option_replied_to": "Whether to include the message that was replied to, if any.", + + "sb_option_category_style": "Style", + "sb_option_display_emoji": "The emoji to show next to the point count. Use \"none\" to remove.", + "sb_option_ping_author": "Whether to mention the author on starboard posts.", + "sb_option_use_server_profile": "Whether to use the per-server avatar and nickname for starboard posts.", + "sb_option_extra_embeds": "Whether to include extra embeds that were on the original message.", + "sb_option_go_to_message": "The style of the \"Go to Message\" link or button.", + "sb_option_use_webhook": "Whether to use a webhook for starboard messages.", + + "sb_option_category_regex": "Regex Matching", + + "validation_display_emoji": "Please specify exactly one emoji for `display-emoji`, or use \"none\" to remove.", + + "pong": "Pong! I'm here." +} \ No newline at end of file diff --git a/locales/pt.json b/locales/pt.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/locales/pt.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/interactions/autocomplete/autoredeem.rs b/src/interactions/autocomplete/autoredeem.rs index 7cab2df2..fcd4bf26 100644 --- a/src/interactions/autocomplete/autoredeem.rs +++ b/src/interactions/autocomplete/autoredeem.rs @@ -22,7 +22,7 @@ pub async fn autoredeem_autocomplete( .cache .guilds .with(&id, |_, g| g.as_ref().map(|g| format!("{} {id}", g.name))) - .unwrap_or_else(|| format!("Unkown Server {id}")) + .unwrap_or_else(|| ctx.user_lang().unknown_server(id)) }; let guild_names = guild_ids .iter() diff --git a/src/interactions/commands/chat/autostar/create.rs b/src/interactions/commands/chat/autostar/create.rs index 1200518c..faf7348c 100644 --- a/src/interactions/commands/chat/autostar/create.rs +++ b/src/interactions/commands/chat/autostar/create.rs @@ -1,4 +1,5 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; +use twilight_mention::Mention; use twilight_model::application::interaction::application_command::InteractionChannel; use crate::{ @@ -8,16 +9,27 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::id_as_i64::GetI64, }; +locale_func!(autostar_create); +locale_func!(autostar_create_option_name); +locale_func!(autostar_create_option_channel); + #[derive(CommandModel, CreateCommand)] -#[command(name = "create", desc = "Create an autostar channel.")] +#[command( + name = "create", + desc = "Create an autostar channel.", + desc_localizations = "autostar_create" +)] pub struct CreateAutoStarChannel { /// The name of the autostar channel. + #[command(desc_localizations = "autostar_create_option_name")] name: String, /// The channel to create an autostar channel in. - #[command(channel_types = r#" + #[command( + channel_types = r#" guild_text guild_voice guild_stage_voice @@ -26,7 +38,9 @@ pub struct CreateAutoStarChannel { public_thread private_thread guild_forum - "#)] + "#, + desc_localizations = "autostar_create_option_channel" + )] channel: InteractionChannel, } @@ -53,11 +67,8 @@ impl CreateAutoStarChannel { }; if count >= limit { ctx.respond_str( - &format!( - "You can only have up to {} autostar channels. The premium limit is {}.", - limit, - constants::MAX_PREM_AUTOSTAR, - ), + &ctx.user_lang() + .autostar_create_limit_reached(limit, constants::MAX_PREM_AUTOSTAR), true, ) .await?; @@ -67,16 +78,14 @@ impl CreateAutoStarChannel { let ret = AutoStarChannel::create(&ctx.bot.pool, &name, channel_id, guild_id).await?; if ret.is_none() { - ctx.respond_str( - &format!("An autostar channel with the name '{name}' already exists."), - true, - ) - .await?; + ctx.respond_str(&ctx.user_lang().autostar_channel_already_exists(name), true) + .await?; } else { ctx.bot.cache.autostar_channel_ids.insert(self.channel.id); ctx.respond_str( - &format!("Created autostar channel '{name}' in <#{channel_id}>."), + &ctx.user_lang() + .autostar_create_success(self.channel.id.mention(), name), false, ) .await?; diff --git a/src/interactions/commands/chat/autostar/delete.rs b/src/interactions/commands/chat/autostar/delete.rs index d8faf2d6..f3cdf9af 100644 --- a/src/interactions/commands/chat/autostar/delete.rs +++ b/src/interactions/commands/chat/autostar/delete.rs @@ -6,14 +6,25 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::{id_as_i64::GetI64, views::confirm}, }; +locale_func!(autostar_delete); +locale_func!(autostar_delete_option_name); + #[derive(CreateCommand, CommandModel)] -#[command(name = "delete", desc = "Delete an autostar channel.")] +#[command( + name = "delete", + desc = "Delete an autostar channel.", + desc_localizations = "autostar_delete" +)] pub struct DeleteAutoStarChannel { /// The name of the autostar channel to delete. - #[command(autocomplete = true)] + #[command( + autocomplete = true, + desc_localizations = "autostar_delete_option_name" + )] name: String, } @@ -21,16 +32,8 @@ impl DeleteAutoStarChannel { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx); - let mut btn_ctx = match confirm::simple( - &mut ctx, - &format!( - "Are you sure you want to delete the autostar channel '{}'?", - self.name - ), - true, - ) - .await? - { + let conf = ctx.user_lang().autostar_delete_confirm(&self.name); + let mut btn_ctx = match confirm::simple(&mut ctx, &conf, true).await? { None => return Ok(()), Some(btn_ctx) => btn_ctx, }; @@ -44,11 +47,11 @@ impl DeleteAutoStarChannel { .await?; if ret.is_none() { btn_ctx - .edit_str("No autostar channel with that name was found.", true) + .edit_str(&ctx.user_lang().autostar_channel_missing(self.name), true) .await?; } else { btn_ctx - .edit_str(&format!("Deleted autostar channel '{}'.", self.name), true) + .edit_str(&ctx.user_lang().autostar_delete_done(self.name), true) .await?; } Ok(()) diff --git a/src/interactions/commands/chat/autostar/edit.rs b/src/interactions/commands/chat/autostar/edit.rs index 1d4a1cd3..455c359f 100644 --- a/src/interactions/commands/chat/autostar/edit.rs +++ b/src/interactions/commands/chat/autostar/edit.rs @@ -9,28 +9,58 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::id_as_i64::GetI64, }; +locale_func!(autostar_edit); +locale_func!(autostar_edit_option_name); +locale_func!(autostar_edit_option_emojis); +locale_func!(autostar_edit_option_min_chars); +locale_func!(autostar_edit_option_max_chars); +locale_func!(autostar_edit_option_require_image); +locale_func!(autostar_edit_option_delete_invalid); + #[derive(CommandModel, CreateCommand)] -#[command(name = "edit", desc = "Set the emojis for an autostar channel.")] +#[command( + name = "edit", + desc = "Edit the settings for an autostar channel.", + desc_localizations = "autostar_edit" +)] pub struct EditAutoStar { /// The name of the autostar channel to edit. - #[command(autocomplete = true)] + #[command(autocomplete = true, desc_localizations = "autostar_edit_option_name")] name: String, + /// The emojis to use. Use "none" to set to none. + #[command(desc_localizations = "autostar_edit_option_emojis")] emojis: Option, + /// The minimum number of characters a message needs. - #[command(rename = "min-chars", min_value = 0, max_value = 5_000)] + #[command( + rename = "min-chars", + min_value = 0, + max_value = 5_000, + desc_localizations = "autostar_edit_option_min_chars" + )] min_chars: Option, + /// The maximum number of characters a message can have. Set to -1 to disable. - #[command(rename = "max-chars", min_value = -1, max_value = 5_000)] + #[command(rename = "max-chars", min_value = -1, max_value = 5_000, desc_localizations = "autostar_edit_option_max_chars")] max_chars: Option, + /// Whether or not a message must include an image. - #[command(rename = "require-image")] + #[command( + rename = "require-image", + desc_localizations = "autostar_edit_option_require_image" + )] require_image: Option, + /// Whether to delete messages that don't meet requirements. - #[command(rename = "delete-invalid")] + #[command( + rename = "delete-invalid", + desc_localizations = "autostar_edit_option_delete_invalid" + )] delete_invalid: Option, } @@ -42,7 +72,7 @@ impl EditAutoStar { let asc = AutoStarChannel::get_by_name(&ctx.bot.pool, &self.name, guild_id_i64).await?; let mut asc = match asc { None => { - ctx.respond_str("No autostar channel with that name was found.", true) + ctx.respond_str(&ctx.user_lang().autostar_channel_missing(self.name), true) .await?; return Ok(()); } @@ -82,17 +112,14 @@ impl EditAutoStar { let asc = asc.update_settings(&ctx.bot.pool).await?; if asc.is_none() { - ctx.respond_str("No autostar channels with that name were found.", true) + ctx.respond_str(&ctx.user_lang().autostar_channel_missing(self.name), true) .await?; return Ok(()); } // set the emojis - ctx.respond_str( - &format!("Updated the settings for autostar channel '{}'.", self.name), - false, - ) - .await?; + ctx.respond_str(&ctx.user_lang().autostar_edit_done(self.name), false) + .await?; Ok(()) } diff --git a/src/interactions/commands/chat/autostar/filters/add.rs b/src/interactions/commands/chat/autostar/filters/add.rs index bfe84c26..944bfaee 100644 --- a/src/interactions/commands/chat/autostar/filters/add.rs +++ b/src/interactions/commands/chat/autostar/filters/add.rs @@ -10,17 +10,34 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::id_as_i64::GetI64, }; +locale_func!(autostar_filters_add); +locale_func!(autostar_filters_add_option_autostar_channel); +locale_func!(autostar_filters_add_option_filter_group); + #[derive(CommandModel, CreateCommand)] -#[command(name = "add", desc = "Add a filter group to an autostar channel.")] +#[command( + name = "add", + desc = "Add a filter group to an autostar channel.", + desc_localizations = "autostar_filters_add" +)] pub struct Add { /// The autostar channel to add the filter to. - #[command(autocomplete = true, rename = "autostar-channel")] + #[command( + autocomplete = true, + rename = "autostar-channel", + desc_localizations = "autostar_filters_add_option_autostar_channel" + )] autostar_channel: String, /// The filter group to add to the autostar channel. - #[command(autocomplete = true, rename = "filter-group")] + #[command( + autocomplete = true, + rename = "filter-group", + desc_localizations = "autostar_filters_add_option_filter_group" + )] filter_group: String, } @@ -32,7 +49,7 @@ impl Add { &ctx.bot.pool, guild_id, &self.filter_group ).await? else { ctx.respond_str( - &format!("No filter group named '{}' exists.", self.filter_group), + &ctx.user_lang().filter_group_missing(self.filter_group), true, ).await?; return Ok(()); @@ -42,7 +59,7 @@ impl Add { &ctx.bot.pool, &self.autostar_channel, guild_id ).await? else { ctx.respond_str( - &format!("No autostar channel named '{}' exists.", self.autostar_channel), + &ctx.user_lang().autostar_channel_missing(self.autostar_channel), true, ).await?; return Ok(()); @@ -51,16 +68,15 @@ impl Add { let ret = AutostarChannelFilterGroup::create(&ctx.bot.pool, group.id, asc.id).await?; if ret.is_some() { ctx.respond_str( - &format!( - "Added filter group '{}' to autostar channel '{}'.", - group.name, asc.name - ), + &ctx.user_lang() + .autostar_filters_add_success(self.autostar_channel, self.filter_group), false, ) .await?; } else { ctx.respond_str( - "That filter group is already applied to that autostar channel.", + &ctx.user_lang() + .autostar_filters_add_already_added(self.autostar_channel, self.filter_group), true, ) .await?; diff --git a/src/interactions/commands/chat/autostar/filters/mod.rs b/src/interactions/commands/chat/autostar/filters/mod.rs index b862605a..a6742a40 100644 --- a/src/interactions/commands/chat/autostar/filters/mod.rs +++ b/src/interactions/commands/chat/autostar/filters/mod.rs @@ -1,13 +1,16 @@ mod add; mod remove; -use crate::{errors::StarboardResult, interactions::context::CommandCtx}; +use crate::{errors::StarboardResult, interactions::context::CommandCtx, locale_func}; use twilight_interactions::command::{CommandModel, CreateCommand}; +locale_func!(autostar_filters); + #[derive(CommandModel, CreateCommand)] #[command( name = "filters", - desc = "Manage filter groups for an autostar channel." + desc = "Manage filter groups for an autostar channel.", + desc_localizations = "autostar_filters" )] pub enum Filters { #[command(name = "add")] diff --git a/src/interactions/commands/chat/autostar/filters/remove.rs b/src/interactions/commands/chat/autostar/filters/remove.rs index 7a858feb..df33f16e 100644 --- a/src/interactions/commands/chat/autostar/filters/remove.rs +++ b/src/interactions/commands/chat/autostar/filters/remove.rs @@ -10,20 +10,34 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::id_as_i64::GetI64, }; +locale_func!(autostar_filters_remove); +locale_func!(autostar_filters_remove_option_autostar_channel); +locale_func!(autostar_filters_remove_option_filter_group); + #[derive(CommandModel, CreateCommand)] #[command( name = "remove", - desc = "Remove a filter group from an autostar channel." + desc = "Remove a filter group from an autostar channel.", + desc_localizations = "autostar_filters_remove" )] pub struct Remove { /// The autostar channel to remove the filter group from. - #[command(autocomplete = true, rename = "autostar-channel")] + #[command( + autocomplete = true, + rename = "autostar-channel", + desc_localizations = "autostar_filters_remove_option_autostar_channel" + )] autostar_channel: String, /// The filter group to remove from the autostar channel. - #[command(autocomplete = true, rename = "filter-group")] + #[command( + autocomplete = true, + rename = "filter-group", + desc_localizations = "autostar_filters_remove_option_filter_group" + )] filter_group: String, } @@ -35,7 +49,7 @@ impl Remove { &ctx.bot.pool, guild_id, &self.filter_group ).await? else { ctx.respond_str( - &format!("No filter group named '{}' exists.", self.filter_group), + &ctx.user_lang().filter_group_missing(self.filter_group), true, ).await?; return Ok(()); @@ -45,7 +59,7 @@ impl Remove { &ctx.bot.pool, &self.autostar_channel, guild_id ).await? else { ctx.respond_str( - &format!("No autostar channel named '{}' exists.", self.autostar_channel), + &ctx.user_lang().autostar_channel_missing(self.autostar_channel), true, ).await?; return Ok(()); @@ -55,16 +69,15 @@ impl Remove { if ret.is_some() { ctx.respond_str( - &format!( - "Removed the filter group '{}' from autostar channel '{}'.", - group.name, asc.name - ), + &ctx.user_lang() + .autostar_filters_remove_success(asc.name, group.name), false, ) .await?; } else { ctx.respond_str( - "That filter group is not applied to that autostar channel.", + &ctx.user_lang() + .autostar_filters_remove_fail(asc.name, group.name), true, ) .await?; diff --git a/src/interactions/commands/chat/autostar/mod.rs b/src/interactions/commands/chat/autostar/mod.rs index 1b1feeca..5b983824 100644 --- a/src/interactions/commands/chat/autostar/mod.rs +++ b/src/interactions/commands/chat/autostar/mod.rs @@ -10,12 +10,16 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use crate::{ errors::StarboardResult, interactions::{commands::permissions::manage_channels, context::CommandCtx}, + locale_func, }; +locale_func!(autostar); + #[derive(CommandModel, CreateCommand)] #[command( name = "autostar", desc = "Manage autostar channels.", + desc_localizations = "autostar", dm_permission = false, default_permissions = "manage_channels" )] diff --git a/src/interactions/commands/chat/autostar/rename.rs b/src/interactions/commands/chat/autostar/rename.rs index 43dbeaf0..cf17b1dd 100644 --- a/src/interactions/commands/chat/autostar/rename.rs +++ b/src/interactions/commands/chat/autostar/rename.rs @@ -5,17 +5,34 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::{id_as_i64::GetI64, pg_error::PgErrorTraits}, }; +locale_func!(autostar_rename); +locale_func!(autostar_rename_option_current_name); +locale_func!(autostar_rename_option_new_name); + #[derive(CreateCommand, CommandModel)] -#[command(name = "rename", desc = "Rename an autostar channel.")] +#[command( + name = "rename", + desc = "Rename an autostar channel.", + desc_localizations = "autostar_rename" +)] pub struct RenameAutoStarChannel { /// The current name of the autostar channel. - #[command(rename = "current-name", autocomplete = true)] + #[command( + rename = "current-name", + autocomplete = true, + desc_localizations = "autostar_rename_option_current_name" + )] current_name: String, + /// The new name for the autostar channel. - #[command(rename = "new-name")] + #[command( + rename = "new-name", + desc_localizations = "autostar_rename_option_new_name" + )] new_name: String, } @@ -43,7 +60,7 @@ impl RenameAutoStarChannel { Err(why) => { if why.is_duplicate() { ctx.respond_str( - &format!("An autostar channel with the name '{new_name}' already exists."), + &ctx.user_lang().autostar_channel_already_exists(new_name), true, ) .await? @@ -52,15 +69,16 @@ impl RenameAutoStarChannel { } } Ok(None) => { - ctx.respond_str("No autostar channel with that name was found.", true) - .await? + ctx.respond_str( + &ctx.user_lang().autostar_channel_missing(self.current_name), + true, + ) + .await? } Ok(Some(_)) => { ctx.respond_str( - &format!( - "Renamed the autostar channel from '{}' to '{}'.", - self.current_name, new_name - ), + &ctx.user_lang() + .autostar_rename_done(new_name, self.current_name), false, ) .await? diff --git a/src/interactions/commands/chat/autostar/view.rs b/src/interactions/commands/chat/autostar/view.rs index e25d1df7..2f791033 100644 --- a/src/interactions/commands/chat/autostar/view.rs +++ b/src/interactions/commands/chat/autostar/view.rs @@ -18,6 +18,8 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, + translations::Lang, utils::{ embed, id_as_i64::GetI64, @@ -25,11 +27,18 @@ use crate::{ }, }; +locale_func!(autostar_view); +locale_func!(autostar_view_option_name); + #[derive(CreateCommand, CommandModel)] -#[command(name = "view", desc = "View your autostar channels.")] +#[command( + name = "view", + desc = "View your autostar channels.", + desc_localizations = "autostar_view" +)] pub struct ViewAutoStarChannels { /// The name of the autostar channel to view. Leave blank to show all. - #[command(autocomplete = true)] + #[command(autocomplete = true, desc_localizations = "autostar_view_option_name")] name: Option, } @@ -38,11 +47,12 @@ impl ViewAutoStarChannels { let guild_id = get_guild_id!(ctx); let guild_id_i64 = guild_id.get_i64(); let bot = ctx.bot.clone(); + let lang = ctx.user_lang(); let asc = AutoStarChannel::list_by_guild(&ctx.bot.pool, guild_id_i64).await?; if asc.is_empty() { - ctx.respond_str("This server has no autostar channels.", true) + ctx.respond_str(ctx.user_lang().autostar_view_no_autostar_channels(), true) .await?; return Ok(()); } @@ -50,7 +60,7 @@ impl ViewAutoStarChannels { let asc = AutoStarChannel::list_by_guild(&ctx.bot.pool, guild_id_i64).await?; if asc.is_empty() { - ctx.respond_str("This server has no autostar channels.", true) + ctx.respond_str(ctx.user_lang().autostar_view_no_autostar_channels(), true) .await?; return Ok(()); } @@ -64,10 +74,11 @@ impl ViewAutoStarChannels { } let mut label = a.name.clone(); if a.premium_locked { - label.push_str(" (premium-locked)"); + label.push(' '); + label.push_str(lang.premium_locked_aside()); } - let emb = autostar_embed(&bot, guild_id, a).await?; + let emb = autostar_embed(&bot, guild_id, a, lang).await?; let page = SelectPaginatorPageBuilder::new(label).add_embed(emb); paginator = paginator.add_page(page); @@ -79,7 +90,7 @@ impl ViewAutoStarChannels { } } -async fn filters_str(bot: &StarboardBot, asc_id: i32) -> StarboardResult { +async fn filters_str(bot: &StarboardBot, asc_id: i32, lang: Lang) -> StarboardResult { let filter_group_ids = AutostarChannelFilterGroup::list_by_autostar_channel(&bot.pool, asc_id).await?; let filter_group_ids = filter_group_ids.into_iter().map(|f| f.filter_group_id); @@ -91,42 +102,32 @@ async fn filters_str(bot: &StarboardBot, asc_id: i32) -> StarboardResult let mut filters = filters.join(", "); if filters.is_empty() { - filters = "No filters set.".to_string(); + filters = lang.autostar_view_filters_none().to_string(); } - Ok(format!( - concat!( - "These are the filters that must pass for a message to be valid:\n\n", - "{}\n\n", - "You can view these filters with `/filters view`, and you can change ", - "which ones apply with `/autostar filters [add|remove]'." - ), - filters, - )) + Ok(lang.autostar_view_filters_info(filters)) } async fn autostar_embed( bot: &StarboardBot, guild_id: Id, asc: AutoStarChannel, + lang: Lang, ) -> StarboardResult { let emojis = Vec::::from_stored(asc.emojis).into_readable(bot, guild_id); let max_chars = asc .max_chars .map(|v| v.to_string()) - .unwrap_or_else(|| "none".to_string()); + .unwrap_or_else(|| lang.disabled().to_string()); let note = if asc.premium_locked { - concat!( - "This autostar channel is locked because it exceeds the non-premium ", - "limit.\n\n" - ) + lang.premium_locked_autostar_info() } else { "" }; let asc_settings = concat_format!( "{}" <- note; - "This autostar channel is in <#{}>.\n\n" <- asc.channel_id; + "{}\n\n" <- lang.autostar_view_channel(asc.channel_id); "emojis: {}\n" <- emojis; "min-chars: {}\n" <- asc.min_chars; "max-chars: {}\n" <- max_chars; @@ -135,11 +136,11 @@ async fn autostar_embed( ); let emb = embed::build() - .title(format!("Autostar Channel '{}'", asc.name)) + .title(lang.autostar_view_title(asc.name)) .description(asc_settings) .field(EmbedFieldBuilder::new( - "Filters", - filters_str(bot, asc.id).await?, + lang.filters_title(), + filters_str(bot, asc.id, lang).await?, )) .build(); diff --git a/src/interactions/commands/chat/exclusive_groups/create.rs b/src/interactions/commands/chat/exclusive_groups/create.rs index f4823c16..c617494b 100644 --- a/src/interactions/commands/chat/exclusive_groups/create.rs +++ b/src/interactions/commands/chat/exclusive_groups/create.rs @@ -6,13 +6,22 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::id_as_i64::GetI64, }; +locale_func!(exclusive_groups_create); +locale_func!(exclusive_groups_create_option_name); + #[derive(CommandModel, CreateCommand)] -#[command(name = "create", desc = "Create an exclusive group for starboards.")] +#[command( + name = "create", + desc = "Create an exclusive group for starboards.", + desc_localizations = "exclusive_groups_create" +)] pub struct Create { /// The name for the exclusive group. + #[command(desc_localizations = "exclusive_groups_create_option_name")] name: String, } @@ -33,10 +42,8 @@ impl Create { let count = ExclusiveGroup::count_by_guild(&ctx.bot.pool, guild_id).await?; if count >= constants::MAX_EXCLUSIVE_GROUPS { ctx.respond_str( - &format!( - "You can only have up to {} exclusive groups.", - constants::MAX_EXCLUSIVE_GROUPS - ), + &ctx.user_lang() + .exclusive_groups_create_limit_reached(constants::MAX_EXCLUSIVE_GROUPS), true, ) .await?; @@ -46,14 +53,11 @@ impl Create { let group = ExclusiveGroup::create(&ctx.bot.pool, &name, guild_id).await?; if group.is_some() { - ctx.respond_str(&format!("Created exclusive group '{name}'."), false) + ctx.respond_str(&ctx.user_lang().exclusive_groups_create_done(name), false) .await?; } else { - ctx.respond_str( - &format!("An exclusive group named '{name}' already exists."), - true, - ) - .await?; + ctx.respond_str(&ctx.user_lang().exclusive_group_already_exists(name), true) + .await?; } Ok(()) diff --git a/src/interactions/commands/chat/exclusive_groups/delete.rs b/src/interactions/commands/chat/exclusive_groups/delete.rs index f0144940..2d025533 100644 --- a/src/interactions/commands/chat/exclusive_groups/delete.rs +++ b/src/interactions/commands/chat/exclusive_groups/delete.rs @@ -2,14 +2,24 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use crate::{ database::ExclusiveGroup, errors::StarboardResult, get_guild_id, - interactions::context::CommandCtx, utils::id_as_i64::GetI64, + interactions::context::CommandCtx, locale_func, utils::id_as_i64::GetI64, }; +locale_func!(exclusive_groups_delete); +locale_func!(exclusive_groups_delete_option_name); + #[derive(CommandModel, CreateCommand)] -#[command(name = "delete", desc = "Delete an exclusive group.")] +#[command( + name = "delete", + desc = "Delete an exclusive group.", + desc_localizations = "exclusive_groups_delete" +)] pub struct Delete { /// The exclusive group to delete. - #[command(autocomplete = true)] + #[command( + autocomplete = true, + desc_localizations = "exclusive_groups_delete_option_name" + )] name: String, } @@ -20,7 +30,7 @@ impl Delete { let ret = ExclusiveGroup::delete(&ctx.bot.pool, &self.name, guild_id).await?; let Some(group) = ret else { ctx.respond_str( - &format!("Exclusive group '{}' does not exist.", self.name), + &ctx.user_lang().exclusive_group_missing(self.name), true, ) .await?; @@ -36,8 +46,11 @@ impl Delete { .fetch_all(&ctx.bot.pool) .await?; - ctx.respond_str(&format!("Deleted exclusive group '{}'.", self.name), false) - .await?; + ctx.respond_str( + &ctx.user_lang().exclusive_groups_delete_done(self.name), + false, + ) + .await?; Ok(()) } diff --git a/src/interactions/commands/chat/exclusive_groups/mod.rs b/src/interactions/commands/chat/exclusive_groups/mod.rs index f65f69c7..d154b993 100644 --- a/src/interactions/commands/chat/exclusive_groups/mod.rs +++ b/src/interactions/commands/chat/exclusive_groups/mod.rs @@ -7,12 +7,16 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use crate::{ errors::StarboardResult, interactions::{commands::permissions::manage_channels, context::CommandCtx}, + locale_func, }; +locale_func!(exclusive_groups); + #[derive(CommandModel, CreateCommand)] #[command( name = "exclusive-groups", desc = "Manage exclusive groups for starboards.", + desc_localizations = "exclusive_groups", default_permissions = "manage_channels", dm_permission = false )] diff --git a/src/interactions/commands/chat/exclusive_groups/rename.rs b/src/interactions/commands/chat/exclusive_groups/rename.rs index 86f7ab8b..9712b1cb 100644 --- a/src/interactions/commands/chat/exclusive_groups/rename.rs +++ b/src/interactions/commands/chat/exclusive_groups/rename.rs @@ -5,17 +5,33 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::{id_as_i64::GetI64, pg_error::PgErrorTraits}, }; +locale_func!(exclusive_groups_rename); +locale_func!(exclusive_groups_rename_option_original_name); +locale_func!(exclusive_groups_rename_option_new_name); + #[derive(CommandModel, CreateCommand)] -#[command(name = "rename", desc = "Rename an exclusive group.")] +#[command( + name = "rename", + desc = "Rename an exclusive group.", + desc_localizations = "exclusive_groups_rename" +)] pub struct Rename { /// The original name for the exclusive group. - #[command(rename = "original-name", autocomplete = true)] + #[command( + rename = "original-name", + autocomplete = true, + desc_localizations = "exclusive_groups_rename_option_original_name" + )] original_name: String, /// The new name for the exclusive group. - #[command(rename = "new-name")] + #[command( + rename = "new-name", + desc_localizations = "exclusive_groups_rename_option_new_name" + )] new_name: String, } @@ -37,14 +53,19 @@ impl Rename { let err = match ret { Err(why) => { if why.is_duplicate() { - format!("An exclusive group named '{new_name}' already exists.") + ctx.user_lang().exclusive_group_already_exists(new_name) } else { return Err(why.into()); } } - Ok(None) => format!("Exclusive group '{}' does not exist.", self.original_name), + Ok(None) => ctx.user_lang().exclusive_group_missing(self.original_name), Ok(Some(_)) => { - ctx.respond_str("Done.", true).await?; + ctx.respond_str( + &ctx.user_lang() + .exclusive_groups_rename_done(new_name, self.original_name), + true, + ) + .await?; return Ok(()); } }; diff --git a/src/interactions/commands/chat/filters/create_filter.rs b/src/interactions/commands/chat/filters/create_filter.rs index 02ef2db7..b48383d8 100644 --- a/src/interactions/commands/chat/filters/create_filter.rs +++ b/src/interactions/commands/chat/filters/create_filter.rs @@ -6,17 +6,33 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::id_as_i64::GetI64, }; +locale_func!(filters_create); +locale_func!(filters_create_option_group); +locale_func!(filters_create_option_position); + #[derive(CommandModel, CreateCommand)] -#[command(name = "create-filter", desc = "Create a filter for a filter group.")] +#[command( + name = "create-filter", + desc = "Create a filter for a filter group.", + desc_localizations = "filters_create" +)] pub struct CreateFilter { /// The filter group to create this filter for. - #[command(autocomplete = true)] + #[command( + autocomplete = true, + desc_localizations = "filters_create_option_group" + )] group: String, /// The position to put the filter in. Use 1 for the start (top) or leave blank for the end. - #[command(min_value = 1, max_value = 1_000)] + #[command( + min_value = 1, + max_value = 1_000, + desc_localizations = "filters_create_option_position" + )] position: Option, } @@ -25,17 +41,15 @@ impl CreateFilter { let guild_id = get_guild_id!(ctx).get_i64(); let Some(group) = FilterGroup::get_by_name(&ctx.bot.pool, guild_id, &self.group).await? else { - ctx.respond_str(&format!("Filter group '{}' does not exist.", self.group), true).await?; + ctx.respond_str(&ctx.user_lang().filter_group_missing(self.group), true).await?; return Ok(()); }; let count = Filter::list_by_filter(&ctx.bot.pool, group.id).await?.len(); if count >= constants::MAX_FILTERS_PER_GROUP { ctx.respond_str( - &format!( - "You can only have up to {} filters per group.", - constants::MAX_FILTERS_PER_GROUP - ), + &ctx.user_lang() + .filters_create_limit_reached(constants::MAX_FILTERS_PER_GROUP), true, ) .await?; @@ -55,7 +69,11 @@ impl CreateFilter { .await? .unwrap(); - ctx.respond_str("Filter created.", false).await?; + ctx.respond_str( + &ctx.user_lang().filters_create_done(group.name, position), + false, + ) + .await?; Ok(()) } } diff --git a/src/interactions/commands/chat/filters/create_group.rs b/src/interactions/commands/chat/filters/create_group.rs index 53a52f28..ac2b7ca1 100644 --- a/src/interactions/commands/chat/filters/create_group.rs +++ b/src/interactions/commands/chat/filters/create_group.rs @@ -6,13 +6,22 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::id_as_i64::GetI64, }; +locale_func!(filters_create_group); +locale_func!(filters_create_group_option_name); + #[derive(CommandModel, CreateCommand)] -#[command(name = "create-group", desc = "Create a filter group.")] +#[command( + name = "create-group", + desc = "Create a filter group.", + desc_localizations = "filters_create_group" +)] pub struct CreateGroup { /// The name of the filter group. + #[command(desc_localizations = "filters_create_group_option_name")] name: String, } @@ -25,10 +34,8 @@ impl CreateGroup { .len(); if count >= constants::MAX_FILTER_GROUPS { ctx.respond_str( - &format!( - "You can only have up to {} filter groups.", - constants::MAX_FILTER_GROUPS - ), + &ctx.user_lang() + .filters_create_group_limit_reached(constants::MAX_FILTER_GROUPS), true, ) .await?; @@ -45,13 +52,10 @@ impl CreateGroup { }; let group = FilterGroup::create(&ctx.bot.pool, guild_id, &name).await?; if group.is_none() { - ctx.respond_str( - &format!("A filter group named '{name}' already exists."), - true, - ) - .await?; + ctx.respond_str(&ctx.user_lang().filter_group_already_exists(name), true) + .await?; } else { - ctx.respond_str(&format!("Created filter group '{name}'."), false) + ctx.respond_str(&ctx.user_lang().filters_create_group_done(name), false) .await?; } diff --git a/src/interactions/commands/chat/filters/delete_filter.rs b/src/interactions/commands/chat/filters/delete_filter.rs index 9b3c2b2f..a6dd200c 100644 --- a/src/interactions/commands/chat/filters/delete_filter.rs +++ b/src/interactions/commands/chat/filters/delete_filter.rs @@ -5,17 +5,33 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::{id_as_i64::GetI64, views::confirm}, }; +locale_func!(filters_delete); +locale_func!(filters_delete_option_group); +locale_func!(filters_delete_option_position); + #[derive(CommandModel, CreateCommand)] -#[command(name = "delete-filter", desc = "Delete a filter from a filter group.")] +#[command( + name = "delete-filter", + desc = "Delete a filter from a filter group.", + desc_localizations = "filters_delete" +)] pub struct DeleteFilter { /// The group to delete the filter from. - #[command(autocomplete = true)] + #[command( + autocomplete = true, + desc_localizations = "filters_delete_option_group" + )] group: String, /// The position of the filter to delete. - #[command(min_value = 1, max_value = 1_000)] + #[command( + min_value = 1, + max_value = 1_000, + desc_localizations = "filters_delete_option_position" + )] position: i64, } @@ -25,16 +41,16 @@ impl DeleteFilter { let group = FilterGroup::get_by_name(&ctx.bot.pool, guild_id, &self.group).await?; let Some(group) = group else { - ctx.respond_str(&format!("Filter group '{}' does not exist.", self.group), true).await?; + ctx.respond_str(&ctx.user_lang().filter_group_missing(self.group), true).await?; return Ok(()); }; + let conf = ctx + .user_lang() + .filters_delete_confirm(&self.group, self.position); let Some(mut btn_ctx) = confirm::simple( &mut ctx, - &format!( - "This will delete the filter at {} for filter group '{}'. Continue?", - self.position, group.name - ), + &conf, true, ) .await? else { @@ -45,15 +61,16 @@ impl DeleteFilter { if ret.is_some() { btn_ctx - .edit_str(&format!("Filter at {} deleted.", self.position), true) + .edit_str( + &ctx.user_lang() + .filters_delete_done(self.group, self.position), + true, + ) .await?; } else { btn_ctx .edit_str( - &format!( - "No filter exists at {} for group '{}'.", - self.position, self.group - ), + &ctx.user_lang().filter_missing(self.group, self.position), true, ) .await?; diff --git a/src/interactions/commands/chat/filters/delete_group.rs b/src/interactions/commands/chat/filters/delete_group.rs index 9503a3d5..80c6b8b8 100644 --- a/src/interactions/commands/chat/filters/delete_group.rs +++ b/src/interactions/commands/chat/filters/delete_group.rs @@ -5,14 +5,25 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::{id_as_i64::GetI64, views::confirm}, }; +locale_func!(filters_delete_group); +locale_func!(filters_delete_group_option_name); + #[derive(CommandModel, CreateCommand)] -#[command(name = "delete-group", desc = "Delete a filter group.")] +#[command( + name = "delete-group", + desc = "Delete a filter group.", + desc_localizations = "filters_delete_group" +)] pub struct DeleteGroup { /// The filter group to delete. - #[command(autocomplete = true)] + #[command( + autocomplete = true, + desc_localizations = "filters_delete_group_option_name" + )] name: String, } @@ -20,9 +31,10 @@ impl DeleteGroup { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx).get_i64(); + let conf = ctx.user_lang().filters_delete_group_confirm(&self.name); let Some(mut btn_ctx) = confirm::simple( &mut ctx, - &format!("This will delete the filter group '{}'. Continue?", self.name), + &conf, true, ).await? else { return Ok(()); @@ -32,14 +44,11 @@ impl DeleteGroup { if filter.is_some() { btn_ctx - .edit_str(&format!("Deleted filter group '{}'.", self.name), true) + .edit_str(&ctx.user_lang().filters_delete_group_done(&self.name), true) .await?; } else { btn_ctx - .edit_str( - &format!("Filter group '{}' does not exist.", self.name), - true, - ) + .edit_str(&ctx.user_lang().filter_group_missing(&self.name), true) .await?; } diff --git a/src/interactions/commands/chat/filters/edit.rs b/src/interactions/commands/chat/filters/edit.rs index 8b3f390b..def4f872 100644 --- a/src/interactions/commands/chat/filters/edit.rs +++ b/src/interactions/commands/chat/filters/edit.rs @@ -13,38 +13,76 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, + translations::Lang, utils::id_as_i64::GetI64, }; -fn validate_roles(length: usize) -> Result<(), String> { +fn validate_roles(length: usize, lang: Lang) -> Result<(), String> { if length > constants::MAX_FILTER_ROLES { - Err(format!( - "You can only have up to {} roles in a list.", - constants::MAX_FILTER_ROLES - )) + Err(lang.filters_max_roles(constants::MAX_FILTER_ROLES)) } else { Ok(()) } } -fn validate_channels(length: usize) -> Result<(), String> { +fn validate_channels(length: usize, lang: Lang) -> Result<(), String> { if length > constants::MAX_FILTER_CHANNELS { - Err(format!( - "You can only have up to {} channels in a list.", - constants::MAX_FILTER_CHANNELS - )) + Err(lang.filters_max_channels(constants::MAX_FILTER_CHANNELS)) } else { Ok(()) } } +locale_func!(filters_edit); +locale_func!(filters_edit_option_group); +locale_func!(filters_edit_option_position); +locale_func!(filters_edit_option_instant_pass); +locale_func!(filters_edit_option_instant_fail); +locale_func!(filters_edit_option_user_has_all_of); +locale_func!(filters_edit_option_user_has_some_of); +locale_func!(filters_edit_option_user_missing_all_of); +locale_func!(filters_edit_option_user_missing_some_of); +locale_func!(filters_edit_option_user_is_bot); +locale_func!(filters_edit_option_in_channel); +locale_func!(filters_edit_option_not_in_channel); +locale_func!(filters_edit_option_in_channel_or_sub_channels); +locale_func!(filters_edit_option_not_in_channel_or_sub_channels); +locale_func!(filters_edit_option_min_attachments); +locale_func!(filters_edit_option_max_attachments); +locale_func!(filters_edit_option_min_length); +locale_func!(filters_edit_option_max_length); +locale_func!(filters_edit_option_matches); +locale_func!(filters_edit_option_not_matches); +locale_func!(filters_edit_option_voter_has_all_of); +locale_func!(filters_edit_option_voter_has_some_of); +locale_func!(filters_edit_option_voter_missing_all_of); +locale_func!(filters_edit_option_voter_missing_some_of); +locale_func!(filters_edit_option_older_than); +locale_func!(filters_edit_option_newer_than); +locale_func!(filters_bot_req_must_be_bot); +locale_func!(filters_bot_req_must_be_human); +locale_func!(filters_bot_req_disabled); + #[derive(CreateOption, CommandOption)] pub enum UserBotRequirement { - #[option(name = "User must be a bot", value = 0)] + #[option( + name = "User must be a bot", + value = 0, + name_localizations = "filters_bot_req_must_be_bot" + )] MustBeBot, - #[option(name = "User must not be a bot", value = 1)] + #[option( + name = "User must not be a bot", + value = 1, + name_localizations = "filters_bot_req_must_be_human" + )] MustBeHuman, - #[option(name = "Disabled", value = 2)] + #[option( + name = "Disabled", + value = 2, + name_localizations = "filters_bot_req_disabled" + )] Disabled, } @@ -59,89 +97,184 @@ impl From for Option { } #[derive(CommandModel, CreateCommand)] -#[command(name = "edit", desc = "Edit a filters conditions.")] +#[command( + name = "edit", + desc = "Edit a filters conditions.", + desc_localizations = "filters_edit" +)] pub struct Edit { /// The name of the filter group containing the filter to be edited. - #[command(autocomplete = true)] + #[command(autocomplete = true, desc_localizations = "filters_edit_option_group")] group: String, + /// The position of the filter to edit. - #[command(min_value = 1, max_value = 1_000)] + #[command( + min_value = 1, + max_value = 1_000, + desc_localizations = "filters_edit_option_position" + )] position: i64, // general info /// If true and this filter passes, the entire filter groups passes. - #[command(rename = "instant-pass")] + #[command( + rename = "instant-pass", + desc_localizations = "filters_edit_option_instant_pass" + )] instant_pass: Option, + /// If true and this filter fails, then the entire filter group fails. - #[command(rename = "instant-fail")] + #[command( + rename = "instant-fail", + desc_localizations = "filters_edit_option_instant_fail" + )] instant_fail: Option, // default context /// Require that the user/author has all of these roles. - #[command(rename = "user-has-all-of")] + #[command( + rename = "user-has-all-of", + desc_localizations = "filters_edit_option_user_has_all_of" + )] user_has_all_of: Option, + /// Require that the user/author has at least one of these roles. - #[command(rename = "user-has-some-of")] + #[command( + rename = "user-has-some-of", + desc_localizations = "filters_edit_option_user_has_some_of" + )] user_has_some_of: Option, + /// Require that the user/author is missing all of these roles. - #[command(rename = "user-missing-all-of")] + #[command( + rename = "user-missing-all-of", + desc_localizations = "filters_edit_option_user_missing_all_of" + )] user_missing_all_of: Option, + /// Require that the user/author is missing at least one of these roles. - #[command(rename = "user-missing-some-of")] + #[command( + rename = "user-missing-some-of", + desc_localizations = "filters_edit_option_user_missing_some_of" + )] user_missing_some_of: Option, + /// Require that the user is or is not a bot. - #[command(rename = "user-is-bot")] + #[command( + rename = "user-is-bot", + desc_localizations = "filters_edit_option_user_is_bot" + )] user_is_bot: Option, // message context /// Require that the message was sent in one of these channels. - #[command(rename = "in-channel")] + #[command( + rename = "in-channel", + desc_localizations = "filters_edit_option_in_channel" + )] in_channel: Option, + /// Require that the message was not sent in one of these channels. - #[command(rename = "not-in-channel")] + #[command( + rename = "not-in-channel", + desc_localizations = "filters_edit_option_not_in_channel" + )] not_in_channel: Option, + /// Require that the message was sent in one of these channels or their sub-channels. - #[command(rename = "in-channel-or-sub-channels")] + #[command( + rename = "in-channel-or-sub-channels", + desc_localizations = "filters_edit_option_in_channel_or_sub_channels" + )] in_channel_or_sub_channels: Option, + /// Require that the message was not sent in one of these channels or their sub-channels. - #[command(rename = "not-in-channel-or-sub-channels")] + #[command( + rename = "not-in-channel-or-sub-channels", + desc_localizations = "filters_edit_option_not_in_channel_or_sub_channels" + )] not_in_channel_or_sub_channels: Option, + /// Require that the message has at least this many attachments. Use 0 to disable. - #[command(rename = "min-attachments")] + #[command( + rename = "min-attachments", + desc_localizations = "filters_edit_option_min_attachments" + )] min_attachments: Option, + /// Require that the message have at most this many attachments. Use -1 to disable. - #[command(rename = "max-attachments")] + #[command( + rename = "max-attachments", + desc_localizations = "filters_edit_option_max_attachments" + )] max_attachments: Option, + /// Require that the message be at least this many characters long. Use 0 to disable. - #[command(rename = "min-length")] + #[command( + rename = "min-length", + desc_localizations = "filters_edit_option_min_length" + )] min_length: Option, + /// Require that the message be at most this many characters long. Use -1 to disable. - #[command(rename = "max-length")] + #[command( + rename = "max-length", + desc_localizations = "filters_edit_option_max_length" + )] max_length: Option, + /// (Premium) Require that the message match this regex. Use `.*` to disable. + #[command(desc_localizations = "filters_edit_option_matches")] matches: Option, + /// (Premium) Require that the message not match this regex. Use `.*` to disable. - #[command(rename = "not-matches")] + #[command( + rename = "not-matches", + desc_localizations = "filters_edit_option_not_matches" + )] not_matches: Option, // vote context /// Require that the voter has all of these roles. - #[command(rename = "voter-has-all-of")] + #[command( + rename = "voter-has-all-of", + desc_localizations = "filters_edit_option_voter_has_all_of" + )] voter_has_all_of: Option, + /// Require that the voter has at least one of these roles. - #[command(rename = "voter-has-some-of")] + #[command( + rename = "voter-has-some-of", + desc_localizations = "filters_edit_option_voter_has_some_of" + )] voter_has_some_of: Option, + /// Require that the voter is missing all of these roles. - #[command(rename = "voter-missing-all-of")] + #[command( + rename = "voter-missing-all-of", + desc_localizations = "filters_edit_option_voter_missing_all_of" + )] voter_missing_all_of: Option, + /// Require that the voter is missing at least one of these roles. - #[command(rename = "voter-missing-some-of")] + #[command( + rename = "voter-missing-some-of", + desc_localizations = "filters_edit_option_voter_missing_some_of" + )] voter_missing_some_of: Option, + /// Require that the message being voted on is over a certain age. Use "disable" to disable. - #[command(rename = "older-than")] + #[command( + rename = "older-than", + desc_localizations = "filters_edit_option_older_than" + )] older_than: Option, + /// Require that the message being voted on is under a certain age. Use "disable" to disable. - #[command(rename = "newer-than")] + #[command( + rename = "newer-than", + desc_localizations = "filters_edit_option_newer_than" + )] newer_than: Option, } @@ -154,7 +287,7 @@ impl Edit { &ctx.bot.pool, guild_id_i64, &self.group ).await? else { ctx.respond_str( - &format!("No filter group named '{}' exists.", self.group), + &ctx.user_lang().filter_group_missing(self.group), true, ).await?; return Ok(()); @@ -164,7 +297,7 @@ impl Edit { &ctx.bot.pool, group.id, self.position as i16 ).await? else { ctx.respond_str( - &format!("No filter for group '{}' at {} exists.", self.group, self.position), + &ctx.user_lang().filter_missing(self.group, self.position), true, ).await?; return Ok(()); @@ -183,7 +316,7 @@ impl Edit { // default context if let Some(val) = self.user_has_all_of { let roles = parse_role_ids(&ctx.bot, guild_id, &val); - if let Err(why) = validate_roles(roles.len()) { + if let Err(why) = validate_roles(roles.len(), ctx.user_lang()) { ctx.respond_str(&why, true).await?; return Ok(()); } @@ -195,7 +328,7 @@ impl Edit { } if let Some(val) = self.user_has_some_of { let roles = parse_role_ids(&ctx.bot, guild_id, &val); - if let Err(why) = validate_roles(roles.len()) { + if let Err(why) = validate_roles(roles.len(), ctx.user_lang()) { ctx.respond_str(&why, true).await?; return Ok(()); } @@ -207,7 +340,7 @@ impl Edit { } if let Some(val) = self.user_missing_all_of { let roles = parse_role_ids(&ctx.bot, guild_id, &val); - if let Err(why) = validate_roles(roles.len()) { + if let Err(why) = validate_roles(roles.len(), ctx.user_lang()) { ctx.respond_str(&why, true).await?; return Ok(()); } @@ -219,7 +352,7 @@ impl Edit { } if let Some(val) = self.user_missing_some_of { let roles = parse_role_ids(&ctx.bot, guild_id, &val); - if let Err(why) = validate_roles(roles.len()) { + if let Err(why) = validate_roles(roles.len(), ctx.user_lang()) { ctx.respond_str(&why, true).await?; return Ok(()); } @@ -236,7 +369,7 @@ impl Edit { // message context if let Some(val) = self.in_channel { let channels = textable_channel_ids(&ctx.bot, guild_id, &val).await?; - if let Err(why) = validate_channels(channels.len()) { + if let Err(why) = validate_channels(channels.len(), ctx.user_lang()) { ctx.respond_str(&why, true).await?; return Ok(()); } @@ -248,7 +381,7 @@ impl Edit { } if let Some(val) = self.not_in_channel { let channels = textable_channel_ids(&ctx.bot, guild_id, &val).await?; - if let Err(why) = validate_channels(channels.len()) { + if let Err(why) = validate_channels(channels.len(), ctx.user_lang()) { ctx.respond_str(&why, true).await?; return Ok(()); } @@ -260,7 +393,7 @@ impl Edit { } if let Some(val) = self.in_channel_or_sub_channels { let channels = textable_channel_ids(&ctx.bot, guild_id, &val).await?; - if let Err(why) = validate_channels(channels.len()) { + if let Err(why) = validate_channels(channels.len(), ctx.user_lang()) { ctx.respond_str(&why, true).await?; return Ok(()); } @@ -272,7 +405,7 @@ impl Edit { } if let Some(val) = self.not_in_channel_or_sub_channels { let channels = textable_channel_ids(&ctx.bot, guild_id, &val).await?; - if let Err(why) = validate_channels(channels.len()) { + if let Err(why) = validate_channels(channels.len(), ctx.user_lang()) { ctx.respond_str(&why, true).await?; return Ok(()); } @@ -285,16 +418,14 @@ impl Edit { if let Some(val) = self.min_attachments { if val > constants::MAX_ATTACHMENTS { ctx.respond_str( - &format!( - "You can only have up to {} attachments.", - constants::MAX_ATTACHMENTS - ), + &ctx.user_lang() + .filters_max_min_attachments(constants::MAX_ATTACHMENTS), true, ) .await?; return Ok(()); } else if val < 0 { - ctx.respond_str("`min-attachments` must be at least 0.", true) + ctx.respond_str(ctx.user_lang().filters_min_min_attachments(), true) .await?; return Ok(()); } @@ -308,16 +439,14 @@ impl Edit { if let Some(val) = self.max_attachments { if val > constants::MAX_ATTACHMENTS { ctx.respond_str( - &format!( - "You can only have up to {} attachments.", - constants::MAX_ATTACHMENTS - ), + &ctx.user_lang() + .filters_max_max_attachments(constants::MAX_ATTACHMENTS), true, ) .await?; return Ok(()); } else if val < -1 { - ctx.respond_str("`max-attachments` must be at least -1.", true) + ctx.respond_str(ctx.user_lang().filters_min_max_attachments(), true) .await?; return Ok(()); } @@ -331,16 +460,14 @@ impl Edit { if let Some(val) = self.min_length { if val > constants::MAX_LENGTH { ctx.respond_str( - &format!( - "`min-length` cannot be longer than {}.", - constants::MAX_LENGTH - ), + &ctx.user_lang() + .filters_max_min_length(constants::MAX_LENGTH), true, ) .await?; return Ok(()); } else if val < 0 { - ctx.respond_str("`min-length` must be at least 0.", true) + ctx.respond_str(ctx.user_lang().filters_min_min_length(), true) .await?; return Ok(()); } @@ -354,16 +481,14 @@ impl Edit { if let Some(val) = self.max_length { if val > constants::MAX_LENGTH { ctx.respond_str( - &format!( - "`max-length` cannot be longer than {}.", - constants::MAX_LENGTH - ), + &ctx.user_lang() + .filters_max_max_length(constants::MAX_LENGTH), true, ) .await?; return Ok(()); } else if val < -1 { - ctx.respond_str("`max-length` must be at least -1.", true) + ctx.respond_str(ctx.user_lang().filters_min_max_length(), true) .await?; return Ok(()); } @@ -377,10 +502,8 @@ impl Edit { if let Some(val) = self.matches { if val.len() > constants::MAX_REGEX_LENGTH as usize { ctx.respond_str( - &format!( - "`matches` cannot be longer than {}.", - constants::MAX_REGEX_LENGTH - ), + &ctx.user_lang() + .filters_max_matches(constants::MAX_REGEX_LENGTH), true, ) .await?; @@ -391,11 +514,8 @@ impl Edit { filter.matches = None; } else { if !premium { - ctx.respond_str( - "Only premium servers can use the `matches` condition.", - true, - ) - .await?; + ctx.respond_str(ctx.user_lang().filters_matches_premium(), true) + .await?; return Ok(()); } @@ -405,10 +525,8 @@ impl Edit { if let Some(val) = self.not_matches { if val.len() > constants::MAX_REGEX_LENGTH as usize { ctx.respond_str( - &format!( - "`not-matches` cannot be longer than {}.", - constants::MAX_REGEX_LENGTH - ), + &ctx.user_lang() + .filters_max_not_matches(constants::MAX_REGEX_LENGTH), true, ) .await?; @@ -419,11 +537,8 @@ impl Edit { filter.not_matches = None; } else { if !premium { - ctx.respond_str( - "Only premium servers can use the `not-matches` condition.", - true, - ) - .await?; + ctx.respond_str(ctx.user_lang().filters_matches_premium(), true) + .await?; return Ok(()); } @@ -434,7 +549,7 @@ impl Edit { // vote context if let Some(val) = self.voter_has_all_of { let roles = parse_role_ids(&ctx.bot, guild_id, &val); - if let Err(why) = validate_roles(roles.len()) { + if let Err(why) = validate_roles(roles.len(), ctx.user_lang()) { ctx.respond_str(&why, true).await?; return Ok(()); } @@ -446,7 +561,7 @@ impl Edit { } if let Some(val) = self.voter_has_some_of { let roles = parse_role_ids(&ctx.bot, guild_id, &val); - if let Err(why) = validate_roles(roles.len()) { + if let Err(why) = validate_roles(roles.len(), ctx.user_lang()) { ctx.respond_str(&why, true).await?; return Ok(()); } @@ -458,7 +573,7 @@ impl Edit { } if let Some(val) = self.voter_missing_all_of { let roles = parse_role_ids(&ctx.bot, guild_id, &val); - if let Err(why) = validate_roles(roles.len()) { + if let Err(why) = validate_roles(roles.len(), ctx.user_lang()) { ctx.respond_str(&why, true).await?; return Ok(()); } @@ -470,7 +585,7 @@ impl Edit { } if let Some(val) = self.voter_missing_some_of { let roles = parse_role_ids(&ctx.bot, guild_id, &val); - if let Err(why) = validate_roles(roles.len()) { + if let Err(why) = validate_roles(roles.len(), ctx.user_lang()) { ctx.respond_str(&why, true).await?; return Ok(()); } @@ -481,7 +596,7 @@ impl Edit { } } if let Some(val) = self.older_than { - if val == "disable" { + if val == "disable" || val == ctx.user_lang().disable() { filter.older_than = None; } else { let delta = match parse_time_delta(&val) { @@ -495,7 +610,7 @@ impl Edit { } } if let Some(val) = self.newer_than { - if val == "disable" { + if val == "disable" || val == ctx.user_lang().disable() { filter.newer_than = None; } else { let delta = match parse_time_delta(&val) { @@ -517,10 +632,7 @@ impl Edit { filter.update_settings(&ctx.bot.pool).await?; ctx.respond_str( - &format!( - "Updated settings for filter at {} for group '{}'.", - self.position, group.name - ), + &ctx.user_lang().filters_edit_done(self.group, self.position), false, ) .await?; diff --git a/src/interactions/commands/chat/filters/mod.rs b/src/interactions/commands/chat/filters/mod.rs index 234d0f84..190fc423 100644 --- a/src/interactions/commands/chat/filters/mod.rs +++ b/src/interactions/commands/chat/filters/mod.rs @@ -12,13 +12,17 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use crate::{ errors::StarboardResult, interactions::{commands::permissions::manage_roles_channels, context::CommandCtx}, + locale_func, }; +locale_func!(filters); + #[allow(clippy::large_enum_variant)] // Edit(edit::Edit) being the culprit #[derive(CommandModel, CreateCommand)] #[command( name = "filters", desc = "Manage filters.", + desc_localizations = "filters", default_permissions = "manage_roles_channels", dm_permission = false )] diff --git a/src/interactions/commands/chat/filters/move_filter.rs b/src/interactions/commands/chat/filters/move_filter.rs index 47babe32..e1e5afa5 100644 --- a/src/interactions/commands/chat/filters/move_filter.rs +++ b/src/interactions/commands/chat/filters/move_filter.rs @@ -5,30 +5,53 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::id_as_i64::GetI64, }; +locale_func!(filters_move); +locale_func!(filters_move_option_group); +locale_func!(filters_move_option_current_position); +locale_func!(filters_move_option_new_position); + #[derive(CommandModel, CreateCommand)] -#[command(name = "move-filter", desc = "Change the position of a filter.")] +#[command( + name = "move-filter", + desc = "Change the position of a filter.", + desc_localizations = "filters_move" +)] pub struct MoveFilter { /// The filter group containing the filter to be moved. - #[command(autocomplete = true)] + #[command(autocomplete = true, desc_localizations = "filters_move_option_group")] group: String, + /// The original position of the filter. - #[command(rename = "current-position", min_value = 1, max_value = 1_000)] + #[command( + rename = "current-position", + min_value = 1, + max_value = 1_000, + desc_localizations = "filters_move_option_current_position" + )] current_position: i64, + /// The new position of the filter. - #[command(rename = "new-position", min_value = 1, max_value = 1_000)] + #[command( + rename = "new-position", + min_value = 1, + max_value = 1_000, + desc_localizations = "filters_move_option_new_position" + )] new_position: i64, } impl MoveFilter { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx).get_i64(); + let lang = ctx.user_lang(); let group = FilterGroup::get_by_name(&ctx.bot.pool, guild_id, &self.group).await?; let Some(group) = group else { - ctx.respond_str(&format!("Filter group '{}' does not exist.", self.group), true).await?; + ctx.respond_str(&lang.filter_group_missing(self.group), true).await?; return Ok(()); }; @@ -41,19 +64,13 @@ impl MoveFilter { .await?; if ret.is_some() { ctx.respond_str( - &format!( - "Filter moved from {} to {}.", - self.current_position, self.new_position - ), + &lang.filters_move_done(self.new_position, self.current_position), false, ) .await?; } else { ctx.respond_str( - &format!( - "There is no filter at {} for group '{}'.", - self.current_position, self.group - ), + &lang.filter_missing(self.group, self.current_position), true, ) .await?; diff --git a/src/interactions/commands/chat/filters/rename_group.rs b/src/interactions/commands/chat/filters/rename_group.rs index c3aca0b2..10495517 100644 --- a/src/interactions/commands/chat/filters/rename_group.rs +++ b/src/interactions/commands/chat/filters/rename_group.rs @@ -5,27 +5,45 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::{id_as_i64::GetI64, pg_error::PgErrorTraits}, }; +locale_func!(filters_rename_group); +locale_func!(filters_rename_group_option_current_name); +locale_func!(filters_rename_group_option_new_name); + #[derive(CommandModel, CreateCommand)] -#[command(name = "rename-group", desc = "Rename a filter group.")] +#[command( + name = "rename-group", + desc = "Rename a filter group.", + desc_localizations = "filters_rename_group" +)] pub struct RenameGroup { /// The current name of the group. - #[command(autocomplete = true, rename = "current-name")] + #[command( + autocomplete = true, + rename = "current-name", + desc_localizations = "filters_rename_group_option_current_name" + )] current_name: String, + /// The new name of the group. - #[command(rename = "new-name")] + #[command( + rename = "new-name", + desc_localizations = "filters_rename_group_option_new_name" + )] new_name: String, } impl RenameGroup { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx).get_i64(); + let lang = ctx.user_lang(); let group = FilterGroup::get_by_name(&ctx.bot.pool, guild_id, &self.current_name).await?; let Some(group) = group else { - ctx.respond_str(&format!("Filter group '{}' does not exist.", self.current_name), true).await?; + ctx.respond_str(&lang.filter_group_missing(self.current_name), true).await?; return Ok(()); }; @@ -33,15 +51,16 @@ impl RenameGroup { match ret { Ok(_) => { - ctx.respond_str("Renamed filter group.", false).await?; + ctx.respond_str( + &lang.filters_rename_group_done(self.new_name, self.current_name), + false, + ) + .await?; } Err(why) => { if why.is_duplicate() { - ctx.respond_str( - &format!("A filter group named '{}' already exists.", self.new_name), - true, - ) - .await?; + ctx.respond_str(&lang.filter_group_already_exists(self.new_name), true) + .await?; } else { return Err(why.into()); } diff --git a/src/interactions/commands/chat/filters/view.rs b/src/interactions/commands/chat/filters/view.rs index 8e90e7b9..6b4607b6 100644 --- a/src/interactions/commands/chat/filters/view.rs +++ b/src/interactions/commands/chat/filters/view.rs @@ -11,41 +11,39 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, + translations::Lang, utils::{ id_as_i64::GetI64, views::select_paginator::{SelectPaginatorBuilder, SelectPaginatorPageBuilder}, }, }; +locale_func!(filters_view); +locale_func!(filters_view_option_group); + #[derive(CommandModel, CreateCommand)] #[command( name = "view", - desc = "View filter groups and filters for this server." + desc = "View filter groups and filters for this server.", + desc_localizations = "filters_view" )] pub struct View { /// The filter group to view. - #[command(autocomplete = true)] + #[command(autocomplete = true, desc_localizations = "filters_view_option_group")] group: Option, } impl View { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx).get_i64(); + let lang = ctx.user_lang(); let groups = FilterGroup::list_by_guild(&ctx.bot.pool, guild_id).await?; if groups.is_empty() { - ctx.respond_str( - &format!( - concat!( - "This server does not have any filter groups. ", - "Read the [filter docs](<{}>) to get started." - ), - constants::DOCS_FILTERS - ), - true, - ) - .await?; + ctx.respond_str(&lang.filters_view_no_groups(constants::DOCS_FILTERS), true) + .await?; return Ok(()); } @@ -58,9 +56,9 @@ impl View { if Some(&group.name) == self.group.as_ref() { start = x; } - let embeds = group_embed(&bot.pool, &group, premium).await?; - let page = SelectPaginatorPageBuilder::new(format!("Filter Group '{}'", group.name)) - .embeds(embeds); + let embeds = group_embed(&bot.pool, &group, premium, lang).await?; + let page = + SelectPaginatorPageBuilder::new(lang.filter_group_title(group.name)).embeds(embeds); paginator = paginator.add_page(page); } @@ -72,15 +70,13 @@ async fn group_embed( pool: &sqlx::PgPool, group: &FilterGroup, premium: bool, + lang: Lang, ) -> StarboardResult> { let mut ret = Vec::new(); let emb = EmbedBuilder::new() .color(constants::EMBED_DARK_BG) - .title(format!("Filter Group '{}'", group.name)) - .description(format!( - "Read the [filter docs]({}) to get started.", - constants::DOCS_FILTERS - )) + .title(lang.filter_group_title(&group.name)) + .description(lang.filters_view_read_docs(constants::DOCS_FILTERS)) .build(); ret.push(emb); @@ -88,21 +84,21 @@ async fn group_embed( if filters.is_empty() { let emb = EmbedBuilder::new() .color(constants::EMBED_DARK_BG) - .description("This filter group has no filters.") + .description(lang.filters_view_group_no_filters()) .build(); ret.push(emb); } for filter in filters { - ret.push(filter_embed(filter, premium)); + ret.push(filter_embed(filter, premium, lang)); } Ok(ret) } -fn format_roles(role_ids: &[i64]) -> String { +fn format_roles(role_ids: &[i64], lang: Lang) -> String { if role_ids.is_empty() { - "None set. This condition always passes.".to_string() + lang.filters_view_condition_no_roles().to_string() } else { role_ids .iter() @@ -112,9 +108,9 @@ fn format_roles(role_ids: &[i64]) -> String { } } -fn format_channels(channel_ids: &[i64]) -> String { +fn format_channels(channel_ids: &[i64], lang: Lang) -> String { if channel_ids.is_empty() { - "None set. This condition always passes.".to_string() + lang.filters_view_condition_no_channels().to_string() } else { channel_ids .iter() @@ -124,157 +120,110 @@ fn format_channels(channel_ids: &[i64]) -> String { } } -fn filter_embed(filter: Filter, premium: bool) -> Embed { +fn filter_embed(filter: Filter, premium: bool, lang: Lang) -> Embed { let mut default_context = Vec::new(); let mut message_context = Vec::new(); let mut vote_context = Vec::new(); // default context if let Some(val) = filter.user_has_all_of { - let desc = format!("User must have all of these roles:\n{}", format_roles(&val)); + let desc = lang.filters_view_user_has_all_of(format_roles(&val, lang)); default_context.push(desc); } if let Some(val) = filter.user_has_some_of { - let desc = format!( - "User must have at least one of these roles:\n{}", - format_roles(&val), - ); + let desc = lang.filters_view_user_has_some_of(format_roles(&val, lang)); default_context.push(desc); } if let Some(val) = filter.user_missing_all_of { - let desc = format!( - "User must be missing all of these roles:\n{}", - format_roles(&val), - ); + let desc = lang.filters_view_user_missing_all_of(format_roles(&val, lang)); default_context.push(desc); } if let Some(val) = filter.user_missing_some_of { - let desc = format!( - "User must be missing at least one of these roles:\n{}", - format_roles(&val), - ); + let desc = lang.filters_view_user_missing_some_of(format_roles(&val, lang)); default_context.push(desc); } if let Some(val) = filter.user_is_bot { let desc = if val { - "User must be a bot." + lang.filters_view_user_must_be_bot() } else { - "User must not be a bot." + lang.filters_view_user_must_be_human() }; default_context.push(desc.to_string()); } // message context if let Some(val) = filter.in_channel { - let desc = format!( - "Message must be in one of these channels:\n{}", - format_channels(&val) - ); + let desc = lang.filters_view_in_channel(format_channels(&val, lang)); message_context.push(desc); } if let Some(val) = filter.not_in_channel { - let desc = format!( - "Message must not be in any of these channels:\n{}", - format_channels(&val) - ); + let desc = lang.filters_view_not_in_channel(format_channels(&val, lang)); message_context.push(desc); } if let Some(val) = filter.in_channel_or_sub_channels { - let desc = format!( - "Message must be in one of these channels or one of their sub-channels:\n{}", - format_channels(&val) - ); + let desc = lang.filters_view_in_channel_or_sub_channels(format_channels(&val, lang)); message_context.push(desc); } if let Some(val) = filter.not_in_channel_or_sub_channels { - let desc = format!( - "Message must not be in any of these channels or any of their sub-channels:\n{}", - format_channels(&val) - ); + let desc = lang.filters_view_not_in_channel_or_sub_channels(format_channels(&val, lang)); message_context.push(desc); } if let Some(val) = filter.min_attachments { - let desc = format!("Message must have at least {val} attachments."); + let desc = lang.filters_view_min_attachments(val); message_context.push(desc); } if let Some(val) = filter.max_attachments { - let desc = format!("Message cannot have more than {val} attachments."); + let desc = lang.filters_view_max_attachments(val); message_context.push(desc); } if let Some(val) = filter.min_length { - let desc = format!( - "Message must be at least {} characters long.", - val.separate_with_commas() - ); + let desc = lang.filters_view_min_length(val.separate_with_commas()); message_context.push(desc); } if let Some(val) = filter.max_length { - let desc = format!( - "Message cannot be longer than {} characters.", - val.separate_with_commas() - ); + let desc = lang.filters_view_max_length(val.separate_with_commas()); message_context.push(desc); } if let Some(val) = filter.matches { - let mut desc = format!("Message must match the following regex:\n```re\n{val}\n```"); + let mut desc = lang.filters_view_matches(val); if !premium { - desc.push_str( - "\n:warning: This setting is ignored because this server doesn't have premium.", - ); + desc.push_str(lang.filters_view_regex_not_applied()); } message_context.push(desc); } if let Some(val) = filter.not_matches { - let mut desc = format!("Message must not match the following regex:\n```re\n{val}\n```"); + let mut desc = lang.filters_view_not_matches(val); if !premium { - desc.push_str( - "\n:warning: This setting is ignored because this server doesn't have premium.", - ); + desc.push_str(lang.filters_view_regex_not_applied()); } message_context.push(desc); } // voter context if let Some(val) = filter.voter_has_all_of { - let desc = format!( - "Voter must have all of these roles:\n{}", - format_roles(&val) - ); + let desc = lang.filters_view_voter_has_all_of(format_roles(&val, lang)); vote_context.push(desc); } if let Some(val) = filter.voter_has_some_of { - let desc = format!( - "Voter must have at least one of these roles:\n{}", - format_roles(&val), - ); + let desc = lang.filters_view_voter_has_some_of(format_roles(&val, lang)); vote_context.push(desc); } if let Some(val) = filter.voter_missing_all_of { - let desc = format!( - "Voter must be missing all of these roles:\n{}", - format_roles(&val), - ); + let desc = lang.filters_view_voter_missing_all_of(format_roles(&val, lang)); vote_context.push(desc); } if let Some(val) = filter.voter_missing_some_of { - let desc = format!( - "Voter must be missing at least one of these roles:\n{}", - format_roles(&val), - ); + let desc = lang.filters_view_voter_missing_some_of(format_roles(&val, lang)); vote_context.push(desc); } if let Some(val) = filter.older_than { - let desc = format!( - "Message must be older than {}.", - humantime::format_duration(Duration::from_secs(val as u64)), - ); + let desc = lang + .filters_view_older_than(humantime::format_duration(Duration::from_secs(val as u64))); vote_context.push(desc); } if let Some(val) = filter.newer_than { - let desc = format!( - "Message must be newer than {}.", - humantime::format_duration(Duration::from_secs(val as u64)), - ); + let desc = lang + .filters_view_newer_than(humantime::format_duration(Duration::from_secs(val as u64))); vote_context.push(desc); } @@ -291,32 +240,32 @@ fn filter_embed(filter: Filter, premium: bool) -> Embed { if !default_context.is_empty() { has_conditions = true; emb = emb.field(EmbedFieldBuilder::new( - "Default Context", + lang.filters_default_context_title(), default_context.join("\n\n"), )); } if !message_context.is_empty() { has_conditions = true; emb = emb.field(EmbedFieldBuilder::new( - "Message Context", + lang.filters_message_context_title(), message_context.join("\n\n"), )); } if !vote_context.is_empty() { has_conditions = true; emb = emb.field(EmbedFieldBuilder::new( - "Vote Context", + lang.filters_vote_context_title(), vote_context.join("\n\n"), )); } if !has_conditions { - desc.push_str("\n\nThis filter has no conditions, so it always passes."); + desc.push_str(lang.filters_view_no_conditions()); } emb = emb .description(desc) - .title(format!("Filter {}", filter.position)); + .title(lang.filter_title(filter.position)); emb.build() } diff --git a/src/interactions/commands/chat/overrides/channels/add.rs b/src/interactions/commands/chat/overrides/channels/add.rs index f8ed1eb8..06ad7fa1 100644 --- a/src/interactions/commands/chat/overrides/channels/add.rs +++ b/src/interactions/commands/chat/overrides/channels/add.rs @@ -5,16 +5,31 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::id_as_i64::GetI64, }; +locale_func!(overrides_channels_add); +locale_func!(overrides_channels_add_option_name); +locale_func!(overrides_channels_add_option_channels); + #[derive(CommandModel, CreateCommand)] -#[command(name = "add", desc = "Add channels to an override.")] +#[command( + name = "add", + desc = "Add channels to an override.", + desc_localizations = "overrides_channels_add" +)] pub struct AddOverrideChannels { /// The override to add channels to. - #[command(autocomplete = true, rename = "override")] + #[command( + autocomplete = true, + rename = "override", + desc_localizations = "overrides_channels_add_option_name" + )] name: String, + /// The channels to add. + #[command(desc_localizations = "overrides_channels_add_option_channels")] channels: String, } @@ -22,6 +37,7 @@ impl AddOverrideChannels { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx); let guild_id_i64 = guild_id.get_i64(); + let lang = ctx.user_lang(); let ov = StarboardOverride::get(&ctx.bot.pool, guild_id_i64, &self.name).await?; if let Some(ov) = ov { @@ -42,20 +58,14 @@ impl AddOverrideChannels { .await?; if ret.is_some() { - ctx.respond_str( - &format!("Updated the channels for override '{}'.", self.name), - false, - ) - .await?; + ctx.respond_str(&lang.overrides_channels_done(self.name), false) + .await?; return Ok(()); } } - ctx.respond_str( - &format!("No override with the name '{}' exists.", self.name), - true, - ) - .await?; + ctx.respond_str(&lang.override_missing(self.name), true) + .await?; Ok(()) } } diff --git a/src/interactions/commands/chat/overrides/channels/mod.rs b/src/interactions/commands/chat/overrides/channels/mod.rs index 046c5a8e..be89fc1d 100644 --- a/src/interactions/commands/chat/overrides/channels/mod.rs +++ b/src/interactions/commands/chat/overrides/channels/mod.rs @@ -4,10 +4,16 @@ mod set; use twilight_interactions::command::{CommandModel, CreateCommand}; -use crate::{errors::StarboardResult, interactions::context::CommandCtx}; +use crate::{errors::StarboardResult, interactions::context::CommandCtx, locale_func}; + +locale_func!(overrides_channels); #[derive(CommandModel, CreateCommand)] -#[command(name = "channels", desc = "Manage the channels an override affects.")] +#[command( + name = "channels", + desc = "Manage the channels an override affects.", + desc_localizations = "overrides_channels" +)] pub enum ManageOverrideChannels { #[command(name = "set")] Set(set::SetOverrideChannels), diff --git a/src/interactions/commands/chat/overrides/channels/remove.rs b/src/interactions/commands/chat/overrides/channels/remove.rs index b90a494c..3a4ee2a4 100644 --- a/src/interactions/commands/chat/overrides/channels/remove.rs +++ b/src/interactions/commands/chat/overrides/channels/remove.rs @@ -5,16 +5,31 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::id_as_i64::GetI64, }; +locale_func!(overrides_channels_remove); +locale_func!(overrides_channels_remove_option_name); +locale_func!(overrides_channels_remove_option_channels); + #[derive(CommandModel, CreateCommand)] -#[command(name = "remove", desc = "Remove channels from an override.")] +#[command( + name = "remove", + desc = "Remove channels from an override.", + desc_localizations = "overrides_channels_remove" +)] pub struct RemoveOverrideChannels { /// The override to remove channels from. - #[command(autocomplete = true, rename = "override")] + #[command( + autocomplete = true, + rename = "override", + desc_localizations = "overrides_channels_remove_option_name" + )] name: String, + /// The channels to remove. + #[command(desc_localizations = "overrides_channels_remove_option_channels")] channels: String, } @@ -22,6 +37,7 @@ impl RemoveOverrideChannels { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx); let guild_id_i64 = guild_id.get_i64(); + let lang = ctx.user_lang(); let ov = StarboardOverride::get(&ctx.bot.pool, guild_id_i64, &self.name).await?; if let Some(ov) = ov { @@ -42,20 +58,14 @@ impl RemoveOverrideChannels { .await?; if ret.is_some() { - ctx.respond_str( - &format!("Updated the channels for override '{}'.", self.name), - false, - ) - .await?; + ctx.respond_str(&lang.overrides_channels_done(self.name), false) + .await?; return Ok(()); } } - ctx.respond_str( - &format!("No override with the name '{}' exists.", self.name), - true, - ) - .await?; + ctx.respond_str(&lang.override_missing(self.name), true) + .await?; Ok(()) } } diff --git a/src/interactions/commands/chat/overrides/channels/set.rs b/src/interactions/commands/chat/overrides/channels/set.rs index 4ad199f2..bd12ac62 100644 --- a/src/interactions/commands/chat/overrides/channels/set.rs +++ b/src/interactions/commands/chat/overrides/channels/set.rs @@ -5,17 +5,32 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::id_as_i64::GetI64, }; +locale_func!(overrides_channels_set); +locale_func!(overrides_channels_set_option_name); +locale_func!(overrides_channels_set_option_channels); + #[derive(CommandModel, CreateCommand)] -#[command(name = "set", desc = "Set the channels that an override affects.")] +#[command( + name = "set", + desc = "Set the channels that an override affects.", + desc_localizations = "overrides_channels_set" +)] pub struct SetOverrideChannels { /// The override to set the channels for. - #[command(autocomplete = true, rename = "override")] + #[command( + autocomplete = true, + rename = "override", + desc_localizations = "overrides_channels_set_option_name" + )] name: String, + /// A list of channels that the override should affect. Use "none" to /// remove all. + #[command(desc_localizations = "overrides_channels_set_option_channels")] channels: String, } @@ -23,6 +38,7 @@ impl SetOverrideChannels { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx); let guild_id_i64 = guild_id.get_i64(); + let lang = ctx.user_lang(); let channel_ids: Vec<_> = textable_channel_ids(&ctx.bot, guild_id, &self.channels) .await? @@ -37,17 +53,11 @@ impl SetOverrideChannels { .await?; if ov.is_none() { - ctx.respond_str( - &format!("No override with the name '{}' exists.", self.name), - true, - ) - .await?; + ctx.respond_str(&lang.override_missing(self.name), true) + .await?; } else { - ctx.respond_str( - &format!("Set the channels for override '{}'.", self.name), - false, - ) - .await?; + ctx.respond_str(&lang.overrides_channels_done(self.name), false) + .await?; } Ok(()) } diff --git a/src/interactions/commands/chat/overrides/create.rs b/src/interactions/commands/chat/overrides/create.rs index d864c0f2..9d1311bb 100644 --- a/src/interactions/commands/chat/overrides/create.rs +++ b/src/interactions/commands/chat/overrides/create.rs @@ -6,22 +6,37 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::id_as_i64::GetI64, }; +locale_func!(overrides_create); +locale_func!(overrides_create_option_name); +locale_func!(overrides_create_option_starboard); + #[derive(CommandModel, CreateCommand)] -#[command(name = "create", desc = "Create an override.")] +#[command( + name = "create", + desc = "Create an override.", + desc_localizations = "overrides_create" +)] pub struct CreateOverride { /// The name of the override. + #[command(desc_localizations = "overrides_create_option_name")] name: String, + /// The starboard this override belongs too. - #[command(autocomplete = true)] + #[command( + autocomplete = true, + desc_localizations = "overrides_create_option_starboard" + )] starboard: String, } impl CreateOverride { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx).get_i64(); + let lang = ctx.user_lang(); let name = match validation::name::validate_name(&self.name) { Err(why) => { @@ -34,7 +49,7 @@ impl CreateOverride { let starboard = Starboard::get_by_name(&ctx.bot.pool, &self.starboard, guild_id).await?; let starboard = match starboard { None => { - ctx.respond_str(&format!("'{}' is not a starboard.", self.starboard), true) + ctx.respond_str(&lang.starboard_missing(self.starboard), true) .await?; return Ok(()); } @@ -44,10 +59,7 @@ impl CreateOverride { let count = StarboardOverride::count_by_starboard(&ctx.bot.pool, starboard.id).await?; if count >= constants::MAX_OVERRIDES_PER_STARBOARD { ctx.respond_str( - &format!( - "You can only have up to {} overrides per starboard.", - constants::MAX_OVERRIDES_PER_STARBOARD - ), + &lang.overrides_create_limit(constants::MAX_OVERRIDES_PER_STARBOARD), true, ) .await?; @@ -57,20 +69,11 @@ impl CreateOverride { let ov = StarboardOverride::create(&ctx.bot.pool, guild_id, &name, starboard.id).await?; if ov.is_none() { - ctx.respond_str( - &format!("An override with the name '{name}' already exists."), - true, - ) - .await?; + ctx.respond_str(&lang.override_already_exists(name), true) + .await?; } else { - ctx.respond_str( - &format!( - "Created override '{}' in starboard '{}'.", - name, self.starboard - ), - false, - ) - .await?; + ctx.respond_str(&lang.overrides_create_done(name, self.starboard), false) + .await?; } Ok(()) diff --git a/src/interactions/commands/chat/overrides/delete.rs b/src/interactions/commands/chat/overrides/delete.rs index 8fe90b7f..29d562aa 100644 --- a/src/interactions/commands/chat/overrides/delete.rs +++ b/src/interactions/commands/chat/overrides/delete.rs @@ -5,30 +5,35 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::{id_as_i64::GetI64, views::confirm}, }; +locale_func!(overrides_delete); +locale_func!(overrides_delete_option_name); + #[derive(CommandModel, CreateCommand)] -#[command(name = "delete", desc = "Delete an override.")] +#[command( + name = "delete", + desc = "Delete an override.", + desc_localizations = "overrides_delete" +)] pub struct DeleteOverride { /// The name of the override to delete. - #[command(autocomplete = true)] + #[command( + autocomplete = true, + desc_localizations = "overrides_delete_option_name" + )] name: String, } impl DeleteOverride { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx).get_i64(); + let lang = ctx.user_lang(); - let btn_ctx = confirm::simple( - &mut ctx, - &format!( - "Are you sure you want to delete the override '{}'?", - self.name - ), - true, - ) - .await?; + let btn_ctx = + confirm::simple(&mut ctx, &lang.overrides_delete_confirm(&self.name), true).await?; let mut btn_ctx = match btn_ctx { None => return Ok(()), Some(btn_ctx) => btn_ctx, @@ -37,14 +42,11 @@ impl DeleteOverride { let ov = StarboardOverride::delete(&ctx.bot.pool, guild_id, &self.name).await?; if ov.is_none() { btn_ctx - .edit_str( - &format!("No override with the name '{}' exists.", self.name), - true, - ) + .edit_str(&lang.override_missing(self.name), true) .await?; } else { btn_ctx - .edit_str(&format!("Deleted override '{}'.", self.name), true) + .edit_str(&lang.overrides_delete_done(self.name), true) .await?; } diff --git a/src/interactions/commands/chat/overrides/edit/behavior.rs b/src/interactions/commands/chat/overrides/edit/behavior.rs index 3ae5e186..bd31b81c 100644 --- a/src/interactions/commands/chat/overrides/edit/behavior.rs +++ b/src/interactions/commands/chat/overrides/edit/behavior.rs @@ -5,48 +5,104 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::{commands::choices::on_delete::OnDelete, context::CommandCtx}, + locale_func, utils::id_as_i64::GetI64, }; +locale_func!(overrides_edit_behavior); +locale_func!(overrides_edit_option_name); + +locale_func!(sb_option_enabled); +locale_func!(sb_option_autoreact_upvote); +locale_func!(sb_option_autoreact_downvote); +locale_func!(sb_option_remove_invalid_reactions); +locale_func!(sb_option_link_deletes); +locale_func!(sb_option_link_edits); +locale_func!(sb_option_on_delete); +locale_func!(sb_option_cooldown_enabled); +locale_func!(sb_option_cooldown); +locale_func!(sb_option_exclusive_group); +locale_func!(sb_option_remove_exclusive_group); +locale_func!(sb_option_exclusive_group_priority); + #[derive(CommandModel, CreateCommand)] -#[command(name = "behavior", desc = "Edit how the starboard should behave.")] +#[command( + name = "behavior", + desc = "Edit how the starboard should behave.", + desc_localizations = "overrides_edit_behavior" +)] pub struct EditBehavior { /// The starboard to edit. - #[command(autocomplete = true)] + #[command(autocomplete = true, desc_localizations = "overrides_edit_option_name")] name: String, /// Whether the starboard is enabled. + #[command(desc_localizations = "sb_option_enabled")] enabled: Option, + /// Whether to automatically react to starboard messages with the upvote emojis. - #[command(rename = "autoreact-upvote")] + #[command( + rename = "autoreact-upvote", + desc_localizations = "sb_option_autoreact_upvote" + )] autoreact_upvote: Option, + /// Whether to automatically react to starboard messages with the downvote emojis. - #[command(rename = "autoreact-downvote")] + #[command( + rename = "autoreact-downvote", + desc_localizations = "sb_option_autoreact_downvote" + )] autoreact_downvote: Option, + /// Whether to remove reactions that don't meet requirements. - #[command(rename = "remove-invalid-reactions")] + #[command( + rename = "remove-invalid-reactions", + desc_localizations = "sb_option_remove_invalid_reactions" + )] remove_invalid_reactions: Option, + /// If the original message is deleted, whether to also delete the starboard message. - #[command(rename = "link-deletes")] + #[command(rename = "link-deletes", desc_localizations = "sb_option_link_deletes")] link_deletes: Option, + /// If the original message is edted, whether to also update the content of the starboard message. - #[command(rename = "link-edits")] + #[command(rename = "link-edits", desc_localizations = "sb_option_link_edits")] link_edits: Option, + /// What to do if a moderator removes a post from the starboard manually. - #[command(rename = "on-delete")] + #[command(rename = "on-delete", desc_localizations = "sb_option_on_delete")] on_delete: Option, + /// Whether to enable the per-user vote cooldown. - #[command(rename = "cooldown-enabled")] + #[command( + rename = "cooldown-enabled", + desc_localizations = "sb_option_cooldown_enabled" + )] cooldown_enabled: Option, + /// The size of the cooldown (e.x. "5/6" means 5 votes per 6 seconds). + #[command(desc_localizations = "sb_option_cooldown")] cooldown: Option, + /// Add this starboard to an exclusive group (only one at a time). - #[command(rename = "exclusive-group", autocomplete = true)] + #[command( + rename = "exclusive-group", + autocomplete = true, + desc_localizations = "sb_option_exclusive_group" + )] exclusive_group: Option, + /// Remove this starboard from the exclusive group. - #[command(rename = "remove-exclusive-group")] + #[command( + rename = "remove-exclusive-group", + desc_localizations = "sb_option_remove_exclusive_group" + )] remove_exclusive_group: Option, - #[command(rename = "exclusive-group-priority", min_value=-50, max_value=50)] + + #[command( + rename = "exclusive-group-priority", min_value=-50, max_value=50, + desc_localizations = "sb_option_exclusive_group_priority" + )] /// Set the priority of this starboard in the exclusive group. exclusive_group_priority: Option, } @@ -54,9 +110,11 @@ pub struct EditBehavior { impl EditBehavior { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx).get_i64(); + let lang = ctx.user_lang(); + let ov = match StarboardOverride::get(&ctx.bot.pool, guild_id, &self.name).await? { None => { - ctx.respond_str("No override with that name was found.", true) + ctx.respond_str(&lang.override_missing(self.name), true) .await?; return Ok(()); } @@ -102,10 +160,7 @@ impl EditBehavior { if let Some(val) = self.exclusive_group { let group = ExclusiveGroup::get_by_name(&ctx.bot.pool, guild_id, &val).await?; let Some(group) = group else { - ctx.respond_str(&format!(concat!( - "Exclusive group '{}' does not exist. If you meant to remove the exclusive ", - "group, use `remove-exclusive-group: True` instead." - ), val), true).await?; + ctx.respond_str(&lang.exclusive_group_missing(val), true).await?; return Ok(()); }; settings.exclusive_group = Some(Some(group.id)); @@ -120,11 +175,8 @@ impl EditBehavior { } StarboardOverride::update_settings(&ctx.bot.pool, ov.id, settings).await?; - ctx.respond_str( - &format!("Updated settings for override '{}'.", self.name), - false, - ) - .await?; + ctx.respond_str(&lang.overrides_edit_done(self.name), false) + .await?; Ok(()) } diff --git a/src/interactions/commands/chat/overrides/edit/embed.rs b/src/interactions/commands/chat/overrides/edit/embed.rs index e937fe55..2227db54 100644 --- a/src/interactions/commands/chat/overrides/edit/embed.rs +++ b/src/interactions/commands/chat/overrides/edit/embed.rs @@ -5,37 +5,53 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::id_as_i64::GetI64, }; +locale_func!(overrides_edit_embed); +locale_func!(overrides_edit_option_name); + +locale_func!(sb_option_color); +locale_func!(sb_option_attachments_list); +locale_func!(sb_option_replied_to); + #[derive(CommandModel, CreateCommand)] #[command( name = "embed", - desc = "Edit the style of the embeds sent to your starboard." + desc = "Edit the style of the embeds sent to your starboard.", + desc_localizations = "overrides_edit_embed" )] pub struct EditEmbedStyle { /// The override to edit. - #[command(autocomplete = true)] + #[command(autocomplete = true, desc_localizations = "overrides_edit_option_name")] name: String, /// The color of the embeds. Use 'none' for default. + #[command(desc_localizations = "sb_option_color")] color: Option, + /// Whether to include a list of attachments. - #[command(rename = "attachments-list")] + #[command( + rename = "attachments-list", + desc_localizations = "sb_option_attachments_list" + )] attachments_list: Option, + /// Whether to include the message that was replied to, if any. - #[command(rename = "replied-to")] + #[command(rename = "replied-to", desc_localizations = "sb_option_replied_to")] replied_to: Option, } impl EditEmbedStyle { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx); + let lang = ctx.user_lang(); let ov = StarboardOverride::get(&ctx.bot.pool, guild_id.get_i64(), &self.name).await?; let ov = match ov { None => { - ctx.respond_str("No override with that name was found.", true) + ctx.respond_str(&lang.override_missing(self.name), true) .await?; return Ok(()); } @@ -44,7 +60,7 @@ impl EditEmbedStyle { let mut settings = ov.get_overrides()?; if let Some(val) = self.color { - if val == "none" { + if val == "default" || val == lang.default() { settings.color = Some(None); } else { match color::parse_color(&val) { @@ -64,11 +80,8 @@ impl EditEmbedStyle { } StarboardOverride::update_settings(&ctx.bot.pool, ov.id, settings).await?; - ctx.respond_str( - &format!("Updated settings for override '{}'.", self.name), - false, - ) - .await?; + ctx.respond_str(&lang.overrides_edit_done(self.name), false) + .await?; Ok(()) } } diff --git a/src/interactions/commands/chat/overrides/edit/mod.rs b/src/interactions/commands/chat/overrides/edit/mod.rs index 48cfa73c..f806414f 100644 --- a/src/interactions/commands/chat/overrides/edit/mod.rs +++ b/src/interactions/commands/chat/overrides/edit/mod.rs @@ -6,10 +6,16 @@ pub mod style; use twilight_interactions::command::{CommandModel, CreateCommand}; -use crate::{errors::StarboardResult, interactions::context::CommandCtx}; +use crate::{errors::StarboardResult, interactions::context::CommandCtx, locale_func}; + +locale_func!(overrides_edit); #[derive(CommandModel, CreateCommand)] -#[command(name = "edit", desc = "Edit an override.")] +#[command( + name = "edit", + desc = "Edit an override.", + desc_localizations = "overrides_edit" +)] pub enum EditOverride { #[command(name = "embed")] Embed(embed::EditEmbedStyle), diff --git a/src/interactions/commands/chat/overrides/edit/requirements.rs b/src/interactions/commands/chat/overrides/edit/requirements.rs index 120d1f63..986348bc 100644 --- a/src/interactions/commands/chat/overrides/edit/requirements.rs +++ b/src/interactions/commands/chat/overrides/edit/requirements.rs @@ -17,49 +17,90 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::id_as_i64::GetI64, }; +locale_func!(overrides_edit_requirements); +locale_func!(overrides_edit_option_name); + +locale_func!(sb_option_required); +locale_func!(sb_option_required_remove); +locale_func!(sb_option_upvote_emojis); +locale_func!(sb_option_downvote_emojis); +locale_func!(sb_option_self_vote); +locale_func!(sb_option_allow_bots); +locale_func!(sb_option_require_image); +locale_func!(sb_option_older_than); +locale_func!(sb_option_newer_than); +locale_func!(sb_option_matches); +locale_func!(sb_option_not_matches); + #[derive(CommandModel, CreateCommand)] #[command( name = "requirements", - desc = "Edit the requirements for messages to appear on the starboard." + desc = "Edit the requirements for messages to appear on the starboard.", + desc_localizations = "overrides_edit_requirements" )] pub struct EditRequirements { /// The override to edit. - #[command(autocomplete = true)] + #[command(autocomplete = true, desc_localizations = "overrides_edit_option_name")] name: String, /// The number of upvotes a message needs. Use "none" to unset. + #[command(desc_localizations = "sb_option_required")] required: Option, + /// How few points the message can have before a starboarded post is removed. Use "none" to unset. - #[command(rename = "required-remove")] + #[command( + rename = "required-remove", + desc_localizations = "sb_option_required_remove" + )] required_remove: Option, + /// The emojis that can be used to upvote a post. Use 'none' to remove all. - #[command(rename = "upvote-emojis")] + #[command( + rename = "upvote-emojis", + desc_localizations = "sb_option_upvote_emojis" + )] upvote_emojis: Option, + /// The emojis that can be used to downvote a post. Use 'none' to remove all. - #[command(rename = "downvote-emojis")] + #[command( + rename = "downvote-emojis", + desc_localizations = "sb_option_downvote_emojis" + )] downvote_emojis: Option, + /// Whether to allow users to vote on their own posts. - #[command(rename = "self-vote")] + #[command(rename = "self-vote", desc_localizations = "sb_option_self_vote")] self_vote: Option, + /// Whether to allow bot messages to be on the starboard. - #[command(rename = "allow-bots")] + #[command(rename = "allow-bots", desc_localizations = "sb_option_allow_bots")] allow_bots: Option, + /// Whether to require posts to have an image to appear on the starboard. - #[command(rename = "require-image")] + #[command( + rename = "require-image", + desc_localizations = "sb_option_require_image" + )] require_image: Option, + /// How old a post must be in order for it to be voted on (e.g. "1 hour"). Use 0 to disable. - #[command(rename = "older-than")] + #[command(rename = "older-than", desc_localizations = "sb_option_older_than")] older_than: Option, + /// How new a post must be in order for it to be voted on (e.g. "1 hour"). Use 0 to disable. - #[command(rename = "newer-than")] + #[command(rename = "newer-than", desc_localizations = "sb_option_newer_than")] newer_than: Option, + /// (Premium) Content that messages must match to be starred (supports regex). Use ".*" to disable. + #[command(desc_localizations = "sb_option_matches")] matches: Option, - #[command(rename = "not-matches")] + /// (Premium) content that messages must not match to be starred (supports regex). Use ".*" to disable. + #[command(rename = "not-matches", desc_localizations = "sb_option_not_matches")] not_matches: Option, } @@ -67,9 +108,11 @@ impl EditRequirements { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx); let guild_id_i64 = guild_id.get_i64(); + let lang = ctx.user_lang(); + let ov = match StarboardOverride::get(&ctx.bot.pool, guild_id_i64, &self.name).await? { None => { - ctx.respond_str("No override with that name was found.", true) + ctx.respond_str(&lang.override_missing(self.name), true) .await?; return Ok(()); } @@ -195,11 +238,8 @@ impl EditRequirements { } StarboardOverride::update_settings(&ctx.bot.pool, ov.id, settings).await?; - ctx.respond_str( - &format!("Updated settings for override '{}'.", self.name), - false, - ) - .await?; + ctx.respond_str(&lang.overrides_edit_done(self.name), false) + .await?; Ok(()) } diff --git a/src/interactions/commands/chat/overrides/edit/reset.rs b/src/interactions/commands/chat/overrides/edit/reset.rs index 6bf80cdc..ad8ed73a 100644 --- a/src/interactions/commands/chat/overrides/edit/reset.rs +++ b/src/interactions/commands/chat/overrides/edit/reset.rs @@ -7,6 +7,7 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::id_as_i64::GetI64, }; @@ -21,27 +22,35 @@ macro_rules! reset_settings { }} } +locale_func!(overrides_edit_reset); +locale_func!(overrides_edit_option_name); +locale_func!(overrides_edit_reset_option_reset); + #[derive(CommandModel, CreateCommand)] #[command( name = "reset", - desc = "Reset override settings to the defaults used by the starboard." + desc = "Reset override settings to the defaults used by the starboard.", + desc_localizations = "overrides_edit_reset" )] pub struct ResetOverrideSettings { /// The override to reset settings for. - #[command(autocomplete = true)] + #[command(autocomplete = true, desc_localizations = "overrides_edit_option_name")] name: String, + /// The settings to reset, space seperated. + #[command(desc_localizations = "overrides_edit_reset_option_reset")] reset: String, } impl ResetOverrideSettings { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx).get_i64(); + let lang = ctx.user_lang(); let ov = StarboardOverride::get(&ctx.bot.pool, guild_id, &self.name).await?; let ov = match ov { None => { - ctx.respond_str("No override with that name was found.", true) + ctx.respond_str(&lang.override_missing(self.name), true) .await?; return Ok(()); } @@ -66,11 +75,7 @@ impl ResetOverrideSettings { StarboardOverride::update_settings(&ctx.bot.pool, ov.id, settings).await?; ctx.respond_str( - &format!( - "Reset {} setting(s) for override '{}'.", - reset.len() - frontend_final_count_sub, - ov.name - ), + &lang.overrides_edit_reset_done(reset.len() - frontend_final_count_sub, ov.name), false, ) .await?; diff --git a/src/interactions/commands/chat/overrides/edit/style.rs b/src/interactions/commands/chat/overrides/edit/style.rs index 9545acca..9fbc93aa 100644 --- a/src/interactions/commands/chat/overrides/edit/style.rs +++ b/src/interactions/commands/chat/overrides/edit/style.rs @@ -9,44 +9,74 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::{commands::choices::go_to_message::GoToMessage, context::CommandCtx}, + locale_func, utils::id_as_i64::GetI64, }; +locale_func!(overrides_edit_style); +locale_func!(overrides_edit_option_name); + +locale_func!(sb_option_display_emoji); +locale_func!(sb_option_ping_author); +locale_func!(sb_option_use_server_profile); +locale_func!(sb_option_extra_embeds); +locale_func!(sb_option_go_to_message); +locale_func!(sb_option_use_webhook); + #[derive(CommandModel, CreateCommand)] -#[command(name = "style", desc = "Edit the general style of your starboard.")] +#[command( + name = "style", + desc = "Edit the general style of your starboard.", + desc_localizations = "overrides_edit_style" +)] pub struct EditGeneralStyle { /// The override to edit. - #[command(autocomplete = true)] + #[command(autocomplete = true, desc_localizations = "overrides_edit_option_name")] name: String, /// The emoji to show next to the point count. Use 'none' for nothing. - #[command(rename = "display-emoji")] + #[command( + rename = "display-emoji", + desc_localizations = "sb_option_display_emoji" + )] display_emoji: Option, + /// Whether to mention the author on starboard posts. - #[command(rename = "ping-author")] + #[command(rename = "ping-author", desc_localizations = "sb_option_ping_author")] ping_author: Option, + /// Whether to use per-server avatar and nicknames for posts. - #[command(rename = "use-server-profile")] + #[command( + rename = "use-server-profile", + desc_localizations = "sb_option_use_server_profile" + )] use_server_profile: Option, + /// Whether to include extra embeds that were on the original message. - #[command(rename = "extra-embeds")] + #[command(rename = "extra-embeds", desc_localizations = "sb_option_extra_embeds")] extra_embeds: Option, + /// Where to put the "Go to Message" link. - #[command(rename = "go-to-message")] + #[command( + rename = "go-to-message", + desc_localizations = "sb_option_go_to_message" + )] go_to_message: Option, + /// Whether to use a webhook for starboard messages. - #[command(rename = "use-webhook")] + #[command(rename = "use-webhook", desc_localizations = "sb_option_use_webhook")] use_webhook: Option, } impl EditGeneralStyle { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx); + let lang = ctx.user_lang(); let ov = StarboardOverride::get(&ctx.bot.pool, guild_id.get_i64(), &self.name).await?; let ov = match ov { None => { - ctx.respond_str("No override with that name was found.", true) + ctx.respond_str(&lang.override_missing(self.name), true) .await?; return Ok(()); } @@ -55,19 +85,13 @@ impl EditGeneralStyle { let mut settings = ov.get_overrides()?; if let Some(val) = self.display_emoji { - let emoji = if val == "none" { + let emoji = if val == "none" || val == lang.none() { None } else { let mut emojis = SimpleEmoji::from_user_input(&val, &ctx.bot, guild_id); if emojis.len() != 1 { - ctx.respond_str( - concat!( - "Please specify exactly one emoji for `display-emoji`, or use 'none' ", - "to remove.", - ), - true, - ) - .await?; + ctx.respond_str(lang.validation_display_emoji(), true) + .await?; return Ok(()); } @@ -101,7 +125,7 @@ impl EditGeneralStyle { StarboardOverride::update_settings(&ctx.bot.pool, ov.id, settings).await?; - let mut response = format!("Updated settings for override '{}'.", self.name); + let mut response = lang.overrides_edit_done(self.name); if let Some(message) = message { response.push_str("\n\n"); response.push_str(message); diff --git a/src/interactions/commands/chat/overrides/mod.rs b/src/interactions/commands/chat/overrides/mod.rs index 416fd921..e3c66fbd 100644 --- a/src/interactions/commands/chat/overrides/mod.rs +++ b/src/interactions/commands/chat/overrides/mod.rs @@ -10,12 +10,16 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use crate::{ errors::StarboardResult, interactions::{commands::permissions::manage_channels, context::CommandCtx}, + locale_func, }; +locale_func!(overrides); + #[derive(CommandModel, CreateCommand)] #[command( name = "overrides", desc = "Manage overrides.", + desc_localizations = "overrides", dm_permission = false, default_permissions = "manage_channels" )] diff --git a/src/interactions/commands/chat/overrides/rename.rs b/src/interactions/commands/chat/overrides/rename.rs index 0953ad50..ed3733c5 100644 --- a/src/interactions/commands/chat/overrides/rename.rs +++ b/src/interactions/commands/chat/overrides/rename.rs @@ -5,25 +5,43 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::{id_as_i64::GetI64, pg_error::PgErrorTraits}, }; +locale_func!(overrides_rename); +locale_func!(overrides_rename_option_current_name); +locale_func!(overrides_rename_option_new_name); + #[derive(CreateCommand, CommandModel)] -#[command(name = "rename", desc = "Rename an override.")] +#[command( + name = "rename", + desc = "Rename an override.", + desc_localizations = "overrides_rename" +)] pub struct RenameOverride { /// The current name of the override. - #[command(autocomplete = true, rename = "current-name")] - old_name: String, + #[command( + autocomplete = true, + rename = "current-name", + desc_localizations = "overrides_rename_option_current_name" + )] + current_name: String, + /// The new name of the override. - #[command(rename = "new-name")] - name: String, + #[command( + rename = "new-name", + desc_localizations = "overrides_rename_option_new_name" + )] + new_name: String, } impl RenameOverride { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx).get_i64(); + let lang = ctx.user_lang(); - let name = match validation::name::validate_name(&self.name) { + let name = match validation::name::validate_name(&self.new_name) { Ok(val) => val, Err(why) => { ctx.respond_str(&why, true).await?; @@ -32,33 +50,24 @@ impl RenameOverride { }; let ov = - StarboardOverride::rename(&ctx.bot.pool, guild_id, &self.old_name, &self.name).await; + StarboardOverride::rename(&ctx.bot.pool, guild_id, &self.current_name, &name).await; match ov { Err(why) => { if why.is_duplicate() { - ctx.respond_str( - &format!("An override with the name '{name}' already exists."), - true, - ) - .await?; + ctx.respond_str(&lang.override_already_exists(name), true) + .await?; } else { return Err(why.into()); } } Ok(None) => { - ctx.respond_str( - &format!("No override with the name '{}' exists.", self.old_name), - true, - ) - .await?; + ctx.respond_str(&lang.override_missing(self.current_name), true) + .await?; } Ok(Some(_)) => { - ctx.respond_str( - &format!("Renamed override '{}' to '{}'.", self.old_name, name), - false, - ) - .await?; + ctx.respond_str(&lang.overrides_rename_done(self.current_name, name), false) + .await?; } } diff --git a/src/interactions/commands/chat/overrides/view.rs b/src/interactions/commands/chat/overrides/view.rs index ecd2e071..1a1e626e 100644 --- a/src/interactions/commands/chat/overrides/view.rs +++ b/src/interactions/commands/chat/overrides/view.rs @@ -12,6 +12,8 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::{commands::format_settings::format_settings, context::CommandCtx}, + locale_func, + translations::Lang, utils::{ embed, id_as_i64::GetI64, @@ -19,11 +21,18 @@ use crate::{ }, }; +locale_func!(overrides_view); +locale_func!(overrides_view_option_name); + #[derive(CreateCommand, CommandModel)] -#[command(name = "view", desc = "View your overrides.")] +#[command( + name = "view", + desc = "View your overrides.", + desc_localizations = "overrides_view" +)] pub struct ViewOverride { /// The name of the override to view. Leave blank to show all. - #[command(autocomplete = true)] + #[command(autocomplete = true, desc_localizations = "overrides_view_option_name")] name: Option, } @@ -32,11 +41,11 @@ impl ViewOverride { let guild_id = get_guild_id!(ctx); let guild_id_i64 = guild_id.get_i64(); let bot = ctx.bot.clone(); + let lang = ctx.user_lang(); let overrides = StarboardOverride::list_by_guild(&ctx.bot.pool, guild_id_i64).await?; if overrides.is_empty() { - ctx.respond_str("This server has no overrides.", true) - .await?; + ctx.respond_str(lang.overrides_view_none(), true).await?; return Ok(()); } @@ -51,16 +60,15 @@ impl ViewOverride { let sb = Starboard::get(&bot.pool, ov.starboard_id).await?.unwrap(); let label = ov.name.clone(); - let description = format!( - "{} overwritten settings for starboard '{}' in {} channel(s)", + let description = lang.overrides_view_slug( + ov.channel_ids.len(), ov.overrides.as_object().unwrap().len(), sb.name, - ov.channel_ids.len(), ); let page = SelectPaginatorPageBuilder::new(label) .description(description) - .add_embed(override_embed(&bot, guild_id, ov).await?); + .add_embed(override_embed(&bot, guild_id, ov, lang).await?); paginator = paginator.add_page(page); } @@ -74,6 +82,7 @@ async fn override_embed( bot: &StarboardBot, guild_id: Id, ov: StarboardOverride, + lang: Lang, ) -> StarboardResult { let name = ov.name.clone(); let sb = Starboard::get(&bot.pool, ov.starboard_id).await?.unwrap(); @@ -84,36 +93,30 @@ async fn override_embed( let pretty = format_settings(bot, guild_id, &config).await?; let embed = embed::build() - .title(format!("Override '{name}'")) - .description(format!( - concat!( - "This override belongs to the starboard '{}'.\n\n", - "This override applies to the following channels: {}", - ), - &config.starboard.name, channels, - )) + .title(lang.override_title(name)) + .description(lang.overrides_view_description(channels, &config.starboard.name)) .field( - EmbedFieldBuilder::new("Requirements", pretty.requirements) + EmbedFieldBuilder::new(lang.sb_option_category_requirements(), pretty.requirements) .inline() .build(), ) .field( - EmbedFieldBuilder::new("Behaviour", pretty.behavior) + EmbedFieldBuilder::new(lang.sb_option_category_behavior(), pretty.behavior) .inline() .build(), ) .field( - EmbedFieldBuilder::new("Style", pretty.style) + EmbedFieldBuilder::new(lang.sb_option_category_style(), pretty.style) .inline() .build(), ) .field( - EmbedFieldBuilder::new("Embed Style", pretty.embed) + EmbedFieldBuilder::new(lang.sb_option_category_embed(), pretty.embed) .inline() .build(), ) - .field(EmbedFieldBuilder::new("Regex Matching", pretty.regex).build()) - .field(EmbedFieldBuilder::new("Filters", pretty.filters)) + .field(EmbedFieldBuilder::new(lang.sb_option_category_regex(), pretty.regex).build()) + .field(EmbedFieldBuilder::new(lang.filters_title(), pretty.filters)) .build(); Ok(embed) diff --git a/src/interactions/commands/chat/permroles/create.rs b/src/interactions/commands/chat/permroles/create.rs index 30049d37..bcd4ddcf 100644 --- a/src/interactions/commands/chat/permroles/create.rs +++ b/src/interactions/commands/chat/permroles/create.rs @@ -4,13 +4,21 @@ use twilight_model::guild::Role; use crate::{ constants, database::PermRole, errors::StarboardResult, get_guild_id, - interactions::context::CommandCtx, utils::id_as_i64::GetI64, + interactions::context::CommandCtx, locale_func, utils::id_as_i64::GetI64, }; +locale_func!(permroles_create); +locale_func!(permroles_create_option_role); + #[derive(CommandModel, CreateCommand)] -#[command(name = "create", desc = "Create a PermRole.")] +#[command( + name = "create", + desc = "Create a PermRole.", + desc_localizations = "permroles_create" +)] pub struct CreatePermRole { /// The role to use as a PermRole. + #[command(desc_localizations = "permroles_create_option_role")] role: Role, } @@ -18,30 +26,26 @@ impl CreatePermRole { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx); let guild_id_i64 = guild_id.get_i64(); + let lang = ctx.user_lang(); let count = PermRole::count_by_guild(&ctx.bot.pool, guild_id_i64).await?; if count >= constants::MAX_PERMROLES { - ctx.respond_str( - &format!( - "You can only have up to {} PermRoles.", - constants::MAX_PERMROLES - ), - true, - ) - .await?; + ctx.respond_str(&lang.permroles_create_limit(constants::MAX_PERMROLES), true) + .await?; return Ok(()); } let pr = PermRole::create(&ctx.bot.pool, self.role.id.get_i64(), guild_id_i64).await?; if pr.is_none() { - ctx.respond_str("That is already a PermRole.", true).await?; - } else { ctx.respond_str( - &format!("{} is now a PermRole.", self.role.mention()), - false, + &lang.permroles_create_already_exists(self.role.mention()), + true, ) .await?; + } else { + ctx.respond_str(&lang.permroles_create_done(self.role.mention()), false) + .await?; } Ok(()) diff --git a/src/interactions/commands/chat/permroles/delete.rs b/src/interactions/commands/chat/permroles/delete.rs index fefb06d4..084ded0f 100644 --- a/src/interactions/commands/chat/permroles/delete.rs +++ b/src/interactions/commands/chat/permroles/delete.rs @@ -3,29 +3,38 @@ use twilight_mention::Mention; use twilight_model::guild::Role; use crate::{ - concat_format, database::PermRole, errors::StarboardResult, get_guild_id, interactions::{commands::deleted_roles::get_deleted_roles, context::CommandCtx}, + locale_func, utils::{id_as_i64::GetI64, into_id::IntoId, views::confirm}, }; +locale_func!(permroles_delete); +locale_func!(permroles_delete_option_role); + #[derive(CommandModel, CreateCommand)] -#[command(name = "delete", desc = "Delete a PermRole.")] +#[command( + name = "delete", + desc = "Delete a PermRole.", + desc_localizations = "permroles_delete" +)] pub struct DeletePermRole { /// The PermRole to delete. + #[command(desc_localizations = "permroles_delete_option_role")] role: Role, } impl DeletePermRole { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { + let lang = ctx.user_lang(); let pr = PermRole::delete(&ctx.bot.pool, self.role.id.get_i64()).await?; if pr.is_none() { - ctx.respond_str(&format!("{} is not a PermRole.", self.role.mention()), true) + ctx.respond_str(&lang.permrole_missing(self.role.mention()), true) .await?; } else { - ctx.respond_str(&format!("Deleted PermRole {}.", self.role.mention()), false) + ctx.respond_str(&lang.permroles_delete_done(self.role.mention()), false) .await?; } @@ -33,10 +42,13 @@ impl DeletePermRole { } } +locale_func!(permroles_clear_deleted); + #[derive(CommandModel, CreateCommand)] #[command( name = "clear-deleted", - desc = "Delete PermRoles if the Discord role has been deleted." + desc = "Delete PermRoles if the Discord role has been deleted.", + desc_localizations = "permroles_clear_deleted" )] pub struct ClearDeleted; @@ -44,6 +56,7 @@ impl ClearDeleted { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx); let guild_id_i64 = guild_id.get_i64(); + let lang = ctx.user_lang(); let pr = PermRole::list_by_guild(&ctx.bot.pool, guild_id_i64).await?; @@ -54,17 +67,14 @@ impl ClearDeleted { ); if to_delete.is_empty() { - ctx.respond_str("Nothing to clear.", true).await?; + ctx.respond_str(lang.permroles_clear_deleted_nothing(), true) + .await?; return Ok(()); } let conf = confirm::simple( &mut ctx, - &concat_format!( - "This will delete the following permroles:\n"; - "{to_delete_pretty}\n"; - "Do you wish to continue?"; - ), + lang.permroles_clear_deleted_confirm(to_delete_pretty), true, ) .await?; @@ -77,7 +87,9 @@ impl ClearDeleted { PermRole::delete(&ctx.bot.pool, role.get_i64()).await?; } - btn_ctx.edit_str("Done.", true).await?; + btn_ctx + .edit_str(lang.permroles_clear_deleted_done(), true) + .await?; Ok(()) } diff --git a/src/interactions/commands/chat/permroles/edit.rs b/src/interactions/commands/chat/permroles/edit.rs index 04ecb412..654af991 100644 --- a/src/interactions/commands/chat/permroles/edit.rs +++ b/src/interactions/commands/chat/permroles/edit.rs @@ -6,30 +6,50 @@ use crate::{ database::PermRole, errors::StarboardResult, interactions::{commands::choices::tribool::Tribool, context::CommandCtx}, + locale_func, utils::id_as_i64::GetI64, }; +locale_func!(permroles_edit); +locale_func!(permroles_edit_option_role); + +locale_func!(permroles_option_vote); +locale_func!(permroles_option_receive_votes); +locale_func!(permroles_option_xproles); + #[derive(CommandModel, CreateCommand)] -#[command(name = "edit", desc = "Edit the global permissions for a PermRole.")] +#[command( + name = "edit", + desc = "Edit the global permissions for a PermRole.", + desc_localizations = "permroles_edit" +)] pub struct EditPermRole { /// The PermRole to edit. + #[command(desc_localizations = "permroles_edit_option_role")] role: Role, /// Whether a user with this role can vote on messages. + #[command(desc_localizations = "permroles_option_vote")] vote: Option, /// Whether a user with this role can receive votes. - #[command(rename = "receive-votes")] + #[command( + rename = "receive-votes", + desc_localizations = "permroles_option_receive_votes" + )] receive_votes: Option, /// Whether a user with this role can gain XPRoles. + #[command(desc_localizations = "permroles_option_xproles")] xproles: Option, } impl EditPermRole { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { + let lang = ctx.user_lang(); + let pr = PermRole::get(&ctx.bot.pool, self.role.id.get_i64()).await?; let mut pr = match pr { None => { - ctx.respond_str(&format!("{} is not a PermRole.", self.role.mention()), true) + ctx.respond_str(lang.permrole_missing(self.role.mention()), true) .await?; return Ok(()); } @@ -48,11 +68,8 @@ impl EditPermRole { pr.update(&ctx.bot.pool).await?; - ctx.respond_str( - &format!("Updated settings for {}", self.role.mention()), - false, - ) - .await?; + ctx.respond_str(lang.permroles_edit_done(self.role.mention()), false) + .await?; Ok(()) } } diff --git a/src/interactions/commands/chat/permroles/edit_starboard.rs b/src/interactions/commands/chat/permroles/edit_starboard.rs index c8d18a84..ffdf9408 100644 --- a/src/interactions/commands/chat/permroles/edit_starboard.rs +++ b/src/interactions/commands/chat/permroles/edit_starboard.rs @@ -7,40 +7,55 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::{commands::choices::tribool::Tribool, context::CommandCtx}, + locale_func, utils::{id_as_i64::GetI64, pg_error::PgErrorTraits}, }; +locale_func!(permroles_edit_sb); +locale_func!(permroles_edit_option_role); +locale_func!(permroles_edit_sb_option_starboard); + +locale_func!(permroles_option_vote); +locale_func!(permroles_option_receive_votes); + #[derive(CommandModel, CreateCommand)] #[command( name = "edit-starboard", - desc = "Edit the settings for a PermRole in a starboard." + desc = "Edit the settings for a PermRole in a starboard.", + desc_localizations = "permroles_edit_sb" )] pub struct EditPermRoleStarboard { /// The PermRole to edit. + #[command(desc_localizations = "permroles_edit_option_role")] role: Role, /// The starboard to edit the PermRole for. - #[command(autocomplete = true)] + #[command( + autocomplete = true, + desc_localizations = "permroles_edit_sb_option_starboard" + )] starboard: String, /// Whether a user can vote on messages. + #[command(desc_localizations = "permroles_option_vote")] vote: Option, /// Whether a user's messages can be voted on. - #[command(rename = "receive-votes")] + #[command( + rename = "receive-votes", + desc_localizations = "permroles_option_receive_votes" + )] receive_vote: Option, } impl EditPermRoleStarboard { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx).get_i64(); + let lang = ctx.user_lang(); let sb = Starboard::get_by_name(&ctx.bot.pool, &self.starboard, guild_id).await?; let sb = match sb { None => { - ctx.respond_str( - &format!("Starboard '{}' does not exist.", self.starboard), - true, - ) - .await?; + ctx.respond_str(lang.starboard_missing(self.starboard), true) + .await?; return Ok(()); } Some(sb) => sb, @@ -54,7 +69,7 @@ impl EditPermRoleStarboard { .unwrap(), Err(why) => { if why.is_fk_violation() { - ctx.respond_str(&format!("{} is not a PermRole.", self.role.mention()), true) + ctx.respond_str(lang.permrole_missing(self.role.mention()), true) .await?; return Ok(()); } @@ -71,11 +86,7 @@ impl EditPermRoleStarboard { pr_sb.update(&ctx.bot.pool).await?; ctx.respond_str( - &format!( - "Updated the settings for {} in '{}'.", - self.role.mention(), - sb.name - ), + lang.permroles_edit_sb_done(self.role.mention(), sb.name), false, ) .await?; diff --git a/src/interactions/commands/chat/permroles/mod.rs b/src/interactions/commands/chat/permroles/mod.rs index a36265f0..41aa2948 100644 --- a/src/interactions/commands/chat/permroles/mod.rs +++ b/src/interactions/commands/chat/permroles/mod.rs @@ -9,12 +9,16 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use crate::{ errors::StarboardResult, interactions::{commands::permissions::manage_channels, context::CommandCtx}, + locale_func, }; +locale_func!(permroles); + #[derive(CommandModel, CreateCommand)] #[command( name = "permroles", desc = "View and manage PermRoles.", + desc_localizations = "permroles", dm_permission = false, default_permissions = "manage_channels" )] diff --git a/src/interactions/commands/chat/permroles/view.rs b/src/interactions/commands/chat/permroles/view.rs index 31f83e7d..75940764 100644 --- a/src/interactions/commands/chat/permroles/view.rs +++ b/src/interactions/commands/chat/permroles/view.rs @@ -8,6 +8,8 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, + translations::Lang, utils::{ embed, id_as_i64::GetI64, @@ -16,6 +18,7 @@ use crate::{ }, }; +// todo: formatter macro_rules! fmt_trib { ($to_fmt: expr) => { $to_fmt @@ -24,10 +27,18 @@ macro_rules! fmt_trib { }; } +locale_func!(permroles_view); +locale_func!(permroles_view_option_role); + #[derive(CommandModel, CreateCommand)] -#[command(name = "view", desc = "View the PermRoles for this server.")] +#[command( + name = "view", + desc = "View the PermRoles for this server.", + desc_localizations = "permroles_view" +)] pub struct ViewPermRoles { /// The PermRole to view settings for. + #[command(desc_localizations = "permroles_view_option_role")] role: Option, } @@ -35,12 +46,12 @@ impl ViewPermRoles { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx); let bot = ctx.bot.clone(); + let lang = ctx.user_lang(); let mut perm_roles = PermRole::list_by_guild(&ctx.bot.pool, guild_id.get_i64()).await?; if perm_roles.is_empty() { - ctx.respond_str("This server has no PermRoles.", true) - .await?; + ctx.respond_str(lang.permroles_view_none(), true).await?; return Ok(()); } @@ -63,8 +74,8 @@ impl ViewPermRoles { .get(&pr.role_id.into_id()) .map(|r| r.name.to_owned()) }); - let label = name.unwrap_or_else(|| format!("Deleted Role {}", pr.role_id)); - let embed = permrole_embed(&bot, pr).await?; + let label = name.unwrap_or_else(|| lang.deleted_role(pr.role_id)); + let embed = permrole_embed(&bot, pr, lang).await?; let page = SelectPaginatorPageBuilder::new(label).add_embed(embed); paginator = paginator.add_page(page); @@ -76,8 +87,8 @@ impl ViewPermRoles { } } -async fn permrole_embed(bot: &StarboardBot, pr: PermRole) -> StarboardResult { - let mut pr_config = format!("Settings for <@&{}>:\n", pr.role_id); +async fn permrole_embed(bot: &StarboardBot, pr: PermRole, lang: Lang) -> StarboardResult { + let mut pr_config = lang.permroles_view_title(pr.role_id); pr_config.push_str(&concat_format!( "vote: {}\n" <- fmt_trib!(pr.give_votes); "receive-votes: {}\n" <- fmt_trib!(pr.receive_votes); @@ -96,10 +107,7 @@ async fn permrole_embed(bot: &StarboardBot, pr: PermRole) -> StarboardResult sb, }; - pr_config.push_str(&format!( - "\nSettings for '{}' in <#{}>:\n", - sb.name, sb.channel_id - )); + pr_config.push_str(&lang.permroles_view_sb_title(sb.channel_id, sb.name)); pr_config.push_str(&concat_format!( "vote: {}\n" <- fmt_trib!(pr_sb.give_votes); "receive-votes: {}\n" <- fmt_trib!(pr_sb.receive_votes); @@ -107,7 +115,7 @@ async fn permrole_embed(bot: &StarboardBot, pr: PermRole) -> StarboardResult StarboardResult<()> { - ctx.respond_str("Pong! I'm here.", false).await?; + ctx.respond_str(ctx.user_lang().pong(), false).await?; Ok(()) } diff --git a/src/interactions/commands/chat/posroles/delete.rs b/src/interactions/commands/chat/posroles/delete.rs index 0ca38eaa..ebf42954 100644 --- a/src/interactions/commands/chat/posroles/delete.rs +++ b/src/interactions/commands/chat/posroles/delete.rs @@ -1,30 +1,40 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; +use twilight_mention::Mention; use twilight_model::guild::Role; use crate::{ - concat_format, - core::premium::is_premium::is_guild_premium, database::PosRole, errors::StarboardResult, get_guild_id, interactions::{commands::deleted_roles::get_deleted_roles, context::CommandCtx}, + locale_func, utils::{id_as_i64::GetI64, into_id::IntoId, views::confirm}, }; +locale_func!(posroles_delete); +locale_func!(posroles_delete_option_posrole); + #[derive(CommandModel, CreateCommand)] -#[command(name = "delete", desc = "Delete a position-based award role.")] +#[command( + name = "delete", + desc = "Delete a position-based award role.", + desc_localizations = "posroles_delete" +)] pub struct Delete { /// The PosRole to delete. + #[command(desc_localizations = "posroles_delete_option_posrole")] posrole: Role, } impl Delete { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { + let lang = ctx.user_lang(); + let role = PosRole::delete(&ctx.bot.pool, self.posrole.id.get_i64()).await?; let (msg, ephemeral) = match role { - None => ("That is not a PosRole.", true), - Some(_) => ("PosRole deleted.", false), + None => (lang.posrole_missing(self.posrole.mention()), true), + Some(_) => (lang.posroles_delete_done(self.posrole.mention()), false), }; ctx.respond_str(msg, ephemeral).await?; @@ -32,10 +42,13 @@ impl Delete { } } +locale_func!(posroles_clear_deleted); + #[derive(CommandModel, CreateCommand)] #[command( name = "clear-deleted", - desc = "Delete PosRoles if the Discord role has been deleted." + desc = "Delete PosRoles if the Discord role has been deleted.", + desc_localizations = "posroles_clear_deleted" )] pub struct ClearDeleted; @@ -43,12 +56,7 @@ impl ClearDeleted { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx); let guild_id_i64 = guild_id.get_i64(); - - if !is_guild_premium(&ctx.bot, guild_id_i64, true).await? { - ctx.respond_str("Only premium servers can use this command.", true) - .await?; - return Ok(()); - } + let lang = ctx.user_lang(); let pr = PosRole::list_by_guild(&ctx.bot.pool, guild_id_i64).await?; @@ -59,17 +67,14 @@ impl ClearDeleted { ); if to_delete.is_empty() { - ctx.respond_str("Nothing to clear.", true).await?; + ctx.respond_str(lang.posroles_clear_deleted_nothing(), true) + .await?; return Ok(()); } let conf = confirm::simple( &mut ctx, - &concat_format!( - "This will delete the following PosRoles:\n"; - "{to_delete_pretty}\n"; - "Do you wish to continue?"; - ), + lang.posroles_clear_deleted_confirm(to_delete_pretty), true, ) .await?; @@ -82,7 +87,9 @@ impl ClearDeleted { PosRole::delete(&ctx.bot.pool, role.get_i64()).await?; } - btn_ctx.edit_str("Done.", true).await?; + btn_ctx + .edit_str(lang.posroles_clear_deleted_done(), true) + .await?; Ok(()) } diff --git a/src/interactions/commands/chat/posroles/mod.rs b/src/interactions/commands/chat/posroles/mod.rs index e1ca2cdf..1bd04d03 100644 --- a/src/interactions/commands/chat/posroles/mod.rs +++ b/src/interactions/commands/chat/posroles/mod.rs @@ -8,12 +8,16 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use crate::{ errors::StarboardResult, interactions::{commands::permissions::manage_roles, context::CommandCtx}, + locale_func, }; +locale_func!(posroles); + #[derive(CommandModel, CreateCommand)] #[command( name = "posroles", desc = "View and manage position-based award roles.", + desc_localizations = "posroles", dm_permission = false, default_permissions = "manage_roles" )] diff --git a/src/interactions/commands/chat/posroles/refresh.rs b/src/interactions/commands/chat/posroles/refresh.rs index 9b8fb903..ce2e5ecf 100644 --- a/src/interactions/commands/chat/posroles/refresh.rs +++ b/src/interactions/commands/chat/posroles/refresh.rs @@ -1,25 +1,31 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use crate::{ - concat_format, core::{posroles::update_posroles_for_guild, premium::is_premium::is_guild_premium}, errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::id_as_i64::GetI64, }; +locale_func!(posroles_refresh); + #[derive(CommandModel, CreateCommand)] -#[command(name = "refresh", desc = "Refresh the PosRoles for the server.")] +#[command( + name = "refresh", + desc = "Refresh the PosRoles for the server.", + desc_localizations = "posroles_refresh" +)] pub struct Refresh; impl Refresh { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx); + let lang = ctx.user_lang(); if !is_guild_premium(&ctx.bot, guild_id.get_i64(), true).await? { - ctx.respond_str("Only premium servers can use this command.", true) - .await?; + ctx.respond_str(lang.premium_command(), true).await?; return Ok(()); } @@ -29,16 +35,17 @@ impl Refresh { if let Some(ret) = ret { ctx.respond_str( - &concat_format!( - "Finished updating.\n"; - "Added {} roles, {} failed.\n" <- ret.added_roles, ret.failed_adds; - "Removed {} roles, {} failed." <- ret.removed_roles, ret.failed_removals; + lang.posroles_refresh_done( + ret.added_roles, + ret.failed_adds, + ret.removed_roles, + ret.failed_removals, ), true, ) .await?; } else { - ctx.respond_str("PosRoles are already being updated.", true) + ctx.respond_str(lang.posroles_refresh_already_refreshing(), true) .await?; } diff --git a/src/interactions/commands/chat/posroles/set_max_members.rs b/src/interactions/commands/chat/posroles/set_max_members.rs index 76ebfe78..775926f5 100644 --- a/src/interactions/commands/chat/posroles/set_max_members.rs +++ b/src/interactions/commands/chat/posroles/set_max_members.rs @@ -1,51 +1,55 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; +use twilight_mention::Mention; use twilight_model::guild::Role; use crate::{ constants, core::premium::is_premium::is_guild_premium, database::PosRole, - errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, locale_func, utils::id_as_i64::GetI64, }; +locale_func!(posroles_edit); +locale_func!(posroles_edit_option_role); +locale_func!(posroles_edit_option_max_members); + #[derive(CommandModel, CreateCommand)] #[command( name = "set-max-members", - desc = "Create or modify a position-based award role." + desc = "Create or modify a position-based award role.", + desc_localizations = "posroles_edit" )] pub struct SetMaxMembers { /// The role to use as a position-based award role. + #[command(desc_localizations = "posroles_edit_option_role")] role: Role, /// How many members can have this award role. - #[command(min_value = 1, rename = "max-members")] + #[command( + min_value = 1, + rename = "max-members", + desc_localizations = "posroles_edit_option_max_members" + )] max_members: i64, } impl SetMaxMembers { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx).get_i64(); + let lang = ctx.user_lang(); if !is_guild_premium(&ctx.bot, guild_id, true).await? { - ctx.respond_str("Only premium servers can use this command.", true) - .await?; + ctx.respond_str(lang.premium_command(), true).await?; return Ok(()); } if self.role.id.get_i64() == guild_id || self.role.managed { - ctx.respond_str("You can't use that role for award roles.", true) - .await?; + ctx.respond_str(lang.award_role_managed(), true).await?; return Ok(()); } let count = PosRole::count(&ctx.bot.pool, guild_id).await?; if count >= constants::MAX_POSROLES { - ctx.respond_str( - &format!( - "You can only have up to {} position-based award roles.", - constants::MAX_POSROLES - ), - true, - ) - .await?; + ctx.respond_str(lang.posroles_edit_limit(constants::MAX_POSROLES), true) + .await?; return Ok(()); } @@ -56,13 +60,16 @@ impl SetMaxMembers { if posrole.is_none() { PosRole::set_max_members(&ctx.bot.pool, role_id, self.max_members as i32).await?; ctx.respond_str( - &format!("Max members changed to {}.", self.max_members), + lang.posroles_edit_edited(self.role.mention(), self.max_members), false, ) .await?; } else { - ctx.respond_str("Position-based award role created.", false) - .await?; + ctx.respond_str( + lang.posroles_edit_created(self.max_members, self.role.mention()), + false, + ) + .await?; } Ok(()) diff --git a/src/interactions/commands/chat/posroles/view.rs b/src/interactions/commands/chat/posroles/view.rs index 64da7322..1c41ddb2 100644 --- a/src/interactions/commands/chat/posroles/view.rs +++ b/src/interactions/commands/chat/posroles/view.rs @@ -1,5 +1,3 @@ -use std::fmt::Write; - use thousands::Separable; use twilight_interactions::command::{CommandModel, CreateCommand}; @@ -9,26 +7,33 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::{embed, id_as_i64::GetI64, views::paginator}, }; +locale_func!(posroles_view); + #[derive(CommandModel, CreateCommand)] -#[command(name = "view", desc = "View all of your position-based award roles.")] +#[command( + name = "view", + desc = "View all of your position-based award roles.", + desc_localizations = "posroles_view" +)] pub struct View; impl View { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx).get_i64(); + let lang = ctx.user_lang(); if !is_guild_premium(&ctx.bot, guild_id, true).await? { - ctx.respond_str("Only premium servers can use this command.", true) - .await?; + ctx.respond_str(lang.premium_command(), true).await?; return Ok(()); } let posroles = PosRole::list_by_guild(&ctx.bot.pool, guild_id).await?; if posroles.is_empty() { - ctx.respond_str("There are no PosRoles.", true).await?; + ctx.respond_str(lang.posroles_view_none(), true).await?; return Ok(()); } @@ -37,17 +42,13 @@ impl View { for chunk in posroles.chunks(10) { let mut desc = String::new(); for xpr in chunk { - writeln!( - desc, - "<@&{}> - `{}` members", - xpr.role_id, - xpr.max_members.separate_with_commas() - ) - .unwrap(); + desc.push_str( + &lang.posrole_description(xpr.max_members.separate_with_commas(), xpr.role_id), + ); } let emb = embed::build() - .title("Position-based Award Roles") + .title(lang.posroles_title()) .description(desc) .build(); diff --git a/src/interactions/commands/chat/premium/autoredeem/disable.rs b/src/interactions/commands/chat/premium/autoredeem/disable.rs index 073d5e44..89a435d1 100644 --- a/src/interactions/commands/chat/premium/autoredeem/disable.rs +++ b/src/interactions/commands/chat/premium/autoredeem/disable.rs @@ -1,27 +1,38 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use crate::{ - database::DbMember, errors::StarboardResult, interactions::context::CommandCtx, + database::DbMember, errors::StarboardResult, interactions::context::CommandCtx, locale_func, utils::id_as_i64::GetI64, }; +locale_func!(autoredeem_disable); +locale_func!(autoredeem_disable_option_server); + #[derive(CommandModel, CreateCommand)] -#[command(name = "disable", desc = "Disable autoredeem for a server.")] +#[command( + name = "disable", + desc = "Disable autoredeem for a server.", + desc_localizations = "autoredeem_disable" +)] pub struct Disable { /// The server to disable autoredeem for. - #[command(autocomplete = true)] + #[command( + autocomplete = true, + desc_localizations = "autoredeem_disable_option_server" + )] server: Option, } impl Disable { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let user_id = ctx.interaction.author_id().unwrap().get_i64(); + let lang = ctx.user_lang(); let guild_id = 'out: { let Some(input_guild) = self.server else { let Some(guild_id) = ctx.interaction.guild_id else { ctx.respond_str( - "Please specify a server, or run this command inside one.", + lang.autoredeem_disable_invalid_guild(), true ).await?; return Ok(()); @@ -32,7 +43,7 @@ impl Disable { let Ok(guild_id) = input_guild.parse::() else { ctx.respond_str( - "Please entire a server ID, or select a server from the options.", + lang.autoredeem_disable_invalid_guild(), true ).await?; return Ok(()); @@ -43,7 +54,8 @@ impl Disable { DbMember::set_autoredeem_enabled(&ctx.bot.pool, user_id, guild_id, false).await?; - ctx.respond_str("Autoredeem disabled.", true).await?; + ctx.respond_str(lang.autoredeem_disable_done(), true) + .await?; Ok(()) } diff --git a/src/interactions/commands/chat/premium/autoredeem/enable.rs b/src/interactions/commands/chat/premium/autoredeem/enable.rs index 7317ce74..489f0b3b 100644 --- a/src/interactions/commands/chat/premium/autoredeem/enable.rs +++ b/src/interactions/commands/chat/premium/autoredeem/enable.rs @@ -1,19 +1,27 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use crate::{ - database::DbMember, errors::StarboardResult, interactions::context::CommandCtx, + database::DbMember, errors::StarboardResult, interactions::context::CommandCtx, locale_func, utils::id_as_i64::GetI64, }; +locale_func!(autoredeem_enable); + #[derive(CommandModel, CreateCommand)] -#[command(name = "enable", desc = "Enable autoredeem for the current server.")] +#[command( + name = "enable", + desc = "Enable autoredeem for the current server.", + desc_localizations = "autoredeem_enable" +)] pub struct Enable; impl Enable { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { + let lang = ctx.user_lang(); + let Some(guild_id) = ctx.interaction.guild_id else { ctx.respond_str( - "Please run this command inside a server.", + lang.autoredeem_enable_dm(), true ).await?; return Ok(()); @@ -24,7 +32,7 @@ impl Enable { DbMember::create(&ctx.bot.pool, user_id, guild_id).await?; DbMember::set_autoredeem_enabled(&ctx.bot.pool, user_id, guild_id, true).await?; - ctx.respond_str("Autoredeem enabled.", true).await?; + ctx.respond_str(lang.autoredeem_enable_done(), true).await?; Ok(()) } diff --git a/src/interactions/commands/chat/premium/autoredeem/mod.rs b/src/interactions/commands/chat/premium/autoredeem/mod.rs index 73381229..31b37f9b 100644 --- a/src/interactions/commands/chat/premium/autoredeem/mod.rs +++ b/src/interactions/commands/chat/premium/autoredeem/mod.rs @@ -3,10 +3,16 @@ mod enable; use twilight_interactions::command::{CommandModel, CreateCommand}; -use crate::{errors::StarboardResult, interactions::context::CommandCtx}; +use crate::{errors::StarboardResult, interactions::context::CommandCtx, locale_func}; + +locale_func!(autoredeem); #[derive(CommandModel, CreateCommand)] -#[command(name = "autoredeem", desc = "Manage autoredeem.")] +#[command( + name = "autoredeem", + desc = "Manage autoredeem.", + desc_localizations = "autoredeem" +)] pub enum Autoredeem { #[command(name = "disable")] Disable(disable::Disable), diff --git a/src/interactions/commands/chat/premium/info.rs b/src/interactions/commands/chat/premium/info.rs index 2c877c79..b2f1f81c 100644 --- a/src/interactions/commands/chat/premium/info.rs +++ b/src/interactions/commands/chat/premium/info.rs @@ -1,24 +1,32 @@ -use std::{borrow::Cow, fmt::Write}; +use std::borrow::Cow; use twilight_interactions::command::{CommandModel, CreateCommand}; use twilight_model::channel::message::MessageFlags; use twilight_util::builder::embed::EmbedFieldBuilder; use crate::{ - concat_format, constants, + constants, database::{DbGuild, DbMember, DbUser}, errors::StarboardResult, interactions::context::CommandCtx, + locale_func, utils::{embed, id_as_i64::GetI64, into_id::IntoId}, }; +locale_func!(premium_info); + #[derive(CommandModel, CreateCommand)] -#[command(name = "info", desc = "Get premium info.")] +#[command( + name = "info", + desc = "Get premium info.", + desc_localizations = "premium_info" +)] pub struct Info; impl Info { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let user_id = ctx.interaction.author_id().unwrap().get_i64(); + let lang = ctx.user_lang(); let user = DbUser::get(&ctx.bot.pool, user_id).await?; let credits = match &user { @@ -27,17 +35,11 @@ impl Info { }; let mut emb = embed::build() - .title("Starboard Premium") - .description(concat_format!( - "Starboard uses a credit system for premium. For each USD you donate (currently "; - "only Patreon is supported), you receive one premium credit. Three premium "; - "credits can be redeemed for one month of premium in any server of your choice."; - "\n\nThis means that premium for one server is $3/month, two servers is $6/month, "; - "and so on. To get premium, visit my [Patreon]({})." <- constants::PATREON_URL; - )) + .title(lang.premium_emb_title()) + .description(lang.premium_emb_desc(constants::PATREON_URL)) .field(EmbedFieldBuilder::new( - "Status", - format!("You currently have {credits} credits."), + lang.premium_emb_status(), + lang.premium_emb_status_value(credits), )); 'out: { @@ -50,39 +52,31 @@ impl Info { break 'out; } - let mut value = "Autoredeem is enabled for the following servers:\n".to_string(); + let mut value = lang.premium_emb_ar_top().to_string(); for guild_id in ar { ctx.bot.cache.guilds.with(&guild_id.into_id(), |_, guild| { if let Some(guild) = &guild { value.push_str(&guild.name); value.push('\n'); } else { - writeln!(value, "Deleted Guild {guild_id}").unwrap(); + value.push_str(&lang.unknown_server(guild_id)); } }); } - value.push_str(concat!( - "\nAutoredeem will automatically take credits from your account when the server ", - "runs out of premium. This will only occur if Starboard is still in that server ", - "and you are still in that server.\n\n Disable it at any time by using ", - "`/premium autoredeem disable`." - )); + value.push_str(lang.premium_emb_ar_desc()); - emb = emb.field(EmbedFieldBuilder::new("Autoredeem", value)); + emb = emb.field(EmbedFieldBuilder::new(lang.premium_emb_ar(), value)); } if let Some(guild_id) = ctx.interaction.guild_id { let guild = DbGuild::get(&ctx.bot.pool, guild_id.get_i64()).await?; let value = match guild.and_then(|g| g.premium_end) { - None => Cow::Borrowed("This server does not have premium."), - Some(end) => Cow::Owned(format!( - "This server has premium until .", - end.timestamp() - )), + None => Cow::Borrowed(lang.premium_end_no_premium()), + Some(end) => Cow::Owned(lang.premium_end_premium_until(end.timestamp())), }; - emb = emb.field(EmbedFieldBuilder::new("Server Premium", value)); + emb = emb.field(EmbedFieldBuilder::new(lang.premium_emb_server(), value)); }; ctx.respond( diff --git a/src/interactions/commands/chat/premium/mod.rs b/src/interactions/commands/chat/premium/mod.rs index 10ed5900..7d7e1e89 100644 --- a/src/interactions/commands/chat/premium/mod.rs +++ b/src/interactions/commands/chat/premium/mod.rs @@ -4,12 +4,15 @@ mod redeem; use twilight_interactions::command::{CommandModel, CreateCommand}; -use crate::{errors::StarboardResult, interactions::context::CommandCtx}; +use crate::{errors::StarboardResult, interactions::context::CommandCtx, locale_func}; + +locale_func!(premium); #[derive(CommandModel, CreateCommand)] #[command( name = "premium", - desc = "Premium-releated commands. See /premium-locks for locks." + desc = "Premium-releated commands. See /premium-locks for locks.", + desc_localizations = "premium" )] pub enum Premium { #[command(name = "info")] diff --git a/src/interactions/commands/chat/premium/redeem.rs b/src/interactions/commands/chat/premium/redeem.rs index 718b6ac1..507702c2 100644 --- a/src/interactions/commands/chat/premium/redeem.rs +++ b/src/interactions/commands/chat/premium/redeem.rs @@ -1,5 +1,3 @@ -use std::borrow::Cow; - use twilight_interactions::command::{CommandModel, CreateCommand}; use crate::{ @@ -8,21 +6,35 @@ use crate::{ database::DbGuild, errors::StarboardResult, interactions::context::CommandCtx, + locale_func, utils::{id_as_i64::GetI64, views::confirm}, }; +locale_func!(premium_redeem); +locale_func!(premium_redeem_option_months); + #[derive(CommandModel, CreateCommand)] -#[command(name = "redeem", desc = "Redeem your premium credits.")] +#[command( + name = "redeem", + desc = "Redeem your premium credits.", + desc_localizations = "premium_redeem" +)] pub struct Redeem { /// The number of months of premium to redeem. Each month is three credits. - #[command(min_value = 1, max_value = 6)] + #[command( + min_value = 1, + max_value = 6, + desc_localizations = "premium_redeem_option_months" + )] months: i64, } impl Redeem { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { + let lang = ctx.user_lang(); + let Some(guild_id) = ctx.interaction.guild_id else { - ctx.respond_str("Please run this command in the server you want premium for.", true).await?; + ctx.respond_str(lang.premium_redeem_dm(), true).await?; return Ok(()); }; let guild_id_i64 = guild_id.get_i64(); @@ -34,29 +46,19 @@ impl Redeem { None => DbGuild::get(&ctx.bot.pool, guild_id_i64).await?.unwrap(), }; - let end_pretty = if let Some(end) = guild.premium_end { - Cow::Owned(format!( - "This server has premium until .", - end.timestamp() - )) + let mut conf = if let Some(end) = guild.premium_end { + lang.premium_end_premium_until(end.timestamp()) } else { - Cow::Borrowed("This server does not have premium.") + lang.premium_end_no_premium().to_string() }; - let ret = confirm::simple( - &mut ctx, - &format!( - concat!( - "{} Doing this will will add {} month(s) of premium (each \"month\" is 31 ", - "days), and cost you {} credits. Do you wish to continue?" - ), - end_pretty, - self.months, - self.months * constants::CREDITS_PER_MONTH as i64 - ), - false, - ) - .await?; + conf.push_str("\n\n"); + conf.push_str(&lang.premium_redeem_confirm( + self.months * constants::CREDITS_PER_MONTH as i64, + self.months, + )); + + let ret = confirm::simple(&mut ctx, conf, false).await?; let Some(mut btn_ctx) = ret else { return Ok(()); }; @@ -71,15 +73,9 @@ impl Redeem { .await?; let resp = match ret { - RedeemPremiumResult::Ok => concat!( - "Done.\n\nTip: Use `/premium autoredeem enable` to enable autoredeem for this ", - "server, so you don't have to repeatedly redeem credits." - ), - RedeemPremiumResult::StateMismatch => concat!( - "This server's premium status changed while you were running the command. ", - "Please try again." - ), - RedeemPremiumResult::TooFewCredits => "You don't have enough credits.", + RedeemPremiumResult::Ok => lang.premium_redeem_done(), + RedeemPremiumResult::StateMismatch => lang.premium_redeem_state_mismatch(), + RedeemPremiumResult::TooFewCredits => lang.premium_redeem_too_few_credits(), }; btn_ctx.edit_str(resp, true).await?; diff --git a/src/interactions/commands/chat/xproles/delete.rs b/src/interactions/commands/chat/xproles/delete.rs index 392dd4f6..61d3366e 100644 --- a/src/interactions/commands/chat/xproles/delete.rs +++ b/src/interactions/commands/chat/xproles/delete.rs @@ -1,30 +1,40 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; +use twilight_mention::Mention; use twilight_model::guild::Role; use crate::{ - concat_format, - core::premium::is_premium::is_guild_premium, database::XPRole, errors::StarboardResult, get_guild_id, interactions::{commands::deleted_roles::get_deleted_roles, context::CommandCtx}, + locale_func, utils::{id_as_i64::GetI64, into_id::IntoId, views::confirm}, }; +locale_func!(xproles_delete); +locale_func!(xproles_delete_option_xprole); + #[derive(CommandModel, CreateCommand)] -#[command(name = "delete", desc = "Delete an XP-based award role.")] +#[command( + name = "delete", + desc = "Delete an XP-based award role.", + desc_localizations = "xproles_delete" +)] pub struct Delete { /// The XPRole to delete. + #[command(desc_localizations = "xproles_delete_option_xprole")] xprole: Role, } impl Delete { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { + let lang = ctx.user_lang(); + let role = XPRole::delete(&ctx.bot.pool, self.xprole.id.get_i64()).await?; let (msg, ephemeral) = match role { - None => ("That is not an XPRole.", true), - Some(_) => ("XPRole deleted.", false), + None => (lang.xprole_missing(self.xprole.mention()), true), + Some(_) => (lang.xproles_delete_done(self.xprole.mention()), false), }; ctx.respond_str(msg, ephemeral).await?; @@ -32,10 +42,13 @@ impl Delete { } } +locale_func!(xproles_clear_deleted); + #[derive(CommandModel, CreateCommand)] #[command( name = "clear-deleted", - desc = "Delete XPRoles if the Discord role has been deleted." + desc = "Delete XPRoles if the Discord role has been deleted.", + desc_localizations = "xproles_clear_deleted" )] pub struct ClearDeleted; @@ -43,12 +56,7 @@ impl ClearDeleted { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx); let guild_id_i64 = guild_id.get_i64(); - - if !is_guild_premium(&ctx.bot, guild_id_i64, true).await? { - ctx.respond_str("Only premium servers can use this command.", true) - .await?; - return Ok(()); - } + let lang = ctx.user_lang(); let xpr = XPRole::list_by_guild(&ctx.bot.pool, guild_id_i64).await?; @@ -59,17 +67,14 @@ impl ClearDeleted { ); if to_delete.is_empty() { - ctx.respond_str("Nothing to clear.", true).await?; + ctx.respond_str(lang.xproles_clear_deleted_none(), true) + .await?; return Ok(()); } let conf = confirm::simple( &mut ctx, - &concat_format!( - "This will delete the following XPRoles:\n"; - "{to_delete_pretty}\n"; - "Do you wish to continue?"; - ), + lang.xproles_clear_deleted_confirm(to_delete_pretty), true, ) .await?; @@ -82,7 +87,9 @@ impl ClearDeleted { XPRole::delete(&ctx.bot.pool, role.get_i64()).await?; } - btn_ctx.edit_str("Done.", true).await?; + btn_ctx + .edit_str(lang.xproles_clear_deleted_done(), true) + .await?; Ok(()) } diff --git a/src/interactions/commands/chat/xproles/mod.rs b/src/interactions/commands/chat/xproles/mod.rs index e60612ea..c887400a 100644 --- a/src/interactions/commands/chat/xproles/mod.rs +++ b/src/interactions/commands/chat/xproles/mod.rs @@ -7,12 +7,16 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use crate::{ errors::StarboardResult, interactions::{commands::permissions::manage_roles, context::CommandCtx}, + locale_func, }; +locale_func!(xproles); + #[derive(CommandModel, CreateCommand)] #[command( name = "xproles", desc = "View and manage XP-based award roles.", + desc_localizations = "xproles", dm_permission = false, default_permissions = "manage_roles" )] diff --git a/src/interactions/commands/chat/xproles/setxp.rs b/src/interactions/commands/chat/xproles/setxp.rs index 76d2dc80..d12069c4 100644 --- a/src/interactions/commands/chat/xproles/setxp.rs +++ b/src/interactions/commands/chat/xproles/setxp.rs @@ -1,48 +1,56 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; +use twilight_mention::Mention; use twilight_model::guild::Role; use crate::{ constants, core::premium::is_premium::is_guild_premium, database::XPRole, - errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, locale_func, utils::id_as_i64::GetI64, }; +locale_func!(xproles_setxp); +locale_func!(xproles_setxp_role); +locale_func!(xproles_setxp_required_xp); + #[derive(CommandModel, CreateCommand)] -#[command(name = "setxp", desc = "Create or modify an XP-based award role.")] +#[command( + name = "setxp", + desc = "Create or modify an XP-based award role.", + desc_localizations = "xproles_setxp" +)] pub struct SetXP { /// The role to use as an XP-based award role. + #[command(desc_localizations = "xproles_setxp_role")] role: Role, /// How much XP is required to obtain this award role. - #[command(min_value = 1, max_value = 32_767, rename = "required-xp")] + #[command( + min_value = 1, + max_value = 32_767, + rename = "required-xp", + desc_localizations = "xproles_setxp_required_xp" + )] required_xp: i64, } impl SetXP { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx).get_i64(); + let lang = ctx.user_lang(); if !is_guild_premium(&ctx.bot, guild_id, true).await? { - ctx.respond_str("Only premium servers can use this command.", true) - .await?; + ctx.respond_str(lang.premium_command(), true).await?; return Ok(()); } if self.role.id.get_i64() == guild_id || self.role.managed { - ctx.respond_str("You can't use that role for award roles.", true) - .await?; + ctx.respond_str(lang.award_role_managed(), true).await?; return Ok(()); } let count = XPRole::count(&ctx.bot.pool, guild_id).await?; if count >= constants::MAX_XPROLES { - ctx.respond_str( - &format!( - "You can only have up to {} XP-based award roles.", - constants::MAX_XPROLES - ), - true, - ) - .await?; + ctx.respond_str(lang.xproles_setxp_limit(constants::MAX_XPROLES), true) + .await?; return Ok(()); } @@ -53,13 +61,16 @@ impl SetXP { if xprole.is_none() { XPRole::set_required(&ctx.bot.pool, role_id, self.required_xp as i16).await?; ctx.respond_str( - &format!("Required XP changed to {}.", self.required_xp,), + lang.xproles_setxp_edit(self.role.mention(), self.required_xp), false, ) .await?; } else { - ctx.respond_str("XP-based award role created.", false) - .await?; + ctx.respond_str( + lang.xproles_setxp_create(self.role.mention(), self.required_xp), + false, + ) + .await?; } Ok(()) diff --git a/src/interactions/commands/chat/xproles/view.rs b/src/interactions/commands/chat/xproles/view.rs index 7a07db73..b4f82815 100644 --- a/src/interactions/commands/chat/xproles/view.rs +++ b/src/interactions/commands/chat/xproles/view.rs @@ -1,5 +1,3 @@ -use std::fmt::Write; - use thousands::Separable; use twilight_interactions::command::{CommandModel, CreateCommand}; @@ -9,26 +7,33 @@ use crate::{ errors::StarboardResult, get_guild_id, interactions::context::CommandCtx, + locale_func, utils::{embed, id_as_i64::GetI64, views::paginator}, }; +locale_func!(xproles_view); + #[derive(CommandModel, CreateCommand)] -#[command(name = "view", desc = "View all of your XP-based award roles.")] +#[command( + name = "view", + desc = "View all of your XP-based award roles.", + desc_localizations = "xproles_view" +)] pub struct View; impl View { pub async fn callback(self, mut ctx: CommandCtx) -> StarboardResult<()> { let guild_id = get_guild_id!(ctx).get_i64(); + let lang = ctx.user_lang(); if !is_guild_premium(&ctx.bot, guild_id, true).await? { - ctx.respond_str("Only premium servers can use this command.", true) - .await?; + ctx.respond_str(lang.premium_command(), true).await?; return Ok(()); } let xproles = XPRole::list_by_guild(&ctx.bot.pool, guild_id).await?; if xproles.is_empty() { - ctx.respond_str("There are no XPRoles.", true).await?; + ctx.respond_str(lang.xproles_view_none(), true).await?; return Ok(()); } @@ -37,17 +42,13 @@ impl View { let mut desc = String::new(); for xpr in chunk { - writeln!( - desc, - "<@&{}> - `{}` XP", - xpr.role_id, - xpr.required.separate_with_commas() - ) - .unwrap(); + desc.push_str( + &lang.xprole_description(xpr.role_id, xpr.required.separate_with_commas()), + ); } let emb = embed::build() - .title("XP-based Award Roles") + .title(lang.xproles_title()) .description(desc) .build(); diff --git a/src/interactions/context.rs b/src/interactions/context.rs index 416dee86..db05b1fb 100644 --- a/src/interactions/context.rs +++ b/src/interactions/context.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use rosetta_i18n::{Language, LanguageId}; use twilight_http::Response; use twilight_model::{ application::interaction::{ @@ -14,7 +15,7 @@ use twilight_model::{ }; use twilight_util::builder::InteractionResponseDataBuilder; -use crate::{client::bot::StarboardBot, errors::StarboardResult}; +use crate::{client::bot::StarboardBot, errors::StarboardResult, translations::Lang}; pub type CommandCtx = Ctx; pub type ComponentCtx = Ctx; @@ -39,6 +40,18 @@ impl Ctx { } } + pub fn guild_lang(&self) -> Lang { + let id = LanguageId::new(&self.interaction.guild_locale.as_deref().unwrap_or("en")[0..2]); + + Lang::from_language_id(&id).unwrap_or(Lang::En) + } + + pub fn user_lang(&self) -> Lang { + let id = LanguageId::new(&self.interaction.locale.as_deref().unwrap_or("en")[0..2]); + + Lang::from_language_id(&id).unwrap_or(Lang::En) + } + pub fn build_resp(&self) -> InteractionResponseDataBuilder { InteractionResponseDataBuilder::new().allowed_mentions(AllowedMentions::default()) } @@ -117,7 +130,7 @@ impl Ctx { .await } - pub async fn respond_str(&mut self, response: &str, ephemeral: bool) -> TwResult { + pub async fn respond_str(&mut self, response: impl Into, ephemeral: bool) -> TwResult { let mut data = self.build_resp().content(response); if ephemeral { data = data.flags(MessageFlags::EPHEMERAL); @@ -135,7 +148,7 @@ impl Ctx { .await } - pub async fn edit_str(&mut self, response: &str, clear_comps: bool) -> TwResult { + pub async fn edit_str(&mut self, response: impl Into, clear_comps: bool) -> TwResult { let mut data = self.build_resp().content(response); if clear_comps { data = data.components([]); diff --git a/src/macros/locale_func.rs b/src/macros/locale_func.rs new file mode 100644 index 00000000..941baacb --- /dev/null +++ b/src/macros/locale_func.rs @@ -0,0 +1,13 @@ +#[macro_export] +macro_rules! locale_func { + ($name:ident) => { + fn $name() -> [(&'static str, &'static str); 3] { + use $crate::translations::Lang; + [ + ("en-US", Lang::En.$name()), + ("en-GB", Lang::En.$name()), + ("pt-BR", Lang::Pt.$name()), + ] + } + }; +} diff --git a/src/macros/mod.rs b/src/macros/mod.rs index bec68afd..451b4c53 100644 --- a/src/macros/mod.rs +++ b/src/macros/mod.rs @@ -2,3 +2,4 @@ pub mod concat_format; pub mod get_guild_id; +pub mod locale_func; diff --git a/src/main.rs b/src/main.rs index 06960ad8..f225c7cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,10 @@ use tokio::main; use crate::client::{bot::StarboardBot, config::Config, runner::run}; +pub mod translations { + rosetta_i18n::include_translations!(); +} + #[main] async fn main() { let config = Config::from_env(); diff --git a/src/utils/views/confirm.rs b/src/utils/views/confirm.rs index 43917828..76575d63 100644 --- a/src/utils/views/confirm.rs +++ b/src/utils/views/confirm.rs @@ -75,7 +75,7 @@ pub async fn wait_for_result( pub async fn simple( ctx: &mut CommandCtx, - prompt: &str, + prompt: impl Into, danger: bool, ) -> StarboardResult> { let cmd = ctx diff --git a/test.txt b/test.txt new file mode 100644 index 00000000..03400d79 --- /dev/null +++ b/test.txt @@ -0,0 +1,5 @@ +"\nAutoredeem will automatically take credits from your account when the server " +"runs out of premium. This will only occur if Starboard is still un that server " +"and you are still in that server.\n\n Disable it at any time by using " +"`/premium autoredeem disable`." +