Skip to content

Commit a6ccf41

Browse files
committed
Add translator docs
1 parent c47028b commit a6ccf41

File tree

3 files changed

+172
-2
lines changed

3 files changed

+172
-2
lines changed

twitchio/ext/commands/context.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,53 @@ async def send(self, content: str, *, me: bool = False) -> SentMessage:
545545
return await self.channel.send_message(sender=self.bot.bot_id, message=new)
546546

547547
async def send_translated(self, content: str, *, me: bool = False, langcode: str | None = None) -> SentMessage:
548+
"""|coro|
549+
550+
Send a translated chat message to the channel associated with this context.
551+
552+
You must have added a :class:`.commands.Translator` to your :class:`.commands.Command` in order to effectively use
553+
this method. If no :class:`.commands.Translator` is found, this method acts identical to :meth:`.send`.
554+
555+
If this method can not find a valid language code, E.g. both :meth:`.commands.Translator.get_langcode` and the parameter
556+
``langcode`` return ``None``, this method acts identical to :meth:`.send`.
557+
558+
See the following documentation for more details on translators:
559+
560+
- :class:`.commands.Translator`
561+
- :class:`.commands.translator`
562+
563+
.. important::
564+
565+
You must have the ``user:write:chat`` scope. If an app access token is used,
566+
then additionally requires the ``user:bot`` scope on the bot,
567+
and either ``channel:bot`` scope from the broadcaster or moderator status.
568+
569+
Parameters
570+
----------
571+
content: str
572+
The content of the message you would like to translate and then send.
573+
This **and** the translated version of this content cannot exceed ``500`` characters.
574+
Additionally the content parameter will be stripped of all leading and trailing whitespace.
575+
me: bool
576+
An optional bool indicating whether you would like to send this message with the ``/me`` chat command.
577+
langcode: str | None
578+
An optional ``langcode`` to override the ``langcode`` returned from :meth:`.commands.Translator.get_langcode`.
579+
This should only be provided if you do custom language code lookups outside of your
580+
:class:`.commands.Translator`. Defaults to ``None``.
581+
582+
583+
Returns
584+
-------
585+
SentMessage
586+
The payload received by Twitch after sending this message.
587+
588+
Raises
589+
------
590+
HTTPException
591+
Twitch failed to process the message, could be ``400``, ``401``, ``403``, ``422`` or any ``5xx`` status code.
592+
MessageRejectedError
593+
Twitch rejected the message from various checks.
594+
"""
548595
translator: Translator | None = getattr(self.command, "translator", None)
549596
new = (f"/me {content}" if me else content).strip()
550597

twitchio/ext/commands/core.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,9 @@ def _get_signature(self) -> None:
272272

273273
@property
274274
def translator(self) -> Translator | None:
275+
"""Property returning the :class:`.commands.Translator` associated with this command or ``None`` if one was not
276+
used.
277+
"""
275278
return self._translator
276279

277280
@property
@@ -1480,6 +1483,19 @@ def wrapper(
14801483

14811484

14821485
def translator(cls: Translator | type[Translator]) -> Any:
1486+
"""|deco|
1487+
1488+
Decorator which adds a :class:`.commands.Translator` to a :class:`.commands.Command`.
1489+
1490+
You can provide the class or instance of your implemented :class:`.commands.Translator` to this decorator.
1491+
1492+
See the :class:`.commands.Translator` documentation for more information on translators.
1493+
1494+
.. note::
1495+
1496+
You can only have one :class:`.commands.Translator` per command.
1497+
"""
1498+
14831499
def wrapper(func: Any) -> Any:
14841500
inst = cls() if inspect.isclass(cls) else cls
14851501

twitchio/ext/commands/translators.py

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,115 @@
3636

3737

3838
class Translator(abc.ABC):
39+
"""Abstract Base Class for command translators.
40+
41+
This class allows you to implement logic to translate messages sent via the :meth:`.commands.Context.send_translated`
42+
method in commands or anywhere :class:`.commands.Context` is available.
43+
44+
You should pass your implemented class to the :meth:`.commands.translator` decorator on top of a :class:`~.commands.Command`.
45+
46+
.. important::
47+
48+
You must implement every method of this ABC.
49+
"""
50+
3951
@abc.abstractmethod
40-
def get_langcode(self, ctx: Context[Any], name: str) -> str | None: ...
52+
def get_langcode(self, ctx: Context[Any], name: str) -> str | None:
53+
"""Method which is called when :meth:`.commands.Context.send_translated` is used on a :class:`.commands.Command`
54+
which has an associated Translator, to determine the ``langcode`` which should be passed to :meth:`.translate` or ``None``
55+
if the content should not be translated.
56+
57+
By default the ``name`` or ``alias`` used to invoke the command is passed alongside :class:`.commands.Context` to aid in
58+
determining the ``langcode`` you should use.
59+
60+
You can use any system for the language codes, however they must be a :class:`str`. We also recommend using a
61+
recognized system such as the ``ISO 639`` language code format.
62+
63+
Parameters
64+
----------
65+
ctx: commands.Context
66+
The context surrounding the command invocation.
67+
name: str
68+
The ``name`` or ``alias`` used to invoke the command. This does not include the prefix, however if you need to
69+
retrieve the prefix see: :attr:`.commands.Context.prefix`.
70+
71+
Returns
72+
-------
73+
str
74+
The language code as a :class:`str` to pass to :meth:`.translate`.
75+
76+
Example
77+
-------
78+
79+
.. code:: python3
80+
# For example purposes only the "get_langcode" method is shown in this example...
81+
# The "translate" method must also be implemented...
82+
83+
class HelloTranslator(commands.Translator):
84+
def __init__(self) -> None:
85+
self.code_mapping = {"hello": "en", "bonjour": "fr"}
86+
87+
def get_langcode(self, ctx: commands.Context, name: str) -> str | None:
88+
# Return a default of "en". Could also be ``None`` to prevent `translate` being called and to
89+
# send the default message...
90+
return self.code_mapping.get(name, "en")
91+
92+
93+
@commands.command(name="hello", aliases=["bonjour"])
94+
@commands.translator(HelloTranslator)
95+
async def hello_command(ctx: commands.Context) -> None:
96+
await ctx.send_translated("Hello!")
97+
"""
4198

4299
@abc.abstractmethod
43-
async def translate(self, ctx: Context[Any], text: str, langcode: str) -> str: ...
100+
async def translate(self, ctx: Context[Any], text: str, langcode: str) -> str:
101+
"""|coro|
102+
103+
Method used to translate the content passed to :meth:`.commands.Context.send_translated` with the language code returned from
104+
:meth:`.get_langcode`. If ``None`` is returned from :meth:`.get_langcode`, this method will not be called and the
105+
default content provided to :meth:`~.commands.Context.send_translated` will be sent instead.
106+
107+
You could use this method to call a local or external translation API, retrieve translations from a database or local
108+
mapping etc.
109+
110+
This method must return a :class:`str`.
111+
112+
Parameters
113+
----------
114+
ctx: commands.Context
115+
The context surrounding the command invocation.
116+
text: str
117+
The content passed to :meth:`~.commands.Context.send_translated` which should be translated.
118+
langcode: str
119+
The language code returned via :meth:`.get_langcode`, which can be used to determine the language the text should
120+
be translated to.
121+
122+
Returns
123+
-------
124+
str
125+
The translated text which should be sent. This should not exceed ``500`` characters.
126+
127+
Example
128+
-------
129+
130+
.. code:: python3
131+
# For example purposes only the "translate" method is shown in this example...
132+
# The "get_langcode" method must also be implemented...
133+
134+
class HelloTranslator(commands.Translator):
135+
136+
async def translate(self, ctx: commands.Context, text: str, langcode: str) -> str:
137+
# Usually you would call an API, or retrieve from a database or dict or some other solution...
138+
# This is just for an example...
139+
140+
if langcode == "en":
141+
return text
142+
143+
elif langcode == "fr":
144+
return "Bonjour!"
145+
146+
@commands.command(name="hello", aliases=["bonjour"])
147+
@commands.translator(HelloTranslator)
148+
async def hello_command(ctx: commands.Context) -> None:
149+
await ctx.send_translated("Hello!")
150+
"""

0 commit comments

Comments
 (0)