Skip to content

Commit

Permalink
Fix multiple issues with i18n (#128)
Browse files Browse the repository at this point in the history
  • Loading branch information
AiroPi authored Dec 17, 2023
2 parents 45e2744 + 4254a6d commit 3945592
Show file tree
Hide file tree
Showing 12 changed files with 96 additions and 46 deletions.
3 changes: 1 addition & 2 deletions src/cogs/calculator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down
8 changes: 5 additions & 3 deletions src/cogs/clear/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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]),
Expand Down
2 changes: 0 additions & 2 deletions src/cogs/game/minesweeper/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/cogs/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
(
Expand Down
2 changes: 1 addition & 1 deletion src/cogs/poll/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
10 changes: 5 additions & 5 deletions src/cogs/poll/edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/cogs/poll/vote_menus.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down
14 changes: 9 additions & 5 deletions src/cogs/translate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 _

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/core/__init__.py
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
49 changes: 39 additions & 10 deletions src/core/constants.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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)
6 changes: 5 additions & 1 deletion src/core/error_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."))
Expand Down
42 changes: 28 additions & 14 deletions src/core/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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,
_l: int = -1, # size limit
**kwargs: Any,
) -> str:
if _locale is MISSING:
frame: FrameType | None = inspect.currentframe()

Expand All @@ -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 _l > 0 and len(result) > _l:
logger.warning("The translated and formatted string is too long: %s\n%s", string, result)
result = result[: _l - 1] + "…"
return result


_ = i18n
Expand Down

0 comments on commit 3945592

Please sign in to comment.