From 4384e7d939e80cfc0a6b4743de00f8fec38fe39c Mon Sep 17 00:00:00 2001 From: "airo.pi_" <47398145+AiroPi@users.noreply.github.com> Date: Sun, 17 Dec 2023 12:17:27 +0100 Subject: [PATCH 1/2] Changes about characters limits - Using Enums in constant.py instead of dict - Add char limit for message content in translations feature - Add a _size_limit attribut to i18n function --- src/cogs/translate/__init__.py | 14 ++++++---- src/core/__init__.py | 2 +- src/core/constants.py | 49 +++++++++++++++++++++++++++------- src/core/i18n.py | 42 +++++++++++++++++++---------- 4 files changed, 77 insertions(+), 30 deletions(-) diff --git a/src/cogs/translate/__init__.py b/src/cogs/translate/__init__.py index 30fd62d..167897c 100644 --- a/src/cogs/translate/__init__.py +++ b/src/cogs/translate/__init__.py @@ -11,8 +11,9 @@ from discord import Embed, Message, app_commands, ui from discord.app_commands import locale_str as __ -from core import CHARACTERS_LIMITS, ExtendedCog, ResponseType, TemporaryCache, db, misc_command, response_constructor +from core import ExtendedCog, ResponseType, TemporaryCache, db, misc_command, response_constructor from core.checkers.misc import bot_required_permissions, is_activated, is_user_authorized, misc_check +from core.constants import EmbedsCharLimits from core.errors import BadArgument, NonSpecificError from core.i18n import _ @@ -74,7 +75,7 @@ def values(self) -> list[str]: def inject_translations(self, translation: Sequence[str]): i = 0 if self.content: - self.content = translation[0] + self.content = translation[0][: EmbedsCharLimits.DESCRIPTION.value - 1] i += 1 for tr_embed in self.tr_embeds: tr_embed.reconstruct(translation[i : i + len(tr_embed)]) # noqa: E203 @@ -150,10 +151,13 @@ def reconstruct(self, translations: Sequence[str]): char_lim_key.append(key) obj = obj[key] - limit = CHARACTERS_LIMITS.get(".".join(char_lim_key + [keys[-1]])) + try: + limit = EmbedsCharLimits["_".join(char_lim_key + [keys[-1]]).upper()] + except KeyError: + limit = None - if limit and limit < len(translation): - obj[keys[-1]] = translation[: limit - 1] + "…" + if limit and limit.value < len(translation): + obj[keys[-1]] = translation[: limit.value - 1] + "…" else: obj[keys[-1]] = translation diff --git a/src/core/__init__.py b/src/core/__init__.py index 57d0ad9..4667755 100644 --- a/src/core/__init__.py +++ b/src/core/__init__.py @@ -1,6 +1,6 @@ from ._config import config as config from .caches import SizedMapping as SizedMapping, SizedSequence as SizedSequence, TemporaryCache as TemporaryCache -from .constants import CHARACTERS_LIMITS as CHARACTERS_LIMITS, Emojis as Emojis +from .constants import Emojis as Emojis from .extended_commands import ( ExtendedCog as ExtendedCog, ExtendedGroupCog as ExtendedGroupCog, diff --git a/src/core/constants.py b/src/core/constants.py index fe3fc09..abe3c1a 100644 --- a/src/core/constants.py +++ b/src/core/constants.py @@ -1,7 +1,10 @@ from __future__ import annotations +import enum from typing import TYPE_CHECKING, Self +from discord.app_commands import TranslationContextLocation + if TYPE_CHECKING: from ._types import Snowflake @@ -99,13 +102,39 @@ class Emojis: thumb_down = "👎" -CHARACTERS_LIMITS = { - "content": 2000, - "title": 256, - "description": 4096, - "fields.name": 256, - "fields.value": 1024, - "footer.text": 2048, - "author.name": 256, - "total": 6000, -} +class EmbedsCharLimits(enum.Enum): + TITLE = 256 + DESCRIPTION = 4096 + FIELDS_NAME = 256 + FIELDS_VALUE = 1024 + FOOTER_TEXT = 2048 + AUTHOR_NAME = 256 + + +class GeneralCharLimits(enum.Enum): + MESSAGE_TOTAL = 6000 + MESSAGE_CONTENT = 2000 + SLASH_COMMAND_TOTAL = 4000 + + +class TranslationContextLimits(enum.Enum): + CHOICE_NAME = 100 + COMMAND_DESCRIPTION = 100 + PARAMETER_DESCRIPTION = 100 + PARAMETER_NAME = 100 + COMMAND_NAME = 100 + GROUP_NAME = 100 + GROUP_DESCRIPTION = 100 + + @classmethod + def from_location(cls, location: TranslationContextLocation) -> TranslationContextLimits | None: + translation_context_limits_bind = { + TranslationContextLocation.choice_name: TranslationContextLimits.CHOICE_NAME, + TranslationContextLocation.command_description: TranslationContextLimits.COMMAND_DESCRIPTION, + TranslationContextLocation.parameter_description: TranslationContextLimits.PARAMETER_DESCRIPTION, + TranslationContextLocation.parameter_name: TranslationContextLimits.PARAMETER_NAME, + TranslationContextLocation.command_name: TranslationContextLimits.COMMAND_NAME, + TranslationContextLocation.group_name: TranslationContextLimits.GROUP_NAME, + TranslationContextLocation.group_description: TranslationContextLimits.GROUP_DESCRIPTION, + } + return translation_context_limits_bind.get(location) diff --git a/src/core/i18n.py b/src/core/i18n.py index 6c4583d..0f4df73 100644 --- a/src/core/i18n.py +++ b/src/core/i18n.py @@ -10,6 +10,8 @@ from discord import Interaction, Locale, app_commands from discord.utils import MISSING, find +from core.constants import TranslationContextLimits + if TYPE_CHECKING: from types import FrameType @@ -40,20 +42,27 @@ def load_translations(): class Translator(app_commands.Translator): async def translate(self, string: locale_str, locale: Locale, context: TranslationContextTypes) -> str: new_string = i18n(str(string), _locale=locale) - if context.location is app_commands.TranslationContextLocation.parameter_description: - if len(new_string) > 100: - logger.warning( - "The translated string is too long: %s for %s from %s\n%s", - context.location, - context.data.name, - context.data.command.name, - new_string, - ) - new_string = new_string[:99] + "…" + char_limit = TranslationContextLimits.from_location(context.location) + if char_limit and len(new_string) > char_limit.value: + logger.warning( + "The translated string is too long: %s from %s\n%s", + context.location, + context.data.name, + new_string, + ) + new_string = new_string[: char_limit.value - 1] + "…" return new_string -def i18n(string: str, /, *args: Any, _locale: Locale | None = MISSING, _silent: bool = False, **kwargs: Any) -> str: +def i18n( + string: str, + /, + *args: Any, + _locale: Locale | None = MISSING, + _silent: bool = False, + _size_limit: int = -1, + **kwargs: Any, +) -> str: if _locale is MISSING: frame: FrameType | None = inspect.currentframe() @@ -73,9 +82,14 @@ def i18n(string: str, /, *args: Any, _locale: Locale | None = MISSING, _silent: _locale = inter.locale if _locale is None: - return string.format(*args, **kwargs) - - return translations.get(_locale, translations[LOCALE_DEFAULT]).gettext(string).format(*args, **kwargs) + result = string.format(*args, **kwargs) + else: + result = translations.get(_locale, translations[LOCALE_DEFAULT]).gettext(string).format(*args, **kwargs) + + if _size_limit > 0 and len(result) > _size_limit: + logger.warning("The translated and formatted string is too long: %s\n%s", string, result) + result = result[: _size_limit - 1] + "…" + return result _ = i18n From 4254a6d22d7cc635671bf9376c0bdf4d896977fa Mon Sep 17 00:00:00 2001 From: "airo.pi_" <47398145+AiroPi@users.noreply.github.com> Date: Sun, 17 Dec 2023 12:38:38 +0100 Subject: [PATCH 2/2] Hardcode some string size limits We don't use the enums created to keep the code simple. Only put translations where I think it car be a problem. --- src/cogs/calculator/__init__.py | 3 +-- src/cogs/clear/__init__.py | 8 +++++--- src/cogs/game/minesweeper/__init__.py | 2 -- src/cogs/help.py | 2 +- src/cogs/poll/__init__.py | 2 +- src/cogs/poll/edit.py | 10 +++++----- src/cogs/poll/vote_menus.py | 2 +- src/core/error_handler.py | 6 +++++- src/core/i18n.py | 6 +++--- 9 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/cogs/calculator/__init__.py b/src/cogs/calculator/__init__.py index 3a93f22..fcc9b5e 100644 --- a/src/cogs/calculator/__init__.py +++ b/src/cogs/calculator/__init__.py @@ -199,8 +199,7 @@ async def compute(self, button: ui.Button[Self], interaction: discord.Interactio elif selection == "result": try: calcul.process() - except decimal.InvalidOperation as error: - print(error) + except decimal.InvalidOperation: avertissements.append("Quelque chose cloche avec votre calcul, vérifiez la syntax.") except ZeroDivisionError: avertissements.append("Vous ne pouvez pas diviser par 0.") diff --git a/src/cogs/clear/__init__.py b/src/cogs/clear/__init__.py index fe4feec..4ca2cb2 100644 --- a/src/cogs/clear/__init__.py +++ b/src/cogs/clear/__init__.py @@ -99,7 +99,9 @@ async def clear( raise UnexpectedError(f"{inter} had its channel set to None") if not 0 < amount < 251: - raise BadArgument(_("You must supply a number between 1 and 250. (0 < {amount} < 251)", amount=amount)) + raise BadArgument( + _("You must supply a number between 1 and 250. (0 < {amount} < 251)", _l=256, amount=amount) + ) available_filters: list[Filter | None] = [ pinned, @@ -189,8 +191,8 @@ async def start(self): if tasks[0] in done: text_response = ( - _("Cannot clear more than 3 minutes. {} message(s) deleted.", self.deleted_messages), - _("Clear cancelled. {} message(s) deleted.", self.deleted_messages), + _("Cannot clear more than 3 minutes. {} message(s) deleted.", self.deleted_messages, _l=256), + _("Clear cancelled. {} message(s) deleted.", self.deleted_messages, _l=256), ) await self.inter.edit_original_response( **response_constructor(ResponseType.warning, text_response[view.pressed]), diff --git a/src/cogs/game/minesweeper/__init__.py b/src/cogs/game/minesweeper/__init__.py index 8e51d1e..b233d1a 100644 --- a/src/cogs/game/minesweeper/__init__.py +++ b/src/cogs/game/minesweeper/__init__.py @@ -115,9 +115,7 @@ async def play(self, inter: Interaction, button: ui.Button[Self]) -> None: self.flag.disabled = False else: play = self.game.play(x, y) - print(play) if play.type == PlayType.BOMB_EXPLODED: - print("bomb exploded") self.clear_items() self.game_embed.description = build_board_display(self.game) diff --git a/src/cogs/help.py b/src/cogs/help.py index e9a6dd6..b37ad3d 100644 --- a/src/cogs/help.py +++ b/src/cogs/help.py @@ -65,7 +65,7 @@ async def feature_identifier_autocompleter(self, inter: Interaction, current: st ) def general_embed(self) -> Embed: - embed = response_constructor(ResponseType.info, _("Commands of MyBot"))["embed"] + embed = response_constructor(ResponseType.info, _("Commands of MyBot", _l=256))["embed"] feature_types_ui = OrderedDict( ( diff --git a/src/cogs/poll/__init__.py b/src/cogs/poll/__init__.py index a6361db..df63525 100644 --- a/src/cogs/poll/__init__.py +++ b/src/cogs/poll/__init__.py @@ -100,7 +100,7 @@ async def edit_poll(self, inter: Interaction, message: discord.Message) -> None: if not poll: raise NonSpecificError(_("This message is not a poll.")) if poll.author_id != inter.user.id: - raise NonSpecificError(_("You are not the author of this poll. You can't edit it.")) + raise NonSpecificError(_("You are not the author of this poll. You can't edit it.", _l=256)) await inter.response.send_message( **(await PollDisplay.build(poll, self.bot)), view=await EditPoll(self, poll, message).build(), diff --git a/src/cogs/poll/edit.py b/src/cogs/poll/edit.py index ac804be..1687e88 100644 --- a/src/cogs/poll/edit.py +++ b/src/cogs/poll/edit.py @@ -216,7 +216,7 @@ async def on_submit(self, inter: discord.Interaction) -> None: class EditEndingTime(EditSubmenu): select_name = _("Edit ending time", _locale=None) - select_description = _("Set a poll duration, it will be closed automatically.", _locale=None) + select_description = _("Set a poll duration, it will be closed automatically.", _locale=None, _l=100) def __init__(self, parent: EditPoll): super().__init__(parent) @@ -314,7 +314,7 @@ async def back(self, inter: discord.Interaction, button: ui.Button[Self]): class EditChoices(EditSubmenu): select_name = _("Edit choices", _locale=None) - select_description = _("Add and removes choices for multiple choices polls.", _locale=None) + select_description = _("Add and removes choices for multiple choices polls.", _locale=None, _l=100) def __init__(self, parent: EditPoll): super().__init__(parent) @@ -387,7 +387,7 @@ def __init__(self, parent: EditChoices) -> None: async def build(self) -> Self: self.back.label = _("Back") self.cancel.label = _("Cancel") - self.choices_to_remove.placeholder = _("Select the choices you want to remove.") + self.choices_to_remove.placeholder = _("Select the choices you want to remove.", _l=100) for choice, i in self.linked_choice.items(): self.choices_to_remove.add_option(label=choice.label[:99] + "…", value=str(i), emoji=LEGEND_EMOJIS[i]) self.choices_to_remove.max_values = len(self.old_value) - 2 @@ -421,7 +421,7 @@ async def back(self, inter: discord.Interaction, button: ui.Button[Self]): class EditMaxChoices(EditSubmenu): select_name = _("Edit max choices", _locale=None) - select_description = _("Set the maximum of simultaneous values users can choose.", _locale=None) + select_description = _("Set the maximum of simultaneous values users can choose.", _locale=None, _l=100) def __init__(self, parent: EditPoll) -> None: super().__init__(parent=parent) @@ -460,7 +460,7 @@ async def back(self, inter: discord.Interaction, button: ui.Button[Self]): class EditAllowedRoles(EditSubmenu): select_name = _("Edit allowed roles", _locale=None) - select_description = _("Only users with one of these role can vote.", _locale=None) + select_description = _("Only users with one of these role can vote.", _locale=None, _l=100) def __init__(self, parent: EditPoll) -> None: super().__init__(parent=parent) diff --git a/src/cogs/poll/vote_menus.py b/src/cogs/poll/vote_menus.py index 5f4ecd0..240b2fc 100644 --- a/src/cogs/poll/vote_menus.py +++ b/src/cogs/poll/vote_menus.py @@ -50,7 +50,7 @@ async def vote(self, inter: discord.Interaction, button: ui.Button[Self]): if poll is None: await inter.response.send_message( **response_constructor( - ResponseType.error, _("Sorry, this poll seems not to exist. Please contact an admin.") + ResponseType.error, _("Sorry, this poll seems not to exist. Please contact an admin.", _l=256) ), ephemeral=True, ) diff --git a/src/core/error_handler.py b/src/core/error_handler.py index 7128d70..a788cb0 100644 --- a/src/core/error_handler.py +++ b/src/core/error_handler.py @@ -71,7 +71,11 @@ async def handle(self, ctx: Interaction | MiscCommandContext[MyBot], error: Exce return await self.send_error(ctx, _("You provided a bad argument.")) case BotUserNotPresent(): return await self.send_error( - ctx, _("It looks like the bot has been added incorrectly. Please ask an admin to re-add the bot.") + ctx, + _( + "It looks like the bot has been added incorrectly. Please ask an admin to re-add the bot.", + _l=256, + ), ) case MiscNoPrivateMessage() | NoPrivateMessage(): return await self.send_error(ctx, _("This command cannot be used in DMs.")) diff --git a/src/core/i18n.py b/src/core/i18n.py index 0f4df73..b244d16 100644 --- a/src/core/i18n.py +++ b/src/core/i18n.py @@ -60,7 +60,7 @@ def i18n( *args: Any, _locale: Locale | None = MISSING, _silent: bool = False, - _size_limit: int = -1, + _l: int = -1, # size limit **kwargs: Any, ) -> str: if _locale is MISSING: @@ -86,9 +86,9 @@ def i18n( else: result = translations.get(_locale, translations[LOCALE_DEFAULT]).gettext(string).format(*args, **kwargs) - if _size_limit > 0 and len(result) > _size_limit: + if _l > 0 and len(result) > _l: logger.warning("The translated and formatted string is too long: %s\n%s", string, result) - result = result[: _size_limit - 1] + "…" + result = result[: _l - 1] + "…" return result