Skip to content

Commit

Permalink
fix(i18n): handle dot keys (#89)
Browse files Browse the repository at this point in the history
* Prevent certain embed values from being translated

* Handle dot keys

This fixes an edge case where the language file path is equal to the auto lookup path

* Add lookup location to avoid "general" usage

* Change lookup priority

* Change embed lookups

* Make locale object importable

* Remove unused import
  • Loading branch information
tibue99 authored Nov 28, 2024
1 parent e9e30de commit 8e46e67
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 43 deletions.
79 changes: 42 additions & 37 deletions ezcord/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import re
from copy import deepcopy
from pathlib import Path
from typing import TYPE_CHECKING, Callable, Literal, Union, overload
from typing import TYPE_CHECKING, Callable, Literal, Union

from .internal.dc import PYCORD, discord
from .logs import log
Expand All @@ -22,6 +22,7 @@
WEBHOOK_EDIT_MESSAGE = discord.Webhook.edit_message
WEBHOOK_EDIT = discord.WebhookMessage.edit

LOCALE = Union[str]

if PYCORD:
INTERACTION_EDIT_ORIGINAL = discord.Interaction.edit_original_response
Expand All @@ -41,19 +42,20 @@
if TYPE_CHECKING:
import discord # type: ignore

LOCALE_OBJECT = Union[
LOCALE = Union[ # type: ignore
discord.Interaction,
discord.ApplicationContext,
discord.InteractionResponse,
discord.Webhook,
discord.Guild,
discord.Member,
str,
]

__all__ = ("t", "TEmbed", "I18N")
__all__ = ("t", "TEmbed", "I18N", "LOCALE")


def t(obj: LOCALE_OBJECT | str, key: str, count: int | None = None, **variables):
def t(obj: LOCALE | str, key: str, count: int | None = None, **variables):
"""Get the localized string for the given key and insert all variables.
Parameters
Expand Down Expand Up @@ -197,7 +199,7 @@ async def wrapper(
content=None,
*,
count: int | None = None,
use_locale: LOCALE_OBJECT | str | None = None,
use_locale: LOCALE | None = None,
**kwargs,
):
"""Wrapper to localize the content and the embed of a message.
Expand Down Expand Up @@ -241,7 +243,7 @@ async def wrapper(
message_id: int | None = None,
*,
count: int | None = None,
use_locale: LOCALE_OBJECT | str | None = None,
use_locale: LOCALE | None = None,
**kwargs,
):
"""The message_id is only needed for followup.edit_message, because it's a positional
Expand Down Expand Up @@ -444,15 +446,7 @@ def __init__(
setattr(discord.WebhookMessage, "edit_message", _localize_edit(WEBHOOK_EDIT))

@staticmethod
@overload
def get_locale(obj: str) -> str: ...

@staticmethod
@overload
def get_locale(obj: LOCALE_OBJECT) -> str: ...

@staticmethod
def get_locale(obj):
def get_locale(obj: LOCALE) -> str:
"""Get the locale from the given object. By default, this is the guild's locale.
This method can be called even if the I18N class has not been initialized.
Expand Down Expand Up @@ -530,7 +524,7 @@ def get_locale(obj):
return locale # I18N class is not in use

@staticmethod
def get_clean_locale(obj: LOCALE_OBJECT | str) -> str:
def get_clean_locale(obj: LOCALE) -> str:
"""Get the clean locale from the given object. This is the locale without the region,
e.g. ``en`` instead of ``en-US``.
Expand Down Expand Up @@ -601,25 +595,31 @@ def _get_text(
"""Looks for the specified key in different locations of the language file."""

file_name, method_name, class_name = I18N.get_location()
lookups: list[list | tuple] = [
(file_name, method_name, key),
(file_name, called_class, key),
(file_name, class_name, key),
(file_name, "general", key),
("general", key),
]
for location in add_locations:
lookups.append((file_name, location, key))

lookups: list[list | tuple]
if "." in key:
lookups.append([file_name] + key.split("."))
lookups.append(key.split("."))
lookups = [key.split("."), [file_name] + key.split(".")]
else:
lookups = [
(file_name, method_name, key),
(file_name, called_class, key),
(file_name, class_name, key),
(file_name, "general", key),
("general", key),
(file_name, key),
]
for location in add_locations:
lookups.append((file_name, location, key))

localizations = I18N.localizations[locale]

for lookup in lookups:
current_section = localizations.copy()
for location in lookup:
current_section = current_section.get(location, {})
try:
current_section = current_section.get(location, {})
except AttributeError:
return key

txt = current_section
if isinstance(txt, str):
Expand Down Expand Up @@ -686,21 +686,23 @@ def replace_keys(m: re.Match):
def load_embed(embed: TEmbed, locale: str) -> discord.Embed:
"""Loads an embed from the language file."""

file_name, cmd_name, class_name = I18N.get_location()
file_name, method_name, class_name = I18N.get_location()

# search not only the location of the embed usage,
# but also the location of the embed creation
original_method, original_class = embed.method_name, embed.class_name

lookups: list[list | tuple] = [
(file_name, cmd_name, embed.key),
(file_name, original_method, embed.key),
(file_name, original_class, embed.key),
(file_name, class_name, embed.key),
]
lookups: list[list | tuple]
if "." in embed.key:
lookups.append([file_name] + embed.key.split("."))
lookups.append(embed.key.split("."))
lookups = [embed.key.split("."), [file_name] + embed.key.split(".")]
else:
lookups = [
(file_name, method_name, embed.key),
(file_name, original_method, embed.key),
(file_name, original_class, embed.key),
(file_name, class_name, embed.key),
(file_name, embed.key),
]

localizations = I18N.localizations[locale]

Expand Down Expand Up @@ -739,6 +741,9 @@ def load_lang_keys(

for key, value in content.items():
if isinstance(value, str):
if key in ["color", "colour", "type", "url", "timestamp", "image", "thumbnail"]:
continue

content[key] = I18N.load_text(
value, locale, count, add_locations=add_locations, **variables
)
Expand Down
6 changes: 3 additions & 3 deletions ezcord/internal/translation.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from .language.languages import load_lang

if TYPE_CHECKING:
from ..i18n import LOCALE_OBJECT
from ..i18n import LOCALE


def plural_de(amount: int, word: str, relative: bool = True) -> str:
Expand Down Expand Up @@ -121,7 +121,7 @@ def tp(
amount: int,
*args: str,
relative: bool = True,
use_locale: LOCALE_OBJECT | None = None,
use_locale: LOCALE | None = None,
) -> str:
"""Load a string in the selected language and pluralize it.
Expand Down Expand Up @@ -161,7 +161,7 @@ def get_locale(obj) -> str:
return EzConfig.lang


def tr(key: str, *args: str, use_locale: LOCALE_OBJECT | None = None) -> str:
def tr(key: str, *args: str, use_locale: LOCALE | None = None) -> str:
"""Load a string in the selected language.
Parameters
Expand Down
6 changes: 3 additions & 3 deletions ezcord/times.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from .internal.dc import discord

if TYPE_CHECKING:
from .i18n import LOCALE_OBJECT
from .i18n import LOCALE

__all__ = (
"set_utc",
Expand All @@ -36,7 +36,7 @@ def set_utc(dt: datetime) -> datetime:


def convert_time(
seconds: int | float, relative: bool = True, *, use_locale: LOCALE_OBJECT | None = None
seconds: int | float, relative: bool = True, *, use_locale: LOCALE | None = None
) -> str:
"""Convert seconds to a human-readable time.
Expand Down Expand Up @@ -80,7 +80,7 @@ def convert_dt(
dt: datetime | timedelta,
relative: bool = True,
*,
use_locale: LOCALE_OBJECT | None = None,
use_locale: LOCALE | None = None,
) -> str:
"""Convert :class:`datetime` or :class:`timedelta` to a human-readable time.
Expand Down

0 comments on commit 8e46e67

Please sign in to comment.