From 8d4174b4ceaf42c630da41856ef787e5b7eeb66e Mon Sep 17 00:00:00 2001 From: SeoulSKY Date: Wed, 28 Feb 2024 11:12:44 -0600 Subject: [PATCH] Increase number of supported languages for translator command --- .github/workflows/docker.yml | 3 ++ .github/workflows/pytest.yml | 22 +++++----- commands/chat.py | 61 +++++++++++++++++++++------ commands/help_.py | 2 +- commands/translator.py | 48 +++++++++++---------- docs/help/chat/set_language/en.md | 11 +++-- docs/help/chat/set_language/ko.md | 9 ++-- locales/en/commands/chat.ftl | 5 ++- locales/en/utils/translator.ftl | 35 ++++++++++++---- locales/ko/commands/chat.ftl | 5 ++- locales/ko/utils/translator.ftl | 35 ++++++++++++---- requirements.txt | 1 - utils/constants.py | 26 ------------ utils/translator.py | 26 ++++++++---- utils/ui.py | 69 ++++++++++++++++++++++--------- 15 files changed, 228 insertions(+), 130 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index c1d75c9..5401f34 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -12,6 +12,9 @@ jobs: build: runs-on: ubuntu-latest steps: + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@main + - name: Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index a3e4e55..dcfce34 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -20,19 +20,13 @@ jobs: env: BOT_TOKEN: ${{ secrets.BOT_TOKEN }} - FIREBASE_AUTH_PROVIDER_X509_CERT_URL: ${{ secrets.FIREBASE_AUTH_PROVIDER_X509_CERT_URL }} - FIREBASE_AUTH_URI: ${{ secrets.FIREBASE_AUTH_URI }} - FIREBASE_CLIENT_EMAIL: ${{ secrets.FIREBASE_CLIENT_EMAIL }} - FIREBASE_CLIENT_ID: ${{ secrets.FIREBASE_CLIENT_ID }} - FIREBASE_CLIENT_X509_CERT_URL: ${{ secrets.FIREBASE_CLIENT_X509_CERT_URL }} - FIREBASE_PRIVATE_KEY: ${{ secrets.FIREBASE_PRIVATE_KEY }} - FIREBASE_PRIVATE_KEY_ID: ${{ secrets.FIREBASE_PRIVATE_KEY_ID }} - FIREBASE_PROJECT_ID: ${{ secrets.FIREBASE_PROJECT_ID }} - FIREBASE_TOKEN_URI: ${{ secrets.FIREBASE_TOKEN_URI }} - FIREBASE_TYPE: ${{ secrets.FIREBASE_TYPE }} TEST_GUILD_ID: ${{ secrets.TEST_GUILD_ID }} steps: + + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@main + - name: Checkout uses: actions/checkout@v3 @@ -41,6 +35,14 @@ jobs: with: python-version: "3.11" + - name: Cache dependencies + uses: actions/cache@v3 + with: + path: ${{ runner.temp }}/cache + key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/commands/chat.py b/commands/chat.py index a8f4ac2..21ebc7b 100644 --- a/commands/chat.py +++ b/commands/chat.py @@ -7,18 +7,51 @@ from characterai import PyAsyncCAI from discord import app_commands, Message, Interaction -from discord.app_commands import Choice from discord.ext.commands import Bot from mongo.user import User, get_user, set_user from utils import defer_response -from utils.constants import languages, BOT_NAME, BUG_REPORT_URL +from utils.constants import BOT_NAME, BUG_REPORT_URL from utils.templates import info, success, error from utils.translator import Language, Localization, DEFAULT_LANGUAGE, get_translator +from utils.ui import LanguageSelectView resources = [os.path.join("commands", "chat.ftl"), Localization.get_resource()] default_loc = Localization(DEFAULT_LANGUAGE, resources) +CURRENT_LANGUAGE_DEFAULT = True + + +async def _select_language(interaction: Interaction, language: Language, send: callable): + user = await get_user(interaction.user.id) + user.locale = language.code + await set_user(user) + + loc = Localization(interaction.locale, resources) + + await send(await loc.format_value_or_translate("updated", {"language": loc.language.name}), + ephemeral=True) + + +class ChatLanguageSelectView(LanguageSelectView): + """ + A view to select a language for the chat + """ + + def __init__(self, interaction: Interaction): + loc = Localization(interaction.locale, resources) + super().__init__(loc.format_value_or_translate("set-language-select"), interaction.locale, max_values=1) + + async def callback(self, interaction: Interaction): + await super().callback(interaction) + + send = await defer_response(interaction) + + await _select_language(interaction, Language(list(self.selected)[0]), send) + + self.clear_items() + self.stop() + class Chat(app_commands.Group): """ @@ -99,22 +132,23 @@ async def _send_message(self, user: User, text: str) -> str: @app_commands.command(name=default_loc.format_value("set-language-name"), description=default_loc.format_value("set-language-description")) - @app_commands.choices(language=[Choice(name=default_loc.format_value(code), value=code) for code in languages]) - @app_commands.describe(language=default_loc.format_value("set-language-language-description")) - async def set_language(self, interaction: Interaction, language: str = None): + @app_commands.describe( + current_language=default_loc.format_value( + "set-language-current-language-description", + {"set-language-current-language-description-default": str(CURRENT_LANGUAGE_DEFAULT)} + ) + ) + async def set_language(self, interaction: Interaction, current_language: bool = CURRENT_LANGUAGE_DEFAULT): """ Set the chat language to the current discord language """ send = await defer_response(interaction) - user = await get_user(interaction.user.id) - user.locale = language if language is not None else str(interaction.locale) - await set_user(user) - - loc = Localization(interaction.locale, resources) + if current_language: + await _select_language(interaction, Language(interaction.locale), send) + return - await send(await loc.format_value_or_translate("updated", {"language": loc.language.name}), - ephemeral=True) + await send(view=await ChatLanguageSelectView(interaction).init(), ephemeral=True) @app_commands.command(name=default_loc.format_value("clear-name"), description=default_loc.format_value("clear-description", @@ -129,7 +163,7 @@ async def clear(self, interaction: Interaction): if user.chat_history_id is None: await interaction.response.send_message( error(await loc.format_value_or_translate("no-history", - {"name": interaction.client.user.display_name})), + {"name": interaction.client.user.display_name})), ephemeral=True) return @@ -148,4 +182,5 @@ async def clear(self, interaction: Interaction): await interaction.followup.send(success(await loc.format_value_or_translate("deleted")), ephemeral=True) + set_language.extras["set-language-current-language-description-default"] = CURRENT_LANGUAGE_DEFAULT clear.extras["clear-description-name"] = BOT_NAME diff --git a/commands/help_.py b/commands/help_.py index 19f333b..86564f3 100644 --- a/commands/help_.py +++ b/commands/help_.py @@ -87,7 +87,7 @@ async def help_(interaction: Interaction): else: text += f"* `{await interaction.translate(context_menu.name)}`\n" - await send(text, view=View().add_item(await HelpSelect(interaction).init())) + await send(text, view=View().add_item(await HelpSelect(interaction).init()), ephemeral=True) help_.extras["help-description-name"] = BOT_NAME diff --git a/commands/translator.py b/commands/translator.py index 88c39d2..3597975 100644 --- a/commands/translator.py +++ b/commands/translator.py @@ -6,14 +6,15 @@ from discord import app_commands, Interaction, Message, Embed, HTTPException, Locale, ChannelType from discord.ext.commands import Bot -from discord.ui import View, ChannelSelect +from discord.ui import ChannelSelect from mongo.channel import get_channel, set_channel from mongo.user import get_user, set_user -from utils import templates, ui, defer_response -from utils.constants import ErrorCode, Limit, languages +from utils import defer_response, templates +from utils.constants import ErrorCode, Limit from utils.templates import success from utils.translator import Localization, Language, DEFAULT_LANGUAGE, get_translator, BaseTranslator +from utils.ui import LanguageSelectView resources = [os.path.join("commands", "translator.ftl")] default_loc = Localization(DEFAULT_LANGUAGE, resources) @@ -21,7 +22,8 @@ ALL_CHANNELS_DEFAULT = True -class ChannelLanguageSelect(ui.LanguageSelect): +class ChannelLanguageSelectView(LanguageSelectView): + """ Select UI to select available languages for a channel """ @@ -31,16 +33,19 @@ def __init__(self, locale: Locale): super().__init__(self.loc.format_value_or_translate("select-channel-languages"), locale) async def callback(self, interaction: Interaction): + await super().callback(interaction) + send = await defer_response(interaction) config = await get_channel(interaction.channel_id) - config.translate_to = self.values + config.translate_to = list(self.selected) await set_channel(config) await send(success(await self.loc.format_value_or_translate("channel-languages-updated")), ephemeral=True) -class UserLanguageSelect(ui.LanguageSelect): +class UserLanguageSelectView(LanguageSelectView): + # pylint: disable=too-few-public-methods """ Select UI to select available languages for a user """ @@ -50,10 +55,12 @@ def __init__(self, locale: Locale): super().__init__(self.loc.format_value_or_translate("select-languages"), locale) async def callback(self, interaction: Interaction): + await super().callback(interaction) + send = await defer_response(interaction) config = await get_user(interaction.user.id) - config.translate_to = self.values + config.translate_to = list(self.selected) await set_user(config) await send(success(await self.loc.format_value_or_translate("languages-updated")), ephemeral=True) @@ -113,15 +120,6 @@ async def on_message(message: Message): ): return - failed = False - for code in usr.translate_to: - if code not in languages: - usr.translate_to.remove(code) - failed = True - - if failed: - await set_user(usr) - if len(usr.translate_to) == 0: return @@ -138,14 +136,14 @@ async def on_message(message: Message): if len(message.content.strip()) == 0 or len(chan.translate_to) == 0: return - failed = False - for code in chan.translate_to: - if code not in languages: - chan.translate_to.remove(code) - failed = True + # failed = False + # for code in chan.translate_to: + # if code not in languages: + # chan.translate_to.remove(code) + # failed = True - if failed: - await set_channel(chan) + # if failed: + # await set_channel(chan) if len(chan.translate_to) == 0: return @@ -210,7 +208,7 @@ async def set_languages(self, interaction: Interaction, all_channels: bool = ALL send = await defer_response(interaction) - view = View().add_item(await UserLanguageSelect(interaction.locale).init()) + view = await UserLanguageSelectView(interaction.locale).init() if all_channels: user = await get_user(interaction.user.id) user.translate_in = [] @@ -230,7 +228,7 @@ async def set_channel_languages(self, interaction: Interaction): send = await defer_response(interaction) - await send(view=View().add_item(await ChannelLanguageSelect(interaction.locale).init()), ephemeral=True) + await send(view=await ChannelLanguageSelectView(interaction.locale).init(), ephemeral=True) @app_commands.command(name=default_loc.format_value("clear-languages-name"), description=default_loc.format_value("clear-languages-description")) diff --git a/docs/help/chat/set_language/en.md b/docs/help/chat/set_language/en.md index 27e5040..7c910c3 100644 --- a/docs/help/chat/set_language/en.md +++ b/docs/help/chat/set_language/en.md @@ -1,9 +1,12 @@ -# /chat update_language +# /chat set_language -Update the chat language to the current discord language. +Update the chat language to the current discord language. Updating the language doesn't affect the chat history. -If the parameter `language` is provided, update the chat language to the provided language. + +## Usage + +* If the parameter `current_language` is set to `False`, you can select the language you want. ## Parameters -* `language` (Optional): The new chat language. Defaults to your current discord language. \ No newline at end of file +* `current_language` (Optional): Set your chat language to your current discord language. Defaults to True. diff --git a/docs/help/chat/set_language/ko.md b/docs/help/chat/set_language/ko.md index f9e8e1b..c18f088 100644 --- a/docs/help/chat/set_language/ko.md +++ b/docs/help/chat/set_language/ko.md @@ -1,9 +1,10 @@ # /채팅 언어변경 -현재 디스코드 언어로 채팅 언어를 변경합니다. +현재 디스코드 언어로 채팅 언어를 변경합니다. 언어를 변경해도 채팅 기록은 사라지지 않습니다. -만약 변수인 `언어`를 입력한다면 해당 언어로 변경됩니다. +## 사용법 +* 만약 현재 디스코드 언어가 아닌 다른 언어로 설정하고 싶다면 `현재언어`를 `False`로 설정하고 언어를 선택하실 수 있습니다. -# 변수 +## 변수 -* `언어` (선택적): 변경할 언어입니다. +* `현재언어` (선택적): 언어를 현재 디스코드 언어로 설정합니다. 기본값은 True 입니다. diff --git a/locales/en/commands/chat.ftl b/locales/en/commands/chat.ftl index f326a84..dba6d7a 100644 --- a/locales/en/commands/chat.ftl +++ b/locales/en/commands/chat.ftl @@ -4,8 +4,9 @@ chat-description = Commands related to AI chats set-language-name = set_language set-language-description = Set the chat language to the current discord language -set-language-language-name = language -set-language-language-description = The new chat language. Defaults to your current discord language +set-language-current-language-name = current_language +set-language-current-language-description = Set your chat language to your current discord language. Defaults to { $set-language-current-language-description-default} +set-language-select = Select your chat language clear-name = clear clear-description = Clear the chat history between you and { $clear-description-name } diff --git a/locales/en/utils/translator.ftl b/locales/en/utils/translator.ftl index c4c729c..d9b560f 100644 --- a/locales/en/utils/translator.ftl +++ b/locales/en/utils/translator.ftl @@ -1,25 +1,46 @@ -zh-CN = Chinese (Simplified) -zh-TW = Chinese (Traditional) -nl = Dutch +ar = Arabic +az = Azerbaijani +bg = Bulgarian +bn = Bengali +ca = Catalan +cs = Czech +da = Danish +de = German +el = Greek en = English -tl = Filipino +eo = Esperanto +es = Spanish +et = Estonian +fa = Persian fi = Finnish fr = French -de = German -el = Greek +ga = Irish +he = Hebrew hi = Hindi +hu = Hungarian id = Indonesian it = Italian ja = Japanese ko = Korean +li = Limburgish +lt = Lithuanian +lv = Latvian ms = Malay +nb = Norwegian (Bokmål) +nl = Dutch no = Norwegian pl = Polish pt = Portuguese ro = Romanian ru = Russian -es = Spanish +sk = Slovak +sl = Slovenian +sq = Albanian sv = Swedish th = Thai +tl = Filipino +tr = Turkish uk = Ukrainian vi = Vietnamese +zh-CN = Chinese (Simplified) +zh-TW = Chinese (Traditional) diff --git a/locales/ko/commands/chat.ftl b/locales/ko/commands/chat.ftl index ccf8dd6..423e792 100644 --- a/locales/ko/commands/chat.ftl +++ b/locales/ko/commands/chat.ftl @@ -4,8 +4,9 @@ chat-description = AI 채팅과 관련된 명령어 입니다 set-language-name = 언어변경 set-language-description = 현재 디스코드 언어로 채팅 언어를 변경합니다 -set-language-language-name = 언어 -set-language-language-description = 변경할 언어. 기본값은 현재 디스코드 언어입니다 +set-language-current-language-name = 현재언어 +set-language-current-language-description = 언어를 현재 디스코드 언어로 설정합니다. 기본값은 { $set-language-current-language-description-default} 입니다 +set-language-select = 채팅 언어를 선택하세요 clear-name = 삭제 clear-description = { $clear-description-name }와(과) 당신 사이의 채팅 기록을 삭제합니다 diff --git a/locales/ko/utils/translator.ftl b/locales/ko/utils/translator.ftl index f62473d..2e4128f 100644 --- a/locales/ko/utils/translator.ftl +++ b/locales/ko/utils/translator.ftl @@ -1,25 +1,46 @@ -zh-CN = 중국어 (간체) -zh-TW = 중국어 (번체) -nl = 네덜란드어 +ar = 아랍어 +az = 아제르바이잔어 +bg = 불가리아어 +bn = 벵골어 +ca = 카탈로니아어 +cs = 체코어 +da = 덴마크어 +de = 독일어 +el = 그리스어 en = 영어 -tl = 필리핀어 +eo = 에스페란토어 +es = 스페인어 +et = 에스토니아어 +fa = 페르시아어 fi = 핀란드어 fr = 프랑스어 -de = 독일어 -el = 그리스어 +ga = 아일랜드어 +he = 히브리어 hi = 힌디어 +hu = 헝가리어 id = 인도네시아어 it = 이탈리아어 ja = 일본어 ko = 한국어 +li = 림뷔르흐어 +lt = 리투아니아어 +lv = 라트비아어 ms = 말레이어 +nb = 노르웨이어 (보크말) +nl = 네덜란드어 no = 노르웨이어 pl = 폴란드어 pt = 포르투갈어 ro = 루마니아어 ru = 러시아어 -es = 스페인어 +sk = 슬로바키아어 +sl = 슬로베니아어 +sq = 알바니아어 sv = 스웨덴어 th = 태국어 +tr = 터키어 +tl = 필리핀어 uk = 우크라이나어 vi = 베트남어 +zh-CN = 중국어 (간체) +zh-TW = 중국어 (번체) diff --git a/requirements.txt b/requirements.txt index 4483c3e..bfa0e49 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ deep-translator==1.11.4 discord.py==2.3.2 -firebase-admin==6.2.0 imageio==2.31.1 imageio-ffmpeg==0.4.8 langid==1.1.6 diff --git a/utils/constants.py b/utils/constants.py index b0c5332..0064df8 100644 --- a/utils/constants.py +++ b/utils/constants.py @@ -59,32 +59,6 @@ def __int__(self): return self.value -languages = { - "zh-CN", - "zh-TW", - "nl", - "en", - "tl", - "fr", - "de", - "el", - "hi", - "id", - "it", - "ja", - "ko", - "ms", - "pl", - "pt", - "ro", - "ru", - "es", - "sv", - "th", - "uk", -} - - BOT_NAME = "SoruSora" DATABASE_NAME = "SoruSora" diff --git a/utils/translator.py b/utils/translator.py index a235e0f..91b0697 100644 --- a/utils/translator.py +++ b/utils/translator.py @@ -53,7 +53,6 @@ def trim_territory(self) -> "Language": """ Trim the territory from the language code - :param code: The language code to trim :return: The language without the territory """ @@ -190,6 +189,15 @@ def is_language_supported(self, language: Language) -> bool: return language in self._languages + def get_supported_languages(self) -> Iterable[Language]: + """ + Get the supported languages of the translator + + :return: The supported languages of the translator + """ + + return self._languages + def is_code_supported(self, code: str) -> bool: """ Check if the language code is supported by the translator @@ -266,6 +274,7 @@ class ArgosTranslator(BaseTranslator): """ _CODE_ALIAS = { + "zh": "zh-CN", "zt": "zh-TW", } @@ -911,18 +920,19 @@ async def translate_texts(language: Language, texts: list[str], is_name: list[bo texts.append(context_menu.name) is_name.append(False) - for i, text in enumerate(texts): + target_texts = [] + target_is_name = [] + for text, name in zip(texts, is_name): if Cache.has(language, text): - texts[i], texts[-1] = texts[-1], texts[i] - is_name[i], is_name[-1] = is_name[-1], is_name[i] + continue - texts.pop() - is_name.pop() + target_texts.append(text) + target_is_name.append(name) - if len(texts) == 0: + if len(target_texts) == 0: continue - tasks.append(asyncio.create_task(translate_texts(language, texts, is_name))) + tasks.append(asyncio.create_task(translate_texts(language, target_texts, target_is_name))) pbar.total += 1 for task in asyncio.as_completed(tasks): diff --git a/utils/ui.py b/utils/ui.py index 5fb0bce..7803edc 100644 --- a/utils/ui.py +++ b/utils/ui.py @@ -3,21 +3,22 @@ classes: Confirm - LanguageSelect + LanguageSelectView + CommandSelect """ import os -from typing import Union, Optional, Type, Coroutine, Any +from typing import Union, Optional, Type, Coroutine, Any, Iterable -import discord -from discord import SelectOption, Interaction, Locale +from discord import SelectOption, Interaction, Locale, ButtonStyle, Button from discord.app_commands import Command, Group +from discord.ui import Select, View, button -from utils.constants import languages +from utils.constants import Limit from utils.templates import info, success -from utils.translator import Localization, Language, DEFAULT_LANGUAGE +from utils.translator import Localization, Language, DEFAULT_LANGUAGE, get_translator -class Confirm(discord.ui.View): +class Confirm(View): """ Buttons for confirmation """ @@ -46,8 +47,8 @@ def __init__(self, confirmed_message: str, cancelled_message: str, locale: Local False: The user cancelled """ - @discord.ui.button(style=discord.ButtonStyle.green) - async def confirm(self, interaction: discord.Interaction, _: discord.ui.Button): + @button(style=ButtonStyle.green) + async def confirm(self, interaction: Interaction, _: Button): """ Confirm when pressed """ @@ -56,8 +57,8 @@ async def confirm(self, interaction: discord.Interaction, _: discord.ui.Button): self.stop() self.clear_items() - @discord.ui.button(style=discord.ButtonStyle.grey) - async def cancel(self, interaction: discord.Interaction, _: discord.ui.Button): + @button(style=ButtonStyle.grey) + async def cancel(self, interaction: Interaction, _: Button): """ Cancel when pressed """ @@ -67,18 +68,20 @@ async def cancel(self, interaction: discord.Interaction, _: discord.ui.Button): self.clear_items() -class LanguageSelect(discord.ui.Select): +class LanguageSelectView(View): """ Select UI to select available languages for a user """ def __init__(self, placeholder: Coroutine[Any, Any, str], locale: Locale, max_values: int = None, **kwargs): + super().__init__() + self._placeholder = placeholder self._locale = locale self._max_values = max_values self._kwargs = kwargs - super().__init__(placeholder="Not initialized. Call init() method first") + self._selected = set() async def init(self): """ @@ -87,18 +90,44 @@ async def init(self): loc = Localization(self._locale) - options = [SelectOption(label=await loc.format_value_or_translate(code), value=code) for code in languages] - super().__init__(placeholder=await self._placeholder, max_values=len(options), - options=sorted(options, key=lambda option: option.label.lower()), - **self._kwargs) + languages = get_translator().get_supported_languages() + placeholder = await self._placeholder + + all_options = sorted([SelectOption(label=await loc.format_value_or_translate(lang.code), value=lang.code) + for lang in languages], key=lambda x: x.label.lower()) + + for i in range(0, len(all_options), int(Limit.SELECT_MAX)): + options = all_options[i:i + int(Limit.SELECT_MAX)] + select = Select( + placeholder=placeholder, + max_values=min(len(options), int(Limit.SELECT_MAX)) if self._max_values is None else self._max_values, + options=options, + **self._kwargs + ) + select.callback = self.callback + self.add_item(select) return self - async def callback(self, interaction: Interaction): - raise NotImplementedError("This method should be overridden in a subclass") + async def callback(self, _: Interaction): + """ + Callback for the select + """ + + select: Select + for select in self.children: + self._selected.update(select.values) + + @property + def selected(self) -> Iterable[str]: + """ + Get the selected languages + """ + + return self._selected -class CommandSelect(discord.ui.Select): +class CommandSelect(Select): """ Select UI to select a command """