mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-04-19 15:36:02 +00:00
Refactor TranslationContext to be more useful
The previous enum was good at accomplishing dynamic key generation for a few cases, but it fell short in others: 1. It could not discern group names and command names 2. It could not give you more contextual data such as the full object currently being translated. On top of that, the context being a required parameter for Translator.translate meant that it wouldn't be possible to re-use the translator for other use cases outside of the rigid ones defined in the library. To alleviate these concerns, new enum attributes were added along with a richer type for obtaining even more context.
This commit is contained in:
parent
d826f4f3a8
commit
c32567ea81
@ -52,7 +52,7 @@ from ..enums import AppCommandOptionType, AppCommandType, ChannelType, Locale
|
||||
from .models import Choice
|
||||
from .transformers import annotation_to_parameter, CommandParameter, NoneType
|
||||
from .errors import AppCommandError, CheckFailure, CommandInvokeError, CommandSignatureMismatch, CommandAlreadyRegistered
|
||||
from .translator import TranslationContext, Translator, locale_str
|
||||
from .translator import TranslationContext, TranslationContextLocation, Translator, locale_str
|
||||
from ..message import Message
|
||||
from ..user import User
|
||||
from ..member import Member
|
||||
@ -737,22 +737,27 @@ class Command(Generic[GroupT, P, T]):
|
||||
base = self.to_dict()
|
||||
name_localizations: Dict[str, str] = {}
|
||||
description_localizations: Dict[str, str] = {}
|
||||
|
||||
# Prevent creating these objects in a heavy loop
|
||||
name_context = TranslationContext(location=TranslationContextLocation.command_name, data=self)
|
||||
description_context = TranslationContext(location=TranslationContextLocation.command_description, data=self)
|
||||
|
||||
for locale in Locale:
|
||||
if self._locale_name:
|
||||
translation = await translator._checked_translate(self._locale_name, locale, TranslationContext.command_name)
|
||||
translation = await translator._checked_translate(self._locale_name, locale, name_context)
|
||||
if translation is not None:
|
||||
name_localizations[locale.value] = translation
|
||||
|
||||
if self._locale_description:
|
||||
translation = await translator._checked_translate(
|
||||
self._locale_description, locale, TranslationContext.command_description
|
||||
)
|
||||
translation = await translator._checked_translate(self._locale_description, locale, description_context)
|
||||
if translation is not None:
|
||||
description_localizations[locale.value] = translation
|
||||
|
||||
base['name_localizations'] = name_localizations
|
||||
base['description_localizations'] = description_localizations
|
||||
base['options'] = [await param.get_translated_payload(translator) for param in self._params.values()]
|
||||
base['options'] = [
|
||||
await param.get_translated_payload(translator, Parameter(param)) for param in self._params.values()
|
||||
]
|
||||
return base
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
@ -1225,10 +1230,11 @@ class ContextMenu:
|
||||
|
||||
async def get_translated_payload(self, translator: Translator) -> Dict[str, Any]:
|
||||
base = self.to_dict()
|
||||
context = TranslationContext(location=TranslationContextLocation.command_name, data=self)
|
||||
if self._locale_name:
|
||||
name_localizations: Dict[str, str] = {}
|
||||
for locale in Locale:
|
||||
translation = await translator._checked_translate(self._locale_name, locale, TranslationContext.command_name)
|
||||
translation = await translator._checked_translate(self._locale_name, locale, context)
|
||||
if translation is not None:
|
||||
name_localizations[locale.value] = translation
|
||||
|
||||
@ -1638,16 +1644,18 @@ class Group:
|
||||
base = self.to_dict()
|
||||
name_localizations: Dict[str, str] = {}
|
||||
description_localizations: Dict[str, str] = {}
|
||||
|
||||
# Prevent creating these objects in a heavy loop
|
||||
name_context = TranslationContext(location=TranslationContextLocation.group_name, data=self)
|
||||
description_context = TranslationContext(location=TranslationContextLocation.group_description, data=self)
|
||||
for locale in Locale:
|
||||
if self._locale_name:
|
||||
translation = await translator._checked_translate(self._locale_name, locale, TranslationContext.command_name)
|
||||
translation = await translator._checked_translate(self._locale_name, locale, name_context)
|
||||
if translation is not None:
|
||||
name_localizations[locale.value] = translation
|
||||
|
||||
if self._locale_description:
|
||||
translation = await translator._checked_translate(
|
||||
self._locale_description, locale, TranslationContext.command_description
|
||||
)
|
||||
translation = await translator._checked_translate(self._locale_description, locale, description_context)
|
||||
if translation is not None:
|
||||
description_localizations[locale.value] = translation
|
||||
|
||||
|
@ -26,7 +26,7 @@ from __future__ import annotations
|
||||
from datetime import datetime
|
||||
|
||||
from .errors import MissingApplicationID
|
||||
from .translator import Translator, TranslationContext, locale_str
|
||||
from .translator import TranslationContextLocation, Translator, TranslationContext, locale_str
|
||||
from ..permissions import Permissions
|
||||
from ..enums import AppCommandOptionType, AppCommandType, AppCommandPermissionType, ChannelType, Locale, try_enum
|
||||
from ..mixins import Hashable
|
||||
@ -463,9 +463,10 @@ class Choice(Generic[ChoiceT]):
|
||||
async def get_translated_payload(self, translator: Translator) -> Dict[str, Any]:
|
||||
base = self.to_dict()
|
||||
name_localizations: Dict[str, str] = {}
|
||||
context = TranslationContext(location=TranslationContextLocation.choice_name, data=self)
|
||||
if self._locale_name:
|
||||
for locale in Locale:
|
||||
translation = await translator._checked_translate(self._locale_name, locale, TranslationContext.choice_name)
|
||||
translation = await translator._checked_translate(self._locale_name, locale, context)
|
||||
if translation is not None:
|
||||
name_localizations[locale.value] = translation
|
||||
|
||||
|
@ -46,7 +46,7 @@ from typing import (
|
||||
|
||||
from .errors import AppCommandError, TransformerError
|
||||
from .models import AppCommandChannel, AppCommandThread, Choice
|
||||
from .translator import locale_str, Translator, TranslationContext
|
||||
from .translator import TranslationContextLocation, locale_str, Translator, TranslationContext
|
||||
from ..channel import StageChannel, VoiceChannel, TextChannel, CategoryChannel
|
||||
from ..abc import GuildChannel
|
||||
from ..threads import Thread
|
||||
@ -70,6 +70,7 @@ NoneType = type(None)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..interactions import Interaction
|
||||
from .commands import Parameter
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -89,29 +90,27 @@ class CommandParameter:
|
||||
_rename: Union[str, locale_str] = MISSING
|
||||
_annotation: Any = MISSING
|
||||
|
||||
async def get_translated_payload(self, translator: Translator) -> Dict[str, Any]:
|
||||
async def get_translated_payload(self, translator: Translator, data: Parameter) -> Dict[str, Any]:
|
||||
base = self.to_dict()
|
||||
|
||||
needs_name_translations = isinstance(self._rename, locale_str)
|
||||
needs_description_translations = isinstance(self.description, locale_str)
|
||||
rename = self._rename
|
||||
description = self.description
|
||||
needs_name_translations = isinstance(rename, locale_str)
|
||||
needs_description_translations = isinstance(description, locale_str)
|
||||
name_localizations: Dict[str, str] = {}
|
||||
description_localizations: Dict[str, str] = {}
|
||||
|
||||
# Prevent creating these objects in a heavy loop
|
||||
name_context = TranslationContext(location=TranslationContextLocation.parameter_name, data=data)
|
||||
description_context = TranslationContext(location=TranslationContextLocation.parameter_description, data=data)
|
||||
for locale in Locale:
|
||||
if needs_name_translations:
|
||||
translation = await translator._checked_translate(
|
||||
self._rename, # type: ignore # This will always be locale_str
|
||||
locale,
|
||||
TranslationContext.parameter_name,
|
||||
)
|
||||
translation = await translator._checked_translate(rename, locale, name_context)
|
||||
if translation is not None:
|
||||
name_localizations[locale.value] = translation
|
||||
|
||||
if needs_description_translations:
|
||||
translation = await translator._checked_translate(
|
||||
self.description, # type: ignore # This will always be locale_str
|
||||
locale,
|
||||
TranslationContext.parameter_description,
|
||||
)
|
||||
translation = await translator._checked_translate(description, locale, description_context)
|
||||
if translation is not None:
|
||||
description_localizations[locale.value] = translation
|
||||
|
||||
|
@ -23,24 +23,100 @@ DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import Any, Optional
|
||||
from typing import TYPE_CHECKING, Any, Literal, Optional, Union
|
||||
from .errors import TranslationError
|
||||
from ..enums import Enum, Locale
|
||||
|
||||
|
||||
__all__ = (
|
||||
'TranslationContextLocation',
|
||||
'TranslationContext',
|
||||
'Translator',
|
||||
'locale_str',
|
||||
)
|
||||
|
||||
|
||||
class TranslationContext(Enum):
|
||||
class TranslationContextLocation(Enum):
|
||||
command_name = 0
|
||||
command_description = 1
|
||||
parameter_name = 2
|
||||
parameter_description = 3
|
||||
choice_name = 4
|
||||
group_name = 2
|
||||
group_description = 3
|
||||
parameter_name = 4
|
||||
parameter_description = 5
|
||||
choice_name = 6
|
||||
other = 7
|
||||
|
||||
|
||||
class TranslationContext: # type: ignore # See below
|
||||
"""A class that provides context for the :class:`locale_str` being translated.
|
||||
|
||||
This is useful to determine where exactly the string is located and aid in looking
|
||||
up the actual translation.
|
||||
|
||||
Attributes
|
||||
-----------
|
||||
location: :class:`TranslationContextLocation`
|
||||
The location where this string is located.
|
||||
data: Any
|
||||
The extraneous data that is being translated.
|
||||
"""
|
||||
|
||||
__slots__ = ('location', 'data')
|
||||
|
||||
def __init__(self, location: TranslationContextLocation, data: Any) -> None:
|
||||
self.location: TranslationContextLocation = location
|
||||
self.data: Any = data
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# For type checking purposes, it makes sense to allow the user to leverage type narrowing
|
||||
# So code like this works as expected:
|
||||
# if context.type is TranslationContextLocation.command_name:
|
||||
# reveal_type(context.data) # Revealed type is Command | ContextMenu
|
||||
#
|
||||
# Unfortunately doing a trick like this requires lying to the type checker so
|
||||
# this is what the code below enables.
|
||||
#
|
||||
# Should this trick stop working then it might be fair to remove this code.
|
||||
# It's purely here for convenience.
|
||||
|
||||
from .commands import Command, ContextMenu, Group, Parameter
|
||||
from .models import Choice
|
||||
|
||||
class _CommandNameTranslationContext:
|
||||
location: Literal[TranslationContextLocation.command_name]
|
||||
data: Union[Command[Any, ..., Any], ContextMenu]
|
||||
|
||||
class _CommandDescriptionTranslationContext:
|
||||
location: Literal[TranslationContextLocation.command_description]
|
||||
data: Command[Any, ..., Any]
|
||||
|
||||
class _GroupTranslationContext:
|
||||
location: Literal[TranslationContextLocation.group_name, TranslationContextLocation.group_description]
|
||||
data: Group
|
||||
|
||||
class _ParameterTranslationContext:
|
||||
location: Literal[TranslationContextLocation.parameter_description, TranslationContextLocation.parameter_name]
|
||||
data: Parameter
|
||||
|
||||
class _ChoiceTranslationContext:
|
||||
location: Literal[TranslationContextLocation.choice_name]
|
||||
data: Choice[Union[int, str, float]]
|
||||
|
||||
class _OtherTranslationContext:
|
||||
location: Literal[TranslationContextLocation.other]
|
||||
data: Any
|
||||
|
||||
class TranslationContext(
|
||||
_CommandNameTranslationContext,
|
||||
_CommandDescriptionTranslationContext,
|
||||
_GroupTranslationContext,
|
||||
_ParameterTranslationContext,
|
||||
_ChoiceTranslationContext,
|
||||
_OtherTranslationContext,
|
||||
):
|
||||
def __init__(self, location: TranslationContextLocation, data: Any) -> None:
|
||||
...
|
||||
|
||||
|
||||
class Translator:
|
||||
|
@ -626,10 +626,18 @@ locale_str
|
||||
TranslationContext
|
||||
+++++++++++++++++++
|
||||
|
||||
.. class:: TranslationContext
|
||||
.. attributetable:: discord.app_commands.TranslationContext
|
||||
|
||||
.. autoclass:: discord.app_commands.TranslationContext
|
||||
:members:
|
||||
|
||||
TranslationContextLocation
|
||||
+++++++++++++++++++++++++++
|
||||
|
||||
.. class:: TranslationContextLocation
|
||||
:module: discord.app_commands
|
||||
|
||||
An enum representing the context that the translation occurs in when requested for translation.
|
||||
An enum representing the location context that the translation occurs in when requested for translation.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
@ -639,6 +647,13 @@ TranslationContext
|
||||
.. attribute:: command_description
|
||||
|
||||
The translation involved a command description.
|
||||
|
||||
.. attribute:: group_name
|
||||
|
||||
The translation involved a group name.
|
||||
.. attribute:: group_description
|
||||
|
||||
The translation involved a group description.
|
||||
.. attribute:: parameter_name
|
||||
|
||||
The translation involved a parameter name.
|
||||
@ -648,7 +663,10 @@ TranslationContext
|
||||
.. attribute:: choice_name
|
||||
|
||||
The translation involved a choice name.
|
||||
.. attribute:: other
|
||||
|
||||
The translation involved something else entirely. This is useful for running
|
||||
:meth:`Translator.translate` for custom usage.
|
||||
|
||||
Exceptions
|
||||
~~~~~~~~~~~
|
||||
|
Loading…
x
Reference in New Issue
Block a user