Skip to content

Commit c47028b

Browse files
committed
Add initial Translator implementation
1 parent b9ac3ec commit c47028b

File tree

4 files changed

+89
-0
lines changed

4 files changed

+89
-0
lines changed

twitchio/ext/commands/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@
2929
from .cooldowns import *
3030
from .core import *
3131
from .exceptions import *
32+
from .translators import *

twitchio/ext/commands/context.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
from .bot import Bot
4848
from .components import Component
4949
from .core import Command
50+
from .translators import Translator
5051

5152
PrefixT: TypeAlias = str | Iterable[str] | Callable[[Bot, ChatMessage], Coroutine[Any, Any, str | Iterable[str]]]
5253

@@ -543,6 +544,24 @@ async def send(self, content: str, *, me: bool = False) -> SentMessage:
543544
new = (f"/me {content}" if me else content).strip()
544545
return await self.channel.send_message(sender=self.bot.bot_id, message=new)
545546

547+
async def send_translated(self, content: str, *, me: bool = False, langcode: str | None = None) -> SentMessage:
548+
translator: Translator | None = getattr(self.command, "translator", None)
549+
new = (f"/me {content}" if me else content).strip()
550+
551+
if not self.command or not translator:
552+
return await self.channel.send_message(sender=self.bot.bot_id, message=new)
553+
554+
invoked = self.invoked_with
555+
code = langcode or translator.get_langcode(self, invoked.lower()) if invoked else None
556+
557+
if not code:
558+
return await self.channel.send_message(sender=self.bot.bot_id, message=new)
559+
560+
translated = await translator.translate(self, content, code)
561+
new_translated = (f"/me {translated}" if me else translated).strip()
562+
563+
return await self.channel.send_message(sender=self.bot.bot_id, message=new_translated)
564+
546565
async def reply(self, content: str, *, me: bool = False) -> SentMessage:
547566
"""|coro|
548567

twitchio/ext/commands/core.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,15 @@
6161
"is_staff",
6262
"is_vip",
6363
"reward_command",
64+
"translator",
6465
)
6566

6667

6768
if TYPE_CHECKING:
6869
from twitchio.user import Chatter
6970

7071
from .context import Context
72+
from .translators import Translator
7173
from .types_ import BotT
7274

7375
P = ParamSpec("P")
@@ -223,6 +225,12 @@ def __init__(
223225
self._before_hook: Callable[[Component_T, Context[Any]], Coro] | Callable[[Context[Any]], Coro] | None = None
224226
self._after_hook: Callable[[Component_T, Context[Any]], Coro] | Callable[[Context[Any]], Coro] | None = None
225227

228+
translator: Translator | type[Translator] | None = getattr(callback, "__command_translator__", None)
229+
if translator and inspect.isclass(translator):
230+
translator = translator()
231+
232+
self._translator: Translator | None = translator
233+
226234
self._help: str = callback.__doc__ or ""
227235
self.__doc__ = self._help
228236

@@ -262,6 +270,10 @@ def _get_signature(self) -> None:
262270

263271
self._signature = help_sig
264272

273+
@property
274+
def translator(self) -> Translator | None:
275+
return self._translator
276+
265277
@property
266278
def parameters(self) -> MappingProxyType[str, inspect.Parameter]:
267279
"""Property returning a copy mapping of name to :class:`inspect.Parameter` pair, which are the parameters
@@ -1467,6 +1479,20 @@ def wrapper(
14671479
return wrapper
14681480

14691481

1482+
def translator(cls: Translator | type[Translator]) -> Any:
1483+
def wrapper(func: Any) -> Any:
1484+
inst = cls() if inspect.isclass(cls) else cls
1485+
1486+
if isinstance(func, Command):
1487+
func._translator = inst
1488+
else:
1489+
func.__command_translator = inst
1490+
1491+
return func # type: ignore
1492+
1493+
return wrapper
1494+
1495+
14701496
def guard(predicate: Callable[..., bool] | Callable[..., CoroC]) -> Any:
14711497
"""A function which takes in a predicate as a either a standard function *or* coroutine function which should
14721498
return either ``True`` or ``False``, and adds it to your :class:`~.commands.Command` as a guard.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""
2+
MIT License
3+
4+
Copyright (c) 2017 - Present PythonistaGuild
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.
23+
"""
24+
25+
from __future__ import annotations
26+
27+
import abc
28+
from typing import TYPE_CHECKING, Any
29+
30+
31+
if TYPE_CHECKING:
32+
from .context import Context
33+
34+
35+
__all__ = ("Translator",)
36+
37+
38+
class Translator(abc.ABC):
39+
@abc.abstractmethod
40+
def get_langcode(self, ctx: Context[Any], name: str) -> str | None: ...
41+
42+
@abc.abstractmethod
43+
async def translate(self, ctx: Context[Any], text: str, langcode: str) -> str: ...

0 commit comments

Comments
 (0)