Skip to content

Commit 7ee564c

Browse files
committed
feat: Implement localisation support for Options and Choices, include type conversion safeguard for Locale usage.
1 parent 460328a commit 7ee564c

File tree

2 files changed

+84
-13
lines changed

2 files changed

+84
-13
lines changed

interactions/client.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -599,9 +599,9 @@ async def message_command(ctx):
599599
:param default_permission?: The default permission of accessibility for the application command. Defaults to ``True``.
600600
:type default_permission: Optional[bool]
601601
:param name_localizations?: The dictionary of localization for the ``name`` field. This enforces the same restrictions as the ``name`` field.
602-
:param name_localizations: Optional[Dict[str, str]]
602+
:param name_localizations: Optional[Dict[Union[str, Locale], str]]
603603
:param description_localizations?: The dictionary of localization for the ``description`` field. This enforces the same restrictions as the ``description`` field.
604-
:param description_localizations: Optional[Dict[str, str]]
604+
:param description_localizations: Optional[Dict[Union[str, Locale], str]]
605605
:return: A callable response.
606606
:rtype: Callable[..., Any]
607607
"""
@@ -645,7 +645,7 @@ def message_command(
645645
name: str,
646646
scope: Optional[Union[int, Guild, List[int], List[Guild]]] = MISSING,
647647
default_permission: Optional[bool] = MISSING,
648-
name_localizations: Optional[Dict[Union[str, Locale], Any]] = MISSING, # This is theory.
648+
name_localizations: Optional[Dict[Union[str, Locale], Any]] = MISSING,
649649
) -> Callable[..., Any]:
650650
"""
651651
A decorator for registering a message context menu to the Discord API,
@@ -669,6 +669,8 @@ async def context_menu_name(ctx):
669669
:type scope: Optional[Union[int, Guild, List[int], List[Guild]]]
670670
:param default_permission?: The default permission of accessibility for the application command. Defaults to ``True``.
671671
:type default_permission: Optional[bool]
672+
:param name_localizations?: The dictionary of localization for the ``name`` field. This enforces the same restrictions as the ``name`` field.
673+
:param name_localizations: Optional[Dict[Union[str, Locale], str]]
672674
:return: A callable response.
673675
:rtype: Callable[..., Any]
674676
"""
@@ -703,7 +705,7 @@ def user_command(
703705
name: str,
704706
scope: Optional[Union[int, Guild, List[int], List[Guild]]] = MISSING,
705707
default_permission: Optional[bool] = MISSING,
706-
name_localizations: Optional[Dict[Union[str, Locale], Any]] = MISSING, # This is theory.
708+
name_localizations: Optional[Dict[Union[str, Locale], Any]] = MISSING,
707709
) -> Callable[..., Any]:
708710
"""
709711
A decorator for registering a user context menu to the Discord API,
@@ -727,6 +729,8 @@ async def context_menu_name(ctx):
727729
:type scope: Optional[Union[int, Guild, List[int], List[Guild]]]
728730
:param default_permission?: The default permission of accessibility for the application command. Defaults to ``True``.
729731
:type default_permission: Optional[bool]
732+
:param name_localizations?: The dictionary of localization for the ``name`` field. This enforces the same restrictions as the ``name`` field.
733+
:param name_localizations: Optional[Dict[Union[str, Locale], str]]
730734
:return: A callable response.
731735
:rtype: Callable[..., Any]
732736
"""

interactions/models/command.py

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from ..api.models.channel import ChannelType
44
from ..api.models.misc import DictSerializerMixin, Snowflake
5-
from ..enums import ApplicationCommandType, OptionType, PermissionType
5+
from ..enums import ApplicationCommandType, Locale, OptionType, PermissionType
66

77

88
class Choice(DictSerializerMixin):
@@ -21,15 +21,29 @@ class Choice(DictSerializerMixin):
2121
2222
:ivar str name: The name of the choice.
2323
:ivar Union[str, int, float] value: The returned value of the choice.
24+
:ivar Optional[Dict[Union[str, Locale], str]] name_localizations?: The dictionary of localization for the ``name`` field. This enforces the same restrictions as the ``name`` field.
2425
"""
2526

26-
__slots__ = ("_json", "name", "value")
27+
__slots__ = ("_json", "name", "value", "name_localizations")
2728
_json: dict
2829
name: str
2930
value: Union[str, int, float]
31+
name_localizations: Optional[Dict[Union[str, Locale], str]]
3032

3133
def __init__(self, **kwargs) -> None:
3234
super().__init__(**kwargs)
35+
if self._json.get("name_localizations"):
36+
if any(
37+
type(x) != str for x in self._json.get("name_localizations")
38+
): # check if Locale object is used to create localisation at any certain point.
39+
self._json["name_localizations"] = {
40+
k.value if isinstance(k, Locale) else k: v
41+
for k, v in self._json["name_localizations"].items()
42+
}
43+
self.name_localizations = {
44+
k if isinstance(k, Locale) else Locale(k): v
45+
for k, v in self._json["name_localizations"].items()
46+
}
3347

3448

3549
class Option(DictSerializerMixin):
@@ -67,6 +81,8 @@ class Option(DictSerializerMixin):
6781
:ivar Optional[int] min_value?: The minimum value supported by the option.
6882
:ivar Optional[int] max_value?: The maximum value supported by the option.
6983
:ivar Optional[bool] autocomplete?: A status denoting whether this option is an autocomplete option.
84+
:ivar Optional[Dict[Union[str, Locale], str]] name_localizations?: The dictionary of localization for the ``name`` field. This enforces the same restrictions as the ``name`` field.
85+
:ivar Optional[Dict[Union[str, Locale], str]] description_localizations?: The dictionary of localization for the ``description`` field. This enforces the same restrictions as the ``description`` field.
7086
"""
7187

7288
__slots__ = (
@@ -99,9 +115,8 @@ class Option(DictSerializerMixin):
99115
min_value: Optional[int]
100116
max_value: Optional[int]
101117
autocomplete: Optional[bool]
102-
103-
name_localizations: Optional[Dict[str, str]] # TODO: post-v4: document these when Discord does.
104-
description_localizations: Optional[Dict[str, str]]
118+
name_localizations: Optional[Dict[Union[str, Locale], str]]
119+
description_localizations: Optional[Dict[Union[str, Locale], str]]
105120

106121
def __init__(self, **kwargs) -> None:
107122
super().__init__(**kwargs)
@@ -126,6 +141,32 @@ def __init__(self, **kwargs) -> None:
126141
elif all(isinstance(choice, Choice) for choice in self.choices):
127142
self._json["choices"] = [choice._json for choice in self.choices]
128143

144+
if self._json.get("name_localizations"):
145+
if any(
146+
type(x) != str for x in self._json.get("name_localizations")
147+
): # check if Locale object is used to create localisation at any certain point.
148+
self._json["name_localizations"] = {
149+
k.value if isinstance(k, Locale) else k: v
150+
for k, v in self._json["name_localizations"].items()
151+
}
152+
self.name_localizations = {
153+
k if isinstance(k, Locale) else Locale(k): v
154+
for k, v in self._json["name_localizations"].items()
155+
}
156+
157+
if self._json.get("description_localizations"):
158+
if any(
159+
type(x) != str for x in self._json.get("name_localizations")
160+
): # check if Locale object is used to create localisation at any certain point.
161+
self._json["description_localizations"] = {
162+
k.value if isinstance(k, Locale) else k: v
163+
for k, v in self._json["description_localizations"].items()
164+
}
165+
self.description_localizations = {
166+
k if isinstance(k, Locale) else Locale(k): v
167+
for k, v in self._json["description_localizations"].items()
168+
}
169+
129170

130171
class Permission(DictSerializerMixin):
131172
"""
@@ -178,8 +219,8 @@ class ApplicationCommand(DictSerializerMixin):
178219
:ivar int version: The Application Command version autoincrement identifier.
179220
:ivar str default_member_permissions: The default member permission state of the application command.
180221
:ivar boolean dm_permission: The application permissions if executed in a Direct Message.
181-
:ivar Optional[Dict[str, str]] name_localizations: The localisation dictionary for the application command name, if any.
182-
:ivar Optional[Dict[str, str]] description_localizations: The localisation dictionary for the application command description, if any.
222+
:ivar Optional[Dict[Union[str, Locale], str]] name_localizations: The localisation dictionary for the application command name, if any.
223+
:ivar Optional[Dict[Union[str, Locale], str]] description_localizations: The localisation dictionary for the application command description, if any.
183224
"""
184225

185226
__slots__ = (
@@ -214,8 +255,8 @@ class ApplicationCommand(DictSerializerMixin):
214255
# TODO: post-v4: Investigate these once documented by Discord.
215256
default_member_permissions: str
216257
dm_permission: bool
217-
name_localizations: Optional[Dict[str, str]]
218-
description_localizations: Optional[Dict[str, str]]
258+
name_localizations: Optional[Dict[Union[str, Locale], str]]
259+
description_localizations: Optional[Dict[Union[str, Locale], str]]
219260

220261
def __init__(self, **kwargs) -> None:
221262
super().__init__(**kwargs)
@@ -232,3 +273,29 @@ def __init__(self, **kwargs) -> None:
232273
if self._json.get("permissions")
233274
else None
234275
)
276+
277+
if self._json.get("name_localizations"):
278+
if any(
279+
type(x) != str for x in self._json.get("name_localizations")
280+
): # check if Locale object is used to create localisation at any certain point.
281+
self._json["name_localizations"] = {
282+
k.value if isinstance(k, Locale) else k: v
283+
for k, v in self._json["name_localizations"].items()
284+
}
285+
self.name_localizations = {
286+
k if isinstance(k, Locale) else Locale(k): v
287+
for k, v in self._json["name_localizations"].items()
288+
}
289+
290+
if self._json.get("description_localizations"):
291+
if any(
292+
type(x) != str for x in self._json.get("description_localizations")
293+
): # check if Locale object is used to create localisation at any certain point.
294+
self._json["description_localizations"] = {
295+
k.value if isinstance(k, Locale) else k: v
296+
for k, v in self._json["description_localizations"].items()
297+
}
298+
self.description_localizations = {
299+
k if isinstance(k, Locale) else Locale(k): v
300+
for k, v in self._json["description_localizations"].items()
301+
}

0 commit comments

Comments
 (0)