From 1f5a2e92cc7577d78f367fc8f0dc3c4392e62ad5 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 15 Nov 2023 14:38:37 +0900 Subject: [PATCH 01/67] =?UTF-8?q?[Add]=20Model:=20ctranslate2=E3=81=AE?= =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 4 +- models/translation/translation_languages.py | 107 +++++++++++++++++++ models/translation/translation_translator.py | 27 +++++ requirements.txt | 5 +- 4 files changed, 140 insertions(+), 3 deletions(-) diff --git a/model.py b/model.py index bff713f4..431898ad 100644 --- a/model.py +++ b/model.py @@ -161,7 +161,7 @@ class Model: elif target_language in ["Portuguese European", "Portuguese Brazilian"]: target_language = "Portuguese" - translation = self.translator.translate( + translation = self.translator.translate_ctranslate2( translator_name=translator_name, source_language=source_language, target_language=target_language, @@ -192,7 +192,7 @@ class Model: elif target_language in ["Portuguese European", "Portuguese Brazilian"]: target_language = "Portuguese" - translation = self.translator.translate( + translation = self.translator.translate_ctranslate2( translator_name=translator_name, source_language=source_language, target_language=target_language, diff --git a/models/translation/translation_languages.py b/models/translation/translation_languages.py index ae57d4cc..d5645cd2 100644 --- a/models/translation/translation_languages.py +++ b/models/translation/translation_languages.py @@ -240,4 +240,111 @@ dict_bing_languages = { translation_lang["Bing"] = { "source":dict_bing_languages, "target":dict_bing_languages, +} + +dict_ctranslate2_lang = { + 'English': 'en', + 'Chinese': 'zh', + 'German': 'de', + 'Spanish': 'es', + 'Russian': 'ru', + 'Korean': 'ko', + 'French': 'fr', + 'Japanese': 'ja', + 'Portuguese': 'pt', + 'Turkish': 'tr', + 'Polish': 'pl', + 'Catalan': 'ca', + 'Dutch': 'nl', + 'Arabic': 'ar', + 'Swedish': 'sv', + 'Italian': 'it', + 'Indonesian': 'id', + 'Hindi': 'hi', + 'Finnish': 'fi', + 'Vietnamese': 'vi', + 'Hebrew': 'he', + 'Ukrainian': 'uk', + 'Greek': 'el', + 'Malay': 'ms', + 'Czech': 'cs', + 'Romanian': 'ro', + 'Danish': 'da', + 'Hungarian': 'hu', + 'Tamil': 'ta', + 'Norwegian': 'no', + 'Thai': 'th', + 'Urdu': 'ur', + 'Croatian': 'hr', + 'Bulgarian': 'bg', + 'Lithuanian': 'lt', + 'Latin': 'la', + 'Maori': 'mi', + 'Malayalam': 'ml', + 'Welsh': 'cy', + 'Slovak': 'sk', + 'Telugu': 'te', + 'Persian': 'fa', + 'Latvian': 'lv', + 'Bengali': 'bn', + 'Serbian': 'sr', + 'Azerbaijani': 'az', + 'Slovenian': 'sl', + 'Kannada': 'kn', + 'Estonian': 'et', + 'Macedonian': 'mk', + 'Breton': 'br', + 'Basque': 'eu', + 'Icelandic': 'is', + 'Armenian': 'hy', + 'Nepali': 'ne', + 'Mongolian': 'mn', + 'Bosnian': 'bs', + 'Kazakh': 'kk', + 'Albanian': 'sq', + 'Swahili': 'sw', + 'Galician': 'gl', + 'Marathi': 'mr', + 'Punjabi': 'pa', + 'Sinhala': 'si', + 'Khmer': 'km', + 'Shona': 'sn', + 'Yoruba': 'yo', + 'Somali': 'so', + 'Afrikaans': 'af', + 'Occitan': 'oc', + 'Georgian': 'ka', + 'Belarusian': 'be', + 'Tajik': 'tg', + 'Sindhi': 'sd', + 'Gujarati': 'gu', + 'Amharic': 'am', + 'Yiddish': 'yi', + 'Lao': 'lo', + 'Uzbek': 'uz', + 'Faroese': 'fo', + 'Haitian creole': 'ht', + 'Pashto': 'ps', + 'Turkmen': 'tk', + 'Nynorsk': 'nn', + 'Maltese': 'mt', + 'Sanskrit': 'sa', + 'Luxembourgish': 'lb', + 'Myanmar': 'my', + 'Tibetan': 'bo', + 'Tagalog': 'tl', + 'Malagasy': 'mg', + 'Assamese': 'as', + 'Tatar': 'tt', + 'Hawaiian': 'haw', + 'Lingala': 'ln', + 'Hausa': 'ha', + 'Bashkir': 'ba', + 'Javanese': 'jw', + 'Sundanese': 'su' +} + +translation_lang["ctranslate2"] = { + "source":dict_ctranslate2_lang, + "target":dict_ctranslate2_lang, } \ No newline at end of file diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index c3a5682b..d15a05c4 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -3,12 +3,24 @@ from deepl_translate import translate as deepl_web_Translator from translators import translate_text as other_web_Translator from .translation_languages import translation_lang +from ctranslate2.converters import TransformersConverter +import ctranslate2 +import transformers + +TRANSLATE_MODELS = { + "small": "facebook/m2m100_418M", + "large": "facebook/m2m100_1.2B" +} + # Translator class Translator(): def __init__(self): pass self.translator_status = {} + self.translator = ctranslate2.Translator("D:\\WORKSPACE\\WORK\\VRChatProject\\VRCT\\weight", device="cpu", device_index=0, compute_type="int8", inter_threads=1, intra_threads=4) + self.tokenizer = transformers.AutoTokenizer.from_pretrained("facebook/m2m100_418M") + def authentication(self, translator_name, authkey=None): result = True match translator_name: @@ -57,4 +69,19 @@ class Translator(): with open('error.log', 'a') as f: traceback.print_exc(file=f) result = False + return result + + def translate_ctranslate2(self, translator_name, source_language, target_language, message): + + source_language=translation_lang["ctranslate2"]["source"][source_language] + target_language=translation_lang["ctranslate2"]["target"][target_language] + + self.tokenizer.src_lang = source_language + source = self.tokenizer.convert_ids_to_tokens(self.tokenizer.encode(message)) + target_prefix = [self.tokenizer.lang_code_to_token[target_language]] + results = self.translator.translate_batch([source], target_prefix=[target_prefix]) + target = results[0].hypotheses[0][1:] + + result = self.tokenizer.decode(self.tokenizer.convert_tokens_to_ids(target)) + print(result) return result \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 42f4be2c..ca48a2b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,7 @@ deepl == 1.15.0 flashtext == 2.7 pyyaml == 6.0.1 python-i18n == 0.3.9 -CTkToolTip == 0.8 \ No newline at end of file +CTkToolTip == 0.8 +transformers[torch] +sentencepiece==0.1.99 +ctranslate2==3.21.0 \ No newline at end of file From 1a766cfde3449ceb2312f8e7819957ed258ab3a0 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 15 Nov 2023 14:39:27 +0900 Subject: [PATCH 02/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20setup=20:=20pip?= =?UTF-8?q?=E3=81=AE=E3=82=A2=E3=83=83=E3=83=97=E3=83=87=E3=83=BC=E3=83=88?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install.bat | 1 + 1 file changed, 1 insertion(+) diff --git a/install.bat b/install.bat index d54ef800..f312bf4d 100644 --- a/install.bat +++ b/install.bat @@ -1,3 +1,4 @@ +python.exe -m pip install --upgrade pip pip install -r requirements.txt pip install git+https://github.com/misyaguziya/translators pip install git+https://github.com/misyaguziya/deepl-translate From dcb6c07eee40eecf3c4a1d8d58101cb6a108b4c0 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 15 Nov 2023 16:06:45 +0900 Subject: [PATCH 03/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20weight?= =?UTF-8?q?=E3=81=AE=E3=83=91=E3=82=B9=E3=82=92PATH=5FLOACL=E3=81=8B?= =?UTF-8?q?=E3=82=89=E6=8E=A5=E7=B6=9A=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 10 +++++----- controller.py | 4 ++-- model.py | 8 ++++---- models/translation/translation_translator.py | 9 ++++----- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/config.py b/config.py index b594a1b0..fb0406d2 100644 --- a/config.py +++ b/config.py @@ -40,8 +40,8 @@ class Config: return self._VERSION @property - def LOCAL_PATH(self): - return self._LOCAL_PATH + def PATH_LOCAL(self): + return self._PATH_LOCAL @property def PATH_CONFIG(self): @@ -530,9 +530,9 @@ class Config: def init_config(self): # Read Only self._VERSION = "2.0.1" - self._LOCAL_PATH = os_path.dirname(sys.argv[0]) - self._PATH_CONFIG = os_path.join(self._LOCAL_PATH, "config.json") - self._PATH_LOGS = os_path.join(self._LOCAL_PATH, "logs") + self._PATH_LOCAL = os_path.dirname(sys.argv[0]) + self._PATH_CONFIG = os_path.join(self._PATH_LOCAL, "config.json") + self._PATH_LOGS = os_path.join(self._PATH_LOCAL, "logs") os_makedirs(self._PATH_LOGS, exist_ok=True) self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" self._BOOTH_URL = "https://misyaguziya.booth.pm/" diff --git a/controller.py b/controller.py index 2e254997..e6352411 100644 --- a/controller.py +++ b/controller.py @@ -20,8 +20,8 @@ def callbackFilepathLogs(): Popen(['explorer', config.PATH_LOGS.replace('/', '\\')], shell=True) def callbackFilepathConfigFile(): - print("callbackFilepathConfigFile", config.LOCAL_PATH.replace('/', '\\')) - Popen(['explorer', config.LOCAL_PATH.replace('/', '\\')], shell=True) + print("callbackFilepathConfigFile", config.PATH_LOCAL.replace('/', '\\')) + Popen(['explorer', config.PATH_LOCAL.replace('/', '\\')], shell=True) # func transcription send message def sendMicMessage(message): diff --git a/model.py b/model.py index 431898ad..071a5ff0 100644 --- a/model.py +++ b/model.py @@ -70,12 +70,12 @@ class Model: self.speaker_audio_recorder = None self.speaker_energy_recorder = None self.speaker_energy_plot_progressbar = None - self.translator = Translator() + self.translator = Translator(config.PATH_LOCAL) self.keyword_processor = KeywordProcessor() def resetTranslator(self): del self.translator - self.translator = Translator() + self.translator = Translator(config.PATH_LOCAL) def resetKeywordProcessor(self): del self.keyword_processor @@ -271,7 +271,7 @@ class Model: folder_name = '_internal' tmp_directory_name = 'tmp' batch_name = 'update.bat' - current_directory = config.LOCAL_PATH + current_directory = config.PATH_LOCAL try: res = requests_get(config.GITHUB_URL) @@ -296,7 +296,7 @@ class Model: program_name = 'VRCT.exe' folder_name = '_internal' batch_name = 'restart.bat' - current_directory = config.LOCAL_PATH + current_directory = config.PATH_LOCAL copyfile(os_path.join(current_directory, folder_name, "batch", batch_name), os_path.join(current_directory, batch_name)) command = [os_path.join(current_directory, batch_name), program_name] Popen(command, cwd=current_directory) diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index d15a05c4..e78c803d 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -1,3 +1,4 @@ +import os from deepl import Translator as deepl_Translator from deepl_translate import translate as deepl_web_Translator from translators import translate_text as other_web_Translator @@ -14,11 +15,10 @@ TRANSLATE_MODELS = { # Translator class Translator(): - def __init__(self): - pass + def __init__(self, path): self.translator_status = {} - - self.translator = ctranslate2.Translator("D:\\WORKSPACE\\WORK\\VRChatProject\\VRCT\\weight", device="cpu", device_index=0, compute_type="int8", inter_threads=1, intra_threads=4) + self.weight_path = os.path.join(path, "weight") + self.translator = ctranslate2.Translator(self.weight_path, device="cpu", device_index=0, compute_type="int8", inter_threads=1, intra_threads=4) self.tokenizer = transformers.AutoTokenizer.from_pretrained("facebook/m2m100_418M") def authentication(self, translator_name, authkey=None): @@ -83,5 +83,4 @@ class Translator(): target = results[0].hypotheses[0][1:] result = self.tokenizer.decode(self.tokenizer.convert_tokens_to_ids(target)) - print(result) return result \ No newline at end of file From e6e62cf35034f5787d77042ede1da69e39af0168 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 19 Nov 2023 00:03:57 +0900 Subject: [PATCH 04/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20CTrans?= =?UTF-8?q?late2=E3=81=AEweight=E3=83=87=E3=83=BC=E3=82=BF=E3=82=92?= =?UTF-8?q?=E8=B5=B7=E5=8B=95=E6=99=82=E3=81=AB=E5=8F=96=E5=BE=97=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- config.py | 28 ++++++++++++++ model.py | 39 +++++++++++++++++++- models/translation/translation_translator.py | 9 +++-- 4 files changed, 72 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 0d95c681..2223cde5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ memo.txt VRCT.spec *.pyc logs/ -.venv/ \ No newline at end of file +.venv/ +weight/ \ No newline at end of file diff --git a/config.py b/config.py index fb0406d2..e9afe1d9 100644 --- a/config.py +++ b/config.py @@ -63,6 +63,10 @@ class Config: def DOCUMENTS_URL(self): return self._DOCUMENTS_URL + @property + def CTRANSLATE2_WIGHTS(self): + return self._CTRANSLATE2_WIGHTS + @property def MAX_MIC_ENERGY_THRESHOLD(self): return self._MAX_MIC_ENERGY_THRESHOLD @@ -447,6 +451,17 @@ class Config: self._AUTH_KEYS[key] = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.AUTH_KEYS) + @property + @json_serializable('WEIGHT_TYPE') + def WEIGHT_TYPE(self): + return self._WEIGHT_TYPE + + @WEIGHT_TYPE.setter + def WEIGHT_TYPE(self, value): + if isinstance(value, str): + self._WEIGHT_TYPE = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property @json_serializable('MESSAGE_FORMAT') def MESSAGE_FORMAT(self): @@ -537,6 +552,18 @@ class Config: self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" self._BOOTH_URL = "https://misyaguziya.booth.pm/" self._DOCUMENTS_URL = "https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246" + self._CTRANSLATE2_WIGHTS = { + "small": { # M2M-100 418M-parameter model + "url": "https://bit.ly/33fM1AO", + "directory_name": "m2m100_418m", + "tokenizer": "facebook/m2m100_418M" + }, + "large": { # M2M-100 1.2B-parameter model + "url": "https://bit.ly/3GYiaed", + "directory_name": "m2m100_12b", + "tokenizer": "facebook/m2m100_12b" + }, + } self._MAX_MIC_ENERGY_THRESHOLD = 2000 self._MAX_SPEAKER_ENERGY_THRESHOLD = 4000 @@ -594,6 +621,7 @@ class Config: "Bing": None, "Google": None, } + self.WEIGHT_TYPE = "small" self._MESSAGE_FORMAT = "[message]([translation])" self._ENABLE_AUTO_CLEAR_MESSAGE_BOX = True self._ENABLE_NOTICE_XSOVERLAY = False diff --git a/model.py b/model.py index 071a5ff0..21ea5fd7 100644 --- a/model.py +++ b/model.py @@ -1,3 +1,4 @@ +import tempfile from zipfile import ZipFile from subprocess import Popen from os import makedirs as os_makedirs @@ -9,9 +10,10 @@ from logging import getLogger, FileHandler, Formatter, INFO from time import sleep from queue import Queue from threading import Thread, Event -from requests import get as requests_get +from requests import get as requests_get, head as requests_head import webbrowser +from tqdm import tqdm from flashtext import KeywordProcessor from models.translation.translation_translator import Translator from models.transcription.transcription_utils import getInputDevices, getDefaultOutputDevice @@ -70,7 +72,8 @@ class Model: self.speaker_audio_recorder = None self.speaker_energy_recorder = None self.speaker_energy_plot_progressbar = None - self.translator = Translator(config.PATH_LOCAL) + self.downloadCTranslate2Weight() + self.translator = Translator(config.PATH_LOCAL, config.CTRANSLATE2_WIGHTS[config.WEIGHT_TYPE]) self.keyword_processor = KeywordProcessor() def resetTranslator(self): @@ -106,6 +109,38 @@ class Model: self.logger.disabled = True self.logger = None + @staticmethod + def downloadCTranslate2Weight(): + weight_type = config.WEIGHT_TYPE + url = config.CTRANSLATE2_WIGHTS[weight_type]["url"] + filename = 'weight.zip' + directory_name = 'weight' + current_directory = config.PATH_LOCAL + weight_directory_name = config.CTRANSLATE2_WIGHTS[weight_type]["directory_name"] + files = ["model.bin", "sentencepiece.model", "shared_vocabulary.txt"] + + # check already downloaded + if all(os_path.exists(os_path.join(current_directory, directory_name, weight_directory_name, file)) for file in files): + return + + try: + os_makedirs(os_path.join(current_directory, directory_name), exist_ok=True) + print(os_path.join(current_directory, directory_name)) + with tempfile.TemporaryDirectory() as tmp_path: + file_size = int(requests_head(url).headers["content-length"]) + res = requests_get(url, stream=True) + pbar = tqdm(total=file_size, unit="B", unit_scale=True) + with open(os_path.join(tmp_path, filename), 'wb') as file: + for chunk in res.iter_content(chunk_size=1024): + file.write(chunk) + pbar.update(len(chunk)) + pbar.close() + + with ZipFile(os_path.join(tmp_path, filename)) as zf: + zf.extractall(os_path.join(current_directory, directory_name)) + except Exception as e: + print("error:downloadCTranslate2Weight()", e) + @staticmethod def getListLanguageAndCountry(): langs = [] diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index e78c803d..aa9c6d5c 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -4,7 +4,6 @@ from deepl_translate import translate as deepl_web_Translator from translators import translate_text as other_web_Translator from .translation_languages import translation_lang -from ctranslate2.converters import TransformersConverter import ctranslate2 import transformers @@ -15,11 +14,13 @@ TRANSLATE_MODELS = { # Translator class Translator(): - def __init__(self, path): + def __init__(self, path, weight_config): self.translator_status = {} - self.weight_path = os.path.join(path, "weight") + directory_name = weight_config["directory_name"] + tokenizer = weight_config["tokenizer"] + self.weight_path = os.path.join(path, "weight", directory_name) self.translator = ctranslate2.Translator(self.weight_path, device="cpu", device_index=0, compute_type="int8", inter_threads=1, intra_threads=4) - self.tokenizer = transformers.AutoTokenizer.from_pretrained("facebook/m2m100_418M") + self.tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer) def authentication(self, translator_name, authkey=None): result = True From 92ea7914b8f4c74862b8c10dfc7711c01282074e Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 26 Nov 2023 00:30:59 +0900 Subject: [PATCH 05/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20model=20:=20?= =?UTF-8?q?=E3=83=A2=E3=83=87=E3=83=AB=E3=81=AE=E3=83=AD=E3=83=BC=E3=83=89?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 4 ++++ model.py | 33 --------------------------------- 2 files changed, 4 insertions(+), 33 deletions(-) diff --git a/main.py b/main.py index def1cb0b..14c97ac4 100644 --- a/main.py +++ b/main.py @@ -7,6 +7,10 @@ if __name__ == "__main__": splash = SplashWindow() splash.showSplash() + from config import config + from models.translation.utils import downloadCTranslate2Weight + downloadCTranslate2Weight(config.PATH_LOCAL, config.WEIGHT_TYPE, config.CTRANSLATE2_WIGHTS) + import controller controller.createMainWindow() splash.destroySplash() diff --git a/model.py b/model.py index 21ea5fd7..ef035613 100644 --- a/model.py +++ b/model.py @@ -72,7 +72,6 @@ class Model: self.speaker_audio_recorder = None self.speaker_energy_recorder = None self.speaker_energy_plot_progressbar = None - self.downloadCTranslate2Weight() self.translator = Translator(config.PATH_LOCAL, config.CTRANSLATE2_WIGHTS[config.WEIGHT_TYPE]) self.keyword_processor = KeywordProcessor() @@ -109,38 +108,6 @@ class Model: self.logger.disabled = True self.logger = None - @staticmethod - def downloadCTranslate2Weight(): - weight_type = config.WEIGHT_TYPE - url = config.CTRANSLATE2_WIGHTS[weight_type]["url"] - filename = 'weight.zip' - directory_name = 'weight' - current_directory = config.PATH_LOCAL - weight_directory_name = config.CTRANSLATE2_WIGHTS[weight_type]["directory_name"] - files = ["model.bin", "sentencepiece.model", "shared_vocabulary.txt"] - - # check already downloaded - if all(os_path.exists(os_path.join(current_directory, directory_name, weight_directory_name, file)) for file in files): - return - - try: - os_makedirs(os_path.join(current_directory, directory_name), exist_ok=True) - print(os_path.join(current_directory, directory_name)) - with tempfile.TemporaryDirectory() as tmp_path: - file_size = int(requests_head(url).headers["content-length"]) - res = requests_get(url, stream=True) - pbar = tqdm(total=file_size, unit="B", unit_scale=True) - with open(os_path.join(tmp_path, filename), 'wb') as file: - for chunk in res.iter_content(chunk_size=1024): - file.write(chunk) - pbar.update(len(chunk)) - pbar.close() - - with ZipFile(os_path.join(tmp_path, filename)) as zf: - zf.extractall(os_path.join(current_directory, directory_name)) - except Exception as e: - print("error:downloadCTranslate2Weight()", e) - @staticmethod def getListLanguageAndCountry(): langs = [] From 0311d4cdfc29c69b58c525fe7b9108cab3d95f9e Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 28 Nov 2023 10:17:50 +0900 Subject: [PATCH 06/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20model=20:=20transl?= =?UTF-8?q?ation/utils.py=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=AB=E4=BC=B4?= =?UTF-8?q?=E3=81=84=E4=B8=80=E9=83=A8=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 14 ++-------- main.py | 2 +- models/translation/utils.py | 54 +++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 models/translation/utils.py diff --git a/config.py b/config.py index e9afe1d9..c1cc4759 100644 --- a/config.py +++ b/config.py @@ -8,6 +8,7 @@ from tkinter import font from languages import selectable_languages from models.translation.translation_languages import translatorEngine from models.transcription.transcription_utils import getInputDevices, getDefaultInputDevice +from models.translation.utils import ctranslate2_weights from utils import generatePercentageStringsList, isUniqueStrings json_serializable_vars = {} @@ -552,18 +553,7 @@ class Config: self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" self._BOOTH_URL = "https://misyaguziya.booth.pm/" self._DOCUMENTS_URL = "https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246" - self._CTRANSLATE2_WIGHTS = { - "small": { # M2M-100 418M-parameter model - "url": "https://bit.ly/33fM1AO", - "directory_name": "m2m100_418m", - "tokenizer": "facebook/m2m100_418M" - }, - "large": { # M2M-100 1.2B-parameter model - "url": "https://bit.ly/3GYiaed", - "directory_name": "m2m100_12b", - "tokenizer": "facebook/m2m100_12b" - }, - } + self._CTRANSLATE2_WEIGHTS = ctranslate2_weights self._MAX_MIC_ENERGY_THRESHOLD = 2000 self._MAX_SPEAKER_ENERGY_THRESHOLD = 4000 diff --git a/main.py b/main.py index 14c97ac4..ff0aa6a6 100644 --- a/main.py +++ b/main.py @@ -9,7 +9,7 @@ if __name__ == "__main__": from config import config from models.translation.utils import downloadCTranslate2Weight - downloadCTranslate2Weight(config.PATH_LOCAL, config.WEIGHT_TYPE, config.CTRANSLATE2_WIGHTS) + downloadCTranslate2Weight(config.PATH_LOCAL, config.WEIGHT_TYPE, config.CTRANSLATE2_WEIGHTS, print) import controller controller.createMainWindow() diff --git a/models/translation/utils.py b/models/translation/utils.py new file mode 100644 index 00000000..5b15f16a --- /dev/null +++ b/models/translation/utils.py @@ -0,0 +1,54 @@ +import tempfile +from zipfile import ZipFile +from os import path as os_path +from os import makedirs as os_makedirs +from requests import get as requests_get, head as requests_head +from tqdm import tqdm +from typing import Callable + +ctranslate2_weights = { + "small": { # M2M-100 418M-parameter model + "url": "https://bit.ly/33fM1AO", + "directory_name": "m2m100_418m", + "tokenizer": "facebook/m2m100_418M" + }, + "large": { # M2M-100 1.2B-parameter model + "url": "https://bit.ly/3GYiaed", + "directory_name": "m2m100_12b", + "tokenizer": "facebook/m2m100_12b" + }, +} + +def downloadCTranslate2Weight(path, weight_type="small", ctranslate2_weights=ctranslate2_weights, func=None): + url = ctranslate2_weights[weight_type]["url"] + filename = 'weight.zip' + directory_name = 'weight' + current_directory = path + weight_directory_name = ctranslate2_weights[weight_type]["directory_name"] + files = ["model.bin", "sentencepiece.model", "shared_vocabulary.txt"] + + # check already downloaded + if all(os_path.exists(os_path.join(current_directory, directory_name, weight_directory_name, file)) for file in files): + return + + try: + os_makedirs(os_path.join(current_directory, directory_name), exist_ok=True) + print(os_path.join(current_directory, directory_name)) + with tempfile.TemporaryDirectory() as tmp_path: + res = requests_get(url, stream=True) + file_size = int(res.headers.get('content-length', 0)) + pbar = tqdm(total=file_size, unit="B", unit_scale=True) + total_chunk = 0 + with open(os_path.join(tmp_path, filename), 'wb') as file: + for chunk in res.iter_content(chunk_size=1024): + file.write(chunk) + pbar.update(len(chunk)) + if isinstance(func, Callable): + total_chunk += len(chunk) + func(total_chunk/file_size) + pbar.close() + + with ZipFile(os_path.join(tmp_path, filename)) as zf: + zf.extractall(os_path.join(current_directory, directory_name)) + except Exception as e: + print("error:downloadCTranslate2Weight()", e) \ No newline at end of file From 52abfb8a97e1ccea2816e9ce96edc3ebbdc6d6ed Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 28 Nov 2023 10:18:51 +0900 Subject: [PATCH 07/67] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20typo=20WIGHTS=20->?= =?UTF-8?q?=20WEIGHTS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 4 ++-- model.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config.py b/config.py index c1cc4759..34a0d341 100644 --- a/config.py +++ b/config.py @@ -65,8 +65,8 @@ class Config: return self._DOCUMENTS_URL @property - def CTRANSLATE2_WIGHTS(self): - return self._CTRANSLATE2_WIGHTS + def CTRANSLATE2_WEIGHTS(self): + return self._CTRANSLATE2_WEIGHTS @property def MAX_MIC_ENERGY_THRESHOLD(self): diff --git a/model.py b/model.py index ef035613..36de1262 100644 --- a/model.py +++ b/model.py @@ -72,7 +72,7 @@ class Model: self.speaker_audio_recorder = None self.speaker_energy_recorder = None self.speaker_energy_plot_progressbar = None - self.translator = Translator(config.PATH_LOCAL, config.CTRANSLATE2_WIGHTS[config.WEIGHT_TYPE]) + self.translator = Translator(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHTS[config.WEIGHT_TYPE]) self.keyword_processor = KeywordProcessor() def resetTranslator(self): From 69f6e4472017a3a82c3a2ca441fee188aaee4aef Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 29 Nov 2023 17:11:47 +0900 Subject: [PATCH 08/67] =?UTF-8?q?[Update]=20UI=E8=BF=BD=E5=8A=A0=20?= =?UTF-8?q?=E5=88=9D=E6=9C=9F=E8=B5=B7=E5=8B=95=E6=99=82=E3=81=AE=E8=A8=80?= =?UTF-8?q?=E8=AA=9E=E3=83=A2=E3=83=87=E3=83=AB=E3=83=80=E3=82=A6=E3=83=B3?= =?UTF-8?q?=E3=83=AD=E3=83=BC=E3=83=89=E4=B8=AD=E3=80=81splash=E7=94=BB?= =?UTF-8?q?=E9=9D=A2=E3=81=AB=E3=83=97=E3=83=AD=E3=82=B0=E3=83=AC=E3=82=B9?= =?UTF-8?q?=E3=83=90=E3=83=BC=E8=A1=A8=E7=A4=BA=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- img/VRCT_now_downloading.png | Bin 0 -> 31294 bytes main.py | 2 +- vrct_gui/splash_window/SplashWindow.py | 34 ++++++++++++++++++++++++- 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 img/VRCT_now_downloading.png diff --git a/img/VRCT_now_downloading.png b/img/VRCT_now_downloading.png new file mode 100644 index 0000000000000000000000000000000000000000..5e5b9daa328daa530d270f8b35fd3ee6eb02da54 GIT binary patch literal 31294 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 literal 0 HcmV?d00001 diff --git a/main.py b/main.py index ff0aa6a6..00bd351a 100644 --- a/main.py +++ b/main.py @@ -9,7 +9,7 @@ if __name__ == "__main__": from config import config from models.translation.utils import downloadCTranslate2Weight - downloadCTranslate2Weight(config.PATH_LOCAL, config.WEIGHT_TYPE, config.CTRANSLATE2_WEIGHTS, print) + downloadCTranslate2Weight(config.PATH_LOCAL, config.WEIGHT_TYPE, config.CTRANSLATE2_WEIGHTS, splash.updateDownloadProgress) import controller controller.createMainWindow() diff --git a/vrct_gui/splash_window/SplashWindow.py b/vrct_gui/splash_window/SplashWindow.py index 2a2db684..c7af9802 100644 --- a/vrct_gui/splash_window/SplashWindow.py +++ b/vrct_gui/splash_window/SplashWindow.py @@ -1,4 +1,4 @@ -from customtkinter import CTkImage, CTkLabel, CTkToplevel +from customtkinter import CTkImage, CTkLabel, CTkToplevel, CTkProgressBar from ..ui_utils import openImageKeepAspectRatio, getImageFileFromUiUtils, setGeometryToCenterOfScreen, fadeInAnimation class SplashWindow(CTkToplevel): @@ -10,6 +10,7 @@ class SplashWindow(CTkToplevel): self.title("SplashWindow") self.wm_attributes("-toolwindow", True) + self.is_showed_progressbar = False sw=self.winfo_screenwidth() @@ -28,6 +29,37 @@ class SplashWindow(CTkToplevel): label.grid(row=1, column=1, padx=int(desired_width/7), pady=int(height/3)) + self.progressbar_widget = CTkProgressBar( + self, + height=10, + corner_radius=0, + fg_color="#4b4c4f", + progress_color="#48a495", + ) + self.progressbar_widget.set(0) + + + (img, desired_width, height) = openImageKeepAspectRatio(getImageFileFromUiUtils("VRCT_now_downloading.png"), 320) + self.text_label = CTkLabel( + self, + text=None, + height=0, + fg_color="#292a2d", + image=CTkImage(img, size=(desired_width, height)) + ) + + + + def updateDownloadProgress(self, progress:float): + if self.is_showed_progressbar is False: + self.progressbar_widget.place(relwidth=0.9, relx=0.5, rely=0.9, anchor="s") + self.text_label.place(relx=0.98, rely=0.98, anchor="se") + self.is_showed_progressbar = True + + self.progressbar_widget.set(progress) + self.update_idletasks() + + def showSplash(self): self.attributes("-alpha", 0) self.deiconify() From 8cc95078e9378b3d624cdf2705014642d9ee7e5b Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 29 Nov 2023 23:36:55 +0900 Subject: [PATCH 09/67] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20typo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index 34a0d341..44529b5a 100644 --- a/config.py +++ b/config.py @@ -611,7 +611,7 @@ class Config: "Bing": None, "Google": None, } - self.WEIGHT_TYPE = "small" + self._WEIGHT_TYPE = "small" self._MESSAGE_FORMAT = "[message]([translation])" self._ENABLE_AUTO_CLEAR_MESSAGE_BOX = True self._ENABLE_NOTICE_XSOVERLAY = False From 8146b8b98870f5cdef17df530a600257b02b3f4a Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 29 Nov 2023 23:37:44 +0900 Subject: [PATCH 10/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20hash?= =?UTF-8?q?=E3=81=AB=E3=82=88=E3=82=8B=E6=A4=9C=E8=A8=BC=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/translation/utils.py | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/models/translation/utils.py b/models/translation/utils.py index 5b15f16a..c41fbb88 100644 --- a/models/translation/utils.py +++ b/models/translation/utils.py @@ -5,30 +5,57 @@ from os import makedirs as os_makedirs from requests import get as requests_get, head as requests_head from tqdm import tqdm from typing import Callable +import hashlib ctranslate2_weights = { "small": { # M2M-100 418M-parameter model "url": "https://bit.ly/33fM1AO", "directory_name": "m2m100_418m", - "tokenizer": "facebook/m2m100_418M" + "tokenizer": "facebook/m2m100_418M", + "hash": { + "model.bin": "e7c26a9abb5260abd0268fbe3040714070dec254a990b4d7fd3f74c5230e3acb", + "sentencepiece.model": "d8f7c76ed2a5e0822be39f0a4f95a55eb19c78f4593ce609e2edbc2aea4d380a", + "shared_vocabulary.txt": "bd440aa21b8ca3453fc792a0018a1f3fe68b3464aadddd4d16a4b72f73c86d8c", + } }, "large": { # M2M-100 1.2B-parameter model "url": "https://bit.ly/3GYiaed", "directory_name": "m2m100_12b", - "tokenizer": "facebook/m2m100_12b" + "tokenizer": "facebook/m2m100_1.2b", + "hash": { + "model.bin": "abb7bf4ba7e5e016b6e3ed480c752459b2f783ac8fca372e7587675e5bf3a919", + "sentencepiece.model": "d8f7c76ed2a5e0822be39f0a4f95a55eb19c78f4593ce609e2edbc2aea4d380a", + "shared_vocabulary.txt": "bd440aa21b8ca3453fc792a0018a1f3fe68b3464aadddd4d16a4b72f73c86d8c", + } }, } +def calculate_file_hash(file_path, block_size=65536): + hash_object = hashlib.sha256() + + with open(file_path, 'rb') as file: + for block in iter(lambda: file.read(block_size), b''): + hash_object.update(block) + + return hash_object.hexdigest() + def downloadCTranslate2Weight(path, weight_type="small", ctranslate2_weights=ctranslate2_weights, func=None): url = ctranslate2_weights[weight_type]["url"] filename = 'weight.zip' directory_name = 'weight' current_directory = path weight_directory_name = ctranslate2_weights[weight_type]["directory_name"] + hash_data = ctranslate2_weights[weight_type]["hash"] files = ["model.bin", "sentencepiece.model", "shared_vocabulary.txt"] # check already downloaded if all(os_path.exists(os_path.join(current_directory, directory_name, weight_directory_name, file)) for file in files): + # check hash + for file in files: + original_hash = hash_data[file] + current_hash = calculate_file_hash(os_path.join(current_directory, directory_name, weight_directory_name, file)) + if original_hash != current_hash: + break return try: @@ -40,7 +67,7 @@ def downloadCTranslate2Weight(path, weight_type="small", ctranslate2_weights=ctr pbar = tqdm(total=file_size, unit="B", unit_scale=True) total_chunk = 0 with open(os_path.join(tmp_path, filename), 'wb') as file: - for chunk in res.iter_content(chunk_size=1024): + for chunk in res.iter_content(chunk_size=1024*5): file.write(chunk) pbar.update(len(chunk)) if isinstance(func, Callable): From 8463ccda694c029770d1e8f5da62fe5037cc0940 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 27 Dec 2023 22:14:10 +0900 Subject: [PATCH 11/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20transl?= =?UTF-8?q?ators=E6=9B=B4=E6=96=B0=E3=81=AB=E4=BC=B4=E3=81=84=E8=A8=80?= =?UTF-8?q?=E8=AA=9E=E3=82=BB=E3=83=83=E3=83=88=E3=82=92=E5=A4=89=E6=9B=B4?= =?UTF-8?q?/Papago=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/translation/translation_languages.py | 94 +++++++++++++-------- 1 file changed, 61 insertions(+), 33 deletions(-) diff --git a/models/translation/translation_languages.py b/models/translation/translation_languages.py index d5645cd2..959b9077 100644 --- a/models/translation/translation_languages.py +++ b/models/translation/translation_languages.py @@ -1,35 +1,41 @@ -translatorEngine = ["DeepL", "DeepL_API", "Google", "Bing"] +translatorEngine = ["DeepL", "DeepL_API", "Google", "Bing", "Papago"] translation_lang = {} dict_deepl_languages = { - "Japanese":"JA", - "English":"EN", - "Korean":"KO", - "Bulgarian":"BG", - "Chinese":"ZH", - "Czech":"CS", - "Danish":"DA", - "Dutch":"NL", - "Estonian":"ET", - "Finnish":"FI", - "French":"FR", - "German":"DE", - "Greek":"EL", - "Hungarian":"HU", - "Italian":"IT", - "Latvian":"LV", - "Lithuanian":"LT", - "Polish":"PL", - "Portuguese":"PT", - "Romanian":"RO", - "Russian":"RU", - "Slovak":"SK", - "Slovenian":"SL", - "Spanish":"ES", - "Swedish":"SV", - "Indonesian":"ID", - "Ukrainian":"UK", - "Turkish":"TR", - "Norwegian":"NB", + 'Arabic':'ar', + 'Bulgarian':'bg', + 'Czech':'cs', + 'Danish':'da', + 'German':'de', + 'Greek':'el', + 'English':'en', + 'Spanish':'es', + 'Estonian':'et', + 'Finnish':'fi', + 'French':'fr', + 'Irish':'ga', + 'Croatian':'hr', + 'Hungarian':'hu', + 'Indonesian':'id', + 'Icelandic':'is', + 'Italian':'it', + 'Japanese':'ja', + 'Korean':'ko', + 'Lithuanian':'lt', + 'Latvian':'lv', + 'Maltese':'mt', + 'Bokmal':'nb', + 'Dutch':'nl', + 'Norwegian':'no', + 'Polish':'pl', + 'Portuguese':'pt', + 'Romanian':'ro', + 'Russian':'ru', + 'Slovak':'sk', + 'Slovenian':'sl', + 'Swedish':'sv', + 'Turkish':'tr', + 'Ukrainian':'uk', + 'Chinese':'zh', } translation_lang["DeepL"] = { "source":dict_deepl_languages, @@ -242,7 +248,29 @@ translation_lang["Bing"] = { "target":dict_bing_languages, } -dict_ctranslate2_lang = { +dict_papago_languages = { + 'German': 'de', + 'English': 'en', + 'Spanish':'es', + 'French': 'fr', + 'Hindi': 'hi', + 'Indonesian': 'id', + 'Italian': 'it', + 'Japanese': 'ja', + 'Korean': 'ko', + 'Portuguese': 'pt', + 'Russian': 'ru', + 'Thai': 'th', + 'Vietnamese': 'vi', + 'Chinese':'zh-CN', +} + +translation_lang["Papago"] = { + "source":dict_papago_languages, + "target":dict_papago_languages, +} + +dict_ctranslate2_languages = { 'English': 'en', 'Chinese': 'zh', 'German': 'de', @@ -345,6 +373,6 @@ dict_ctranslate2_lang = { } translation_lang["ctranslate2"] = { - "source":dict_ctranslate2_lang, - "target":dict_ctranslate2_lang, + "source":dict_ctranslate2_languages, + "target":dict_ctranslate2_languages, } \ No newline at end of file From a82435dc29a9ad4d1707524996207f588d408b7a Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 27 Dec 2023 22:37:38 +0900 Subject: [PATCH 12/67] =?UTF-8?q?[Update]=20Model=20:=20deepl=5Ftranslate?= =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install.bat | 1 - model.py | 17 +++++++- models/translation/translation_translator.py | 44 +++++++++++++------- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/install.bat b/install.bat index f312bf4d..036f6a51 100644 --- a/install.bat +++ b/install.bat @@ -1,5 +1,4 @@ python.exe -m pip install --upgrade pip pip install -r requirements.txt pip install git+https://github.com/misyaguziya/translators -pip install git+https://github.com/misyaguziya/deepl-translate pip install git+https://github.com/misyaguziya/custom_speech_recognition \ No newline at end of file diff --git a/model.py b/model.py index 36de1262..f7fa4642 100644 --- a/model.py +++ b/model.py @@ -163,7 +163,14 @@ class Model: elif target_language in ["Portuguese European", "Portuguese Brazilian"]: target_language = "Portuguese" - translation = self.translator.translate_ctranslate2( + # translation = self.translator.translate_ctranslate2( + # translator_name=translator_name, + # source_language=source_language, + # target_language=target_language, + # message=message + # ) + + translation = self.translator.translate( translator_name=translator_name, source_language=source_language, target_language=target_language, @@ -194,7 +201,13 @@ class Model: elif target_language in ["Portuguese European", "Portuguese Brazilian"]: target_language = "Portuguese" - translation = self.translator.translate_ctranslate2( + # translation = self.translator.translate_ctranslate2( + # translator_name=translator_name, + # source_language=source_language, + # target_language=target_language, + # message=message + # ) + translation = self.translator.translate( translator_name=translator_name, source_language=source_language, target_language=target_language, diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index aa9c6d5c..496a9b44 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -1,6 +1,5 @@ import os from deepl import Translator as deepl_Translator -from deepl_translate import translate as deepl_web_Translator from translators import translate_text as other_web_Translator from .translation_languages import translation_lang @@ -40,10 +39,11 @@ class Translator(): target_language=translation_lang[translator_name]["target"][target_language] match translator_name: case "DeepL": - result = deepl_web_Translator( - source_language=source_language, - target_language=target_language, - text=message + result = other_web_Translator( + query_text=message, + translator="deepl", + from_language=source_language, + to_language=target_language, ) case "DeepL_API": result = self.deepl_client.translate_text( @@ -65,6 +65,20 @@ class Translator(): from_language=source_language, to_language=target_language, ) + case "Papago": + result = other_web_Translator( + query_text=message, + translator="papago", + from_language=source_language, + to_language=target_language, + ) + case "CTranslate2": + self.tokenizer.src_lang = source_language + source = self.tokenizer.convert_ids_to_tokens(self.tokenizer.encode(message)) + target_prefix = [self.tokenizer.lang_code_to_token[target_language]] + results = self.translator.translate_batch([source], target_prefix=[target_prefix]) + target = results[0].hypotheses[0][1:] + result = self.tokenizer.decode(self.tokenizer.convert_tokens_to_ids(target)) except Exception: import traceback with open('error.log', 'a') as f: @@ -72,16 +86,16 @@ class Translator(): result = False return result - def translate_ctranslate2(self, translator_name, source_language, target_language, message): + # def translate_ctranslate2(self, translator_name, source_language, target_language, message): - source_language=translation_lang["ctranslate2"]["source"][source_language] - target_language=translation_lang["ctranslate2"]["target"][target_language] + # source_language=translation_lang["ctranslate2"]["source"][source_language] + # target_language=translation_lang["ctranslate2"]["target"][target_language] - self.tokenizer.src_lang = source_language - source = self.tokenizer.convert_ids_to_tokens(self.tokenizer.encode(message)) - target_prefix = [self.tokenizer.lang_code_to_token[target_language]] - results = self.translator.translate_batch([source], target_prefix=[target_prefix]) - target = results[0].hypotheses[0][1:] + # self.tokenizer.src_lang = source_language + # source = self.tokenizer.convert_ids_to_tokens(self.tokenizer.encode(message)) + # target_prefix = [self.tokenizer.lang_code_to_token[target_language]] + # results = self.translator.translate_batch([source], target_prefix=[target_prefix]) + # target = results[0].hypotheses[0][1:] - result = self.tokenizer.decode(self.tokenizer.convert_tokens_to_ids(target)) - return result \ No newline at end of file + # result = self.tokenizer.decode(self.tokenizer.convert_tokens_to_ids(target)) + # return result \ No newline at end of file From 1777988294f01cac4974c7c1fa8a9a7932e6346e Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sat, 30 Dec 2023 23:38:09 +0900 Subject: [PATCH 13/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Main=20:=20?= =?UTF-8?q?=E7=BF=BB=E8=A8=B3=E3=82=A8=E3=83=B3=E3=82=B8=E3=83=B3=E3=81=AE?= =?UTF-8?q?=E6=89=8B=E5=8B=95=E8=A8=AD=E5=AE=9A=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 翻訳エンジンの自動設定機能を削除 input/outputの翻訳エンジンを別に設定できるように機能を追加 Aurhkeyの認証をDeepLのみ削減 --- config.py | 54 ++++++++++++++++-- controller.py | 52 +++++++++++++----- model.py | 58 +++++++------------- models/translation/translation_translator.py | 14 ++--- 4 files changed, 112 insertions(+), 66 deletions(-) diff --git a/config.py b/config.py index 44529b5a..93e70c2d 100644 --- a/config.py +++ b/config.py @@ -150,13 +150,22 @@ class Config: self._TARGET_LANGUAGE = value @property - def CHOICE_TRANSLATOR(self): - return self._CHOICE_TRANSLATOR + def CHOICE_INPUT_TRANSLATOR(self): + return self._CHOICE_INPUT_TRANSLATOR - @CHOICE_TRANSLATOR.setter - def CHOICE_TRANSLATOR(self, value): + @CHOICE_INPUT_TRANSLATOR.setter + def CHOICE_INPUT_TRANSLATOR(self, value): if value in translatorEngine: - self._CHOICE_TRANSLATOR = value + self._CHOICE_INPUT_TRANSLATOR= value + + @property + def CHOICE_OUTPUT_TRANSLATOR(self): + return self._CHOICE_OUTPUT_TRANSLATOR + + @CHOICE_OUTPUT_TRANSLATOR.setter + def CHOICE_OUTPUT_TRANSLATOR(self, value): + if value in translatorEngine: + self._CHOICE_OUTPUT_TRANSLATOR = value # Save Json Data ## Main Window @@ -171,6 +180,28 @@ class Config: self._SELECTED_TAB_NO = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property + @json_serializable('SELECTED_TAB_YOUR_TRANSLATOR_ENGINES') + def SELECTED_TAB_YOUR_TRANSLATOR_ENGINES(self): + return self._SELECTED_TAB_YOUR_TRANSLATOR_ENGINES + + @SELECTED_TAB_YOUR_TRANSLATOR_ENGINES.setter + def SELECTED_TAB_YOUR_TRANSLATOR_ENGINES(self, value): + if isinstance(value, dict): + self._SELECTED_TAB_YOUR_TRANSLATOR_ENGINES = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SELECTED_TAB_TARGET_TRANSLATOR_ENGINES') + def SELECTED_TAB_TARGET_TRANSLATOR_ENGINES(self): + return self._SELECTED_TAB_TARGET_TRANSLATOR_ENGINES + + @SELECTED_TAB_TARGET_TRANSLATOR_ENGINES.setter + def SELECTED_TAB_TARGET_TRANSLATOR_ENGINES(self, value): + if isinstance(value, dict): + self._SELECTED_TAB_TARGET_TRANSLATOR_ENGINES = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property @json_serializable('SELECTED_TAB_YOUR_LANGUAGES') def SELECTED_TAB_YOUR_LANGUAGES(self): @@ -562,7 +593,8 @@ class Config: self._ENABLE_TRANSCRIPTION_SEND = False self._ENABLE_TRANSCRIPTION_RECEIVE = False self._ENABLE_FOREGROUND = False - self._CHOICE_TRANSLATOR = translatorEngine[0] + self._CHOICE_INPUT_TRANSLATOR = translatorEngine[0] + self._CHOICE_OUTPUT_TRANSLATOR = translatorEngine[0] self._SOURCE_LANGUAGE = "Japanese" self._SOURCE_COUNTRY = "Japan" self._TARGET_LANGUAGE = "English" @@ -571,6 +603,16 @@ class Config: # Save Json Data ## Main Window self._SELECTED_TAB_NO = "1" + self._SELECTED_TAB_YOUR_TRANSLATOR_ENGINES = { + "1":translatorEngine[0], + "2":translatorEngine[0], + "3":translatorEngine[0], + } + self._SELECTED_TAB_TARGET_TRANSLATOR_ENGINES = { + "1":translatorEngine[0], + "2":translatorEngine[0], + "3":translatorEngine[0], + } self._SELECTED_TAB_YOUR_LANGUAGES = { "1":"Japanese\n(Japan)", "2":"Japanese\n(Japan)", diff --git a/controller.py b/controller.py index e6352411..fceebc2c 100644 --- a/controller.py +++ b/controller.py @@ -209,6 +209,12 @@ def messageBoxFocusOut(e): model.oscStopSendTyping() # func select languages +def initSetTranslateEngine(): + engine = config.SELECTED_TAB_YOUR_TRANSLATOR_ENGINES[config.SELECTED_TAB_NO] + config.CHOICE_INPUT_TRANSLATOR = engine + engine = config.SELECTED_TAB_TARGET_TRANSLATOR_ENGINES[config.SELECTED_TAB_NO] + config.CHOICE_OUTPUT_TRANSLATOR = engine + def initSetLanguageAndCountry(): select = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] language, country = model.getLanguageAndCountry(select) @@ -218,7 +224,18 @@ def initSetLanguageAndCountry(): language, country = model.getLanguageAndCountry(select) config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + +def setYourTranslateEngine(select): + engines = config.SELECTED_TAB_YOUR_TRANSLATOR_ENGINES + engines[config.SELECTED_TAB_NO] = select + config.SELECTED_TAB_YOUR_TRANSLATOR_ENGINES = engines + config.CHOICE_INPUT_TRANSLATOR = select + +def setTargetTranslateEngine(select): + engines = config.SELECTED_TAB_TARGET_TRANSLATOR_ENGINES + engines[config.SELECTED_TAB_NO] = select + config.SELECTED_TAB_TARGET_TRANSLATOR_ENGINES = engines + config.CHOICE_OUTPUT_TRANSLATOR = select def setYourLanguageAndCountry(select): languages = config.SELECTED_TAB_YOUR_LANGUAGES @@ -227,7 +244,6 @@ def setYourLanguageAndCountry(select): language, country = model.getLanguageAndCountry(select) config.SOURCE_LANGUAGE = language config.SOURCE_COUNTRY = country - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) view.printToTextbox_selectedYourLanguages(select) def setTargetLanguageAndCountry(select): @@ -237,7 +253,6 @@ def setTargetLanguageAndCountry(select): language, country = model.getLanguageAndCountry(select) config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) view.printToTextbox_selectedTargetLanguages(select) def swapYourLanguageAndTargetLanguage(): @@ -252,17 +267,26 @@ def swapYourLanguageAndTargetLanguage(): def callbackSelectedLanguagePresetTab(selected_tab_no): config.SELECTED_TAB_NO = selected_tab_no view.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) + + engines = config.SELECTED_TAB_YOUR_TRANSLATOR_ENGINES + engine = engines[config.SELECTED_TAB_NO] + config.CHOICE_INPUT_TRANSLATOR = engine + + engines = config.SELECTED_TAB_TARGET_TRANSLATOR_ENGINES + engine = engines[config.SELECTED_TAB_NO] + config.CHOICE_OUTPUT_TRANSLATOR = engine + languages = config.SELECTED_TAB_YOUR_LANGUAGES select = languages[config.SELECTED_TAB_NO] language, country = model.getLanguageAndCountry(select) config.SOURCE_LANGUAGE = language config.SOURCE_COUNTRY = country + languages = config.SELECTED_TAB_TARGET_LANGUAGES select = languages[config.SELECTED_TAB_NO] language, country = model.getLanguageAndCountry(select) config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) view.printToTextbox_changedLanguagePresetTab(config.SELECTED_TAB_NO) # command func @@ -392,7 +416,7 @@ def callbackSetUiLanguage(value): def callbackSetDeeplAuthkey(value): print("callbackSetDeeplAuthkey", str(value)) if len(value) == 39: - result = model.authenticationTranslator(choice_translator="DeepL_API", auth_key=value) + result = model.authenticationTranslatorDeepLAuthKey(auth_key=value) if result is True: key = value view.printToTextbox_AuthenticationSuccess() @@ -402,12 +426,10 @@ def callbackSetDeeplAuthkey(value): auth_keys = config.AUTH_KEYS auth_keys["DeepL_API"] = key config.AUTH_KEYS = auth_keys - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) elif len(value) == 0: auth_keys = config.AUTH_KEYS auth_keys["DeepL_API"] = None config.AUTH_KEYS = auth_keys - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) # Transcription Tab (Mic) def callbackSetMicHost(value): @@ -703,15 +725,17 @@ def createMainWindow(): # init config initSetConfigByExeArguments() + initSetTranslateEngine() initSetLanguageAndCountry() - if model.authenticationTranslator(config.CHOICE_TRANSLATOR, config.AUTH_KEYS[config.CHOICE_TRANSLATOR]) is False: - # error update Auth key - auth_keys = config.AUTH_KEYS - auth_keys[config.CHOICE_TRANSLATOR] = None - config.AUTH_KEYS = auth_keys - view.printToTextbox_AuthenticationError() - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + # if (config.SELECTED_TAB_YOUR_TRANSLATOR_ENGINES[config.SELECTED_TAB_NO] == "DeepL_API" or + # config.SELECTED_TAB_TARGET_TRANSLATOR_ENGINES[config.SELECTED_TAB_NO] == "DeepL_API"): + # if model.authenticationTranslator("DeepL_API", config.AUTH_KEYS["DeepL_API"]) is False: + # # error update Auth key + # auth_keys = config.AUTH_KEYS + # auth_keys["DeepL_API"] = None + # config.AUTH_KEYS = auth_keys + # view.printToTextbox_AuthenticationError() # set word filter model.addKeywords() diff --git a/model.py b/model.py index f7fa4642..30e6a852 100644 --- a/model.py +++ b/model.py @@ -83,13 +83,8 @@ class Model: del self.keyword_processor self.keyword_processor = KeywordProcessor() - def authenticationTranslator(self, choice_translator=None, auth_key=None): - if choice_translator is None: - choice_translator = config.CHOICE_TRANSLATOR - if auth_key is None: - auth_key = config.AUTH_KEYS[choice_translator] - - result = self.translator.authentication(choice_translator, auth_key) + def authenticationTranslatorDeepLAuthKey(self, auth_key): + result = self.translator.authenticationDeepLAuthKey(auth_key) return result def startLogger(self): @@ -123,25 +118,25 @@ class Model: country = parts[1][1:-1] return language, country - def findTranslationEngine(self, source_lang, target_lang): - compatible_engines = [] - for engine in translatorEngine: - source_languages = translation_lang.get(engine, {}).get("source", {}) - target_languages = translation_lang.get(engine, {}).get("target", {}) - if source_lang in source_languages and target_lang in target_languages: - compatible_engines.append(engine) - engine_name = compatible_engines[0] + # def findTranslationEngine(self, source_lang, target_lang): + # compatible_engines = [] + # for engine in translatorEngine: + # source_languages = translation_lang.get(engine, {}).get("source", {}) + # target_languages = translation_lang.get(engine, {}).get("target", {}) + # if source_lang in source_languages and target_lang in target_languages: + # compatible_engines.append(engine) + # engine_name = compatible_engines[0] - if engine_name == "DeepL" and config.AUTH_KEYS["DeepL_API"] is not None: - if self.authenticationTranslator(engine_name, config.AUTH_KEYS["DeepL_API"]) is True: - engine_name = "DeepL_API" - elif engine_name == "DeepL_API" and config.AUTH_KEYS["DeepL_API"] is None: - engine_name = "DeepL" + # if engine_name == "DeepL" and config.AUTH_KEYS["DeepL_API"] is not None: + # if self.authenticationTranslator(engine_name, config.AUTH_KEYS["DeepL_API"]) is True: + # engine_name = "DeepL_API" + # elif engine_name == "DeepL_API" and config.AUTH_KEYS["DeepL_API"] is None: + # engine_name = "DeepL" - return engine_name + # return engine_name def getInputTranslate(self, message): - translator_name=config.CHOICE_TRANSLATOR + translator_name=config.CHOICE_INPUT_TRANSLATOR source_language=config.SOURCE_LANGUAGE target_language=config.TARGET_LANGUAGE target_country = config.TARGET_COUNTRY @@ -157,19 +152,12 @@ class Model: target_language = "Portuguese European" else: target_language = "Portuguese Brazilian" - elif translator_name == "DeepL": + else: if target_language in ["English American", "English British"]: target_language = "English" elif target_language in ["Portuguese European", "Portuguese Brazilian"]: target_language = "Portuguese" - # translation = self.translator.translate_ctranslate2( - # translator_name=translator_name, - # source_language=source_language, - # target_language=target_language, - # message=message - # ) - translation = self.translator.translate( translator_name=translator_name, source_language=source_language, @@ -179,7 +167,7 @@ class Model: return translation def getOutputTranslate(self, message): - translator_name=config.CHOICE_TRANSLATOR + translator_name=config.CHOICE_OUTPUT_TRANSLATOR source_language=config.TARGET_LANGUAGE target_language=config.SOURCE_LANGUAGE target_country = config.SOURCE_COUNTRY @@ -195,18 +183,12 @@ class Model: target_language = "Portuguese European" else: target_language = "Portuguese Brazilian" - elif translator_name == "DeepL": + else: if target_language in ["English American", "English British"]: target_language = "English" elif target_language in ["Portuguese European", "Portuguese Brazilian"]: target_language = "Portuguese" - # translation = self.translator.translate_ctranslate2( - # translator_name=translator_name, - # source_language=source_language, - # target_language=target_language, - # message=message - # ) translation = self.translator.translate( translator_name=translator_name, source_language=source_language, diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index 496a9b44..371c907e 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -21,15 +21,13 @@ class Translator(): self.translator = ctranslate2.Translator(self.weight_path, device="cpu", device_index=0, compute_type="int8", inter_threads=1, intra_threads=4) self.tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer) - def authentication(self, translator_name, authkey=None): + def authenticationDeepLAuthKey(self, authkey): result = True - match translator_name: - case "DeepL_API": - try: - self.deepl_client = deepl_Translator(authkey) - self.deepl_client.translate_text(" ", target_lang="EN-US") - except Exception: - result = False + try: + self.deepl_client = deepl_Translator(authkey) + self.deepl_client.translate_text(" ", target_lang="EN-US") + except Exception: + result = False return result def translate(self, translator_name, source_language, target_language, message): From fa32d0ff87de3b6955d86d807923f57cea323aae Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 31 Dec 2023 00:36:50 +0900 Subject: [PATCH 14/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20?= =?UTF-8?q?=E6=96=87=E5=AD=97=E8=B5=B7=E3=81=93=E3=81=97=E3=81=A8=E7=BF=BB?= =?UTF-8?q?=E8=A8=B3=E3=82=A8=E3=83=B3=E3=82=B8=E3=83=B3=E3=81=AE=E7=B5=84?= =?UTF-8?q?=E3=82=8F=E3=81=9B=E3=81=AE=E6=9C=89=E5=8A=B9=E6=80=A7=E8=BE=9E?= =?UTF-8?q?=E6=9B=B8=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getDictTranslationEngineValidity --- model.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/model.py b/model.py index 30e6a852..f5d4c0d7 100644 --- a/model.py +++ b/model.py @@ -126,15 +126,32 @@ class Model: # if source_lang in source_languages and target_lang in target_languages: # compatible_engines.append(engine) # engine_name = compatible_engines[0] - # if engine_name == "DeepL" and config.AUTH_KEYS["DeepL_API"] is not None: # if self.authenticationTranslator(engine_name, config.AUTH_KEYS["DeepL_API"]) is True: # engine_name = "DeepL_API" # elif engine_name == "DeepL_API" and config.AUTH_KEYS["DeepL_API"] is None: # engine_name = "DeepL" - # return engine_name + def getDictTranslationEngineValidity(self): + ts_keys = transcription_lang.keys() + tl_keys = translation_lang.keys() + te_dict = {} + for ts_key in ts_keys: + te_dict[ts_key] = {} + for tl_key in tl_keys: + te_dict[ts_key][tl_key] = False + if ts_key in translation_lang[tl_key]["source"]: + te_dict[ts_key][tl_key] = True + + keys_to_remove = [] + for language, translation_dict in te_dict.items(): + if all(value is False for value in translation_dict.values()): + keys_to_remove.append(language) + for key in keys_to_remove: + del te_dict[key] + return te_dict + def getInputTranslate(self, message): translator_name=config.CHOICE_INPUT_TRANSLATOR source_language=config.SOURCE_LANGUAGE From aac8f08da96bfa6ad2a277a68fe33b5c9d4ddfba Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 4 Jan 2024 00:46:20 +0900 Subject: [PATCH 15/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20?= =?UTF-8?q?=E7=BF=BB=E8=A8=B3=E5=A4=B1=E6=95=97=E6=99=82=E3=81=AE=E3=83=95?= =?UTF-8?q?=E3=82=A7=E3=83=BC=E3=83=AB=E3=82=BB=E3=83=BC=E3=83=95=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/model.py b/model.py index f5d4c0d7..bf048737 100644 --- a/model.py +++ b/model.py @@ -187,7 +187,7 @@ class Model: translator_name=config.CHOICE_OUTPUT_TRANSLATOR source_language=config.TARGET_LANGUAGE target_language=config.SOURCE_LANGUAGE - target_country = config.SOURCE_COUNTRY + target_country=config.SOURCE_COUNTRY if translator_name == "DeepL_API": if target_language == "English": @@ -212,6 +212,15 @@ class Model: target_language=target_language, message=message ) + + # 翻訳失敗時のフェールセーフ処理 + if translation is False and "Filipino": + translation = self.translator.translate( + translator_name="CTranslate2", + source_language=config.TARGET_LANGUAGE, + target_language=config.SOURCE_LANGUAGE, + message=message + ) return translation def addKeywords(self): @@ -496,4 +505,4 @@ class Model: def notificationXSOverlay(self, message): xsoverlayForVRCT(content=f"{message}") -model = Model() +model = Model() \ No newline at end of file From 9e91be145e5792a6350f1aed1a0d3facfcc17ac8 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 4 Jan 2024 03:01:29 +0900 Subject: [PATCH 16/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20getLis?= =?UTF-8?q?tLanguageAndCountry=E3=82=92=E5=8B=95=E7=9A=84=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E5=8C=96=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit findTranslationEnginesで翻訳エンジンのリストを取得するように変更 --- model.py | 68 ++++++++++++++++++-------------------------------------- 1 file changed, 22 insertions(+), 46 deletions(-) diff --git a/model.py b/model.py index bf048737..72d7a0cb 100644 --- a/model.py +++ b/model.py @@ -45,15 +45,6 @@ class threadFnc(Thread): self.fnc(*self._args, **self._kwargs) class Model: - # Languages available for both transcription and translation - SUPPORTED_LANGUAGES = [ - 'Afrikaans', 'Arabic', 'Basque', 'Bulgarian', 'Catalan', 'Chinese', 'Croatian', - 'Czech', 'Danish', 'Dutch', 'English', 'Filipino', 'Finnish', 'French', 'German', - 'Greek', 'Hebrew', 'Hindi', 'Hungarian', 'Indonesian', 'Italian', 'Japanese', - 'Korean', 'Lithuanian', 'Malay', 'Norwegian', 'Polish', 'Portuguese', 'Romanian', - 'Russian', 'Serbian', 'Slovak', 'Slovenian', 'Spanish', 'Swedish', 'Thai', 'Turkish', - 'Ukrainian', 'Vietnamese' - ] _instance = None def __new__(cls): @@ -103,13 +94,23 @@ class Model: self.logger.disabled = True self.logger = None - @staticmethod - def getListLanguageAndCountry(): + def getListLanguageAndCountry(self): + transcription_langs = list(transcription_lang.keys()) + tl_keys = translation_lang.keys() + translation_langs = [] + for tl_key in tl_keys: + for lang in translation_lang[tl_key]["source"]: + translation_langs.append(lang) + for lang in translation_lang[tl_key]["target"]: + translation_langs.append(lang) + translation_langs = list(set(translation_langs)) + supported_langs = list(filter(lambda x: x in transcription_langs, translation_langs)) + langs = [] - for lang in model.SUPPORTED_LANGUAGES: + for lang in supported_langs: for country in transcription_lang[lang]: langs.append(f"{lang}\n({country})") - return langs + return sorted(langs) @staticmethod def getLanguageAndCountry(select): @@ -118,39 +119,14 @@ class Model: country = parts[1][1:-1] return language, country - # def findTranslationEngine(self, source_lang, target_lang): - # compatible_engines = [] - # for engine in translatorEngine: - # source_languages = translation_lang.get(engine, {}).get("source", {}) - # target_languages = translation_lang.get(engine, {}).get("target", {}) - # if source_lang in source_languages and target_lang in target_languages: - # compatible_engines.append(engine) - # engine_name = compatible_engines[0] - # if engine_name == "DeepL" and config.AUTH_KEYS["DeepL_API"] is not None: - # if self.authenticationTranslator(engine_name, config.AUTH_KEYS["DeepL_API"]) is True: - # engine_name = "DeepL_API" - # elif engine_name == "DeepL_API" and config.AUTH_KEYS["DeepL_API"] is None: - # engine_name = "DeepL" - # return engine_name - - def getDictTranslationEngineValidity(self): - ts_keys = transcription_lang.keys() - tl_keys = translation_lang.keys() - te_dict = {} - for ts_key in ts_keys: - te_dict[ts_key] = {} - for tl_key in tl_keys: - te_dict[ts_key][tl_key] = False - if ts_key in translation_lang[tl_key]["source"]: - te_dict[ts_key][tl_key] = True - - keys_to_remove = [] - for language, translation_dict in te_dict.items(): - if all(value is False for value in translation_dict.values()): - keys_to_remove.append(language) - for key in keys_to_remove: - del te_dict[key] - return te_dict + def findTranslationEngines(self, source_lang, target_lang): + compatible_engines = [] + for engine in translatorEngine: + source_languages = translation_lang.get(engine, {}).get("source", {}) + target_languages = translation_lang.get(engine, {}).get("target", {}) + if source_lang in source_languages and target_lang in target_languages: + compatible_engines.append(engine) + return compatible_engines def getInputTranslate(self, message): translator_name=config.CHOICE_INPUT_TRANSLATOR From 0742d635af70e13f6d6998d6184c2d6757eaf5f1 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 7 Jan 2024 01:43:50 +0900 Subject: [PATCH 17/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20DeepL?= =?UTF-8?q?=5FAPI=E3=81=AE=E8=AA=8D=E8=A8=BC=E5=87=A6=E7=90=86=E3=81=AE?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 3 --- controller.py | 15 +++++++-------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/config.py b/config.py index 93e70c2d..e206305d 100644 --- a/config.py +++ b/config.py @@ -649,9 +649,6 @@ class Config: self._OSC_PORT = 9000 self._AUTH_KEYS = { "DeepL_API": None, - "DeepL": None, - "Bing": None, - "Google": None, } self._WEIGHT_TYPE = "small" self._MESSAGE_FORMAT = "[message]([translation])" diff --git a/controller.py b/controller.py index fceebc2c..05c3cb91 100644 --- a/controller.py +++ b/controller.py @@ -728,14 +728,13 @@ def createMainWindow(): initSetTranslateEngine() initSetLanguageAndCountry() - # if (config.SELECTED_TAB_YOUR_TRANSLATOR_ENGINES[config.SELECTED_TAB_NO] == "DeepL_API" or - # config.SELECTED_TAB_TARGET_TRANSLATOR_ENGINES[config.SELECTED_TAB_NO] == "DeepL_API"): - # if model.authenticationTranslator("DeepL_API", config.AUTH_KEYS["DeepL_API"]) is False: - # # error update Auth key - # auth_keys = config.AUTH_KEYS - # auth_keys["DeepL_API"] = None - # config.AUTH_KEYS = auth_keys - # view.printToTextbox_AuthenticationError() + if config.AUTH_KEYS["DeepL_API"] is not None: + if model.authenticationTranslatorDeepLAuthKey("DeepL_API", config.AUTH_KEYS["DeepL_API"]) is False: + # error update Auth key + auth_keys = config.AUTH_KEYS + auth_keys["DeepL_API"] = None + config.AUTH_KEYS = auth_keys + view.printToTextbox_AuthenticationError() # set word filter model.addKeywords() From 82dde149ee70398b9abf53a1729c42b3f4c3b91d Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 7 Jan 2024 01:46:29 +0900 Subject: [PATCH 18/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20?= =?UTF-8?q?=E7=BF=BB=E8=A8=B3=E5=A4=B1=E6=95=97=E6=99=82=E3=81=ABCTranslat?= =?UTF-8?q?e2=E3=81=A7=E3=81=AE=E7=BF=BB=E8=A8=B3=E5=87=A6=E7=90=86?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 不要なコードを削除 --- model.py | 83 ++++++++------------ models/translation/translation_languages.py | 4 +- models/translation/translation_translator.py | 43 +++++----- 3 files changed, 58 insertions(+), 72 deletions(-) diff --git a/model.py b/model.py index 72d7a0cb..16fc086b 100644 --- a/model.py +++ b/model.py @@ -1,4 +1,3 @@ -import tempfile from zipfile import ZipFile from subprocess import Popen from os import makedirs as os_makedirs @@ -10,10 +9,9 @@ from logging import getLogger, FileHandler, Formatter, INFO from time import sleep from queue import Queue from threading import Thread, Event -from requests import get as requests_get, head as requests_head +from requests import get as requests_get import webbrowser -from tqdm import tqdm from flashtext import KeywordProcessor from models.translation.translation_translator import Translator from models.transcription.transcription_utils import getInputDevices, getDefaultOutputDevice @@ -68,7 +66,7 @@ class Model: def resetTranslator(self): del self.translator - self.translator = Translator(config.PATH_LOCAL) + self.translator = Translator(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHTS[config.WEIGHT_TYPE]) def resetKeywordProcessor(self): del self.keyword_processor @@ -101,8 +99,6 @@ class Model: for tl_key in tl_keys: for lang in translation_lang[tl_key]["source"]: translation_langs.append(lang) - for lang in translation_lang[tl_key]["target"]: - translation_langs.append(lang) translation_langs = list(set(translation_langs)) supported_langs = list(filter(lambda x: x in transcription_langs, translation_langs)) @@ -122,81 +118,70 @@ class Model: def findTranslationEngines(self, source_lang, target_lang): compatible_engines = [] for engine in translatorEngine: - source_languages = translation_lang.get(engine, {}).get("source", {}) - target_languages = translation_lang.get(engine, {}).get("target", {}) - if source_lang in source_languages and target_lang in target_languages: + languages = translation_lang.get(engine, {}).get("source", {}) + if source_lang in languages and target_lang in languages: compatible_engines.append(engine) + if "DeepL_API" in compatible_engines: + if self.translator.deepl_client is None: + compatible_engines.remove('DeepL_API') return compatible_engines - def getInputTranslate(self, message): + def getInputTranslate(self, message, fnc=None): translator_name=config.CHOICE_INPUT_TRANSLATOR source_language=config.SOURCE_LANGUAGE target_language=config.TARGET_LANGUAGE target_country = config.TARGET_COUNTRY - if translator_name == "DeepL_API": - if target_language == "English": - if target_country in ["United States", "Canada", "Philippines"]: - target_language = "English American" - else: - target_language = "English British" - elif target_language == "Portuguese": - if target_country in ["Portugal"]: - target_language = "Portuguese European" - else: - target_language = "Portuguese Brazilian" - else: - if target_language in ["English American", "English British"]: - target_language = "English" - elif target_language in ["Portuguese European", "Portuguese Brazilian"]: - target_language = "Portuguese" - translation = self.translator.translate( translator_name=translator_name, source_language=source_language, target_language=target_language, + target_country=target_country, message=message ) + + # 翻訳失敗時のフェールセーフ処理 + if translation is False: + translation = self.translator.translate( + translator_name="CTranslate2", + source_language=source_language, + target_language=target_language, + target_country=target_country, + message=message + ) + try: + fnc() + except Exception: + pass return translation - def getOutputTranslate(self, message): + def getOutputTranslate(self, message, fnc=None): translator_name=config.CHOICE_OUTPUT_TRANSLATOR source_language=config.TARGET_LANGUAGE target_language=config.SOURCE_LANGUAGE target_country=config.SOURCE_COUNTRY - if translator_name == "DeepL_API": - if target_language == "English": - if target_country in ["United States", "Canada", "Philippines"]: - target_language = "English American" - else: - target_language = "English British" - elif target_language == "Portuguese": - if target_country in ["Portugal"]: - target_language = "Portuguese European" - else: - target_language = "Portuguese Brazilian" - else: - if target_language in ["English American", "English British"]: - target_language = "English" - elif target_language in ["Portuguese European", "Portuguese Brazilian"]: - target_language = "Portuguese" - translation = self.translator.translate( translator_name=translator_name, source_language=source_language, target_language=target_language, + target_country=target_country, message=message ) - + # 翻訳失敗時のフェールセーフ処理 - if translation is False and "Filipino": + if translation is False: translation = self.translator.translate( translator_name="CTranslate2", - source_language=config.TARGET_LANGUAGE, - target_language=config.SOURCE_LANGUAGE, + source_language=source_language, + target_language=target_language, + target_country=target_country, message=message ) + try: + fnc() + except Exception: + pass return translation def addKeywords(self): diff --git a/models/translation/translation_languages.py b/models/translation/translation_languages.py index 959b9077..1103a5c0 100644 --- a/models/translation/translation_languages.py +++ b/models/translation/translation_languages.py @@ -360,7 +360,7 @@ dict_ctranslate2_languages = { 'Luxembourgish': 'lb', 'Myanmar': 'my', 'Tibetan': 'bo', - 'Tagalog': 'tl', + 'Filipino': 'tl', 'Malagasy': 'mg', 'Assamese': 'as', 'Tatar': 'tt', @@ -372,7 +372,7 @@ dict_ctranslate2_languages = { 'Sundanese': 'su' } -translation_lang["ctranslate2"] = { +translation_lang["CTranslate2"] = { "source":dict_ctranslate2_languages, "target":dict_ctranslate2_languages, } \ No newline at end of file diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index 371c907e..21710e2d 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -20,6 +20,7 @@ class Translator(): self.weight_path = os.path.join(path, "weight", directory_name) self.translator = ctranslate2.Translator(self.weight_path, device="cpu", device_index=0, compute_type="int8", inter_threads=1, intra_threads=4) self.tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer) + self.deepl_client = None def authenticationDeepLAuthKey(self, authkey): result = True @@ -27,10 +28,11 @@ class Translator(): self.deepl_client = deepl_Translator(authkey) self.deepl_client.translate_text(" ", target_lang="EN-US") except Exception: + self.deepl_client = None result = False return result - def translate(self, translator_name, source_language, target_language, message): + def translate(self, translator_name, source_language, target_language, target_country, message): try: result = "" source_language=translation_lang[translator_name]["source"][source_language] @@ -44,11 +46,24 @@ class Translator(): to_language=target_language, ) case "DeepL_API": - result = self.deepl_client.translate_text( - message, - source_lang=source_language, - target_lang=target_language, - ).text + if self.deepl_client is None: + result = False + else: + if target_language == "English": + if target_country in ["United States", "Canada", "Philippines"]: + target_language = "English American" + else: + target_language = "English British" + elif target_language == "Portuguese": + if target_country in ["Portugal"]: + target_language = "Portuguese European" + else: + target_language = "Portuguese Brazilian" + result = self.deepl_client.translate_text( + message, + source_lang=source_language, + target_lang=target_language, + ).text case "Google": result = other_web_Translator( query_text=message, @@ -82,18 +97,4 @@ class Translator(): with open('error.log', 'a') as f: traceback.print_exc(file=f) result = False - return result - - # def translate_ctranslate2(self, translator_name, source_language, target_language, message): - - # source_language=translation_lang["ctranslate2"]["source"][source_language] - # target_language=translation_lang["ctranslate2"]["target"][target_language] - - # self.tokenizer.src_lang = source_language - # source = self.tokenizer.convert_ids_to_tokens(self.tokenizer.encode(message)) - # target_prefix = [self.tokenizer.lang_code_to_token[target_language]] - # results = self.translator.translate_batch([source], target_prefix=[target_prefix]) - # target = results[0].hypotheses[0][1:] - - # result = self.tokenizer.decode(self.tokenizer.convert_tokens_to_ids(target)) - # return result \ No newline at end of file + return result \ No newline at end of file From fe3b38c5fddb3f67b611711ed495462f41ee1274 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 7 Jan 2024 01:50:50 +0900 Subject: [PATCH 19/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20?= =?UTF-8?q?=E7=BF=BB=E8=A8=B3=E8=87=AA=E4=BD=93=E3=81=8C=E5=A4=B1=E6=95=97?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=81=93=E3=81=A8=E3=81=8C=E3=81=AA=E3=81=8F?= =?UTF-8?q?=E3=81=AA=E3=81=A3=E3=81=9F=E3=81=AE=E3=81=A7=E3=83=95=E3=82=A7?= =?UTF-8?q?=E3=83=BC=E3=83=AB=E3=82=BB=E3=83=BC=E3=83=95=E3=81=AE=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ※別途、ポップアップにて翻訳エンジンが使用できなかった旨のメッセージを出す必要がある --- controller.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/controller.py b/controller.py index 05c3cb91..9c3bd9e0 100644 --- a/controller.py +++ b/controller.py @@ -34,10 +34,10 @@ def sendMicMessage(message): pass else: translation = model.getInputTranslate(message) - if translation is False: - config.ENABLE_TRANSLATION = False - translation = "" - view.translationEngineLimitErrorProcess() + # if translation is False: + # config.ENABLE_TRANSLATION = False + # translation = "" + # view.translationEngineLimitErrorProcess() if config.ENABLE_TRANSCRIPTION_SEND is True: if config.ENABLE_SEND_MESSAGE_TO_VRC is True: @@ -98,10 +98,10 @@ def receiveSpeakerMessage(message): pass else: translation = model.getOutputTranslate(message) - if translation is False: - config.ENABLE_TRANSLATION = False - translation = "" - view.translationEngineLimitErrorProcess() + # if translation is False: + # config.ENABLE_TRANSLATION = False + # translation = "" + # view.translationEngineLimitErrorProcess() if config.ENABLE_TRANSCRIPTION_RECEIVE is True: if config.ENABLE_NOTICE_XSOVERLAY is True: @@ -164,10 +164,10 @@ def sendChatMessage(message): pass else: translation = model.getInputTranslate(message) - if translation is False: - config.ENABLE_TRANSLATION = False - translation = "" - view.translationEngineLimitErrorProcess() + # if translation is False: + # config.ENABLE_TRANSLATION = False + # translation = "" + # view.translationEngineLimitErrorProcess() # send OSC message if config.ENABLE_SEND_MESSAGE_TO_VRC is True: From de66233b90dcbfe290e2449820fc219d4281ddd0 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 7 Jan 2024 02:05:59 +0900 Subject: [PATCH 20/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20?= =?UTF-8?q?=E4=B8=8D=E8=A6=81=E3=81=AA=E5=A4=89=E6=95=B0=E2=80=9Dtranslato?= =?UTF-8?q?rEngine=E2=80=9D=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 22 ++++++++++----------- model.py | 4 ++-- models/translation/translation_languages.py | 1 - 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/config.py b/config.py index e206305d..4dfae139 100644 --- a/config.py +++ b/config.py @@ -6,7 +6,7 @@ from json import dump as json_dump import tkinter as tk from tkinter import font from languages import selectable_languages -from models.translation.translation_languages import translatorEngine +from models.translation.translation_languages import translation_lang from models.transcription.transcription_utils import getInputDevices, getDefaultInputDevice from models.translation.utils import ctranslate2_weights from utils import generatePercentageStringsList, isUniqueStrings @@ -155,7 +155,7 @@ class Config: @CHOICE_INPUT_TRANSLATOR.setter def CHOICE_INPUT_TRANSLATOR(self, value): - if value in translatorEngine: + if value in list(translation_lang.keys()): self._CHOICE_INPUT_TRANSLATOR= value @property @@ -164,7 +164,7 @@ class Config: @CHOICE_OUTPUT_TRANSLATOR.setter def CHOICE_OUTPUT_TRANSLATOR(self, value): - if value in translatorEngine: + if value in list(translation_lang.keys()): self._CHOICE_OUTPUT_TRANSLATOR = value # Save Json Data @@ -593,8 +593,8 @@ class Config: self._ENABLE_TRANSCRIPTION_SEND = False self._ENABLE_TRANSCRIPTION_RECEIVE = False self._ENABLE_FOREGROUND = False - self._CHOICE_INPUT_TRANSLATOR = translatorEngine[0] - self._CHOICE_OUTPUT_TRANSLATOR = translatorEngine[0] + self._CHOICE_INPUT_TRANSLATOR = "CTranslate2" + self._CHOICE_OUTPUT_TRANSLATOR = "CTranslate2" self._SOURCE_LANGUAGE = "Japanese" self._SOURCE_COUNTRY = "Japan" self._TARGET_LANGUAGE = "English" @@ -604,14 +604,14 @@ class Config: ## Main Window self._SELECTED_TAB_NO = "1" self._SELECTED_TAB_YOUR_TRANSLATOR_ENGINES = { - "1":translatorEngine[0], - "2":translatorEngine[0], - "3":translatorEngine[0], + "1":"CTranslate2", + "2":"CTranslate2", + "3":"CTranslate2", } self._SELECTED_TAB_TARGET_TRANSLATOR_ENGINES = { - "1":translatorEngine[0], - "2":translatorEngine[0], - "3":translatorEngine[0], + "1":"CTranslate2", + "2":"CTranslate2", + "3":"CTranslate2", } self._SELECTED_TAB_YOUR_LANGUAGES = { "1":"Japanese\n(Japan)", diff --git a/model.py b/model.py index 16fc086b..ee72b973 100644 --- a/model.py +++ b/model.py @@ -20,7 +20,7 @@ from models.transcription.transcription_recorder import SelectedMicRecorder, Sel from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakeEnergyRecorder from models.transcription.transcription_transcriber import AudioTranscriber from models.xsoverlay.notification import xsoverlayForVRCT -from models.translation.translation_languages import translatorEngine, translation_lang +from models.translation.translation_languages import translation_lang from models.transcription.transcription_languages import transcription_lang from config import config @@ -117,7 +117,7 @@ class Model: def findTranslationEngines(self, source_lang, target_lang): compatible_engines = [] - for engine in translatorEngine: + for engine in list(translation_lang.keys()): languages = translation_lang.get(engine, {}).get("source", {}) if source_lang in languages and target_lang in languages: compatible_engines.append(engine) diff --git a/models/translation/translation_languages.py b/models/translation/translation_languages.py index 1103a5c0..44051f8a 100644 --- a/models/translation/translation_languages.py +++ b/models/translation/translation_languages.py @@ -1,4 +1,3 @@ -translatorEngine = ["DeepL", "DeepL_API", "Google", "Bing", "Papago"] translation_lang = {} dict_deepl_languages = { 'Arabic':'ar', From 0a410d2c1a992ec3a93dfa87e9e03d54f42aa987 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 9 Jan 2024 22:52:41 +0900 Subject: [PATCH 21/67] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Conflict=20:=20Con?= =?UTF-8?q?flict=E6=99=82=E3=81=AE=E4=BF=AE=E6=AD=A3=E6=BC=8F=E3=82=8C?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/config.py b/config.py index f462dbc2..7079fe2c 100644 --- a/config.py +++ b/config.py @@ -5,7 +5,6 @@ from json import load as json_load from json import dump as json_dump import tkinter as tk from tkinter import font -from languages import selectable_languages from models.translation.translation_languages import translation_lang from models.transcription.transcription_utils import getInputDevices, getDefaultInputDevice from models.translation.utils import ctranslate2_weights @@ -561,19 +560,6 @@ class Config: self._WEIGHT_TYPE = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - @property - @json_serializable('MESSAGE_FORMAT') - def MESSAGE_FORMAT(self): - return self._MESSAGE_FORMAT - - @MESSAGE_FORMAT.setter - def MESSAGE_FORMAT(self, value): - if isinstance(value, str): - if isUniqueStrings(["[message]", "[translation]"], value) is False: - value = "[message]([translation])" - self._MESSAGE_FORMAT = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - @property @json_serializable('ENABLE_AUTO_CLEAR_MESSAGE_BOX') def ENABLE_AUTO_CLEAR_MESSAGE_BOX(self): From 5f1eb7051617db6f0838e47e0844fd2cac636fd6 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 10 Jan 2024 15:37:56 +0900 Subject: [PATCH 22/67] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20Cransl?= =?UTF-8?q?ate2=E3=81=AE=E4=B8=8D=E8=A6=81=E3=81=AA=E3=82=B3=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/translation/translation_translator.py | 5 ----- models/translation/utils.py | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index 21710e2d..8c3e401b 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -6,11 +6,6 @@ from .translation_languages import translation_lang import ctranslate2 import transformers -TRANSLATE_MODELS = { - "small": "facebook/m2m100_418M", - "large": "facebook/m2m100_1.2B" -} - # Translator class Translator(): def __init__(self, path, weight_config): diff --git a/models/translation/utils.py b/models/translation/utils.py index c41fbb88..f1c40b9e 100644 --- a/models/translation/utils.py +++ b/models/translation/utils.py @@ -2,7 +2,7 @@ import tempfile from zipfile import ZipFile from os import path as os_path from os import makedirs as os_makedirs -from requests import get as requests_get, head as requests_head +from requests import get as requests_get from tqdm import tqdm from typing import Callable import hashlib From bf72e417d5dff691ef88156cf1bc42d49b2c6b11 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 10 Jan 2024 17:20:46 +0900 Subject: [PATCH 23/67] [Update] add some files to gitignore for comfortable development --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2223cde5..626bae32 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ VRCT.spec *.pyc logs/ .venv/ -weight/ \ No newline at end of file +weight/ +.vscode +error.log \ No newline at end of file From ae45ff3e4426612084925ac17cf3e1dfbff6cc77 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 11 Jan 2024 22:24:35 +0900 Subject: [PATCH 24/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20config?= =?UTF-8?q?.CTRANSLATE2=5FWEIGHTS=E3=81=8C=E4=B8=8D=E8=A6=81=E3=81=AA?= =?UTF-8?q?=E7=82=BA=E9=96=A2=E9=80=A3=E5=87=A6=E7=90=86=E3=82=92=E5=89=8A?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 8 +--- main.py | 2 +- model.py | 7 ++- models/translation/translation_translator.py | 45 ++++++++++++++------ models/translation/utils.py | 6 +-- 5 files changed, 41 insertions(+), 27 deletions(-) diff --git a/config.py b/config.py index 7079fe2c..be98a68f 100644 --- a/config.py +++ b/config.py @@ -7,7 +7,6 @@ import tkinter as tk from tkinter import font from models.translation.translation_languages import translation_lang from models.transcription.transcription_utils import getInputDevices, getDefaultInputDevice -from models.translation.utils import ctranslate2_weights from utils import generatePercentageStringsList, isUniqueStrings json_serializable_vars = {} @@ -95,10 +94,6 @@ class Config: def SELECTABLE_UI_LANGUAGES_DICT(self): return self._SELECTABLE_UI_LANGUAGES_DICT - @property - def CTRANSLATE2_WEIGHTS(self): - return self._CTRANSLATE2_WEIGHTS - @property def MAX_MIC_ENERGY_THRESHOLD(self): return self._MAX_MIC_ENERGY_THRESHOLD @@ -731,7 +726,6 @@ class Config: "ko": "한국어" # If you want to add a new language and key, please append it here. } - self._CTRANSLATE2_WEIGHTS = ctranslate2_weights self._MAX_MIC_ENERGY_THRESHOLD = 2000 self._MAX_SPEAKER_ENERGY_THRESHOLD = 4000 @@ -805,7 +799,7 @@ class Config: self._AUTH_KEYS = { "DeepL_API": None, } - self._WEIGHT_TYPE = "small" + self._WEIGHT_TYPE = "m2m100_418m" self._SEND_MESSAGE_FORMAT = "[message]" self._SEND_MESSAGE_FORMAT_WITH_T = "[message]([translation])" self._RECEIVED_MESSAGE_FORMAT = "[message]" diff --git a/main.py b/main.py index 00bd351a..f990a89d 100644 --- a/main.py +++ b/main.py @@ -9,7 +9,7 @@ if __name__ == "__main__": from config import config from models.translation.utils import downloadCTranslate2Weight - downloadCTranslate2Weight(config.PATH_LOCAL, config.WEIGHT_TYPE, config.CTRANSLATE2_WEIGHTS, splash.updateDownloadProgress) + downloadCTranslate2Weight(config.PATH_LOCAL, config.WEIGHT_TYPE, splash.updateDownloadProgress) import controller controller.createMainWindow() diff --git a/model.py b/model.py index 33c30973..185e6dcc 100644 --- a/model.py +++ b/model.py @@ -63,12 +63,11 @@ class Model: self.speaker_audio_recorder = None self.speaker_energy_recorder = None self.speaker_energy_plot_progressbar = None - self.translator = Translator(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHTS[config.WEIGHT_TYPE]) + self.translator = Translator(config.PATH_LOCAL, config.WEIGHT_TYPE) self.keyword_processor = KeywordProcessor() - def resetTranslator(self): - del self.translator - self.translator = Translator(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHTS[config.WEIGHT_TYPE]) + def updateTranslator(self): + self.translator.changeCTranslate2Model(config.PATH_LOCAL, config.WEIGHT_TYPE) def resetKeywordProcessor(self): del self.keyword_processor diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index 8c3e401b..47bd7dd1 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -2,20 +2,27 @@ import os from deepl import Translator as deepl_Translator from translators import translate_text as other_web_Translator from .translation_languages import translation_lang +from .utils import ctranslate2_weights import ctranslate2 import transformers # Translator class Translator(): - def __init__(self, path, weight_config): - self.translator_status = {} - directory_name = weight_config["directory_name"] - tokenizer = weight_config["tokenizer"] - self.weight_path = os.path.join(path, "weight", directory_name) - self.translator = ctranslate2.Translator(self.weight_path, device="cpu", device_index=0, compute_type="int8", inter_threads=1, intra_threads=4) - self.tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer) + def __init__(self, path, model_type): self.deepl_client = None + directory_name = ctranslate2_weights[model_type]["directory_name"] + tokenizer = ctranslate2_weights[model_type]["tokenizer"] + weight_path = os.path.join(path, "weight", directory_name) + self.ctranslate2_translator = ctranslate2.Translator( + weight_path, + device="cpu", + device_index=0, + compute_type="int8", + inter_threads=1, + intra_threads=4 + ) + self.ctranslate2_tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer) def authenticationDeepLAuthKey(self, authkey): result = True @@ -27,6 +34,20 @@ class Translator(): result = False return result + def changeCTranslate2Model(self, path, model_type): + directory_name = ctranslate2_weights[model_type]["directory_name"] + tokenizer = ctranslate2_weights[model_type]["tokenizer"] + weight_path = os.path.join(path, "weight", directory_name) + self.ctranslate2_translator = ctranslate2.Translator( + weight_path, + device="cpu", + device_index=0, + compute_type="int8", + inter_threads=1, + intra_threads=4 + ) + self.ctranslate2_tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer) + def translate(self, translator_name, source_language, target_language, target_country, message): try: result = "" @@ -81,12 +102,12 @@ class Translator(): to_language=target_language, ) case "CTranslate2": - self.tokenizer.src_lang = source_language - source = self.tokenizer.convert_ids_to_tokens(self.tokenizer.encode(message)) - target_prefix = [self.tokenizer.lang_code_to_token[target_language]] - results = self.translator.translate_batch([source], target_prefix=[target_prefix]) + self.ctranslate2_tokenizer.src_lang = source_language + source = self.ctranslate2_tokenizer.convert_ids_to_tokens(self.ctranslate2_tokenizer.encode(message)) + target_prefix = [self.ctranslate2_tokenizer.lang_code_to_token[target_language]] + results = self.ctranslate2_translator.translate_batch([source], target_prefix=[target_prefix]) target = results[0].hypotheses[0][1:] - result = self.tokenizer.decode(self.tokenizer.convert_tokens_to_ids(target)) + result = self.ctranslate2_tokenizer.decode(self.ctranslate2_tokenizer.convert_tokens_to_ids(target)) except Exception: import traceback with open('error.log', 'a') as f: diff --git a/models/translation/utils.py b/models/translation/utils.py index f1c40b9e..3b71f87e 100644 --- a/models/translation/utils.py +++ b/models/translation/utils.py @@ -8,7 +8,7 @@ from typing import Callable import hashlib ctranslate2_weights = { - "small": { # M2M-100 418M-parameter model + "m2m100_418m": { # M2M-100 418M-parameter model "url": "https://bit.ly/33fM1AO", "directory_name": "m2m100_418m", "tokenizer": "facebook/m2m100_418M", @@ -18,7 +18,7 @@ ctranslate2_weights = { "shared_vocabulary.txt": "bd440aa21b8ca3453fc792a0018a1f3fe68b3464aadddd4d16a4b72f73c86d8c", } }, - "large": { # M2M-100 1.2B-parameter model + "m2m100_12b": { # M2M-100 1.2B-parameter model "url": "https://bit.ly/3GYiaed", "directory_name": "m2m100_12b", "tokenizer": "facebook/m2m100_1.2b", @@ -39,7 +39,7 @@ def calculate_file_hash(file_path, block_size=65536): return hash_object.hexdigest() -def downloadCTranslate2Weight(path, weight_type="small", ctranslate2_weights=ctranslate2_weights, func=None): +def downloadCTranslate2Weight(path, weight_type="small", func=None): url = ctranslate2_weights[weight_type]["url"] filename = 'weight.zip' directory_name = 'weight' From 5e40fde99a19256daf7455b7974c2bbbbaca99e4 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 12 Jan 2024 13:33:11 +0900 Subject: [PATCH 25/67] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20tqdm?= =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/translation/utils.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/models/translation/utils.py b/models/translation/utils.py index 3b71f87e..2f60459b 100644 --- a/models/translation/utils.py +++ b/models/translation/utils.py @@ -3,7 +3,6 @@ from zipfile import ZipFile from os import path as os_path from os import makedirs as os_makedirs from requests import get as requests_get -from tqdm import tqdm from typing import Callable import hashlib @@ -39,7 +38,7 @@ def calculate_file_hash(file_path, block_size=65536): return hash_object.hexdigest() -def downloadCTranslate2Weight(path, weight_type="small", func=None): +def downloadCTranslate2Weight(path, weight_type="m2m100_418m", func=None): url = ctranslate2_weights[weight_type]["url"] filename = 'weight.zip' directory_name = 'weight' @@ -64,16 +63,13 @@ def downloadCTranslate2Weight(path, weight_type="small", func=None): with tempfile.TemporaryDirectory() as tmp_path: res = requests_get(url, stream=True) file_size = int(res.headers.get('content-length', 0)) - pbar = tqdm(total=file_size, unit="B", unit_scale=True) total_chunk = 0 with open(os_path.join(tmp_path, filename), 'wb') as file: for chunk in res.iter_content(chunk_size=1024*5): file.write(chunk) - pbar.update(len(chunk)) if isinstance(func, Callable): total_chunk += len(chunk) func(total_chunk/file_size) - pbar.close() with ZipFile(os_path.join(tmp_path, filename)) as zf: zf.extractall(os_path.join(current_directory, directory_name)) From 8c64b918394e62cd2ec0244e311bb1c170d789e4 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 13 Jan 2024 12:37:37 +0900 Subject: [PATCH 26/67] =?UTF-8?q?[Update]=20Main=20Window:=20(WIP=20UI)=20?= =?UTF-8?q?CTranslate2=E3=80=81=E5=86=85=E9=83=A8=E7=BF=BB=E8=A8=B3?= =?UTF-8?q?=E6=A9=9F=E8=83=BD=E3=81=AA=E3=81=A9=E3=81=AE=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=E3=81=AB=E3=82=88=E3=82=8A=E3=80=81=E3=83=A1=E3=82=A4=E3=83=B3?= =?UTF-8?q?=E7=94=BB=E9=9D=A2=E3=81=ABUI=E8=BF=BD=E5=8A=A0=E3=80=82?= =?UTF-8?q?=E3=83=87=E3=82=B6=E3=82=A4=E3=83=B3=E3=81=AF=E7=A2=BA=E5=AE=9A?= =?UTF-8?q?=E3=81=A7=E3=81=AF=E3=81=AA=E3=81=8F=E3=80=81UI=20Scaling?= =?UTF-8?q?=E6=9C=AA=E5=AF=BE=E5=BF=9C=E3=81=AA=E3=81=A9=E3=83=86=E3=82=B9?= =?UTF-8?q?=E3=83=88=E7=8A=B6=E6=85=8B=E3=81=A7=E3=81=99=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 19 ++++ locales/en.yml | 2 + locales/ja.yml | 2 + view.py | 21 ++++ vrct_gui/_CreateDropdownMenuWindow.py | 43 +++++-- .../createSidebarLanguagesSettings.py | 106 +++++++++++++++++- vrct_gui/ui_managers/UiScalingManager.py | 18 +-- vrct_gui/ui_utils/ui_utils.py | 11 ++ 8 files changed, 202 insertions(+), 20 deletions(-) diff --git a/controller.py b/controller.py index 00292d5c..7ef2f457 100644 --- a/controller.py +++ b/controller.py @@ -250,6 +250,9 @@ def messageBoxFocusOut(e): model.oscStopSendTyping() # func select languages +def getLatestSelectableTranslationEngines(): + return model.findTranslationEngines(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + def initSetTranslateEngine(): engine = config.SELECTED_TAB_YOUR_TRANSLATOR_ENGINES[config.SELECTED_TAB_NO] config.CHOICE_INPUT_TRANSLATOR = engine @@ -285,6 +288,8 @@ def setYourLanguageAndCountry(select): language, country = model.getLanguageAndCountry(select) config.SOURCE_LANGUAGE = language config.SOURCE_COUNTRY = country + view.updateSelectableTranslationEngineList(getLatestSelectableTranslationEngines()) + view.setGuiVariable_SelectedTranslationEngine(config.CHOICE_OUTPUT_TRANSLATOR) view.printToTextbox_selectedYourLanguages(select) def setTargetLanguageAndCountry(select): @@ -294,6 +299,8 @@ def setTargetLanguageAndCountry(select): language, country = model.getLanguageAndCountry(select) config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country + view.updateSelectableTranslationEngineList(getLatestSelectableTranslationEngines()) + view.setGuiVariable_SelectedTranslationEngine(config.CHOICE_OUTPUT_TRANSLATOR) view.printToTextbox_selectedTargetLanguages(select) def swapYourLanguageAndTargetLanguage(): @@ -329,6 +336,14 @@ def callbackSelectedLanguagePresetTab(selected_tab_no): config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country view.printToTextbox_changedLanguagePresetTab(config.SELECTED_TAB_NO) + view.updateSelectableTranslationEngineList(getLatestSelectableTranslationEngines()) + view.setGuiVariable_SelectedTranslationEngine(config.CHOICE_OUTPUT_TRANSLATOR) + +def callbackSelectedTranslationEngine(selected_translation_engine): + print("callbackSelectedTranslationEngine", selected_translation_engine) + setYourTranslateEngine(selected_translation_engine) + setTargetTranslateEngine(selected_translation_engine) + view.setGuiVariable_SelectedTranslationEngine(config.CHOICE_OUTPUT_TRANSLATOR) # command func def callbackToggleTranslation(is_turned_on): @@ -825,6 +840,7 @@ def createMainWindow(): initSetConfigByExeArguments() initSetTranslateEngine() initSetLanguageAndCountry() + view.updateSelectableTranslationEngineList(getLatestSelectableTranslationEngines()) if config.AUTH_KEYS["DeepL_API"] is not None: if model.authenticationTranslatorDeepLAuthKey("DeepL_API", config.AUTH_KEYS["DeepL_API"]) is False: @@ -876,6 +892,9 @@ def createMainWindow(): "callback_swap_languages": swapYourLanguageAndTargetLanguage, "callback_selected_language_preset_tab": callbackSelectedLanguagePresetTab, + + "callback_selected_translation_engine": callbackSelectedTranslationEngine, + "message_box_bind_Return": messageBoxPressKeyEnter, "message_box_bind_Any_KeyPress": messageBoxPressKeyAny, "message_box_bind_FocusIn": messageBoxFocusIn, diff --git a/locales/en.yml b/locales/en.yml index 2cad19ef..fdb5f4aa 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -9,6 +9,8 @@ main_window: both_direction_desc: Translate Each Other swap_button_label: Swap Languages target_language: Target Language + translator: Translator + translator_ctranslate2: Internal textbox_tab_all: All textbox_tab_sent: Sent diff --git a/locales/ja.yml b/locales/ja.yml index eb937747..72d79c41 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -9,6 +9,8 @@ main_window: both_direction_desc: 双方向に翻訳 swap_button_label: 言語を入れ替え target_language: 相手の言語 + translator: 翻訳エンジン + translator_ctranslate2: オフライン翻訳 textbox_tab_all: 全て textbox_tab_sent: 送信 diff --git a/view.py b/view.py index 546ab972..16b913dd 100644 --- a/view.py +++ b/view.py @@ -35,6 +35,8 @@ class View(): else: VERSION_TEXT=i18n.t("config_window.version", version=config.VERSION) + " (Speaker2Chatbox)" + self.TEXT_TRANSLATOR_CTRANSLATE2 = i18n.t("main_window.translator") + ": " + i18n.t("main_window.translator_ctranslate2") + self.settings = SimpleNamespace() theme = get_appearance_mode() if config.APPEARANCE_THEME == "System" else config.APPEARANCE_THEME all_ctm = ColorThemeManager(theme) @@ -163,6 +165,8 @@ class View(): IS_OPENED_SELECTABLE_TARGET_LANGUAGE_WINDOW=False, CALLBACK_SELECTED_TARGET_LANGUAGE=None, + VAR_SELECTED_TRANSLATION_ENGINE = StringVar(value="Translator: INIT"), + CALLBACK_SELECTED_TRANSLATION_ENGINE = None, VAR_LABEL_TEXTBOX_ALL=StringVar(value=i18n.t("main_window.textbox_tab_all")), VAR_LABEL_TEXTBOX_SENT=StringVar(value=i18n.t("main_window.textbox_tab_sent")), @@ -515,6 +519,7 @@ class View(): self.view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB = main_window_registers.get("callback_selected_language_preset_tab", None) + self.view_variable.CALLBACK_SELECTED_TRANSLATION_ENGINE = main_window_registers.get("callback_selected_translation_engine", None) def adjustedMessageBoxReturnFunction(_e): if self.view_variable.IS_ENTRY_MESSAGE_BOX_DISABLED is True: @@ -899,6 +904,15 @@ class View(): case "SPEAKER_OFF": vrct_gui.sls__box_target_language_speaker_status__enabled.place_forget() + def updateSelectableTranslationEngineList(self, selectable_translation_engines_list): + translation_dict = {item: item for item in selectable_translation_engines_list} + translation_dict["CTranslate2"] = self.TEXT_TRANSLATOR_CTRANSLATE2 + + vrct_gui.translation_engine_dropdown_menu_window.updateDropdownMenuValues( + dropdown_menu_widget_id="translation_engine_dropdown_menu", + dropdown_menu_values=translation_dict, + ) + # Config Window def enableConfigWindowCompactMode(self): @@ -1332,6 +1346,13 @@ class View(): # Set GuiVariable (view_variable) + def setGuiVariable_SelectedTranslationEngine(self, value): + if value == "CTranslate2": + self.view_variable.VAR_SELECTED_TRANSLATION_ENGINE.set(self.TEXT_TRANSLATOR_CTRANSLATE2) + value = self.TEXT_TRANSLATOR_CTRANSLATE2 + else: + self.view_variable.VAR_SELECTED_TRANSLATION_ENGINE.set(i18n.t("main_window.translator") + ": " + value) + def setGuiVariable_MicEnergyThreshold(self, value): self.view_variable.VAR_MIC_ENERGY_THRESHOLD__SLIDER.set(int(value)) self.view_variable.VAR_MIC_ENERGY_THRESHOLD__ENTRY.set(str(value)) diff --git a/vrct_gui/_CreateDropdownMenuWindow.py b/vrct_gui/_CreateDropdownMenuWindow.py index 0eade683..5ca598e0 100644 --- a/vrct_gui/_CreateDropdownMenuWindow.py +++ b/vrct_gui/_CreateDropdownMenuWindow.py @@ -1,9 +1,10 @@ +from typing import Union from types import SimpleNamespace from customtkinter import CTkToplevel, CTkFrame, CTkLabel, CTkFont from time import sleep -from .ui_utils import bindButtonReleaseFunction, bindEnterAndLeaveColor, bindButtonPressColor, getLatestHeight, applyUiScalingAndFixTheBugScrollBar, getLatestWidth, getLongestText, CustomizedCTkScrollableFrame +from .ui_utils import bindButtonReleaseFunction, bindEnterAndLeaveColor, bindButtonPressColor, getLatestHeight, applyUiScalingAndFixTheBugScrollBar, getLatestWidth, getLongestText, getLongestText_Dict, CustomizedCTkScrollableFrame from functools import partial from utils import isEven, makeEven @@ -90,7 +91,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): - def updateDropdownMenuValues(self, dropdown_menu_widget_id, dropdown_menu_values): + def updateDropdownMenuValues(self, dropdown_menu_widget_id, dropdown_menu_values:Union[dict, list],): self.dropdown_menu_widgets[dropdown_menu_widget_id].widget.destroy() self.createDropdownMenuBox( dropdown_menu_widget_id=dropdown_menu_widget_id, @@ -105,7 +106,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): ) - def createDropdownMenuBox(self, dropdown_menu_widget_id, dropdown_menu_values, command, wrapper_widget, attach_widget, dropdown_menu_min_width=None, dropdown_menu_height=None, max_display_length=None): + def createDropdownMenuBox(self, dropdown_menu_widget_id, dropdown_menu_values:Union[dict, list], command, wrapper_widget, attach_widget, dropdown_menu_min_width=None, dropdown_menu_height=None, max_display_length=None): self.attach_widget = attach_widget self.wrapper_widget = wrapper_widget @@ -166,9 +167,12 @@ class _CreateDropdownMenuWindow(CTkToplevel): self.dropdown_menu_container.grid_remove() - def _createDropdownMenuValues(self, dropdown_menu_widget_id, dropdown_menu_values, command): + def _createDropdownMenuValues(self, dropdown_menu_widget_id, dropdown_menu_values:Union[dict, list], command): + if isinstance(dropdown_menu_values, list): + longest_text = getLongestText(dropdown_menu_values) + elif isinstance(dropdown_menu_values, dict): + longest_text = getLongestText_Dict(dropdown_menu_values) - longest_text = getLongestText(dropdown_menu_values) self.dropdown_menu_values_wrapper = CTkFrame(self.scroll_frame_container, corner_radius=0, fg_color=self.window_bg_color) self.dropdown_menu_values_wrapper.grid(row=0, column=0, sticky="nsew") self.dropdown_menu_values_wrapper.grid_columnconfigure(0, weight=1) @@ -220,19 +224,31 @@ class _CreateDropdownMenuWindow(CTkToplevel): - row=0 - for dropdown_menu_value in dropdown_menu_values: + IS_LIST_TYPE = False + if isinstance(dropdown_menu_values, list): + for_in_values = dropdown_menu_values + IS_LIST_TYPE = True + elif isinstance(dropdown_menu_values, dict): + for_in_values = dropdown_menu_values.keys() + IS_LIST_TYPE = False + row=0 + for dropdown_menu_value in for_in_values: dropdown_menu_value_wrapper = CTkFrame(self.dropdown_menu_values_wrapper, corner_radius=0, fg_color=self.values_bg_color, width=0, height=0, cursor="hand2") dropdown_menu_value_wrapper.grid(row=row, column=0, pady=self.value_pady, sticky="nsew") setattr(self, f"{dropdown_menu_widget_id}__{row}", dropdown_menu_value_wrapper) + if IS_LIST_TYPE is True: + dropdown_menu_value_text = dropdown_menu_value + else: + dropdown_menu_value_text = dropdown_menu_values[dropdown_menu_value] + dropdown_menu_value_wrapper.grid_rowconfigure((0,2), weight=1) label_widget = CTkLabel( dropdown_menu_value_wrapper, - text=dropdown_menu_value, + text=dropdown_menu_value_text, height=0, corner_radius=0, font=CTkFont(family=self.settings.FONT_FAMILY, size=self.value_font_size, weight="normal"), @@ -252,7 +268,11 @@ class _CreateDropdownMenuWindow(CTkToplevel): command(value) self._withdraw() - callback = partial(optimizedCommand, dropdown_menu_value) + if IS_LIST_TYPE is True: + callback = partial(optimizedCommand, dropdown_menu_value_text) + else: + callback = partial(optimizedCommand, dropdown_menu_value) + bindButtonReleaseFunction([dropdown_menu_value_wrapper, label_widget], callback) row+=1 @@ -267,7 +287,10 @@ class _CreateDropdownMenuWindow(CTkToplevel): if self.active_dropdown_menu_widget is not None: - self.active_dropdown_menu_widget.grid_remove() + try: + self.active_dropdown_menu_widget.grid_remove() + except: + pass target_data = self.dropdown_menu_widgets[dropdown_menu_widget_id] self.attach_widget = target_data.attach_widget diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index cbe96b4e..e6c6c691 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -1,5 +1,7 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkImage +from ...._CreateDropdownMenuWindow import _CreateDropdownMenuWindow + from ....ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, switchActiveTabAndPassiveTab, switchTabsColor, createOptionMenuBox, bindButtonFunctionAndColor, bindEnterAndLeaveFunction from utils import callFunctionIfCallable @@ -336,4 +338,106 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): height=0, corner_radius=0, image=CTkImage(settings.image_file.HEADPHONES_ICON_DISABLED, size=settings.uism.SLS__BOX_TRANSCRIPTION_STATUS_IMAGE_SIZE), - ) \ No newline at end of file + ) + + + + + + sls__box_translation_optionmenu_wrapper = CTkFrame(main_window.sls__box_frame, corner_radius=0, fg_color=settings.ctm.SLS__BG_COLOR, width=0, height=0) + sls__box_translation_optionmenu_wrapper.grid(row=5, column=0, sticky="ew") + + sls__box_translation_optionmenu_wrapper.grid_columnconfigure((0,2), weight=1, minsize=settings.uism.SLS__BOX_ARROWS_SWAP_BUTTON_PADX) + sls__box_translation_optionmenu_wrapper.grid_columnconfigure(1, weight=1) + + + + main_window.translation_engine_dropdown_menu_window = _CreateDropdownMenuWindow( + settings=settings, + view_variable=main_window._view_variable, + + window_additional_y_pos=4, + window_border_width=1, + scrollbar_ipadx=(2,2), + scrollbar_width=16, + value_ipadx=(8,0), + value_ipady=(6,6), + value_pady=0, + value_font_size=14, + dropdown_menu_default_min_width=200, + + window_bg_color="#1f2022", + window_border_color="#7f8084", + values_bg_color="#323336", + values_hovered_bg_color="#4b4c4f", + values_clicked_bg_color="#292a2d", + values_text_color=settings.ctm.BASIC_TEXT_COLOR, + ) + + # main_window.translation_engine_dropdown_menu_window = _CreateDropdownMenuWindow( + # settings=settings.config_window, + # view_variable=main_window._view_variable, + + # window_additional_y_pos=settings.config_window.uism.SB__DROPDOWN_MENU_WINDOW_ADDITIONAL_Y_POS, + # window_border_width=settings.config_window.uism.SB__DROPDOWN_MENU_WINDOW_BORDER_WIDTH, + # scrollbar_ipadx=settings.config_window.uism.SB__DROPDOWN_MENU_SCROLLBAR_IPADX, + # scrollbar_width=settings.config_window.uism.SB__DROPDOWN_MENU_SCROLLBAR_WIDTH, + # value_ipadx=settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_IPADX, + # value_ipady=settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_IPADY, + # value_pady=settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_PADY, + # value_font_size=settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_FONT_SIZE, + # dropdown_menu_default_min_width=settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_DEFAULT_MIN_WIDTH, + + # window_bg_color=settings.config_window.ctm.SB__DROPDOWN_MENU_WINDOW_BG_COLOR, + # window_border_color=settings.config_window.ctm.SB__DROPDOWN_MENU_WINDOW_BORDER_COLOR, + # values_bg_color=settings.config_window.ctm.SB__DROPDOWN_MENU_BG_COLOR, + # values_hovered_bg_color=settings.config_window.ctm.SB__DROPDOWN_MENU_HOVERED_BG_COLOR, + # values_clicked_bg_color=settings.config_window.ctm.SB__DROPDOWN_MENU_CLICKED_BG_COLOR, + # values_text_color=settings.config_window.ctm.BASIC_TEXT_COLOR, + # ) + + + + + + + def adjustedCommand(value): + callFunctionIfCallable(view_variable.CALLBACK_SELECTED_TRANSLATION_ENGINE, value) + + main_window.translation_engine_dropdown_menu_window.createDropdownMenuBox( + dropdown_menu_widget_id="translation_engine_dropdown_menu", + dropdown_menu_values=[], + command=adjustedCommand, + wrapper_widget=main_window, + attach_widget=sls__box_translation_optionmenu_wrapper, + dropdown_menu_min_width=200, + ) + + + + + (sls__selected_translation_engine_box, optionmenu_label_widget, optionmenu_img_widget) = createOptionMenuBox( + parent_widget=sls__box_translation_optionmenu_wrapper, + optionmenu_bg_color=settings.ctm.SLS__BG_COLOR, + optionmenu_hovered_bg_color=settings.ctm.SLS__OPTIONMENU_HOVERED_BG_COLOR, + optionmenu_clicked_bg_color=settings.ctm.SLS__OPTIONMENU_CLICKED_BG_COLOR, + optionmenu_ipadx=(0,10), + optionmenu_ipady=6, + variable=view_variable.VAR_SELECTED_TRANSLATION_ENGINE, + font_family=settings.FONT_FAMILY, + font_size=12, + text_color=settings.ctm.LABELS_TEXT_COLOR, + image_file=settings.image_file.REFRESH_ICON.rotate(45), + image_size=(14,14), + # image_size=settings.uism.SLS__BOX_OPTION_MENU_ARROW_IMAGE_SIZE, + optionmenu_clicked_command=lambda _e: main_window.translation_engine_dropdown_menu_window.show( + dropdown_menu_widget_id="translation_engine_dropdown_menu" + ), + + optionmenu_position="center", + setattr_widget=main_window, + image_widget_attr_name="sls__arrow_img_select", + ) + sls__selected_translation_engine_box.grid(row=0, column=1, sticky="ew") + optionmenu_img_widget.grid_remove() + diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 2e02b1bf..64550683 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -66,7 +66,7 @@ class UiScalingManager(): # Sidebar # Sidebar Features self.main.SF__LOGO_MAX_SIZE = self._calculateUiSize(120) - self.main.SF__LOGO_PADY = (self._calculateUiSize(12),self._calculateUiSize(8)) + self.main.SF__LOGO_PADY = (self._calculateUiSize(10),self._calculateUiSize(6)) self.main.SF__LOGO_HEIGHT_FOR_ADJUSTMENT = (self._calculateUiSize(6)) self.main.SF__LABELS_IPADY = self._calculateUiSize(16) @@ -84,34 +84,34 @@ class UiScalingManager(): # Sidebar Quick Language Settings, SQLS - self.main.SLS__TITLE_FONT_SIZE = self._calculateUiSize(16) - self.main.SLS__TITLE_PADY = (self._calculateUiSize(12), self._calculateUiSize(6)) + self.main.SLS__TITLE_FONT_SIZE = self._calculateUiSize(14) + self.main.SLS__TITLE_PADY = (self._calculateUiSize(10), self._calculateUiSize(4)) self.main.SLS__PRESET_TAB_NUMBER_FONT_SIZE = self._calculateUiSize(16) self.main.SLS__PRESET_TAB_NUMBER_HEIGHT = self._calculateUiSize(30) self.main.SLS__PRESET_TAB_NUMBER_CORNER_RADIUS = self._calculateUiSize(6) self.main.SLS__PRESET_TAB_NUMBER_ADJUSTED_HEIGHT = self._calculateUiSize(36) - self.main.SLS__BOX_TRANSCRIPTION_STATUS_IMAGE_SIZE = self.dupTuple(self._calculateUiSize(14)) + self.main.SLS__BOX_TOP_PADY = self._calculateUiSize(12) + self.main.SLS__BOX_TRANSCRIPTION_STATUS_IMAGE_SIZE = self.dupTuple(self._calculateUiSize(14)) self.main.SLS__BOX_SECTION_TITLE_FONT_SIZE = self._calculateUiSize(16) self.main.SLS__BOX_SECTION_TITLE_BOTTOM_PADY = self._calculateUiSize(10) self.main.SLS__BOX_IPADX = self._calculateUiSize(10) - self.main.SLS__BOX_IPADY = (self._calculateUiSize(8),self._calculateUiSize(18)) + self.main.SLS__BOX_IPADY = (self._calculateUiSize(8),self._calculateUiSize(14)) self.main.SLS__BOX_OPTION_MENU_FONT_SIZE = self._calculateUiSize(14) self.main.SLS__BOX_OPTION_MENU_IPADY = self._calculateUiSize(2) - self.main.SLS__BOX_OPTION_MENU_ARROW_IMAGE_SIZE = (self._calculateUiSize(20), self._calculateUiSize(20)) + self.main.SLS__BOX_OPTION_MENU_ARROW_IMAGE_SIZE = self.dupTuple(self._calculateUiSize(20)) # self.main.SLS__BOX_OPTION_MENU_WIDTH = self._calculateUiSize(200) - self.main.SLS__BOX_ARROWS_PADY = self._calculateUiSize(4) + self.main.SLS__BOX_ARROWS_PADY = self._calculateUiSize(6) self.main.SLS__BOX_ARROWS_SWAP_BUTTON_CORNER_RADIUS = self._calculateUiSize(6) self.main.SLS__BOX_ARROWS_SWAP_BUTTON_PADX = self._calculateUiSize(20) self.main.SLS__BOX_ARROWS_SWAP_BUTTON_IPADX = self._calculateUiSize(8) - self.main.SLS__BOX_ARROWS_SWAP_BUTTON_IPADY = self._calculateUiSize(6) + self.main.SLS__BOX_ARROWS_SWAP_BUTTON_IPADY = self._calculateUiSize(4) self.main.SLS__BOX_ARROWS_IMAGE_SIZE = self.dupTuple(self._calculateUiSize(16)) self.main.SLS__BOX_ARROWS_DESC_FONT_SIZE = self._calculateUiSize(12) self.main.SLS__BOX_ARROWS_DESC_PADX = self._calculateUiSize(6) - self.main.SLS__BOX_TOP_PADY = self._calculateUiSize(16) self.main.SIDEBAR_CONFIG_BUTTON_CORNER_RADIUS = self._calculateUiSize(6) self.main.SIDEBAR_CONFIG_BUTTON_IMAGE_SIZE = self.main.SF__COMPACT_MODE_IMAGE_SIZE diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py index bd6e5a9c..9a8e992b 100644 --- a/vrct_gui/ui_utils/ui_utils.py +++ b/vrct_gui/ui_utils/ui_utils.py @@ -42,6 +42,17 @@ def getLongestText(text_list:list): longest_text = text return longest_text +def getLongestText_Dict(text_dict:dict): + max_length = 0 + longest_text = "" + + for key, text in text_dict.items(): + if len(text) > max_length: + max_length = len(text) + longest_text = text + + return longest_text + def calculateUiSize(default_size, scaling_float, is_allowed_odd:bool=False, is_zero_allowed:bool=False): size = int(default_size * scaling_float) size += 1 if not is_allowed_odd and size % 2 != 0 else 0 From d03330ea03ab90a9fe88afc254f2257b1a6801ca Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 14 Jan 2024 11:45:19 +0900 Subject: [PATCH 27/67] =?UTF-8?q?=20=F0=9F=91=8D[Update]=20Controller=20:?= =?UTF-8?q?=20UI=E3=81=AE=E7=BF=BB=E8=A8=B3=E3=82=A8=E3=83=B3=E3=82=B8?= =?UTF-8?q?=E3=83=B3=E3=81=A8Controller=E3=82=92=E6=8E=A5=E7=B6=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 55 +++++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/controller.py b/controller.py index 7ef2f457..d752ec34 100644 --- a/controller.py +++ b/controller.py @@ -65,11 +65,11 @@ def sendMicMessage(message): elif config.ENABLE_TRANSLATION is False: pass else: - translation = model.getInputTranslate(message) - # if translation is False: - # config.ENABLE_TRANSLATION = False - # translation = "" - # view.translationEngineLimitErrorProcess() + translation, success = model.getInputTranslate(message) + if success is False: + config.SOURCE_LANGUAGE = "CTranslate2" + config.TARGET_LANGUAGE = "CTranslate2" + updateTranslationEngineAndEngineList() if config.ENABLE_TRANSCRIPTION_SEND is True: if config.ENABLE_SEND_MESSAGE_TO_VRC is True: @@ -132,11 +132,11 @@ def receiveSpeakerMessage(message): if config.ENABLE_TRANSLATION is False: pass else: - translation = model.getOutputTranslate(message) - # if translation is False: - # config.ENABLE_TRANSLATION = False - # translation = "" - # view.translationEngineLimitErrorProcess() + translation, success = model.getOutputTranslate(message) + if success is False: + config.SOURCE_LANGUAGE = "CTranslate2" + config.TARGET_LANGUAGE = "CTranslate2" + updateTranslationEngineAndEngineList() if config.ENABLE_TRANSCRIPTION_RECEIVE is True: if config.ENABLE_NOTICE_XSOVERLAY is True: @@ -202,12 +202,11 @@ def sendChatMessage(message): if config.ENABLE_TRANSLATION is False: pass else: - translation = model.getInputTranslate(message) - # if translation is False: - # config.ENABLE_TRANSLATION = False - # translation = "" - # view.translationEngineLimitErrorProcess() - + translation, success = model.getInputTranslate(message) + if success is False: + config.SOURCE_LANGUAGE = "CTranslate2" + config.TARGET_LANGUAGE = "CTranslate2" + updateTranslationEngineAndEngineList() # send OSC message if config.ENABLE_SEND_MESSAGE_TO_VRC is True: if config.ENABLE_SEND_ONLY_TRANSLATED_MESSAGES is True: @@ -249,9 +248,15 @@ def messageBoxFocusOut(e): if config.ENABLE_SEND_MESSAGE_TO_VRC is True: model.oscStopSendTyping() -# func select languages -def getLatestSelectableTranslationEngines(): - return model.findTranslationEngines(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) +def updateTranslationEngineAndEngineList(): + engine = config.CHOICE_INPUT_TRANSLATOR + engines = model.findTranslationEngines(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + if engine not in engines: + engine = engines[0] + config.CHOICE_INPUT_TRANSLATOR = engine + config.CHOICE_OUTPUT_TRANSLATOR = engine + view.updateSelectableTranslationEngineList(engines) + view.setGuiVariable_SelectedTranslationEngine(engine) def initSetTranslateEngine(): engine = config.SELECTED_TAB_YOUR_TRANSLATOR_ENGINES[config.SELECTED_TAB_NO] @@ -288,8 +293,7 @@ def setYourLanguageAndCountry(select): language, country = model.getLanguageAndCountry(select) config.SOURCE_LANGUAGE = language config.SOURCE_COUNTRY = country - view.updateSelectableTranslationEngineList(getLatestSelectableTranslationEngines()) - view.setGuiVariable_SelectedTranslationEngine(config.CHOICE_OUTPUT_TRANSLATOR) + updateTranslationEngineAndEngineList() view.printToTextbox_selectedYourLanguages(select) def setTargetLanguageAndCountry(select): @@ -299,8 +303,7 @@ def setTargetLanguageAndCountry(select): language, country = model.getLanguageAndCountry(select) config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country - view.updateSelectableTranslationEngineList(getLatestSelectableTranslationEngines()) - view.setGuiVariable_SelectedTranslationEngine(config.CHOICE_OUTPUT_TRANSLATOR) + updateTranslationEngineAndEngineList() view.printToTextbox_selectedTargetLanguages(select) def swapYourLanguageAndTargetLanguage(): @@ -336,8 +339,7 @@ def callbackSelectedLanguagePresetTab(selected_tab_no): config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country view.printToTextbox_changedLanguagePresetTab(config.SELECTED_TAB_NO) - view.updateSelectableTranslationEngineList(getLatestSelectableTranslationEngines()) - view.setGuiVariable_SelectedTranslationEngine(config.CHOICE_OUTPUT_TRANSLATOR) + updateTranslationEngineAndEngineList() def callbackSelectedTranslationEngine(selected_translation_engine): print("callbackSelectedTranslationEngine", selected_translation_engine) @@ -497,6 +499,7 @@ def callbackSetDeeplAuthkey(value): auth_keys = config.AUTH_KEYS auth_keys["DeepL_API"] = None config.AUTH_KEYS = auth_keys + updateTranslationEngineAndEngineList() # Transcription Tab # Transcription (Mic) @@ -840,7 +843,7 @@ def createMainWindow(): initSetConfigByExeArguments() initSetTranslateEngine() initSetLanguageAndCountry() - view.updateSelectableTranslationEngineList(getLatestSelectableTranslationEngines()) + updateTranslationEngineAndEngineList() if config.AUTH_KEYS["DeepL_API"] is not None: if model.authenticationTranslatorDeepLAuthKey("DeepL_API", config.AUTH_KEYS["DeepL_API"]) is False: From 12bbec013a12f0f173eafef6eff4e71e0e07b1b5 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 14 Jan 2024 11:46:37 +0900 Subject: [PATCH 28/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20?= =?UTF-8?q?=E7=BF=BB=E8=A8=B3=E5=A4=B1=E6=95=97=E6=99=82=E3=81=AE=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/model.py b/model.py index 185e6dcc..3f89ad12 100644 --- a/model.py +++ b/model.py @@ -127,7 +127,8 @@ class Model: compatible_engines.remove('DeepL_API') return compatible_engines - def getInputTranslate(self, message, fnc=None): + def getInputTranslate(self, message): + translation_success_flag = True translator_name=config.CHOICE_INPUT_TRANSLATOR source_language=config.SOURCE_LANGUAGE target_language=config.TARGET_LANGUAGE @@ -143,6 +144,7 @@ class Model: # 翻訳失敗時のフェールセーフ処理 if translation is False: + translation_success_flag = False translation = self.translator.translate( translator_name="CTranslate2", source_language=source_language, @@ -150,13 +152,10 @@ class Model: target_country=target_country, message=message ) - try: - fnc() - except Exception: - pass - return translation + return translation, translation_success_flag - def getOutputTranslate(self, message, fnc=None): + def getOutputTranslate(self, message): + translation_success_flag = True translator_name=config.CHOICE_OUTPUT_TRANSLATOR source_language=config.TARGET_LANGUAGE target_language=config.SOURCE_LANGUAGE @@ -172,6 +171,7 @@ class Model: # 翻訳失敗時のフェールセーフ処理 if translation is False: + translation_success_flag = False translation = self.translator.translate( translator_name="CTranslate2", source_language=source_language, @@ -179,11 +179,7 @@ class Model: target_country=target_country, message=message ) - try: - fnc() - except Exception: - pass - return translation + return translation, translation_success_flag def addKeywords(self): for f in config.INPUT_MIC_WORD_FILTER: From 3783a0fff2459f70f1fba8bd6c80848b3c307552 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 14 Jan 2024 12:03:05 +0900 Subject: [PATCH 29/67] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Controller=20:=20?= =?UTF-8?q?=E5=A4=89=E6=95=B0=E8=A8=AD=E5=AE=9A=E9=96=93=E9=81=95=E3=81=84?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/controller.py b/controller.py index d752ec34..72bbeb04 100644 --- a/controller.py +++ b/controller.py @@ -67,8 +67,8 @@ def sendMicMessage(message): else: translation, success = model.getInputTranslate(message) if success is False: - config.SOURCE_LANGUAGE = "CTranslate2" - config.TARGET_LANGUAGE = "CTranslate2" + config.CHOICE_INPUT_TRANSLATOR = "CTranslate2" + config.CHOICE_OUTPUT_TRANSLATOR = "CTranslate2" updateTranslationEngineAndEngineList() if config.ENABLE_TRANSCRIPTION_SEND is True: @@ -134,8 +134,8 @@ def receiveSpeakerMessage(message): else: translation, success = model.getOutputTranslate(message) if success is False: - config.SOURCE_LANGUAGE = "CTranslate2" - config.TARGET_LANGUAGE = "CTranslate2" + config.CHOICE_INPUT_TRANSLATOR = "CTranslate2" + config.CHOICE_OUTPUT_TRANSLATOR = "CTranslate2" updateTranslationEngineAndEngineList() if config.ENABLE_TRANSCRIPTION_RECEIVE is True: @@ -204,8 +204,8 @@ def sendChatMessage(message): else: translation, success = model.getInputTranslate(message) if success is False: - config.SOURCE_LANGUAGE = "CTranslate2" - config.TARGET_LANGUAGE = "CTranslate2" + config.CHOICE_INPUT_TRANSLATOR = "CTranslate2" + config.CHOICE_OUTPUT_TRANSLATOR = "CTranslate2" updateTranslationEngineAndEngineList() # send OSC message if config.ENABLE_SEND_MESSAGE_TO_VRC is True: From 135f634c36bc2792bee87e8e1b99309094609ce6 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 14 Jan 2024 12:08:58 +0900 Subject: [PATCH 30/67] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Controller=20:=20D?= =?UTF-8?q?eepL=20AuthKey=E3=81=AE=E8=AA=8D=E8=A8=BC=E3=82=BF=E3=82=A4?= =?UTF-8?q?=E3=83=9F=E3=83=B3=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/controller.py b/controller.py index 72bbeb04..22d9eb2d 100644 --- a/controller.py +++ b/controller.py @@ -843,16 +843,18 @@ def createMainWindow(): initSetConfigByExeArguments() initSetTranslateEngine() initSetLanguageAndCountry() - updateTranslationEngineAndEngineList() if config.AUTH_KEYS["DeepL_API"] is not None: - if model.authenticationTranslatorDeepLAuthKey("DeepL_API", config.AUTH_KEYS["DeepL_API"]) is False: + if model.authenticationTranslatorDeepLAuthKey(auth_key=config.AUTH_KEYS["DeepL_API"]) is False: # error update Auth key auth_keys = config.AUTH_KEYS auth_keys["DeepL_API"] = None config.AUTH_KEYS = auth_keys view.printToTextbox_AuthenticationError() + # set Translation Engine + updateTranslationEngineAndEngineList() + # set word filter model.addKeywords() From d65829175ea630ca3f6682f207ba1688e9b96eb7 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 14 Jan 2024 13:13:43 +0900 Subject: [PATCH 31/67] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20deepl?= =?UTF-8?q?=20authkey=20=E5=89=8A=E9=99=A4=E6=99=82=E3=81=AB=20=E7=BF=BB?= =?UTF-8?q?=E8=A8=B3=E3=82=A8=E3=83=B3=E3=82=B8=E3=83=B3=E3=81=AE"DeepL=5F?= =?UTF-8?q?API"=E3=81=8C=E5=89=8A=E9=99=A4=E3=81=95=E3=82=8C=E3=81=AA?= =?UTF-8?q?=E3=81=84=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model.py b/model.py index 3f89ad12..01277bb5 100644 --- a/model.py +++ b/model.py @@ -123,7 +123,7 @@ class Model: if source_lang in languages and target_lang in languages: compatible_engines.append(engine) if "DeepL_API" in compatible_engines: - if self.translator.deepl_client is None: + if config.AUTH_KEYS["DeepL_API"] is None: compatible_engines.remove('DeepL_API') return compatible_engines From 6cd56991c35635e827f2580d3b7bb4d182f699be Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 14 Jan 2024 19:11:09 +0900 Subject: [PATCH 32/67] =?UTF-8?q?[bugfix/chore]=20Main=20Window:=20Selecta?= =?UTF-8?q?ble=20Translation=20Engines.=20=E3=83=BB=E3=83=89=E3=83=AD?= =?UTF-8?q?=E3=83=83=E3=83=97=E3=83=80=E3=82=A6=E3=83=B3=E3=83=A1=E3=83=8B?= =?UTF-8?q?=E3=83=A5=E3=83=BC=E3=81=AE=E6=96=87=E8=A8=80=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E3=80=82=EF=BC=88CTranslate2=E3=81=AE=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E3=81=A8=E3=80=81=E3=83=87=E3=83=95=E3=82=A9?= =?UTF-8?q?=E3=83=AB=E3=83=88=E3=81=A7=E3=81=82=E3=82=8B=E6=97=A8=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0=EF=BC=89=20=E3=83=BB=E5=B9=85=E3=81=8C?= =?UTF-8?q?=E8=A6=8F=E5=AE=9A=E5=80=A4=E3=82=88=E3=82=8A=E8=B6=85=E3=81=88?= =?UTF-8?q?=E3=81=9F=E6=99=82=E3=81=AE=E5=87=A6=E7=90=86=E3=81=A7=E3=82=A8?= =?UTF-8?q?=E3=83=A9=E3=83=BC=E3=81=8C=E7=99=BA=E7=94=9F=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 2 +- locales/ja.yml | 2 +- view.py | 8 +++++- .../createSidebarLanguagesSettings.py | 22 ---------------- vrct_gui/ui_managers/UiScalingManager.py | 3 ++- vrct_gui/vrct_gui.py | 25 ++++++++++++++++++- 6 files changed, 35 insertions(+), 27 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index fdb5f4aa..d967956d 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -10,7 +10,7 @@ main_window: swap_button_label: Swap Languages target_language: Target Language translator: Translator - translator_ctranslate2: Internal + translator_ctranslate2: Internal (Default) textbox_tab_all: All textbox_tab_sent: Sent diff --git a/locales/ja.yml b/locales/ja.yml index 72d79c41..4443c822 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -10,7 +10,7 @@ main_window: swap_button_label: 言語を入れ替え target_language: 相手の言語 translator: 翻訳エンジン - translator_ctranslate2: オフライン翻訳 + translator_ctranslate2: オフライン翻訳(デフォルト) textbox_tab_all: 全て textbox_tab_sent: 送信 diff --git a/view.py b/view.py index 16b913dd..76089ece 100644 --- a/view.py +++ b/view.py @@ -89,6 +89,12 @@ class View(): **common_args ) + self.settings.dropdown_menu_window = SimpleNamespace( + # ctm=all_ctm.dropdown_menu_window, + uism=all_uism.dropdown_menu_window, + **common_args + ) + self.view_variable = SimpleNamespace( # Common CALLBACK_RESTART_SOFTWARE=None, @@ -906,7 +912,7 @@ class View(): def updateSelectableTranslationEngineList(self, selectable_translation_engines_list): translation_dict = {item: item for item in selectable_translation_engines_list} - translation_dict["CTranslate2"] = self.TEXT_TRANSLATOR_CTRANSLATE2 + translation_dict["CTranslate2"] = i18n.t("main_window.translator_ctranslate2") vrct_gui.translation_engine_dropdown_menu_window.updateDropdownMenuValues( dropdown_menu_widget_id="translation_engine_dropdown_menu", diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index e6c6c691..0c3a8e25 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -352,28 +352,6 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): - main_window.translation_engine_dropdown_menu_window = _CreateDropdownMenuWindow( - settings=settings, - view_variable=main_window._view_variable, - - window_additional_y_pos=4, - window_border_width=1, - scrollbar_ipadx=(2,2), - scrollbar_width=16, - value_ipadx=(8,0), - value_ipady=(6,6), - value_pady=0, - value_font_size=14, - dropdown_menu_default_min_width=200, - - window_bg_color="#1f2022", - window_border_color="#7f8084", - values_bg_color="#323336", - values_hovered_bg_color="#4b4c4f", - values_clicked_bg_color="#292a2d", - values_text_color=settings.ctm.BASIC_TEXT_COLOR, - ) - # main_window.translation_engine_dropdown_menu_window = _CreateDropdownMenuWindow( # settings=settings.config_window, # view_variable=main_window._view_variable, diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 64550683..8fea6a2c 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -13,6 +13,7 @@ class UiScalingManager(): self.main_window_cover = SimpleNamespace() self.error_message_window = SimpleNamespace() self.confirmation_modal = SimpleNamespace() + self.dropdown_menu_window = SimpleNamespace() self._calculatedUiSizes() @@ -170,7 +171,7 @@ class UiScalingManager(): self.confirmation_modal.BUTTONS_IPADY = self._calculateUiSize(6) # Dropdown Menu Window - self.config_window.MARGIN_WIDTH = self._calculateUiSize(16) + self.dropdown_menu_window.MARGIN_WIDTH = self._calculateUiSize(16) # Config Window self.config_window.DEFAULT_WIDTH = self._calculateUiSize(1080) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 7c0bdc7f..7a4970c4 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -71,14 +71,37 @@ class VRCT_GUI(CTk): self.settings = settings self._view_variable = view_variable + self.translation_engine_dropdown_menu_window = _CreateDropdownMenuWindow( + settings=settings.dropdown_menu_window, + view_variable=self._view_variable, + + window_additional_y_pos=4, + window_border_width=1, + scrollbar_ipadx=(2,2), + scrollbar_width=16, + value_ipadx=(8,0), + value_ipady=(6,6), + value_pady=0, + value_font_size=14, + dropdown_menu_default_min_width=200, + + window_bg_color="#1f2022", + window_border_color="#7f8084", + values_bg_color="#323336", + values_hovered_bg_color="#4b4c4f", + values_clicked_bg_color="#292a2d", + values_text_color=settings.main.ctm.BASIC_TEXT_COLOR, + ) + createMainWindowWidgets( vrct_gui=self, settings=self.settings.main, view_variable=self._view_variable ) + # For Config Window self.dropdown_menu_window = _CreateDropdownMenuWindow( - settings=self.settings.config_window, + settings=self.settings.dropdown_menu_window, view_variable=self._view_variable, window_additional_y_pos=self.settings.config_window.uism.SB__DROPDOWN_MENU_WINDOW_ADDITIONAL_Y_POS, From f2321fcbc43037da9d4d805d42a6cb22c5c6b012 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 15 Jan 2024 12:05:06 +0900 Subject: [PATCH 33/67] =?UTF-8?q?[Update]=20Main=20Window:=20Selectable=20?= =?UTF-8?q?Translation=20Engines.=20=E3=83=BBUI=E8=AA=BF=E6=95=B4=E3=80=81?= =?UTF-8?q?=E6=96=87=E8=A8=80=E5=A4=89=E6=9B=B4=E3=80=82=E6=97=A5=E6=9C=AC?= =?UTF-8?q?=E8=AA=9E=E3=81=AF=E5=B9=85=E3=82=92=E5=8F=96=E3=82=8A=E3=81=99?= =?UTF-8?q?=E3=81=8E=E3=82=8B=E3=81=AE=E3=81=A7=E3=80=81"=EF=BC=88?= =?UTF-8?q?=E3=83=87=E3=83=95=E3=82=A9=E3=83=AB=E3=83=88=EF=BC=89"?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=82=92"(Default)"=E3=81=A8=E8=8B=B1?= =?UTF-8?q?=E8=AA=9E=E8=A1=A8=E8=A8=98=E3=81=AB=E3=80=82=E4=BB=96=E3=81=AE?= =?UTF-8?q?=E8=A8=80=E8=AA=9E=E3=81=A8=E3=81=AE=E5=85=BC=E3=81=AD=E5=90=88?= =?UTF-8?q?=E3=81=84=E3=82=82=E3=81=82=E3=82=8B=E3=81=AE=E3=81=A7=E3=80=81?= =?UTF-8?q?=E4=BB=8A=E3=81=AF=E3=81=A8=E3=82=8A=E3=81=82=E3=81=88=E3=81=9A?= =?UTF-8?q?locales=E3=81=AEyml=E3=81=AB=E3=81=9D=E3=82=8C=E3=81=9E?= =?UTF-8?q?=E3=82=8C=E7=9B=B4=E6=8E=A5=E8=A8=98=E8=BF=B0=E3=80=82=20?= =?UTF-8?q?=E3=83=BBUI=20Scaling=E5=AF=BE=E5=BF=9C=20=E3=83=BBUI=20Theme(L?= =?UTF-8?q?ight)=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/ja.yml | 2 +- .../createSidebarLanguagesSettings.py | 61 ++++--------------- vrct_gui/ui_managers/Themes/_darkTheme.py | 5 ++ vrct_gui/ui_managers/Themes/_lightTheme.py | 5 ++ vrct_gui/ui_managers/UiScalingManager.py | 19 +++++- vrct_gui/ui_utils/ui_utils.py | 49 +++++++++++++++ vrct_gui/vrct_gui.py | 30 ++++----- 7 files changed, 106 insertions(+), 65 deletions(-) diff --git a/locales/ja.yml b/locales/ja.yml index 4443c822..25748b8e 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -10,7 +10,7 @@ main_window: swap_button_label: 言語を入れ替え target_language: 相手の言語 translator: 翻訳エンジン - translator_ctranslate2: オフライン翻訳(デフォルト) + translator_ctranslate2: オフライン翻訳 (Default) textbox_tab_all: 全て textbox_tab_sent: 送信 diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index 0c3a8e25..f529898f 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -2,7 +2,7 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkImage from ...._CreateDropdownMenuWindow import _CreateDropdownMenuWindow -from ....ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, switchActiveTabAndPassiveTab, switchTabsColor, createOptionMenuBox, bindButtonFunctionAndColor, bindEnterAndLeaveFunction +from ....ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, switchActiveTabAndPassiveTab, switchTabsColor, createOptionMenuBox, bindButtonFunctionAndColor, bindEnterAndLeaveFunction, createLabelButton from utils import callFunctionIfCallable @@ -345,39 +345,13 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): sls__box_translation_optionmenu_wrapper = CTkFrame(main_window.sls__box_frame, corner_radius=0, fg_color=settings.ctm.SLS__BG_COLOR, width=0, height=0) - sls__box_translation_optionmenu_wrapper.grid(row=5, column=0, sticky="ew") + sls__box_translation_optionmenu_wrapper.grid(row=5, column=0, pady=settings.uism.SLS__SELECTABLE_TRANSLATION_PADY, sticky="ew") - sls__box_translation_optionmenu_wrapper.grid_columnconfigure((0,2), weight=1, minsize=settings.uism.SLS__BOX_ARROWS_SWAP_BUTTON_PADX) + sls__box_translation_optionmenu_wrapper.grid_columnconfigure((0,2), weight=0, minsize=settings.uism.SLS__SELECTABLE_TRANSLATION_MIN_PADX) sls__box_translation_optionmenu_wrapper.grid_columnconfigure(1, weight=1) - # main_window.translation_engine_dropdown_menu_window = _CreateDropdownMenuWindow( - # settings=settings.config_window, - # view_variable=main_window._view_variable, - - # window_additional_y_pos=settings.config_window.uism.SB__DROPDOWN_MENU_WINDOW_ADDITIONAL_Y_POS, - # window_border_width=settings.config_window.uism.SB__DROPDOWN_MENU_WINDOW_BORDER_WIDTH, - # scrollbar_ipadx=settings.config_window.uism.SB__DROPDOWN_MENU_SCROLLBAR_IPADX, - # scrollbar_width=settings.config_window.uism.SB__DROPDOWN_MENU_SCROLLBAR_WIDTH, - # value_ipadx=settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_IPADX, - # value_ipady=settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_IPADY, - # value_pady=settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_PADY, - # value_font_size=settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_FONT_SIZE, - # dropdown_menu_default_min_width=settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_DEFAULT_MIN_WIDTH, - - # window_bg_color=settings.config_window.ctm.SB__DROPDOWN_MENU_WINDOW_BG_COLOR, - # window_border_color=settings.config_window.ctm.SB__DROPDOWN_MENU_WINDOW_BORDER_COLOR, - # values_bg_color=settings.config_window.ctm.SB__DROPDOWN_MENU_BG_COLOR, - # values_hovered_bg_color=settings.config_window.ctm.SB__DROPDOWN_MENU_HOVERED_BG_COLOR, - # values_clicked_bg_color=settings.config_window.ctm.SB__DROPDOWN_MENU_CLICKED_BG_COLOR, - # values_text_color=settings.config_window.ctm.BASIC_TEXT_COLOR, - # ) - - - - - def adjustedCommand(value): callFunctionIfCallable(view_variable.CALLBACK_SELECTED_TRANSLATION_ENGINE, value) @@ -388,34 +362,25 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): command=adjustedCommand, wrapper_widget=main_window, attach_widget=sls__box_translation_optionmenu_wrapper, - dropdown_menu_min_width=200, + dropdown_menu_min_width=settings.uism.SIDEBAR_MIN_WIDTH, ) - - - - (sls__selected_translation_engine_box, optionmenu_label_widget, optionmenu_img_widget) = createOptionMenuBox( + (sls__selected_translation_engine_box, label_button_label_widget) = createLabelButton( parent_widget=sls__box_translation_optionmenu_wrapper, - optionmenu_bg_color=settings.ctm.SLS__BG_COLOR, - optionmenu_hovered_bg_color=settings.ctm.SLS__OPTIONMENU_HOVERED_BG_COLOR, - optionmenu_clicked_bg_color=settings.ctm.SLS__OPTIONMENU_CLICKED_BG_COLOR, - optionmenu_ipadx=(0,10), - optionmenu_ipady=6, + label_button_bg_color=settings.ctm.SLS__BG_COLOR, + label_button_hovered_bg_color=settings.ctm.SLS__OPTIONMENU_HOVERED_BG_COLOR, + label_button_clicked_bg_color=settings.ctm.SLS__OPTIONMENU_CLICKED_BG_COLOR, + label_button_ipadx=settings.uism.SLS__SELECTABLE_TRANSLATION_IPADX, + label_button_ipady=settings.uism.SLS__SELECTABLE_TRANSLATION_IPADY, variable=view_variable.VAR_SELECTED_TRANSLATION_ENGINE, font_family=settings.FONT_FAMILY, - font_size=12, + font_size=settings.uism.SLS__SELECTABLE_TRANSLATION_FONT_SIZE, text_color=settings.ctm.LABELS_TEXT_COLOR, - image_file=settings.image_file.REFRESH_ICON.rotate(45), - image_size=(14,14), - # image_size=settings.uism.SLS__BOX_OPTION_MENU_ARROW_IMAGE_SIZE, - optionmenu_clicked_command=lambda _e: main_window.translation_engine_dropdown_menu_window.show( + label_button_clicked_command=lambda _e: main_window.translation_engine_dropdown_menu_window.show( dropdown_menu_widget_id="translation_engine_dropdown_menu" ), - optionmenu_position="center", - setattr_widget=main_window, - image_widget_attr_name="sls__arrow_img_select", + label_button_position="center", ) sls__selected_translation_engine_box.grid(row=0, column=1, sticky="ew") - optionmenu_img_widget.grid_remove() diff --git a/vrct_gui/ui_managers/Themes/_darkTheme.py b/vrct_gui/ui_managers/Themes/_darkTheme.py index fe3c1aa9..15721fb8 100644 --- a/vrct_gui/ui_managers/Themes/_darkTheme.py +++ b/vrct_gui/ui_managers/Themes/_darkTheme.py @@ -90,6 +90,11 @@ def _darkTheme(base_color): SLS__OPTIONMENU_HOVERED_BG_COLOR = base_color.DARK_875_COLOR, SLS__OPTIONMENU_CLICKED_BG_COLOR = base_color.DARK_900_COLOR, + SLS__DROPDOWN_MENU_WINDOW_BG_COLOR = base_color.DARK_888_COLOR, + SLS__DROPDOWN_MENU_WINDOW_BORDER_COLOR = base_color.DARK_650_COLOR, + SLS__DROPDOWN_MENU_BG_COLOR = base_color.DARK_888_COLOR, + SLS__DROPDOWN_MENU_HOVERED_BG_COLOR = base_color.DARK_825_COLOR, + SLS__DROPDOWN_MENU_CLICKED_BG_COLOR = base_color.DARK_900_COLOR, CONFIG_BUTTON_BG_COLOR = base_color.DARK_850_COLOR, CONFIG_BUTTON_HOVERED_BG_COLOR = base_color.DARK_800_COLOR, diff --git a/vrct_gui/ui_managers/Themes/_lightTheme.py b/vrct_gui/ui_managers/Themes/_lightTheme.py index 0815e602..2261386e 100644 --- a/vrct_gui/ui_managers/Themes/_lightTheme.py +++ b/vrct_gui/ui_managers/Themes/_lightTheme.py @@ -90,6 +90,11 @@ def _lightTheme(base_color): SLS__OPTIONMENU_HOVERED_BG_COLOR = base_color.LIGHT_250_COLOR, SLS__OPTIONMENU_CLICKED_BG_COLOR = base_color.LIGHT_400_COLOR, + SLS__DROPDOWN_MENU_WINDOW_BG_COLOR = base_color.LIGHT_300_COLOR, + SLS__DROPDOWN_MENU_WINDOW_BORDER_COLOR = base_color.LIGHT_700_COLOR, + SLS__DROPDOWN_MENU_BG_COLOR = base_color.LIGHT_300_COLOR, + SLS__DROPDOWN_MENU_HOVERED_BG_COLOR = base_color.LIGHT_200_COLOR, + SLS__DROPDOWN_MENU_CLICKED_BG_COLOR = base_color.LIGHT_400_COLOR, CONFIG_BUTTON_BG_COLOR = base_color.LIGHT_250_COLOR, CONFIG_BUTTON_HOVERED_BG_COLOR = base_color.LIGHT_350_COLOR, diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 8fea6a2c..bbf7326d 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -141,6 +141,24 @@ class UiScalingManager(): self.main.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_Y = self._calculateUiSize(26) + self.main.SLS__SELECTABLE_TRANSLATION_IPADX = self.dupTuple(self._calculateUiSize(10)) + self.main.SLS__SELECTABLE_TRANSLATION_IPADY = self._calculateUiSize(6) + self.main.SLS__SELECTABLE_TRANSLATION_FONT_SIZE = self._calculateUiSize(12) + self.main.SLS__SELECTABLE_TRANSLATION_IMAGE_SIZE = self.dupTuple(self._calculateUiSize(10)) + self.main.SLS__SELECTABLE_TRANSLATION_PADY = self._calculateUiSize(4) + self.main.SLS__SELECTABLE_TRANSLATION_MIN_PADX = self._calculateUiSize(8) + + + self.main.SLS__DROPDOWN_MENU_WINDOW_ADDITIONAL_Y_POS = self._calculateUiSize(4) + self.main.SLS__DROPDOWN_MENU_WINDOW_BORDER_WIDTH = self._calculateUiSize(0, is_allowed_odd=True) + self.main.SLS__DROPDOWN_MENU_SCROLLBAR_IPADX = self.common.SCROLLBAR_IPADX + self.main.SLS__DROPDOWN_MENU_SCROLLBAR_WIDTH = self.common.SCROLLBAR_WIDTH + self.main.SLS__DROPDOWN_MENU_VALUE_IPADX = (self._calculateUiSize(8), 0) + self.main.SLS__DROPDOWN_MENU_VALUE_IPADY = self.dupTuple(self._calculateUiSize(8)) + self.main.SLS__DROPDOWN_MENU_VALUE_PADY = 0 + self.main.SLS__DROPDOWN_MENU_VALUE_FONT_SIZE = self._calculateUiSize(14) + self.main.SLS__DROPDOWN_MENU_VALUE_DEFAULT_MIN_WIDTH = self._calculateUiSize(200) + # Selectable Language Window self.selectable_language_window.TOP_BAR_MIN_HEIGHT = self._calculateUiSize(50) @@ -257,7 +275,6 @@ class UiScalingManager(): self.config_window.SB__OPTIONMENU_IMG_SIZE = (self._calculateUiSize(14), self._calculateUiSize(14)) self.config_window.SB__DROPDOWN_MENU_WINDOW_ADDITIONAL_Y_POS = self._calculateUiSize(4) - self.config_window.SB__DROPDOWN_MENU_WIDTH = self.config_window.SB__OPTIONMENU_MIN_WIDTH self.config_window.SB__DROPDOWN_MENU_WINDOW_BORDER_WIDTH = self._calculateUiSize(1, is_allowed_odd=True) self.config_window.SB__DROPDOWN_MENU_SCROLLBAR_IPADX = self.common.SCROLLBAR_IPADX self.config_window.SB__DROPDOWN_MENU_SCROLLBAR_WIDTH = self.common.SCROLLBAR_WIDTH diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py index 9a8e992b..3e6dcccf 100644 --- a/vrct_gui/ui_utils/ui_utils.py +++ b/vrct_gui/ui_utils/ui_utils.py @@ -168,6 +168,55 @@ def createButtonWithImage(parent_widget, button_image_size, button_ipadxy, butto return button_wrapper +def createLabelButton(parent_widget, label_button_bg_color, label_button_hovered_bg_color, label_button_clicked_bg_color, label_button_ipadx, label_button_ipady, variable, font_family, font_size, text_color, label_button_clicked_command, label_button_position=None, label_button_padx_between_img=0, label_button_min_height=None, label_button_min_width=None): + + label_button_box = CTkFrame(parent_widget, corner_radius=6, fg_color=label_button_bg_color, cursor="hand2") + + label_button_box.grid_rowconfigure(0, weight=1) + if label_button_min_height is not None: + label_button_box.grid_rowconfigure(0, minsize=label_button_min_height) + + label_button_box.grid_columnconfigure(0, weight=1) + if label_button_min_width is not None: + label_button_box.grid_columnconfigure(0, minsize=label_button_min_width) + + label_button_label_wrapper = CTkFrame(label_button_box, corner_radius=0, fg_color=label_button_bg_color) + label_button_label_wrapper.grid(row=0, column=0, padx=label_button_ipadx, pady=label_button_ipady, sticky="ew") + + LABEL_COLUMN=0 + if label_button_position == "center": + label_button_label_wrapper.grid_columnconfigure((0,2), weight=1) + LABEL_COLUMN=1 + + label_button_label_widget = CTkLabel( + label_button_label_wrapper, + textvariable=variable, + height=0, + font=CTkFont(family=font_family, size=font_size, weight="normal"), + text_color=text_color + ) + label_button_label_widget.grid(row=0, column=LABEL_COLUMN, padx=(0, label_button_padx_between_img)) + + + bindEnterAndLeaveColor([label_button_label_wrapper, label_button_box, label_button_label_widget], label_button_hovered_bg_color, label_button_bg_color) + bindButtonPressColor([label_button_label_wrapper, label_button_box, label_button_label_widget], label_button_clicked_bg_color, label_button_hovered_bg_color) + + + + bindButtonReleaseFunction([label_button_label_wrapper, label_button_box, label_button_label_widget], label_button_clicked_command) + + def unbindEventFromWidgets(): + unbindEnterLEaveButtonPressButtonReleaseFunction([label_button_label_wrapper, label_button_box, label_button_label_widget]) + + label_button_box.unbindFunction = unbindEventFromWidgets + + + return (label_button_box, label_button_label_widget) + + + + + def createOptionMenuBox(parent_widget, optionmenu_bg_color, optionmenu_hovered_bg_color, optionmenu_clicked_bg_color, optionmenu_ipadx, optionmenu_ipady, variable, font_family, font_size, text_color, image_file, image_size, optionmenu_clicked_command, optionmenu_position=None, optionmenu_padx_between_img=0, optionmenu_min_height=None, optionmenu_min_width=None, setattr_widget=None, image_widget_attr_name=None): option_menu_box = CTkFrame(parent_widget, corner_radius=6, fg_color=optionmenu_bg_color, cursor="hand2") diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 7a4970c4..e59b332d 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -75,22 +75,22 @@ class VRCT_GUI(CTk): settings=settings.dropdown_menu_window, view_variable=self._view_variable, - window_additional_y_pos=4, - window_border_width=1, - scrollbar_ipadx=(2,2), - scrollbar_width=16, - value_ipadx=(8,0), - value_ipady=(6,6), - value_pady=0, - value_font_size=14, - dropdown_menu_default_min_width=200, + window_additional_y_pos=self.settings.main.uism.SLS__DROPDOWN_MENU_WINDOW_ADDITIONAL_Y_POS, + window_border_width=self.settings.main.uism.SLS__DROPDOWN_MENU_WINDOW_BORDER_WIDTH, + scrollbar_ipadx=self.settings.main.uism.SLS__DROPDOWN_MENU_SCROLLBAR_IPADX, + scrollbar_width=self.settings.main.uism.SLS__DROPDOWN_MENU_SCROLLBAR_WIDTH, + value_ipadx=self.settings.main.uism.SLS__DROPDOWN_MENU_VALUE_IPADX, + value_ipady=self.settings.main.uism.SLS__DROPDOWN_MENU_VALUE_IPADY, + value_pady=self.settings.main.uism.SLS__DROPDOWN_MENU_VALUE_PADY, + value_font_size=self.settings.main.uism.SLS__DROPDOWN_MENU_VALUE_FONT_SIZE, + dropdown_menu_default_min_width=self.settings.main.uism.SLS__DROPDOWN_MENU_VALUE_DEFAULT_MIN_WIDTH, - window_bg_color="#1f2022", - window_border_color="#7f8084", - values_bg_color="#323336", - values_hovered_bg_color="#4b4c4f", - values_clicked_bg_color="#292a2d", - values_text_color=settings.main.ctm.BASIC_TEXT_COLOR, + window_bg_color=self.settings.main.ctm.SLS__DROPDOWN_MENU_WINDOW_BG_COLOR, + window_border_color=self.settings.main.ctm.SLS__DROPDOWN_MENU_WINDOW_BORDER_COLOR, + values_bg_color=self.settings.main.ctm.SLS__DROPDOWN_MENU_BG_COLOR, + values_hovered_bg_color=self.settings.main.ctm.SLS__DROPDOWN_MENU_HOVERED_BG_COLOR, + values_clicked_bg_color=self.settings.main.ctm.SLS__DROPDOWN_MENU_CLICKED_BG_COLOR, + values_text_color=self.settings.main.ctm.BASIC_TEXT_COLOR, ) createMainWindowWidgets( From 8dfc7023254a358abd8e155342cad36edeb1d74e Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 15 Jan 2024 22:58:02 +0900 Subject: [PATCH 34/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Installer=20:=20?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=82=B9=E3=83=88=E3=83=BC=E3=83=AB=E6=99=82?= =?UTF-8?q?=E3=81=AEUI=E8=A8=80=E8=AA=9E=E8=A8=AD=E5=AE=9A=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 ++-- installer/config/English/config.json | 3 +++ installer/config/Japanese/config.json | 3 +++ installer/config/Korean/config.json | 3 +++ installer/installer.nsi | 29 +++++++++++++++++++++++++++ 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 installer/config/English/config.json create mode 100644 installer/config/Japanese/config.json create mode 100644 installer/config/Korean/config.json diff --git a/.gitignore b/.gitignore index 626bae32..c6967444 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ build/ dist/ -config.json +/config.json memo.txt VRCT.spec *.pyc @@ -8,4 +8,4 @@ logs/ .venv/ weight/ .vscode -error.log \ No newline at end of file +error.log diff --git a/installer/config/English/config.json b/installer/config/English/config.json new file mode 100644 index 00000000..ba9bd8de --- /dev/null +++ b/installer/config/English/config.json @@ -0,0 +1,3 @@ +{ + "UI_LANGUAGE": "en" +} \ No newline at end of file diff --git a/installer/config/Japanese/config.json b/installer/config/Japanese/config.json new file mode 100644 index 00000000..d670ee58 --- /dev/null +++ b/installer/config/Japanese/config.json @@ -0,0 +1,3 @@ +{ + "UI_LANGUAGE": "ja" +} \ No newline at end of file diff --git a/installer/config/Korean/config.json b/installer/config/Korean/config.json new file mode 100644 index 00000000..619536dc --- /dev/null +++ b/installer/config/Korean/config.json @@ -0,0 +1,3 @@ +{ + "UI_LANGUAGE": "ko" +} \ No newline at end of file diff --git a/installer/installer.nsi b/installer/installer.nsi index f02335ee..53084928 100644 --- a/installer/installer.nsi +++ b/installer/installer.nsi @@ -61,12 +61,15 @@ Var Dialog_Options Var InstallDocs Var InstallShortcut Var Label_DescriptionOptions +Var Label_DescriptionComboBox +Var ComboBox_Language ; 初期化時コールバック Function .onInit ; オプション値を初期化します。 StrCpy $InstallDocs ${BST_CHECKED} StrCpy $InstallShortcut ${BST_CHECKED} + StrCpy $ComboBox_Language "Japanese" FunctionEnd ; オプション ページ @@ -92,6 +95,18 @@ Function OptionPage ${NSD_CreateCheckbox} 0 26u 100% 12u "デスクトップにショートカットを作成(&D)" Pop $Checkbox_InstallShortcut + ; ComboBoxを作成します。 + ${NSD_CreateLabel} 0 42u 100% 12u "UIの言語を設定してください。" + ; ラベルを変数に代入します。 + Pop $Label_DescriptionComboBox + + ${NSD_CreateComboBox} 0 55u 100% 12u "" + Pop $ComboBox_Language + + ${NSD_CB_AddString} $ComboBox_Language "English" + ${NSD_CB_AddString} $ComboBox_Language "日本語" + ${NSD_CB_AddString} $ComboBox_Language "한국어" + ${If} $InstallDocs == ${BST_CHECKED} ; チェックが入力済の場合、チェックボックスにチェックを入れます。 ${NSD_Check} $Checkbox_InstallDocs @@ -100,6 +115,7 @@ Function OptionPage ; チェックが入力済の場合、チェックボックスにチェックを入れます。 ${NSD_Check} $Checkbox_InstallShortcut ${EndIf} + ${NSD_CB_SelectString} $ComboBox_Language "English" nsDialogs::Show FunctionEnd @@ -107,6 +123,7 @@ FunctionEnd Function OptionPageLeave ${NSD_GetState} $Checkbox_InstallDocs $InstallDocs ${NSD_GetState} $Checkbox_InstallShortcut $InstallShortcut + ${NSD_GetText} $ComboBox_Language $ComboBox_Language FunctionEnd ; デフォルト セクション @@ -150,6 +167,18 @@ Section CreateShortCut "$DESKTOP\VRCT.lnk" "$INSTDIR\VRCT.exe" ${EndIf} + ; ComboBoxの選択値から言語を判定しinstaller\config\***\config.jsonを$INSTDIRにコピー + ${If} $ComboBox_Language == "English" + SetOutPath "$INSTDIR" + File "..\installer\config\English\config.json" + ${ElseIf} $ComboBox_Language == "日本語" + SetOutPath "$INSTDIR" + File "..\installer\config\Japanese\config.json" + ${ElseIf} $ComboBox_Language == "한국어" + SetOutPath "$INSTDIR" + File "..\installer\config\Korean\config.json" + ${EndIf} + ; スタート メニューにショートカットを登録 CreateDirectory "$SMPROGRAMS\VRCT" SetOutPath "$INSTDIR" From b9ae2e107a6e0e06771188a36eee6823c534a957 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 16 Jan 2024 10:29:26 +0900 Subject: [PATCH 35/67] =?UTF-8?q?[Update/Refactor]=20=E3=83=95=E3=82=A7?= =?UTF-8?q?=E3=83=BC=E3=83=AB=E3=82=BB=E3=83=BC=E3=83=95=E3=81=A8=E3=81=97?= =?UTF-8?q?=E3=81=A6CTranslate2=E3=81=B8=E3=81=AE=E5=88=87=E3=82=8A?= =?UTF-8?q?=E6=9B=BF=E3=82=8F=E3=82=8A=E6=99=82=E3=81=AB=E3=80=81=E3=82=B7?= =?UTF-8?q?=E3=82=B9=E3=83=86=E3=83=A0=E3=83=AD=E3=82=B0=E3=81=A8=E3=81=97?= =?UTF-8?q?=E3=81=A6=E8=A1=A8=E7=A4=BA=E3=82=92=E5=A4=89=E6=9B=B4=E3=80=82?= =?UTF-8?q?=E3=83=9D=E3=83=83=E3=83=97=E3=82=A2=E3=83=83=E3=83=97=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=81=AE=E3=82=B3=E3=83=BC=E3=83=89=E3=81=AA=E3=81=A9?= =?UTF-8?q?=E3=81=AF=E4=B8=80=E6=97=A6=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=82=A2=E3=82=A6=E3=83=88=E3=80=82=20controller.py:=20?= =?UTF-8?q?=E9=87=8D=E8=A4=87=E3=81=97=E3=81=A6=E3=81=84=E3=81=9F=E3=82=B3?= =?UTF-8?q?=E3=83=BC=E3=83=89=E3=82=92=E9=96=A2=E6=95=B0=E3=81=A8=E3=81=97?= =?UTF-8?q?=E3=81=A6=E5=88=87=E3=82=8A=E5=87=BA=E3=81=97=E3=81=AA=E3=81=A9?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 19 ++++++++++--------- locales/en.yml | 8 ++++---- locales/ja.yml | 8 ++++---- locales/ko.yml | 7 ++++--- view.py | 40 ++++++++++++++++++++-------------------- 5 files changed, 42 insertions(+), 40 deletions(-) diff --git a/controller.py b/controller.py index 22d9eb2d..73d98cae 100644 --- a/controller.py +++ b/controller.py @@ -55,6 +55,12 @@ def messageFormatter(format_type:str, translation, message): osc_message = FORMAT.replace("[message]", message) return osc_message +def changeToCTranslate2Process(): + config.CHOICE_INPUT_TRANSLATOR = "CTranslate2" + config.CHOICE_OUTPUT_TRANSLATOR = "CTranslate2" + updateTranslationEngineAndEngineList() + view.printToTextbox_TranslationEngineLimitError() + # func transcription send message def sendMicMessage(message): if len(message) > 0: @@ -67,9 +73,7 @@ def sendMicMessage(message): else: translation, success = model.getInputTranslate(message) if success is False: - config.CHOICE_INPUT_TRANSLATOR = "CTranslate2" - config.CHOICE_OUTPUT_TRANSLATOR = "CTranslate2" - updateTranslationEngineAndEngineList() + changeToCTranslate2Process() if config.ENABLE_TRANSCRIPTION_SEND is True: if config.ENABLE_SEND_MESSAGE_TO_VRC is True: @@ -134,9 +138,7 @@ def receiveSpeakerMessage(message): else: translation, success = model.getOutputTranslate(message) if success is False: - config.CHOICE_INPUT_TRANSLATOR = "CTranslate2" - config.CHOICE_OUTPUT_TRANSLATOR = "CTranslate2" - updateTranslationEngineAndEngineList() + changeToCTranslate2Process() if config.ENABLE_TRANSCRIPTION_RECEIVE is True: if config.ENABLE_NOTICE_XSOVERLAY is True: @@ -204,9 +206,8 @@ def sendChatMessage(message): else: translation, success = model.getInputTranslate(message) if success is False: - config.CHOICE_INPUT_TRANSLATOR = "CTranslate2" - config.CHOICE_OUTPUT_TRANSLATOR = "CTranslate2" - updateTranslationEngineAndEngineList() + changeToCTranslate2Process() + # send OSC message if config.ENABLE_SEND_MESSAGE_TO_VRC is True: if config.ENABLE_SEND_ONLY_TRANSLATED_MESSAGES is True: diff --git a/locales/en.yml b/locales/en.yml index d967956d..3c4febc2 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -32,7 +32,7 @@ main_window: no_mic_device_detected_error: No mic device detected. no_speaker_device_detected_error: No speaker device detected. - translation_engine_limit_error: It has automatically disabled the translation feature. Access has been temporarily restricted due to an excessive number of requests to the translation engine. Please wait for a while, restart VRCT, and try again. + translation_engine_limit_error: It has automatically changed the translation engine. Access has been temporarily restricted due to an excessive number of requests to the translation engine. If you want to use the same translation engine, please wait for a while, restart VRCT, and try again. detected_by_word_filter: The word %{detected_message} has not been sent due to detection by the word filter. @@ -58,9 +58,9 @@ main_window: deny_adjust_ui_size: "Keep it at this size" accept_adjust_ui_size: "Set it smaller and restart" - - translation_engine_limit_error: "It has automatically disabled the translation feature.\nAccess has been temporarily restricted\ndue to an excessive number of requests to the translation engine.\nPlease wait for a while, restart VRCT, and try again." - accept_translation_engine_limit_error: Accept and close +# [Deprecated] + # translation_engine_limit_error: "It has automatically disabled the translation feature.\nAccess has been temporarily restricted\ndue to an excessive number of requests to the translation engine.\nPlease wait for a while, restart VRCT, and try again." + # accept_translation_engine_limit_error: Accept and close selectable_language_window: diff --git a/locales/ja.yml b/locales/ja.yml index 25748b8e..13b0f847 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -32,7 +32,7 @@ main_window: no_mic_device_detected_error: マイクデバイスが検出されませんでした。 no_speaker_device_detected_error: スピーカーデバイスが検出されませんでした。 - translation_engine_limit_error: 翻訳機能を自動的に停止しました。翻訳エンジンへのリクエストが多すぎるため、一時的にアクセスが制限されています。しばらく待ってから、VRCTの再起動をしてもう一度試してみてください。 + translation_engine_limit_error: 翻訳エンジンを自動的に変更しました。対象翻訳エンジンへのリクエストが多すぎるため、一時的にアクセスが制限されています。同じ翻訳エンジンを使用したい場合はしばらく待ってから、VRCTの再起動をしてもう一度試してみてください。 detected_by_word_filter: ワードフィルターに登録されている単語 %{detected_message} が検出されたため送信しませんでした。 @@ -58,9 +58,9 @@ main_window: deny_adjust_ui_size: このサイズのままで良い accept_adjust_ui_size: 小さく設定して再起動 - - translation_engine_limit_error: "翻訳機能を自動的に停止しました。\n翻訳エンジンへのリクエストが多すぎるため\n一時的にアクセスが制限されています。\nしばらく待ってから、VRCTの再起動をしてもう一度試してみてください。" - accept_translation_engine_limit_error: 了承して閉じる +# [Deprecated] + # translation_engine_limit_error: "翻訳機能を自動的に停止しました。\n翻訳エンジンへのリクエストが多すぎるため\n一時的にアクセスが制限されています。\nしばらく待ってから、VRCTの再起動をしてもう一度試してみてください。" + # accept_translation_engine_limit_error: 了承して閉じる selectable_language_window: diff --git a/locales/ko.yml b/locales/ko.yml index 692abd2c..11e23fae 100644 --- a/locales/ko.yml +++ b/locales/ko.yml @@ -30,7 +30,7 @@ main_window: no_mic_device_detected_error: 마이크 디바이스를 찾지 못했습니다. no_speaker_device_detected_error: 스피커 디바이스를 찾지 못했습니다. - translation_engine_limit_error: 번역 기능이 자동으로 중지되었습니다. 번역 엔진에 대한 요청이 너무 많아 일시적으로 사용이 제한되고 있습니다. 잠시 기다렸다가 VRCT를 재부팅하고 다시 시도해 보세요. + # translation_engine_limit_error: 번역 기능이 자동으로 중지되었습니다. 번역 엔진에 대한 요청이 너무 많아 일시적으로 사용이 제한되고 있습니다. 잠시 기다렸다가 VRCT를 재부팅하고 다시 시도해 보세요. detected_by_word_filter: 단어 필터에 등록된 단어 %{detected_message}(이)가 감지되어 전송하지 않았습니다. @@ -57,8 +57,9 @@ main_window: accept_adjust_ui_size: "작게 줄이고 재부팅" - translation_engine_limit_error: "번역 기능이 자동으로 중지되었습니다. \n번역 엔진에 대한 요청이 너무 많아 \n일시적으로 사용이 제한되었습니다. \n잠시 기다렸다가 VRCT를 재부팅한 후 다시 시도해 보십시오." - accept_translation_engine_limit_error: 확인하고 닫기 +# [Deprecated] + # translation_engine_limit_error: "번역 기능이 자동으로 중지되었습니다. \n번역 엔진에 대한 요청이 너무 많아 \n일시적으로 사용이 제한되었습니다. \n잠시 기다렸다가 VRCT를 재부팅한 후 다시 시도해 보십시오." + # accept_translation_engine_limit_error: 확인하고 닫기 selectable_language_window: diff --git a/view.py b/view.py index 76089ece..0181f7de 100644 --- a/view.py +++ b/view.py @@ -1214,18 +1214,18 @@ class View(): # ※Below 40% of the UI size is not supported, and we cannot handle it at this time. +# [Deprecated] + # def translationEngineLimitErrorProcess(self): + # # turn off translation switch. + # vrct_gui.translation_switch_box.deselect() + # vrct_gui.translation_frame.markToggleManually(False) - def translationEngineLimitErrorProcess(self): - # turn off translation switch. - vrct_gui.translation_switch_box.deselect() - vrct_gui.translation_frame.markToggleManually(False) + # # disable translation feature. + # vrct_gui._changeMainWindowWidgetsStatus("disabled", ["translation_switch"], to_hold_state=True) - # disable translation feature. - vrct_gui._changeMainWindowWidgetsStatus("disabled", ["translation_switch"], to_hold_state=True) - - # print system message that mention to stopped translation feature. - view.printToTextbox_TranslationEngineLimitError() - view.showTheLimitOfTranslationEngineConfirmationModal() + # # print system message that mention to stopped translation feature. + # view.printToTextbox_TranslationEngineLimitError() + # view.showTheLimitOfTranslationEngineConfirmationModal() @@ -1267,19 +1267,19 @@ class View(): +# [Deprecated] + # def showTheLimitOfTranslationEngineConfirmationModal(self): + # self.foregroundOffIfForegroundEnabled() - def showTheLimitOfTranslationEngineConfirmationModal(self): - self.foregroundOffIfForegroundEnabled() + # self.view_variable.VAR_LABEL_MAIN_WINDOW_COVER_MESSAGE.set("") + # vrct_gui.main_window_cover.show() - self.view_variable.VAR_LABEL_MAIN_WINDOW_COVER_MESSAGE.set("") - vrct_gui.main_window_cover.show() + # self.view_variable.CALLBACK_HIDE_CONFIRMATION_MODAL=self._hideInformationModal + # self.view_variable.CALLBACK_ACCEPTED_CONFIRMATION_MODAL=self._hideInformationModal - self.view_variable.CALLBACK_HIDE_CONFIRMATION_MODAL=self._hideInformationModal - self.view_variable.CALLBACK_ACCEPTED_CONFIRMATION_MODAL=self._hideInformationModal - - self.view_variable.VAR_MESSAGE_CONFIRMATION_MODAL.set(i18n.t("main_window.confirmation_message.translation_engine_limit_error")) - self.view_variable.VAR_LABEL_CONFIRMATION_MODAL_ACCEPT_BUTTON.set(i18n.t("main_window.confirmation_message.accept_translation_engine_limit_error")) - vrct_gui.information_modal.show(hide_title_bar=False, close_when_focusout=False) + # self.view_variable.VAR_MESSAGE_CONFIRMATION_MODAL.set(i18n.t("main_window.confirmation_message.translation_engine_limit_error")) + # self.view_variable.VAR_LABEL_CONFIRMATION_MODAL_ACCEPT_BUTTON.set(i18n.t("main_window.confirmation_message.accept_translation_engine_limit_error")) + # vrct_gui.information_modal.show(hide_title_bar=False, close_when_focusout=False) From 52b39e92d194606eb7f2e9d66c4955ebd8d083ac Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 16 Jan 2024 10:48:28 +0900 Subject: [PATCH 36/67] [Chore] remove the deprecated code that I forgot to do it before. --- view.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/view.py b/view.py index 0181f7de..e006a056 100644 --- a/view.py +++ b/view.py @@ -1476,9 +1476,6 @@ class View(): self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.translation_engine_limit_error")) - # def printToTextbox_OSCError(self): [Deprecated] - # self._printToTextbox_Info("OSC is not enabled, please enable OSC and rejoin. or turn off the \"Send Message To VRChat\" setting") - def printToTextbox_DetectedByWordFilter(self, detected_message): self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.detected_by_word_filter", detected_message=detected_message)) From e34a4e630b8309e0ce7b5754beb9aa34a582f4d6 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 16 Jan 2024 14:28:11 +0900 Subject: [PATCH 37/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Installer=20:=20in?= =?UTF-8?q?staller.nsi=E5=86=85=E9=83=A8=E3=81=A7config.json=E3=82=92?= =?UTF-8?q?=E7=94=9F=E6=88=90=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- installer/config/English/config.json | 3 --- installer/config/Japanese/config.json | 3 --- installer/config/Korean/config.json | 3 --- installer/installer.nsi | 19 +++++++++++-------- 4 files changed, 11 insertions(+), 17 deletions(-) delete mode 100644 installer/config/English/config.json delete mode 100644 installer/config/Japanese/config.json delete mode 100644 installer/config/Korean/config.json diff --git a/installer/config/English/config.json b/installer/config/English/config.json deleted file mode 100644 index ba9bd8de..00000000 --- a/installer/config/English/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "UI_LANGUAGE": "en" -} \ No newline at end of file diff --git a/installer/config/Japanese/config.json b/installer/config/Japanese/config.json deleted file mode 100644 index d670ee58..00000000 --- a/installer/config/Japanese/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "UI_LANGUAGE": "ja" -} \ No newline at end of file diff --git a/installer/config/Korean/config.json b/installer/config/Korean/config.json deleted file mode 100644 index 619536dc..00000000 --- a/installer/config/Korean/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "UI_LANGUAGE": "ko" -} \ No newline at end of file diff --git a/installer/installer.nsi b/installer/installer.nsi index 53084928..6e246132 100644 --- a/installer/installer.nsi +++ b/installer/installer.nsi @@ -63,13 +63,14 @@ Var InstallShortcut Var Label_DescriptionOptions Var Label_DescriptionComboBox Var ComboBox_Language +Var Set_Langage ; 初期化時コールバック Function .onInit ; オプション値を初期化します。 StrCpy $InstallDocs ${BST_CHECKED} StrCpy $InstallShortcut ${BST_CHECKED} - StrCpy $ComboBox_Language "Japanese" + StrCpy $ComboBox_Language "English" FunctionEnd ; オプション ページ @@ -167,18 +168,20 @@ Section CreateShortCut "$DESKTOP\VRCT.lnk" "$INSTDIR\VRCT.exe" ${EndIf} - ; ComboBoxの選択値から言語を判定しinstaller\config\***\config.jsonを$INSTDIRにコピー + ; ComboBoxの選択値から言語を判定しconfig.jsonを$INSTDIRに作成 ${If} $ComboBox_Language == "English" - SetOutPath "$INSTDIR" - File "..\installer\config\English\config.json" + StrCpy $Set_Langage "en" ${ElseIf} $ComboBox_Language == "日本語" - SetOutPath "$INSTDIR" - File "..\installer\config\Japanese\config.json" + StrCpy $Set_Langage "ja" ${ElseIf} $ComboBox_Language == "한국어" - SetOutPath "$INSTDIR" - File "..\installer\config\Korean\config.json" + StrCpy $Set_Langage "ko" ${EndIf} + StrCpy $1 '{"UI_LANGUAGE": "$Set_Langage"}' + FileOpen $0 "$INSTDIR\config.json" w + FileWrite $0 $1 + FileClose $0 + ; スタート メニューにショートカットを登録 CreateDirectory "$SMPROGRAMS\VRCT" SetOutPath "$INSTDIR" From 369506013f24b05da938f128aa0d9c836dbd16e1 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 17 Jan 2024 14:12:18 +0900 Subject: [PATCH 38/67] =?UTF-8?q?[Update]=20Config=20Window:=20Translation?= =?UTF-8?q?=20Tab:=20add=20"Use=20Translation=20Feature"=20and=20"Select?= =?UTF-8?q?=20Internal=20Translation=20Model"=20=E7=BF=BB=E8=A8=B3?= =?UTF-8?q?=E6=A9=9F=E8=83=BD=E3=81=9D=E3=81=AE=E3=82=82=E3=81=AE=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E3=81=86=E3=81=8B=E3=81=A9=E3=81=86=E3=81=8B=E3=81=AE?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E3=81=A8=E3=80=81=E5=86=85=E9=83=A8=E7=BF=BB?= =?UTF-8?q?=E8=A8=B3=E3=83=A2=E3=83=87=E3=83=AB=E3=81=AE=E9=81=B8=E6=8A=9E?= =?UTF-8?q?UI=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ・Use Translation Feature からは True or Falseを渡し、config.USE_TRANSLATION_FEATUREへ保存します。 ・Select Internal Translation Model からは 文字列 "Small" か "Large" を渡し、config.WEIGHT_TYPEへ保存します。 ※機能側実装に合わせて、初回起動時config.WEIGHT_TYPEには"Small"ではなく"m2m100_418m"が入ります。 ※それに合わせ、起動時はSmall固定にしています。文字列"Small"対応後、一つ下のコメントアウト部分と入れ替えてください。 --- config.py | 22 ++++++++++ controller.py | 16 +++++++ locales/en.yml | 10 +++++ locales/ja.yml | 10 +++++ view.py | 42 +++++++++++++++++++ .../createSettingBox_Translation.py | 29 +++++++++++++ 6 files changed, 129 insertions(+) diff --git a/config.py b/config.py index be98a68f..669c2e64 100644 --- a/config.py +++ b/config.py @@ -94,6 +94,10 @@ class Config: def SELECTABLE_UI_LANGUAGES_DICT(self): return self._SELECTABLE_UI_LANGUAGES_DICT + @property + def SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT(self): + return self._SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT + @property def MAX_MIC_ENERGY_THRESHOLD(self): return self._MAX_MIC_ENERGY_THRESHOLD @@ -544,6 +548,17 @@ class Config: self._AUTH_KEYS[key] = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.AUTH_KEYS) + @property + @json_serializable('USE_TRANSLATION_FEATURE') + def USE_TRANSLATION_FEATURE(self): + return self._USE_TRANSLATION_FEATURE + + @USE_TRANSLATION_FEATURE.setter + def USE_TRANSLATION_FEATURE(self, value): + if isinstance(value, bool): + self._USE_TRANSLATION_FEATURE = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property @json_serializable('WEIGHT_TYPE') def WEIGHT_TYPE(self): @@ -551,6 +566,7 @@ class Config: @WEIGHT_TYPE.setter def WEIGHT_TYPE(self, value): + # if isinstance(value, str) and value in self.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT: if isinstance(value, str): self._WEIGHT_TYPE = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @@ -726,6 +742,11 @@ class Config: "ko": "한국어" # If you want to add a new language and key, please append it here. } + self._SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT = { + # {Save json str}: {i18n_placeholder} pairs + "Small": "Small", + "Large": "Large", + } self._MAX_MIC_ENERGY_THRESHOLD = 2000 self._MAX_SPEAKER_ENERGY_THRESHOLD = 4000 @@ -799,6 +820,7 @@ class Config: self._AUTH_KEYS = { "DeepL_API": None, } + self._USE_TRANSLATION_FEATURE = True self._WEIGHT_TYPE = "m2m100_418m" self._SEND_MESSAGE_FORMAT = "[message]" self._SEND_MESSAGE_FORMAT_WITH_T = "[message]([translation])" diff --git a/controller.py b/controller.py index 73d98cae..0c6d368f 100644 --- a/controller.py +++ b/controller.py @@ -483,6 +483,20 @@ def callbackSetEnableRestoreMainWindowGeometry(value): config.ENABLE_RESTORE_MAIN_WINDOW_GEOMETRY = value # Translation Tab +def callbackSetUseTranslationFeature(value): + print("callbackSetUseTranslationFeature", value) + config.USE_TRANSLATION_FEATURE = value + if config.USE_TRANSLATION_FEATURE is True: + view.setLatestCTranslate2WeightType() + view.openCtranslate2WeightTypeWidget() + else: + view.closeCtranslate2WeightTypeWidget() + +def callbackSetCtranslate2WeightType(value): + print("callbackSetCtranslate2WeightType", value) + config.WEIGHT_TYPE = str(value) + view.updateSelectedCtranslate2WeightType(config.WEIGHT_TYPE) + def callbackSetDeeplAuthkey(value): print("callbackSetDeeplAuthkey", str(value)) if len(value) == 39: @@ -923,6 +937,8 @@ def createMainWindow(): "callback_set_enable_restore_main_window_geometry": callbackSetEnableRestoreMainWindowGeometry, # Translation Tab + "callback_set_use_translation_feature": callbackSetUseTranslationFeature, + "callback_set_ctranslate2_weight_type": callbackSetCtranslate2WeightType, "callback_set_deepl_authkey": callbackSetDeeplAuthkey, # Transcription Tab (Mic) diff --git a/locales/en.yml b/locales/en.yml index 3c4febc2..31406a23 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -119,6 +119,16 @@ config_window: label: Remember The Main Window Position desc: Restore the position and size of the previous window upon startup. + use_translation_feature: + label: Use Translation Feature + desc: You can't use the translation feature while this is turned off. Instead, the VRCT startup becomes a little faster. This is for users who don't need the translation feature and only use VRCT as a chatbox and transcription tool. + + ctranslate2_weight_type: + label: Select Internal Translation Model + desc: You can choose the translation model to use for the internal translation engine. + small: "Basic model (%{capacity})" + large: "High accuracy model (%{capacity})" + deepl_auth_key: label: DeepL Auth Key diff --git a/locales/ja.yml b/locales/ja.yml index 13b0f847..2b3eb2a8 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -116,6 +116,16 @@ config_window: label: メイン画面の位置を記憶する desc: 起動時、前回の画面の位置とサイズを復元します。 + use_translation_feature: + label: 翻訳機能を使用する + desc: "オフにしている間は、翻訳機能を使わない代わり、VRCTの起動が少し速くなります。\n翻訳機能を必要とせず、VRCTをチャット送信と文字起こしツールとしてのみ使用するユーザー用です。" + + ctranslate2_weight_type: + label: オフライン翻訳のタイプ + desc: 翻訳エンジン(オフライン翻訳)で翻訳する際に、使用する翻訳モデルを選択できます。 + small: "通常モデル (%{capacity})" + large: "高精度モデル (%{capacity})" + deepl_auth_key: label: DeepL 認証キー diff --git a/view.py b/view.py index e006a056..a3c7c166 100644 --- a/view.py +++ b/view.py @@ -269,6 +269,18 @@ class View(): VAR_ENABLE_RESTORE_MAIN_WINDOW_GEOMETRY=BooleanVar(value=config.ENABLE_RESTORE_MAIN_WINDOW_GEOMETRY), # Translation Tab + VAR_LABEL_USE_TRANSLATION_FEATURE=StringVar(value=i18n.t("config_window.use_translation_feature.label")), + VAR_DESC_USE_TRANSLATION_FEATURE=StringVar(value=i18n.t("config_window.use_translation_feature.desc")), + CALLBACK_SET_USE_TRANSLATION_FEATURE=None, + VAR_USE_TRANSLATION_FEATURE=BooleanVar(value=config.USE_TRANSLATION_FEATURE), + + VAR_LABEL_CTRANSLATE2_WEIGHT_TYPE=StringVar(value=i18n.t("config_window.ctranslate2_weight_type.label")), + VAR_DESC_CTRANSLATE2_WEIGHT_TYPE=StringVar(value=i18n.t("config_window.ctranslate2_weight_type.desc")), + DICT_CTRANSLATE2_WEIGHT_TYPE=self.getSelectableCtranslate2WeightTypeDict(), + CALLBACK_SET_CTRANSLATE2_WEIGHT_TYPE=None, + VAR_CTRANSLATE2_WEIGHT_TYPE=StringVar(value=self.getSelectableCtranslate2WeightTypeDict()["Small"]), + # VAR_CTRANSLATE2_WEIGHT_TYPE=StringVar(value=self.getSelectableCtranslate2WeightTypeDict()[config.WEIGHT_TYPE]), + VAR_LABEL_DEEPL_AUTH_KEY=StringVar(value=i18n.t("config_window.deepl_auth_key.label")), VAR_DESC_DEEPL_AUTH_KEY=None, CALLBACK_SET_DEEPL_AUTH_KEY=None, @@ -578,6 +590,8 @@ class View(): # Translation Tab + self.view_variable.CALLBACK_SET_USE_TRANSLATION_FEATURE = config_window_registers.get("callback_set_use_translation_feature", None) + self.view_variable.CALLBACK_SET_CTRANSLATE2_WEIGHT_TYPE = config_window_registers.get("callback_set_ctranslate2_weight_type", None) self.view_variable.CALLBACK_SET_DEEPL_AUTHKEY = config_window_registers.get("callback_set_deepl_authkey", None) # Transcription Tab (Mic) @@ -635,6 +649,11 @@ class View(): self.setMainWindowMessageBoxRatio(config.MESSAGE_BOX_RATIO) + if config.USE_TRANSLATION_FEATURE is True: + self.openCtranslate2WeightTypeWidget() + else: + self.closeCtranslate2WeightTypeWidget() + if config.CHOICE_MIC_HOST == "NoHost": self.view_variable.VAR_MIC_HOST.set("No Mic Host Detected") @@ -852,6 +871,13 @@ class View(): def getPreUiScaling(self): return self.restart_required_configs_pre_data.ui_scaling + @staticmethod + def getSelectableCtranslate2WeightTypeDict(): + return { + config._SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT["Small"]: i18n.t("config_window.ctranslate2_weight_type.small", capacity="418MB"), + config._SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT["Large"]: i18n.t("config_window.ctranslate2_weight_type.large", capacity="1.2GB"), + } + # Open Webpage Functions def openWebPage_Booth(self): self.openWebPage(config.BOOTH_URL) @@ -966,6 +992,22 @@ class View(): vrct_gui.config_window.setting_box_compact_mode_switch_box.configure(state="normal") + def updateSelectedCtranslate2WeightType(self, selected_weight_type:str): + self.view_variable.VAR_CTRANSLATE2_WEIGHT_TYPE.set(self.getSelectableCtranslate2WeightTypeDict()[selected_weight_type]) + + def setLatestCTranslate2WeightType(self): + selected_weight_type = self.getSelectableCtranslate2WeightTypeDict()[config.WEIGHT_TYPE] + self.view_variable.VAR_CTRANSLATE2_WEIGHT_TYPE.set(selected_weight_type) + + + def openCtranslate2WeightTypeWidget(self): + vrct_gui.config_window.sb__use_translation_feature.grid(pady=0) + vrct_gui.config_window.sb__ctranslate2_weight_type.grid() + + def closeCtranslate2WeightTypeWidget(self): + vrct_gui.config_window.sb__use_translation_feature.grid(pady=(0,1)) + vrct_gui.config_window.sb__ctranslate2_weight_type.grid_remove() + def openMicEnergyThresholdWidget(self): self.view_variable.VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD.set(i18n.t("config_window.mic_dynamic_energy_threshold.label_for_manual")) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py index d2975d0d..574856fe 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py @@ -4,14 +4,43 @@ from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_Translation(setting_box_wrapper, config_window, settings, view_variable): sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings, view_variable) + createSettingBoxSwitch = sbg.createSettingBoxSwitch + createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu createSettingBoxEntry = sbg.createSettingBoxEntry + def switch_use_translation_feature_callback(switch_widget): + callFunctionIfCallable(view_variable.CALLBACK_SET_USE_TRANSLATION_FEATURE, switch_widget.get()) + + def optionmenu_ctranslate2_weight_type_callback(value): + callFunctionIfCallable(view_variable.CALLBACK_SET_CTRANSLATE2_WEIGHT_TYPE, value) def deepl_authkey_callback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_DEEPL_AUTHKEY, value) row=0 + config_window.sb__use_translation_feature = createSettingBoxSwitch( + for_var_label_text=view_variable.VAR_LABEL_USE_TRANSLATION_FEATURE, + for_var_desc_text=view_variable.VAR_DESC_USE_TRANSLATION_FEATURE, + switch_attr_name="sb__switch_use_translation_feature", + command=lambda: switch_use_translation_feature_callback(config_window.sb__switch_use_translation_feature), + variable=view_variable.VAR_USE_TRANSLATION_FEATURE + ) + config_window.sb__use_translation_feature.grid(row=row, pady=0) + row+=1 + + config_window.sb__ctranslate2_weight_type = createSettingBoxDropdownMenu( + for_var_label_text=view_variable.VAR_LABEL_CTRANSLATE2_WEIGHT_TYPE, + for_var_desc_text=view_variable.VAR_DESC_CTRANSLATE2_WEIGHT_TYPE, + optionmenu_attr_name="sb__optionmenu_ctranslate2_weight_type", + dropdown_menu_values=view_variable.DICT_CTRANSLATE2_WEIGHT_TYPE, + command=lambda value: optionmenu_ctranslate2_weight_type_callback(value), + variable=view_variable.VAR_CTRANSLATE2_WEIGHT_TYPE, + ) + config_window.sb__ctranslate2_weight_type.grid(row=row) + row+=1 + + config_window.sb__deepl_authkey = createSettingBoxEntry( for_var_label_text=view_variable.VAR_LABEL_DEEPL_AUTH_KEY, for_var_desc_text=view_variable.VAR_DESC_DEEPL_AUTH_KEY, From 6e64edb1c698f252774c1300a8f4ca869c8df831 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 17 Jan 2024 16:52:44 +0900 Subject: [PATCH 39/67] =?UTF-8?q?[Update]=20=E8=A8=AD=E5=AE=9A=E7=94=BB?= =?UTF-8?q?=E9=9D=A2=E3=81=AB=E3=81=A6=E3=80=8C=E7=BF=BB=E8=A8=B3=E6=A9=9F?= =?UTF-8?q?=E8=83=BD=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B=E3=80=8D?= =?UTF-8?q?=E3=82=92=E3=82=AA=E3=83=95=E3=81=AB=E3=81=97=E3=81=9F=E6=99=82?= =?UTF-8?q?=E3=81=AB=E3=80=81=E3=83=A1=E3=82=A4=E3=83=B3=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E3=81=AE=E7=BF=BB=E8=A8=B3=E6=A9=9F=E8=83=BD=E3=82=92Disabled?= =?UTF-8?q?=E3=81=A8=E3=81=97=E3=81=A6=E3=81=95=E3=82=8F=E3=82=8C=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=82=88=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B=E5=87=A6?= =?UTF-8?q?=E7=90=86=E8=BF=BD=E5=8A=A0=E3=80=82=EF=BC=88=E3=81=9D=E3=81=AE?= =?UTF-8?q?=E9=80=86=E3=82=82=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 2 ++ view.py | 6 +++++- vrct_gui/_changeMainWindowWidgetsStatus.py | 18 +++++++++++------- vrct_gui/vrct_gui.py | 5 +++-- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/controller.py b/controller.py index 0c6d368f..b7ed3c2a 100644 --- a/controller.py +++ b/controller.py @@ -489,8 +489,10 @@ def callbackSetUseTranslationFeature(value): if config.USE_TRANSLATION_FEATURE is True: view.setLatestCTranslate2WeightType() view.openCtranslate2WeightTypeWidget() + view.setTranslationSwitchStatus("normal", release_locked_state=True) else: view.closeCtranslate2WeightTypeWidget() + view.setTranslationSwitchStatus("disabled", to_lock_state=True) def callbackSetCtranslate2WeightType(value): print("callbackSetCtranslate2WeightType", value) diff --git a/view.py b/view.py index a3c7c166..8159120b 100644 --- a/view.py +++ b/view.py @@ -652,6 +652,7 @@ class View(): if config.USE_TRANSLATION_FEATURE is True: self.openCtranslate2WeightTypeWidget() else: + self.setTranslationSwitchStatus("disabled", to_lock_state=True) self.closeCtranslate2WeightTypeWidget() if config.CHOICE_MIC_HOST == "NoHost": @@ -916,6 +917,9 @@ class View(): def setMainWindowAllWidgetsStatusToDisabled(): vrct_gui._changeMainWindowWidgetsStatus("disabled", "All") + @staticmethod + def setTranslationSwitchStatus(status:str, to_lock_state:bool=False, release_locked_state:bool=False): + vrct_gui._changeMainWindowWidgetsStatus(status, ["translation_switch"], to_lock_state, release_locked_state) def enableMainWindowSidebarCompactMode(self): self.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = True @@ -1263,7 +1267,7 @@ class View(): # vrct_gui.translation_frame.markToggleManually(False) # # disable translation feature. - # vrct_gui._changeMainWindowWidgetsStatus("disabled", ["translation_switch"], to_hold_state=True) + # vrct_gui._changeMainWindowWidgetsStatus("disabled", ["translation_switch"], to_lock_state=True) # # print system message that mention to stopped translation feature. # view.printToTextbox_TranslationEngineLimitError() diff --git a/vrct_gui/_changeMainWindowWidgetsStatus.py b/vrct_gui/_changeMainWindowWidgetsStatus.py index 95fc38dc..3a77cb32 100644 --- a/vrct_gui/_changeMainWindowWidgetsStatus.py +++ b/vrct_gui/_changeMainWindowWidgetsStatus.py @@ -1,12 +1,16 @@ from customtkinter import CTkImage -hold_state_list=[] -def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, target_names:list, to_hold_state:bool=False): - global hold_state_list +lock_state_list=[] +def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, target_names:list, to_lock_state:bool=False, release_locked_state:bool=False): + global lock_state_list if target_names == "All": target_names = ["translation_switch", "transcription_send_switch", "transcription_receive_switch", "foreground_switch", "quick_language_settings", "config_button", "minimize_sidebar_button", "entry_message_box", "send_message_button"] + if release_locked_state is True: + for item in target_names: + if item in lock_state_list: + lock_state_list.remove(item) - for item in hold_state_list: + for item in lock_state_list: if item in target_names: target_names.remove(item) @@ -158,9 +162,9 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta raise ValueError(f"No matching case for target_name: {target_name}") - if to_hold_state is True: + if to_lock_state is True: for item in target_names: - if item not in hold_state_list: - hold_state_list.append(item) + if item not in lock_state_list: + lock_state_list.append(item) vrct_gui.update() \ No newline at end of file diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index e59b332d..25369a6a 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -250,14 +250,15 @@ class VRCT_GUI(CTk): - def _changeMainWindowWidgetsStatus(self, status, target_names, to_hold_state:bool=False): + def _changeMainWindowWidgetsStatus(self, status, target_names, to_lock_state:bool=False, release_locked_state:bool=False): _changeMainWindowWidgetsStatus( vrct_gui=self, settings=self.settings.main, view_variable=self._view_variable, status=status, target_names=target_names, - to_hold_state=to_hold_state, + to_lock_state=to_lock_state, + release_locked_state=release_locked_state, ) def _changeConfigWindowWidgetsStatus(self, status, target_names): From a067bfa733840c88e88268ed1dfba209cd6faae1 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 17 Jan 2024 18:17:46 +0900 Subject: [PATCH 40/67] =?UTF-8?q?[Update/Refactor]=20=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E7=94=BB=E9=9D=A2=E3=81=AB=E3=81=A6=E3=80=8C=E7=BF=BB=E8=A8=B3?= =?UTF-8?q?=E6=A9=9F=E8=83=BD=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=80=8D=E3=81=AE=E3=82=AA=E3=83=B3=E3=82=AA=E3=83=95=E3=81=A7?= =?UTF-8?q?=E3=80=81=E3=83=A1=E3=82=A4=E3=83=B3=E7=94=BB=E9=9D=A2=E7=BF=BB?= =?UTF-8?q?=E8=A8=B3=E3=82=A8=E3=83=B3=E3=82=B8=E3=83=B3=E9=81=B8=E6=8A=9E?= =?UTF-8?q?Widget=E3=81=AE=E8=A1=A8=E7=A4=BA=E9=9D=9E=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=E3=82=92=E5=88=87=E3=82=8A=E6=9B=BF=E3=81=88=E3=80=82=20?= =?UTF-8?q?=E3=80=8C=E7=BF=BB=E8=A8=B3=E6=A9=9F=E8=83=BD=E3=82=92=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E3=81=99=E3=82=8B=E3=80=8D=E3=82=AA=E3=83=B3=E3=82=AA?= =?UTF-8?q?=E3=83=95=E3=82=92process=E3=81=A8=E3=81=97=E3=81=A6=E5=88=87?= =?UTF-8?q?=E3=82=8A=E5=87=BA=E3=81=97=E3=81=A6view=E3=81=A7=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 7 ++----- view.py | 16 +++++++++++++--- .../createSidebarLanguagesSettings.py | 12 ++++++------ 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/controller.py b/controller.py index b7ed3c2a..90ebf0ac 100644 --- a/controller.py +++ b/controller.py @@ -487,12 +487,9 @@ def callbackSetUseTranslationFeature(value): print("callbackSetUseTranslationFeature", value) config.USE_TRANSLATION_FEATURE = value if config.USE_TRANSLATION_FEATURE is True: - view.setLatestCTranslate2WeightType() - view.openCtranslate2WeightTypeWidget() - view.setTranslationSwitchStatus("normal", release_locked_state=True) + view.useTranslationFeatureProcess("Normal") else: - view.closeCtranslate2WeightTypeWidget() - view.setTranslationSwitchStatus("disabled", to_lock_state=True) + view.useTranslationFeatureProcess("Disable") def callbackSetCtranslate2WeightType(value): print("callbackSetCtranslate2WeightType", value) diff --git a/view.py b/view.py index 8159120b..d2a5a47f 100644 --- a/view.py +++ b/view.py @@ -650,10 +650,9 @@ class View(): self.setMainWindowMessageBoxRatio(config.MESSAGE_BOX_RATIO) if config.USE_TRANSLATION_FEATURE is True: - self.openCtranslate2WeightTypeWidget() + self.useTranslationFeatureProcess("Normal") else: - self.setTranslationSwitchStatus("disabled", to_lock_state=True) - self.closeCtranslate2WeightTypeWidget() + self.useTranslationFeatureProcess("Disable") if config.CHOICE_MIC_HOST == "NoHost": self.view_variable.VAR_MIC_HOST.set("No Mic Host Detected") @@ -879,6 +878,17 @@ class View(): config._SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT["Large"]: i18n.t("config_window.ctranslate2_weight_type.large", capacity="1.2GB"), } + def useTranslationFeatureProcess(self, state:str): + if state == "Normal": + self.setLatestCTranslate2WeightType() + self.openCtranslate2WeightTypeWidget() + self.setTranslationSwitchStatus("normal", release_locked_state=True) + vrct_gui.sls__box_translation_optionmenu_wrapper.grid() + elif state == "Disable": + view.closeCtranslate2WeightTypeWidget() + view.setTranslationSwitchStatus("disabled", to_lock_state=True) + vrct_gui.sls__box_translation_optionmenu_wrapper.grid_remove() + # Open Webpage Functions def openWebPage_Booth(self): self.openWebPage(config.BOOTH_URL) diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index f529898f..e85e084a 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -344,11 +344,11 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): - sls__box_translation_optionmenu_wrapper = CTkFrame(main_window.sls__box_frame, corner_radius=0, fg_color=settings.ctm.SLS__BG_COLOR, width=0, height=0) - sls__box_translation_optionmenu_wrapper.grid(row=5, column=0, pady=settings.uism.SLS__SELECTABLE_TRANSLATION_PADY, sticky="ew") + main_window.sls__box_translation_optionmenu_wrapper = CTkFrame(main_window.sls__box_frame, corner_radius=0, fg_color=settings.ctm.SLS__BG_COLOR, width=0, height=0) + main_window.sls__box_translation_optionmenu_wrapper.grid(row=5, column=0, pady=settings.uism.SLS__SELECTABLE_TRANSLATION_PADY, sticky="ew") - sls__box_translation_optionmenu_wrapper.grid_columnconfigure((0,2), weight=0, minsize=settings.uism.SLS__SELECTABLE_TRANSLATION_MIN_PADX) - sls__box_translation_optionmenu_wrapper.grid_columnconfigure(1, weight=1) + main_window.sls__box_translation_optionmenu_wrapper.grid_columnconfigure((0,2), weight=0, minsize=settings.uism.SLS__SELECTABLE_TRANSLATION_MIN_PADX) + main_window.sls__box_translation_optionmenu_wrapper.grid_columnconfigure(1, weight=1) @@ -361,12 +361,12 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): dropdown_menu_values=[], command=adjustedCommand, wrapper_widget=main_window, - attach_widget=sls__box_translation_optionmenu_wrapper, + attach_widget=main_window.sls__box_translation_optionmenu_wrapper, dropdown_menu_min_width=settings.uism.SIDEBAR_MIN_WIDTH, ) (sls__selected_translation_engine_box, label_button_label_widget) = createLabelButton( - parent_widget=sls__box_translation_optionmenu_wrapper, + parent_widget=main_window.sls__box_translation_optionmenu_wrapper, label_button_bg_color=settings.ctm.SLS__BG_COLOR, label_button_hovered_bg_color=settings.ctm.SLS__OPTIONMENU_HOVERED_BG_COLOR, label_button_clicked_bg_color=settings.ctm.SLS__OPTIONMENU_CLICKED_BG_COLOR, From d0a9be1b79a2e77db35d9fcce90e6172d1fd13e6 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 17 Jan 2024 18:45:19 +0900 Subject: [PATCH 41/67] =?UTF-8?q?[bugfix]=20=E8=A8=AD=E5=AE=9A=E7=94=BB?= =?UTF-8?q?=E9=9D=A2=E3=80=8C=E7=BF=BB=E8=A8=B3=E6=A9=9F=E8=83=BD=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B=E3=80=8D=E5=88=87=E6=9B=BF?= =?UTF-8?q?=E6=99=82=E3=81=AB=E3=80=81=E3=83=A1=E3=82=A4=E3=83=B3=E7=94=BB?= =?UTF-8?q?=E9=9D=A2=E3=82=AB=E3=83=90=E3=83=BC=E3=81=8C=E5=89=8D=E3=81=AB?= =?UTF-8?q?=E5=87=BA=E3=81=A6=E3=81=8D=E3=81=A6=E3=81=97=E3=81=BE=E3=81=86?= =?UTF-8?q?=E3=81=AE=E3=82=92=E7=84=A1=E7=90=86=E3=82=84=E3=82=8A=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit config.WEIGHT_TYPEがm2m100_418mの時に、view.py側で"Small"と解釈するように一時的な処理をいれています。 --- view.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/view.py b/view.py index d2a5a47f..ae487ccc 100644 --- a/view.py +++ b/view.py @@ -884,10 +884,13 @@ class View(): self.openCtranslate2WeightTypeWidget() self.setTranslationSwitchStatus("normal", release_locked_state=True) vrct_gui.sls__box_translation_optionmenu_wrapper.grid() + vrct_gui.config_window.after(200, vrct_gui.config_window.lift) + elif state == "Disable": view.closeCtranslate2WeightTypeWidget() view.setTranslationSwitchStatus("disabled", to_lock_state=True) vrct_gui.sls__box_translation_optionmenu_wrapper.grid_remove() + vrct_gui.config_window.after(200, vrct_gui.config_window.lift) # Open Webpage Functions def openWebPage_Booth(self): @@ -1010,7 +1013,9 @@ class View(): self.view_variable.VAR_CTRANSLATE2_WEIGHT_TYPE.set(self.getSelectableCtranslate2WeightTypeDict()[selected_weight_type]) def setLatestCTranslate2WeightType(self): - selected_weight_type = self.getSelectableCtranslate2WeightTypeDict()[config.WEIGHT_TYPE] + if config.WEIGHT_TYPE == "m2m100_418m": + WEIGHT_TYPE = "Small" + selected_weight_type = self.getSelectableCtranslate2WeightTypeDict()[WEIGHT_TYPE] self.view_variable.VAR_CTRANSLATE2_WEIGHT_TYPE.set(selected_weight_type) From c20f27fadfba3eb5701734da02b5816cb6b36445 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 17 Jan 2024 23:22:00 +0900 Subject: [PATCH 42/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20CTrans?= =?UTF-8?q?late2=E3=81=AE=E3=83=80=E3=82=A6=E3=83=B3=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=83=89=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 2 +- controller.py | 10 ++++++++++ main.py | 3 ++- model.py | 10 ++++++++-- models/translation/translation_translator.py | 16 +++------------- models/translation/utils.py | 19 ++++++++++++++----- view.py | 12 +++++++----- 7 files changed, 45 insertions(+), 27 deletions(-) diff --git a/config.py b/config.py index 669c2e64..54b057a5 100644 --- a/config.py +++ b/config.py @@ -821,7 +821,7 @@ class Config: "DeepL_API": None, } self._USE_TRANSLATION_FEATURE = True - self._WEIGHT_TYPE = "m2m100_418m" + self._WEIGHT_TYPE = "Small" self._SEND_MESSAGE_FORMAT = "[message]" self._SEND_MESSAGE_FORMAT_WITH_T = "[message]([translation])" self._RECEIVED_MESSAGE_FORMAT = "[message]" diff --git a/controller.py b/controller.py index 90ebf0ac..d65d010d 100644 --- a/controller.py +++ b/controller.py @@ -488,6 +488,11 @@ def callbackSetUseTranslationFeature(value): config.USE_TRANSLATION_FEATURE = value if config.USE_TRANSLATION_FEATURE is True: view.useTranslationFeatureProcess("Normal") + if model.checkCTranslatorCTranslate2ModelWeight(): + model.changeTranslatorCTranslate2Model() + else: + view.useTranslationFeatureProcess("Disable") + # CTranslate2 weight is not downloaded else: view.useTranslationFeatureProcess("Disable") @@ -495,6 +500,11 @@ def callbackSetCtranslate2WeightType(value): print("callbackSetCtranslate2WeightType", value) config.WEIGHT_TYPE = str(value) view.updateSelectedCtranslate2WeightType(config.WEIGHT_TYPE) + if model.checkCTranslatorCTranslate2ModelWeight(): + model.changeTranslatorCTranslate2Model() + else: + view.useTranslationFeatureProcess("Disable") + # CTranslate2 weight is not downloaded def callbackSetDeeplAuthkey(value): print("callbackSetDeeplAuthkey", str(value)) diff --git a/main.py b/main.py index f990a89d..c160383d 100644 --- a/main.py +++ b/main.py @@ -9,7 +9,8 @@ if __name__ == "__main__": from config import config from models.translation.utils import downloadCTranslate2Weight - downloadCTranslate2Weight(config.PATH_LOCAL, config.WEIGHT_TYPE, splash.updateDownloadProgress) + if config.USE_TRANSLATION_FEATURE is True: + downloadCTranslate2Weight(config.PATH_LOCAL, config.WEIGHT_TYPE, splash.updateDownloadProgress) import controller controller.createMainWindow() diff --git a/model.py b/model.py index 01277bb5..8580e85d 100644 --- a/model.py +++ b/model.py @@ -24,6 +24,7 @@ from models.transcription.transcription_transcriber import AudioTranscriber from models.xsoverlay.notification import xsoverlayForVRCT from models.translation.translation_languages import translation_lang from models.transcription.transcription_languages import transcription_lang +from models.translation.utils import checkCTranslate2Weight from config import config class threadFnc(Thread): @@ -63,10 +64,15 @@ class Model: self.speaker_audio_recorder = None self.speaker_energy_recorder = None self.speaker_energy_plot_progressbar = None - self.translator = Translator(config.PATH_LOCAL, config.WEIGHT_TYPE) + self.translator = Translator() + if config.USE_TRANSLATION_FEATURE is True: + self.translator.changeCTranslate2Model(config.PATH_LOCAL, config.WEIGHT_TYPE) self.keyword_processor = KeywordProcessor() - def updateTranslator(self): + def checkCTranslatorCTranslate2ModelWeight(self): + return checkCTranslate2Weight(config.PATH_LOCAL, config.WEIGHT_TYPE) + + def changeTranslatorCTranslate2Model(self): self.translator.changeCTranslate2Model(config.PATH_LOCAL, config.WEIGHT_TYPE) def resetKeywordProcessor(self): diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index 47bd7dd1..fbe6cf3f 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -9,20 +9,10 @@ import transformers # Translator class Translator(): - def __init__(self, path, model_type): + def __init__(self): self.deepl_client = None - directory_name = ctranslate2_weights[model_type]["directory_name"] - tokenizer = ctranslate2_weights[model_type]["tokenizer"] - weight_path = os.path.join(path, "weight", directory_name) - self.ctranslate2_translator = ctranslate2.Translator( - weight_path, - device="cpu", - device_index=0, - compute_type="int8", - inter_threads=1, - intra_threads=4 - ) - self.ctranslate2_tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer) + self.ctranslate2_translator = None + self.ctranslate2_tokenizer = None def authenticationDeepLAuthKey(self, authkey): result = True diff --git a/models/translation/utils.py b/models/translation/utils.py index 2f60459b..d47401cf 100644 --- a/models/translation/utils.py +++ b/models/translation/utils.py @@ -7,7 +7,7 @@ from typing import Callable import hashlib ctranslate2_weights = { - "m2m100_418m": { # M2M-100 418M-parameter model + "Small": { # M2M-100 418M-parameter model "url": "https://bit.ly/33fM1AO", "directory_name": "m2m100_418m", "tokenizer": "facebook/m2m100_418M", @@ -17,7 +17,7 @@ ctranslate2_weights = { "shared_vocabulary.txt": "bd440aa21b8ca3453fc792a0018a1f3fe68b3464aadddd4d16a4b72f73c86d8c", } }, - "m2m100_12b": { # M2M-100 1.2B-parameter model + "Large": { # M2M-100 1.2B-parameter model "url": "https://bit.ly/3GYiaed", "directory_name": "m2m100_12b", "tokenizer": "facebook/m2m100_1.2b", @@ -38,9 +38,7 @@ def calculate_file_hash(file_path, block_size=65536): return hash_object.hexdigest() -def downloadCTranslate2Weight(path, weight_type="m2m100_418m", func=None): - url = ctranslate2_weights[weight_type]["url"] - filename = 'weight.zip' +def checkCTranslate2Weight(path, weight_type="Small"): directory_name = 'weight' current_directory = path weight_directory_name = ctranslate2_weights[weight_type]["directory_name"] @@ -48,6 +46,7 @@ def downloadCTranslate2Weight(path, weight_type="m2m100_418m", func=None): files = ["model.bin", "sentencepiece.model", "shared_vocabulary.txt"] # check already downloaded + already_downloaded = False if all(os_path.exists(os_path.join(current_directory, directory_name, weight_directory_name, file)) for file in files): # check hash for file in files: @@ -55,6 +54,16 @@ def downloadCTranslate2Weight(path, weight_type="m2m100_418m", func=None): current_hash = calculate_file_hash(os_path.join(current_directory, directory_name, weight_directory_name, file)) if original_hash != current_hash: break + already_downloaded = True + return already_downloaded + +def downloadCTranslate2Weight(path, weight_type="Small", func=None): + url = ctranslate2_weights[weight_type]["url"] + filename = 'weight.zip' + directory_name = 'weight' + current_directory = path + + if checkCTranslate2Weight(path, weight_type): return try: diff --git a/view.py b/view.py index ae487ccc..52fb174c 100644 --- a/view.py +++ b/view.py @@ -278,8 +278,7 @@ class View(): VAR_DESC_CTRANSLATE2_WEIGHT_TYPE=StringVar(value=i18n.t("config_window.ctranslate2_weight_type.desc")), DICT_CTRANSLATE2_WEIGHT_TYPE=self.getSelectableCtranslate2WeightTypeDict(), CALLBACK_SET_CTRANSLATE2_WEIGHT_TYPE=None, - VAR_CTRANSLATE2_WEIGHT_TYPE=StringVar(value=self.getSelectableCtranslate2WeightTypeDict()["Small"]), - # VAR_CTRANSLATE2_WEIGHT_TYPE=StringVar(value=self.getSelectableCtranslate2WeightTypeDict()[config.WEIGHT_TYPE]), + VAR_CTRANSLATE2_WEIGHT_TYPE=StringVar(value=self.getSelectableCtranslate2WeightTypeDict()[config.WEIGHT_TYPE]), VAR_LABEL_DEEPL_AUTH_KEY=StringVar(value=i18n.t("config_window.deepl_auth_key.label")), VAR_DESC_DEEPL_AUTH_KEY=None, @@ -974,6 +973,11 @@ class View(): additional_widget.grid() self._closeMicWordFilterList() + def showRestartButton(self): + self._showRestartButton() + + def hideRestartButton(self): + self._hideRestartButton() def showRestartButtonIfRequired(self, locale:Union[None,str]=None): is_restart_required = not ( @@ -1013,9 +1017,7 @@ class View(): self.view_variable.VAR_CTRANSLATE2_WEIGHT_TYPE.set(self.getSelectableCtranslate2WeightTypeDict()[selected_weight_type]) def setLatestCTranslate2WeightType(self): - if config.WEIGHT_TYPE == "m2m100_418m": - WEIGHT_TYPE = "Small" - selected_weight_type = self.getSelectableCtranslate2WeightTypeDict()[WEIGHT_TYPE] + selected_weight_type = self.getSelectableCtranslate2WeightTypeDict()[config.WEIGHT_TYPE] self.view_variable.VAR_CTRANSLATE2_WEIGHT_TYPE.set(selected_weight_type) From a1b53dc2b098184022ecb120385a0be544d3aa0b Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 19 Jan 2024 01:27:41 +0900 Subject: [PATCH 43/67] =?UTF-8?q?[WIP/TEST]=20Controller=20:=20CTranslate2?= =?UTF-8?q?=E3=81=AE=E3=83=A2=E3=83=87=E3=83=AB=E5=A4=89=E6=9B=B4=E6=99=82?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 10 ++++++---- view.py | 13 ++++++++++++- vrct_gui/_CreateDropdownMenuWindow.py | 6 ++++-- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/controller.py b/controller.py index d65d010d..a01fc008 100644 --- a/controller.py +++ b/controller.py @@ -491,8 +491,8 @@ def callbackSetUseTranslationFeature(value): if model.checkCTranslatorCTranslate2ModelWeight(): model.changeTranslatorCTranslate2Model() else: - view.useTranslationFeatureProcess("Disable") - # CTranslate2 weight is not downloaded + view.showRestartButtonIfRequired() + view.useTranslationFeatureProcess("Restart") else: view.useTranslationFeatureProcess("Disable") @@ -502,9 +502,11 @@ def callbackSetCtranslate2WeightType(value): view.updateSelectedCtranslate2WeightType(config.WEIGHT_TYPE) if model.checkCTranslatorCTranslate2ModelWeight(): model.changeTranslatorCTranslate2Model() + view.showRestartButtonIfRequired() + view.useTranslationFeatureProcess("Normal") else: - view.useTranslationFeatureProcess("Disable") - # CTranslate2 weight is not downloaded + view.showRestartButtonIfRequired() + view.useTranslationFeatureProcess("Restart") def callbackSetDeeplAuthkey(value): print("callbackSetDeeplAuthkey", str(value)) diff --git a/view.py b/view.py index 52fb174c..02acb490 100644 --- a/view.py +++ b/view.py @@ -28,6 +28,8 @@ class View(): ui_scaling=config.UI_SCALING, font_family=config.FONT_FAMILY, ui_language=config.UI_LANGUAGE, + use_translation_feature=config.USE_TRANSLATION_FEATURE, + ctranslate2_weight_type=config.WEIGHT_TYPE, ) if config.ENABLE_SPEAKER2CHATBOX is False: @@ -891,6 +893,13 @@ class View(): vrct_gui.sls__box_translation_optionmenu_wrapper.grid_remove() vrct_gui.config_window.after(200, vrct_gui.config_window.lift) + elif state == "Restart": + view.setLatestCTranslate2WeightType() + view.setTranslationSwitchStatus("disabled", to_lock_state=True) + vrct_gui.sls__box_translation_optionmenu_wrapper.grid() + vrct_gui.config_window.after(200, vrct_gui.config_window.lift) + + # Open Webpage Functions def openWebPage_Booth(self): self.openWebPage(config.BOOTH_URL) @@ -984,7 +993,9 @@ class View(): self.restart_required_configs_pre_data.appearance_theme == config.APPEARANCE_THEME and self.restart_required_configs_pre_data.ui_scaling == config.UI_SCALING and self.restart_required_configs_pre_data.font_family == config.FONT_FAMILY and - self.restart_required_configs_pre_data.ui_language == config.UI_LANGUAGE + self.restart_required_configs_pre_data.ui_language == config.UI_LANGUAGE and + self.restart_required_configs_pre_data.use_translation_feature == config.USE_TRANSLATION_FEATURE and + self.restart_required_configs_pre_data.ctranslate2_weight_type == config.WEIGHT_TYPE ) if locale is None: diff --git a/vrct_gui/_CreateDropdownMenuWindow.py b/vrct_gui/_CreateDropdownMenuWindow.py index 5ca598e0..7bb233cb 100644 --- a/vrct_gui/_CreateDropdownMenuWindow.py +++ b/vrct_gui/_CreateDropdownMenuWindow.py @@ -326,9 +326,11 @@ class _CreateDropdownMenuWindow(CTkToplevel): def _withdraw(self, e=None): self.withdraw() - self.attach_widget.winfo_toplevel().unbind("", self.BIND_CONFIGURE_FUNC_ID) + if self.BIND_CONFIGURE_FUNC_ID in self.attach_widget.winfo_toplevel().bind(): + self.attach_widget.winfo_toplevel().unbind("", self.BIND_CONFIGURE_FUNC_ID) self.attach_widget.unbind("", self.BIND_UNMAP_FUNC_ID) - self.attach_widget.winfo_toplevel().unbind("", self.BIND_BUTTON_1_FUNC_ID) + if self.BIND_BUTTON_1_FUNC_ID in self.attach_widget.winfo_toplevel().bind(): + self.attach_widget.winfo_toplevel().unbind("", self.BIND_BUTTON_1_FUNC_ID) self.hide = True From ee7589ba93cd7ab50c73256331ab93fa745f3995 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 19 Jan 2024 12:21:05 +0900 Subject: [PATCH 44/67] =?UTF-8?q?[Update]=20=E6=96=87=E8=A8=80=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E3=80=82=E3=82=AB=E3=83=83=E3=82=B3=E3=81=AA=E3=81=A9?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 8 ++++---- locales/ja.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 31406a23..cce6552b 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -84,8 +84,8 @@ config_window: transcription_mic: Mic transcription_speaker: Speaker others: Others - others_send_message_formats: Message Formats(Send) - others_received_message_formats: Message Formats(Received) + others_send_message_formats: Message Formats (Send) + others_received_message_formats: Message Formats (Received) others_speaker2chatbox: Speaker2Chatbox advanced_settings: Advanced Settings @@ -223,7 +223,7 @@ config_window: error_message: "The characters '[message]' cannot be used." send_message_format_with_t: - label: Message Format(With translation) + label: Message Format (With translation) desc: "You can change the decoration of the message you want to send.\n[message] will be replaced with the message, and [translation] will be replaced with the translated message." example_text: This is an example sentence. Fonts, line breaks, etc. may differ from the actual display. error_message: "The characters '[message]' and '[translation]' cannot be used." @@ -235,7 +235,7 @@ config_window: error_message: "The characters '[message]' cannot be used." received_message_format_with_t: - label: Message Format(With translation) + label: Message Format (With translation) desc: It will be used in Notification XSOverlay too. example_text: This is an example sentence. Fonts, line breaks, etc. may differ from the actual display. error_message: "The characters '[message]' and '[translation]' cannot be used." diff --git a/locales/ja.yml b/locales/ja.yml index 2b3eb2a8..c7132340 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -42,7 +42,7 @@ main_window: latest_language_setting: 現在「あなたの言語」は %{your_language}、「相手の言語」は %{target_language} に設定されています。 opened_web_page_booth: お使いのブラウザで、Boothのページを開きました。 - opened_web_page_vrct_documents: "お使いのブラウザで、VRCTのドキュメントを開きました。使用方法などはそちらに記載されています。\n不具合、ご要望、その他お問い合わせはドキュメント最下部にあるLinks、「お問合せフォーム」もしくはX(元Twitter)にて気軽にご連絡ください!" + opened_web_page_vrct_documents: "お使いのブラウザで、VRCTのドキュメントを開きました。使用方法などはそちらに記載されています。\n不具合、ご要望、その他お問い合わせはドキュメント最下部にあるLinks、「お問合せフォーム」もしくはX (元Twitter) にて気軽にご連絡ください!" update_available: 新しいバージョンが出ました! @@ -202,7 +202,7 @@ config_window: notice_xsoverlay: label: XSOverlayでの通知受け取り機能を有効 (VR限定) - desc: 文字起こし(受信)されたメッセージをXSOverlayの機能を使って通知として受け取れます。 + desc: 文字起こし (受信) されたメッセージをXSOverlayの機能を使って通知として受け取れます。 auto_export_message_logs: label: 会話ログを自動的に保存する From 379c5bfdb617e3898130269f716892583e25efe2 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 19 Jan 2024 14:04:11 +0900 Subject: [PATCH 45/67] =?UTF-8?q?[Chore]=20=E5=A4=89=E6=95=B0=E5=90=8D?= =?UTF-8?q?=E5=A4=89=E6=9B=B4:=20AUTHKEY=20->=20AUTH=5FKEY=20=E3=81=AE?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E5=88=86=E3=81=91=E3=81=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 6 +++--- locales.yml | 6 +++--- view.py | 2 +- .../createSettingBox_Translation.py | 12 ++++++------ 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/controller.py b/controller.py index d65d010d..fc5a7ee9 100644 --- a/controller.py +++ b/controller.py @@ -506,8 +506,8 @@ def callbackSetCtranslate2WeightType(value): view.useTranslationFeatureProcess("Disable") # CTranslate2 weight is not downloaded -def callbackSetDeeplAuthkey(value): - print("callbackSetDeeplAuthkey", str(value)) +def callbackSetDeeplAuthKey(value): + print("callbackSetDeeplAuthKey", str(value)) if len(value) == 39: result = model.authenticationTranslatorDeepLAuthKey(auth_key=value) if result is True: @@ -948,7 +948,7 @@ def createMainWindow(): # Translation Tab "callback_set_use_translation_feature": callbackSetUseTranslationFeature, "callback_set_ctranslate2_weight_type": callbackSetCtranslate2WeightType, - "callback_set_deepl_authkey": callbackSetDeeplAuthkey, + "callback_set_deepl_auth_key": callbackSetDeeplAuthKey, # Transcription Tab (Mic) "callback_set_mic_host": callbackSetMicHost, diff --git a/locales.yml b/locales.yml index f4b80300..3261f636 100644 --- a/locales.yml +++ b/locales.yml @@ -55,7 +55,7 @@ en: # tab Parameter label_ip_address: "OSC IP address" label_port: "OSC Port" - label_authkey: "DeepL Auth Key" + label_auth_key: "DeepL Auth Key" label_message_format: "Message Format" # tab Others @@ -121,7 +121,7 @@ ja: # tab Parameter # label_ip_address: "" # label_port: "" - # label_authkey: "" + # label_auth_key: "" label_message_format: "送信するメッセージのフォーマット" # tab Others @@ -186,7 +186,7 @@ ko: # tab Parameter label_ip_address: "OSC IP 주소" label_port: "OSC 포트" - label_authkey: "DeepL 인증키" + label_auth_key: "DeepL 인증키" label_message_format: "전송 형식" # tab Others diff --git a/view.py b/view.py index 52fb174c..28007ec5 100644 --- a/view.py +++ b/view.py @@ -591,7 +591,7 @@ class View(): # Translation Tab self.view_variable.CALLBACK_SET_USE_TRANSLATION_FEATURE = config_window_registers.get("callback_set_use_translation_feature", None) self.view_variable.CALLBACK_SET_CTRANSLATE2_WEIGHT_TYPE = config_window_registers.get("callback_set_ctranslate2_weight_type", None) - self.view_variable.CALLBACK_SET_DEEPL_AUTHKEY = config_window_registers.get("callback_set_deepl_authkey", None) + self.view_variable.CALLBACK_SET_DEEPL_AUTH_KEY = config_window_registers.get("callback_set_deepl_auth_key", None) # Transcription Tab (Mic) self.view_variable.CALLBACK_SET_MIC_HOST = config_window_registers.get("callback_set_mic_host", None) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py index 574856fe..62ed881a 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py @@ -14,8 +14,8 @@ def createSettingBox_Translation(setting_box_wrapper, config_window, settings, v def optionmenu_ctranslate2_weight_type_callback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_CTRANSLATE2_WEIGHT_TYPE, value) - def deepl_authkey_callback(value): - callFunctionIfCallable(view_variable.CALLBACK_SET_DEEPL_AUTHKEY, value) + def deepl_auth_key_callback(value): + callFunctionIfCallable(view_variable.CALLBACK_SET_DEEPL_AUTH_KEY, value) row=0 @@ -41,13 +41,13 @@ def createSettingBox_Translation(setting_box_wrapper, config_window, settings, v row+=1 - config_window.sb__deepl_authkey = createSettingBoxEntry( + config_window.sb__deepl_auth_key = createSettingBoxEntry( for_var_label_text=view_variable.VAR_LABEL_DEEPL_AUTH_KEY, for_var_desc_text=view_variable.VAR_DESC_DEEPL_AUTH_KEY, - entry_attr_name="sb__entry_deepl_authkey", + entry_attr_name="sb__entry_deepl_auth_key", entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_300, - entry_bind__Any_KeyRelease=lambda value: deepl_authkey_callback(value), + entry_bind__Any_KeyRelease=lambda value: deepl_auth_key_callback(value), entry_textvariable=view_variable.VAR_DEEPL_AUTH_KEY, ) - config_window.sb__deepl_authkey.grid(row=row, pady=0) + config_window.sb__deepl_auth_key.grid(row=row, pady=0) row+=1 \ No newline at end of file From 43b09de16701da1572fd6911d19490aae6b21392 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 19 Jan 2024 17:35:01 +0900 Subject: [PATCH 46/67] =?UTF-8?q?[Refactor]=20Config=20Window:=20=E5=A4=89?= =?UTF-8?q?=E6=95=B0=E5=90=8D=E5=A4=89=E6=9B=B4=20snake=5Fcase=20to=20came?= =?UTF-8?q?lCase.=20config.py:=20=E3=83=86=E3=82=B9=E3=83=88=E7=94=A8?= =?UTF-8?q?=E3=81=AE=E4=B8=80=E9=83=A8print=E6=96=87=E3=81=8C=E9=96=93?= =?UTF-8?q?=E9=81=95=E3=81=A3=E3=81=A6=E3=81=84=E3=81=9F=E3=81=AE=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 2 +- .../createSettingBox_AdvancedSettings.py | 12 ++--- .../createSettingBox_Appearance.py | 35 +++++++------- .../createSettingBox_Others.py | 28 +++++------ .../createSettingBox_Others_Additional.py | 4 +- ...ettingBox_Others_ReceivedMessageFormats.py | 12 ++--- ...ateSettingBox_Others_SendMessageFormats.py | 12 ++--- .../createSettingBox_Mic.py | 47 ++++++++++--------- .../createSettingBox_Speaker.py | 26 +++++----- .../createSettingBox_Translation.py | 12 ++--- 10 files changed, 95 insertions(+), 95 deletions(-) diff --git a/controller.py b/controller.py index fc5a7ee9..949c12e1 100644 --- a/controller.py +++ b/controller.py @@ -479,7 +479,7 @@ def callbackSetUiLanguage(value): view.showRestartButtonIfRequired(locale=config.UI_LANGUAGE) def callbackSetEnableRestoreMainWindowGeometry(value): - print("callbackSetEnableAutoClearMessageBox", value) + print("callbackSetEnableRestoreMainWindowGeometry", value) config.ENABLE_RESTORE_MAIN_WINDOW_GEOMETRY = value # Translation Tab diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py index bb305ecf..330adc6e 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py @@ -8,13 +8,13 @@ def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settin createSettingBoxButtonWithImage = sbg.createSettingBoxButtonWithImage - def entry_ip_address_callback(value): + def entryIpAddressCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_OSC_IP_ADDRESS, value) - def entry_port_callback(value): + def entryPortCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_OSC_PORT, value) - def open_config_filepath_callback(): + def openConfigFilepathCallback(): callFunctionIfCallable(view_variable.CALLBACK_OPEN_FILEPATH_CONFIG_FILE) row=0 @@ -23,7 +23,7 @@ def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settin for_var_desc_text=view_variable.VAR_DESC_OSC_IP_ADDRESS, entry_attr_name="sb__entry_ip_address", entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_150, - entry_bind__Any_KeyRelease=lambda value: entry_ip_address_callback(value), + entry_bind__Any_KeyRelease=lambda value: entryIpAddressCallback(value), entry_textvariable=view_variable.VAR_OSC_IP_ADDRESS, ) config_window.sb__ip_address.grid(row=row) @@ -35,7 +35,7 @@ def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settin for_var_desc_text=view_variable.VAR_DESC_OSC_PORT, entry_attr_name="sb__entry_port", entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_150, - entry_bind__Any_KeyRelease=lambda value: entry_port_callback(value), + entry_bind__Any_KeyRelease=lambda value: entryPortCallback(value), entry_textvariable=view_variable.VAR_OSC_PORT, ) config_window.sb__port.grid(row=row) @@ -45,7 +45,7 @@ def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settin for_var_label_text=view_variable.VAR_LABEL_OPEN_CONFIG_FILEPATH, for_var_desc_text=view_variable.VAR_DESC_OPEN_CONFIG_FILEPATH, button_attr_name="sb__button_open_config_filepath", - button_command=lambda _e: open_config_filepath_callback(), + button_command=lambda _e: openConfigFilepathCallback(), button_image=settings.image_file.FOLDER_OPEN_ICON, ) config_window.sb__open_config_filepath.grid(row=row, pady=0) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py index 615c0e4e..8eecc9c5 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -8,30 +8,29 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi createSettingBoxSlider = sbg.createSettingBoxSlider createSettingBoxCheckbox = sbg.createSettingBoxCheckbox - # 関数名は変えるかもしれない。 - # テーマ変更、フォント変更時、 Widget再生成か再起動かは検討中 - def slider_transparency_callback(value): + + def sliderTransparencyCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_TRANSPARENCY, value) - def optionmenu_appearance_theme_callback(value): + def optionmenuAppearanceThemeCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_APPEARANCE, value) - def optionmenu_ui_scaling_callback(value): + def optionmenuUiScalingCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_UI_SCALING, value) - def slider_text_box_ui_scaling_callback(value): + def sliderTextBoxUiScalingCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_TEXTBOX_UI_SCALING, value) - def slider_message_box_ratio_callback(value): + def sliderMessageBoxRatioCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_MESSAGE_BOX_RATIO, value) - def optionmenu_font_family_callback(value): + def optionmenuFontFamilyCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_FONT_FAMILY, value) - def optionmenu_ui_language_callback(value): + def optionmenuUiLanguageCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_UI_LANGUAGE, value) - def checkbox_enable_restore_main_window_geometry_callback(checkbox_box_widget): + def checkboxEnableRestoreMainWindowGeometryCallback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_RESTORE_MAIN_WINDOW_GEOMETRY, checkbox_box_widget.get()) row=0 @@ -40,7 +39,7 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi for_var_desc_text=view_variable.VAR_DESC_TRANSPARENCY, slider_attr_name="sb__slider_transparency", slider_range=view_variable.SLIDER_RANGE_TRANSPARENCY, - command=lambda value: slider_transparency_callback(value), + command=lambda value: sliderTransparencyCallback(value), variable=view_variable.VAR_TRANSPARENCY, slider_bind__ButtonPress=view_variable.CALLBACK_BUTTON_PRESS_TRANSPARENCY, slider_bind__ButtonRelease=view_variable.CALLBACK_BUTTON_RELEASE_TRANSPARENCY, @@ -55,7 +54,7 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi for_var_desc_text=view_variable.VAR_DESC_APPEARANCE_THEME, optionmenu_attr_name="sb__optionmenu_appearance_theme", dropdown_menu_values=view_variable.LIST_APPEARANCE_THEME, - command=lambda value: optionmenu_appearance_theme_callback(value), + command=lambda value: optionmenuAppearanceThemeCallback(value), variable=view_variable.VAR_APPEARANCE_THEME, ) config_window.sb__appearance_theme.grid(row=row) @@ -68,7 +67,7 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi for_var_desc_text=view_variable.VAR_DESC_UI_SCALING, optionmenu_attr_name="sb__optionmenu_ui_scaling", dropdown_menu_values=view_variable.LIST_UI_SCALING, - command=lambda value: optionmenu_ui_scaling_callback(value), + command=lambda value: optionmenuUiScalingCallback(value), variable=view_variable.VAR_UI_SCALING, ) config_window.sb__ui_scaling.grid(row=row) @@ -79,7 +78,7 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi for_var_desc_text=view_variable.VAR_DESC_TEXTBOX_UI_SCALING, slider_attr_name="sb__slider_transparency", slider_range=view_variable.SLIDER_RANGE_TEXTBOX_UI_SCALING, - command=lambda value: slider_text_box_ui_scaling_callback(value), + command=lambda value: sliderTextBoxUiScalingCallback(value), variable=view_variable.VAR_TEXTBOX_UI_SCALING, slider_bind__ButtonPress=view_variable.CALLBACK_BUTTON_PRESS_TEXTBOX_UI_SCALING, slider_bind__ButtonRelease=view_variable.CALLBACK_BUTTON_RELEASE_TEXTBOX_UI_SCALING, @@ -93,7 +92,7 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi for_var_desc_text=view_variable.VAR_DESC_MESSAGE_BOX_RATIO, slider_attr_name="sb__slider_message_box_ratio", slider_range=view_variable.SLIDER_RANGE_MESSAGE_BOX_RATIO, - command=lambda value: slider_message_box_ratio_callback(value), + command=lambda value: sliderMessageBoxRatioCallback(value), variable=view_variable.VAR_MESSAGE_BOX_RATIO, slider_bind__ButtonPress=view_variable.CALLBACK_BUTTON_PRESS_MESSAGE_BOX_RATIO, slider_bind__ButtonRelease=view_variable.CALLBACK_BUTTON_RELEASE_MESSAGE_BOX_RATIO, @@ -107,7 +106,7 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi for_var_desc_text=view_variable.VAR_DESC_FONT_FAMILY, optionmenu_attr_name="sb__optionmenu_font_family", dropdown_menu_values=view_variable.LIST_FONT_FAMILY, - command=lambda value: optionmenu_font_family_callback(value), + command=lambda value: optionmenuFontFamilyCallback(value), variable=view_variable.VAR_FONT_FAMILY, ) config_window.sb__font_family.grid(row=row) @@ -119,7 +118,7 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi for_var_desc_text=view_variable.VAR_DESC_UI_LANGUAGE, optionmenu_attr_name="sb__optionmenu_ui_language", dropdown_menu_values=view_variable.LIST_UI_LANGUAGE, - command=lambda value: optionmenu_ui_language_callback(value), + command=lambda value: optionmenuUiLanguageCallback(value), variable=view_variable.VAR_UI_LANGUAGE, ) config_window.sb__ui_language.grid(row=row) @@ -129,7 +128,7 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi for_var_label_text=view_variable.VAR_LABEL_ENABLE_RESTORE_MAIN_WINDOW_GEOMETRY, for_var_desc_text=view_variable.VAR_DESC_ENABLE_RESTORE_MAIN_WINDOW_GEOMETRY, checkbox_attr_name="sb__checkbox_enable_restore_main_window_geometry", - command=lambda: checkbox_enable_restore_main_window_geometry_callback(config_window.sb__checkbox_enable_restore_main_window_geometry), + command=lambda: checkboxEnableRestoreMainWindowGeometryCallback(config_window.sb__checkbox_enable_restore_main_window_geometry), variable=view_variable.VAR_ENABLE_RESTORE_MAIN_WINDOW_GEOMETRY, ) config_window.sb__enable_restore_main_window_geometry.grid(row=row, pady=0) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index bacfa71f..b32116fe 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -9,25 +9,25 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v createSettingBoxAutoExportMessageLogs = sbg.createSettingBoxAutoExportMessageLogs - def checkbox_auto_clear_message_box_callback(checkbox_box_widget): + def checkboxAutoClearMessageBoxCallback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX, checkbox_box_widget.get()) - def checkbox_send_only_translated_messages_callback(checkbox_box_widget): + def checkboxSendOnlyTranslatedMessagesCallback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_SEND_ONLY_TRANSLATED_MESSAGES, checkbox_box_widget.get()) - def checkbox_send_message_button_type_callback(): + def checkboxSendMessageButtonTypeCallback(): callFunctionIfCallable(view_variable.CALLBACK_SET_SEND_MESSAGE_BUTTON_TYPE, view_variable.VAR_SEND_MESSAGE_BUTTON_TYPE.get()) - def checkbox_notice_xsoverlay_callback(checkbox_box_widget): + def checkboxNoticeXsoverlayCallback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY, checkbox_box_widget.get()) - def checkbox_auto_export_message_logs_callback(checkbox_box_widget): + def checkboxAutoExportMessageLogsCallback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, checkbox_box_widget.get()) - def button_auto_export_message_logs_callback(): + def buttonAutoExportMessageLogsCallback(): callFunctionIfCallable(view_variable.CALLBACK_OPEN_FILEPATH_LOGS) - def checkbox_enable_send_message_to_vrc_callback(checkbox_box_widget): + def checkboxEnableSendMessageToVrcCallback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_SEND_MESSAGE_TO_VRC, checkbox_box_widget.get()) @@ -36,7 +36,7 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v for_var_label_text=view_variable.VAR_LABEL_ENABLE_AUTO_CLEAR_MESSAGE_BOX, for_var_desc_text=view_variable.VAR_DESC_ENABLE_AUTO_CLEAR_MESSAGE_BOX, checkbox_attr_name="sb__checkbox_auto_clear_message_box", - command=lambda: checkbox_auto_clear_message_box_callback(config_window.sb__checkbox_auto_clear_message_box), + command=lambda: checkboxAutoClearMessageBoxCallback(config_window.sb__checkbox_auto_clear_message_box), variable=view_variable.VAR_ENABLE_AUTO_CLEAR_MESSAGE_BOX, ) config_window.sb__auto_clear_message_box.grid(row=row) @@ -46,7 +46,7 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v for_var_label_text=view_variable.VAR_LABEL_ENABLE_SEND_ONLY_TRANSLATED_MESSAGES, for_var_desc_text=view_variable.VAR_DESC_ENABLE_SEND_ONLY_TRANSLATED_MESSAGES, checkbox_attr_name="sb__checkbox_send_only_translated_messages", - command=lambda: checkbox_send_only_translated_messages_callback(config_window.sb__checkbox_send_only_translated_messages), + command=lambda: checkboxSendOnlyTranslatedMessagesCallback(config_window.sb__checkbox_send_only_translated_messages), variable=view_variable.VAR_ENABLE_SEND_ONLY_TRANSLATED_MESSAGES, ) config_window.sb__send_only_translated_messages.grid(row=row) @@ -56,7 +56,7 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v for_var_label_text=view_variable.VAR_LABEL_SEND_MESSAGE_BUTTON_TYPE, for_var_desc_text=view_variable.VAR_DESC_SEND_MESSAGE_BUTTON_TYPE, radio_button_attr_name="sb__radiobutton_send_message_button_type", - command=lambda: checkbox_send_message_button_type_callback(), + command=lambda: checkboxSendMessageButtonTypeCallback(), variable=view_variable.VAR_SEND_MESSAGE_BUTTON_TYPE, radiobutton_keys_values=view_variable.KEYS_VALUES_SEND_MESSAGE_BUTTON_TYPE, ) @@ -67,7 +67,7 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v for_var_label_text=view_variable.VAR_LABEL_ENABLE_NOTICE_XSOVERLAY, for_var_desc_text=view_variable.VAR_DESC_ENABLE_NOTICE_XSOVERLAY, checkbox_attr_name="sb__checkbox_notice_xsoverlay", - command=lambda: checkbox_notice_xsoverlay_callback(config_window.sb__checkbox_notice_xsoverlay), + command=lambda: checkboxNoticeXsoverlayCallback(config_window.sb__checkbox_notice_xsoverlay), variable=view_variable.VAR_ENABLE_NOTICE_XSOVERLAY, ) config_window.sb__notice_xsoverlay.grid(row=row) @@ -78,8 +78,8 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v for_var_label_text=view_variable.VAR_LABEL_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, for_var_desc_text=view_variable.VAR_DESC_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, checkbox_attr_name="sb__checkbox_auto_export_message_logs", - checkbox_command=lambda: checkbox_auto_export_message_logs_callback(config_window.sb__checkbox_auto_export_message_logs), - button_command=lambda _e: button_auto_export_message_logs_callback(), + checkbox_command=lambda: checkboxAutoExportMessageLogsCallback(config_window.sb__checkbox_auto_export_message_logs), + button_command=lambda _e: buttonAutoExportMessageLogsCallback(), variable=view_variable.VAR_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, ) config_window.sb__auto_export_message_logs.grid(row=row) @@ -90,7 +90,7 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v for_var_label_text=view_variable.VAR_LABEL_ENABLE_SEND_MESSAGE_TO_VRC, for_var_desc_text=view_variable.VAR_DESC_ENABLE_SEND_MESSAGE_TO_VRC, checkbox_attr_name="sb__checkbox_enable_send_message_to_vrc", - command=lambda: checkbox_enable_send_message_to_vrc_callback(config_window.sb__checkbox_enable_send_message_to_vrc), + command=lambda: checkboxEnableSendMessageToVrcCallback(config_window.sb__checkbox_enable_send_message_to_vrc), variable=view_variable.VAR_ENABLE_SEND_MESSAGE_TO_VRC, ) config_window.sb__enable_send_message_to_vrc.grid(row=row, pady=0) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others_Additional.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others_Additional.py index b7f39f91..577ef373 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others_Additional.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others_Additional.py @@ -6,7 +6,7 @@ def createSettingBox_Others_Additional(setting_box_wrapper, config_window, setti sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings, view_variable) createSettingBoxCheckbox = sbg.createSettingBoxCheckbox - def checkbox_enable_send_received_message_to_vrc_callback(checkbox_box_widget): + def checkboxEnableSendReceivedMessageToVrcCallback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC, checkbox_box_widget.get()) @@ -15,7 +15,7 @@ def createSettingBox_Others_Additional(setting_box_wrapper, config_window, setti for_var_label_text=view_variable.VAR_LABEL_ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC, for_var_desc_text=view_variable.VAR_DESC_ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC, checkbox_attr_name="sb__checkbox_enable_send_received_message_to_vrc", - command=lambda: checkbox_enable_send_received_message_to_vrc_callback(config_window.sb__checkbox_enable_send_received_message_to_vrc), + command=lambda: checkboxEnableSendReceivedMessageToVrcCallback(config_window.sb__checkbox_enable_send_received_message_to_vrc), variable=view_variable.VAR_ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC, ) config_window.sb__enable_send_received_message_to_vrc.grid(row=row, pady=0) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others_ReceivedMessageFormats.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others_ReceivedMessageFormats.py index 0d0bbca7..ac8bffa6 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others_ReceivedMessageFormats.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others_ReceivedMessageFormats.py @@ -8,14 +8,14 @@ def createSettingBox_Others_ReceivedMessageFormats(setting_box_wrapper, config_w createSettingBoxMessageFormatEntries = sbg.createSettingBoxMessageFormatEntries createSettingBoxMessageFormatEntries_WithTranslation = sbg.createSettingBoxMessageFormatEntries_WithTranslation - def entry_received_message_format_callback(value): + def entryReceivedMessageFormatCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_RECEIVED_MESSAGE_FORMAT, value) - def entry_received_message_format_with_t_callback(value): + def entryReceivedMessageFormatWithTCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_RECEIVED_MESSAGE_FORMAT_WITH_T, value) - def entry_swap_received_message_format_with_t_callback(_e): + def entrySwapReceivedMessageFormatWithTCallback(_e): callFunctionIfCallable(view_variable.CALLBACK_SWAP_RECEIVED_MESSAGE_FORMAT_WITH_T_REQUIRED_TEXT) row=0 @@ -33,7 +33,7 @@ def createSettingBox_Others_ReceivedMessageFormats(setting_box_wrapper, config_w entry_textvariable_1=view_variable.VAR_ENTRY_1_RECEIVED_MESSAGE_FORMAT, textvariable_0=view_variable.VAR_TEXT_REQUIRED_0_RECEIVED_MESSAGE_FORMAT, example_label_textvariable=view_variable.VAR_LABEL_EXAMPLE_TEXT_RECEIVED_MESSAGE_FORMAT, - entry_bind__Any_KeyRelease=lambda value: entry_received_message_format_callback(value), + entry_bind__Any_KeyRelease=lambda value: entryReceivedMessageFormatCallback(value), entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_RECEIVED_MESSAGE_FORMAT, ) config_window.sb__received_message_format.grid(row=row) @@ -57,8 +57,8 @@ def createSettingBox_Others_ReceivedMessageFormats(setting_box_wrapper, config_w textvariable_0=view_variable.VAR_TEXT_REQUIRED_0_RECEIVED_MESSAGE_FORMAT_WITH_T, textvariable_1=view_variable.VAR_TEXT_REQUIRED_1_RECEIVED_MESSAGE_FORMAT_WITH_T, example_label_textvariable=view_variable.VAR_LABEL_EXAMPLE_TEXT_RECEIVED_MESSAGE_FORMAT_WITH_T, - entry_bind__Any_KeyRelease=lambda value: entry_received_message_format_with_t_callback(value), - swap_button_command=entry_swap_received_message_format_with_t_callback, + entry_bind__Any_KeyRelease=lambda value: entryReceivedMessageFormatWithTCallback(value), + swap_button_command=entrySwapReceivedMessageFormatWithTCallback, entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_RECEIVED_MESSAGE_FORMAT_WITH_T, ) config_window.sb__received_message_format_with_t.grid(row=row, pady=0) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others_SendMessageFormats.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others_SendMessageFormats.py index 6180ffd8..61c89dd2 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others_SendMessageFormats.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others_SendMessageFormats.py @@ -8,14 +8,14 @@ def createSettingBox_Others_SendMessageFormats(setting_box_wrapper, config_windo createSettingBoxMessageFormatEntries = sbg.createSettingBoxMessageFormatEntries createSettingBoxMessageFormatEntries_WithTranslation = sbg.createSettingBoxMessageFormatEntries_WithTranslation - def entry_send_message_format_callback(value): + def entrySendMessageFormatCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_SEND_MESSAGE_FORMAT, value) - def entry_send_message_format_with_t_callback(value): + def entrySendMessageFormatWithTCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_SEND_MESSAGE_FORMAT_WITH_T, value) - def entry_swap_message_format_with_t_callback(_e): + def entrySwapMessageFormatWithTCallback(_e): callFunctionIfCallable(view_variable.CALLBACK_SWAP_SEND_MESSAGE_FORMAT_WITH_T_REQUIRED_TEXT) @@ -34,7 +34,7 @@ def createSettingBox_Others_SendMessageFormats(setting_box_wrapper, config_windo entry_textvariable_1=view_variable.VAR_ENTRY_1_SEND_MESSAGE_FORMAT, textvariable_0=view_variable.VAR_TEXT_REQUIRED_0_SEND_MESSAGE_FORMAT, example_label_textvariable=view_variable.VAR_LABEL_EXAMPLE_TEXT_SEND_MESSAGE_FORMAT, - entry_bind__Any_KeyRelease=lambda value: entry_send_message_format_callback(value), + entry_bind__Any_KeyRelease=lambda value: entrySendMessageFormatCallback(value), entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_SEND_MESSAGE_FORMAT, ) config_window.sb__message_format.grid(row=row) @@ -58,8 +58,8 @@ def createSettingBox_Others_SendMessageFormats(setting_box_wrapper, config_windo textvariable_0=view_variable.VAR_TEXT_REQUIRED_0_SEND_MESSAGE_FORMAT_WITH_T, textvariable_1=view_variable.VAR_TEXT_REQUIRED_1_SEND_MESSAGE_FORMAT_WITH_T, example_label_textvariable=view_variable.VAR_LABEL_EXAMPLE_TEXT_SEND_MESSAGE_FORMAT_WITH_T, - entry_bind__Any_KeyRelease=lambda value: entry_send_message_format_with_t_callback(value), - swap_button_command=entry_swap_message_format_with_t_callback, + entry_bind__Any_KeyRelease=lambda value: entrySendMessageFormatWithTCallback(value), + swap_button_command=entrySwapMessageFormatWithTCallback, entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_SEND_MESSAGE_FORMAT_WITH_T, ) config_window.sb__message_format_with_t.grid(row=row, pady=0) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index d5ece1b6..50eb8d2e 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -12,39 +12,40 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari createSettingBoxAddAndDeleteAbleList = sbg.createSettingBoxAddAndDeleteAbleList - def checkbox_input_mic_threshold_check_callback(is_turned_on): + def checkboxInputMicThresholdCheckCallback(is_turned_on): callFunctionIfCallable(view_variable.CALLBACK_CHECK_MIC_THRESHOLD, is_turned_on) - def optionmenu_mic_host_callback(value): + def optionmenuMicHostCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_MIC_HOST, value) - def optionmenu_input_mic_device_callback(value): + def optionmenuInputMicDeviceCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_MIC_DEVICE, value) - def slider_input_mic_energy_threshold_callback(value): + def sliderInputMicEnergyThresholdCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_MIC_ENERGY_THRESHOLD, value) - def checkbox_input_mic_dynamic_energy_threshold_callback(checkbox_box_widget): + def checkboxInputMicDynamicEnergyThresholdCallback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD, checkbox_box_widget.get()) - def entry_input_mic_record_timeout_callback(value): + def entryInputMicRecordTimeoutCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_MIC_RECORD_TIMEOUT, value) - def entry_input_mic_phrase_timeout_callback(value): + def entryInputMicPhraseTimeoutCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_MIC_PHRASE_TIMEOUT, value) - def entry_input_mic_max_phrases_callback(value): + def entryInputMicMaxPhrasesCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_MIC_MAX_PHRASES, value) - def arrow_switch_mic_word_filter_list_open_callback(value): + def arrowSwitchMicWordFilterListOpenCallback(value): callFunctionIfCallable(view_variable.CALLBACK_ARROW_SWITCH_MIC_WORD_FILTER_LIST_OPEN) - def arrow_switch_mic_word_filter_list_close_callback(value): + def arrowSwitchMicWordFilterListCloseCallback(value): callFunctionIfCallable(view_variable.CALLBACK_ARROW_SWITCH_MIC_WORD_FILTER_LIST_CLOSE) - def entry_input_mic_word_filters_callback(value): - callFunctionIfCallable(view_variable.CALLBACK_SET_MIC_WORD_FILTER, value) +# 直接 SettingBoxGenerator.pyでcallFunctionIfCallableから呼んでいます。(word filter 専用関数になっているのでそのままですが、良くはない) + # def entry_input_mic_word_filters_callback(value): + # callFunctionIfCallable(view_variable.CALLBACK_SET_MIC_WORD_FILTER, value) row=0 @@ -54,7 +55,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari for_var_desc_text=view_variable.VAR_DESC_MIC_HOST, optionmenu_attr_name="sb__optionmenu_mic_host", dropdown_menu_values=view_variable.LIST_MIC_HOST, - command=lambda value: optionmenu_mic_host_callback(value), + command=lambda value: optionmenuMicHostCallback(value), variable=view_variable.VAR_MIC_HOST, ) config_window.sb__mic_host.grid(row=row) @@ -65,7 +66,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari for_var_desc_text=view_variable.VAR_DESC_MIC_DEVICE, optionmenu_attr_name="sb__optionmenu_mic_device", dropdown_menu_values=view_variable.LIST_MIC_DEVICE, - command=lambda value: optionmenu_input_mic_device_callback(value), + command=lambda value: optionmenuInputMicDeviceCallback(value), variable=view_variable.VAR_MIC_DEVICE, ) config_window.sb__mic_device.grid(row=row) @@ -75,14 +76,14 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari for_var_label_text=view_variable.VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD, for_var_desc_text=view_variable.VAR_DESC_MIC_DYNAMIC_ENERGY_THRESHOLD, switch_attr_name="sb__checkbox_mic_dynamic_energy_threshold", - command=lambda: checkbox_input_mic_dynamic_energy_threshold_callback(config_window.sb__checkbox_mic_dynamic_energy_threshold), + command=lambda: checkboxInputMicDynamicEnergyThresholdCallback(config_window.sb__checkbox_mic_dynamic_energy_threshold), variable=view_variable.VAR_MIC_DYNAMIC_ENERGY_THRESHOLD ) config_window.sb__mic_dynamic_energy_threshold.grid(row=row, pady=0) row+=1 config_window.sb__mic_energy_threshold = createSettingBoxProgressbarXSlider( - command=slider_input_mic_energy_threshold_callback, + command=sliderInputMicEnergyThresholdCallback, progressbar_x_slider_attr_name="sb__mic_energy_threshold", entry_attr_name="sb__progressbar_x_slider__entry_mic_energy_threshold", @@ -97,9 +98,9 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari progressbar_attr_name="sb__progressbar_x_slider__progressbar_mic_energy_threshold", passive_button_attr_name="sb__progressbar_x_slider__passive_button_mic_energy_threshold", - passive_button_command=lambda _e: checkbox_input_mic_threshold_check_callback(True), + passive_button_command=lambda _e: checkboxInputMicThresholdCheckCallback(True), active_button_attr_name="sb__progressbar_x_slider__active_button_mic_energy_threshold", - active_button_command=lambda _e: checkbox_input_mic_threshold_check_callback(False), + active_button_command=lambda _e: checkboxInputMicThresholdCheckCallback(False), button_image_file=settings.image_file.MIC_ICON, disabled_button_attr_name="sb__progressbar_x_slider__disabled_button_mic_energy_threshold", disabled_button_image_file=settings.image_file.MIC_ICON_DISABLED, @@ -114,7 +115,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari for_var_desc_text=view_variable.VAR_DESC_MIC_RECORD_TIMEOUT, entry_attr_name="sb__entry_mic_record_timeout", entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_100, - entry_bind__Any_KeyRelease=lambda value: entry_input_mic_record_timeout_callback(value), + entry_bind__Any_KeyRelease=lambda value: entryInputMicRecordTimeoutCallback(value), entry_textvariable=view_variable.VAR_MIC_RECORD_TIMEOUT, entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_MIC_RECORD_TIMEOUT, ) @@ -126,7 +127,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari for_var_desc_text=view_variable.VAR_DESC_MIC_PHRASE_TIMEOUT, entry_attr_name="sb__entry_mic_phrase_timeout", entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_100, - entry_bind__Any_KeyRelease=lambda value: entry_input_mic_phrase_timeout_callback(value), + entry_bind__Any_KeyRelease=lambda value: entryInputMicPhraseTimeoutCallback(value), entry_textvariable=view_variable.VAR_MIC_PHRASE_TIMEOUT, entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_MIC_PHRASE_TIMEOUT, ) @@ -138,7 +139,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari for_var_desc_text=view_variable.VAR_DESC_MIC_MAX_PHRASES, entry_attr_name="sb__entry_mic_max_phrases", entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_100, - entry_bind__Any_KeyRelease=lambda value: entry_input_mic_max_phrases_callback(value), + entry_bind__Any_KeyRelease=lambda value: entryInputMicMaxPhrasesCallback(value), entry_textvariable=view_variable.VAR_MIC_MAX_PHRASES, entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_MIC_MAX_PHRASES, ) @@ -151,8 +152,8 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari for_var_label_text=view_variable.VAR_LABEL_MIC_WORD_FILTER, for_var_desc_text=view_variable.VAR_DESC_MIC_WORD_FILTER, arrow_switch_attr_name="sb__arrow_switch_mic_word_filter", - open_command=lambda value: arrow_switch_mic_word_filter_list_open_callback(value), - close_command=lambda value: arrow_switch_mic_word_filter_list_close_callback(value), + open_command=lambda value: arrowSwitchMicWordFilterListOpenCallback(value), + close_command=lambda value: arrowSwitchMicWordFilterListCloseCallback(value), var_switch_desc=view_variable.VAR_SWITCH_DESC_MIC_WORD_FILTER, ) config_window.sb__mic_word_filter.grid(row=row, pady=0) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index a9d1ad9b..846dbd8e 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -9,24 +9,24 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ createSettingBoxEntry = sbg.createSettingBoxEntry - def checkbox_input_speaker_threshold_check_callback(is_turned_on): + def checkboxInputSpeakerThresholdCheckCallback(is_turned_on): callFunctionIfCallable(view_variable.CALLBACK_CHECK_SPEAKER_THRESHOLD, is_turned_on) - def slider_input_speaker_energy_threshold_callback(value): + def sliderInputSpeakerEnergyThresholdCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD, value) - def checkbox_input_speaker_dynamic_energy_threshold_callback(checkbox_box_widget): + def checkboxInputSpeakerDynamicEnergyThresholdCallback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, checkbox_box_widget.get()) - def entry_input_speaker_record_timeout_callback(value): + def entryInputSpeakerRecordTimeoutCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT, value) - def entry_input_speaker_phrase_timeout_callback(value): + def entryInputSpeakerPhraseTimeoutCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT, value) - def entry_input_speaker_max_phrases_callback(value): + def entryInputSpeakerMaxPhrasesCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_SPEAKER_MAX_PHRASES, value) @@ -36,14 +36,14 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ for_var_label_text=view_variable.VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, for_var_desc_text=view_variable.VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, switch_attr_name="sb__checkbox_speaker_dynamic_energy_threshold", - command=lambda: checkbox_input_speaker_dynamic_energy_threshold_callback(config_window.sb__checkbox_speaker_dynamic_energy_threshold), + command=lambda: checkboxInputSpeakerDynamicEnergyThresholdCallback(config_window.sb__checkbox_speaker_dynamic_energy_threshold), variable=view_variable.VAR_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, ) config_window.sb__speaker_dynamic_energy_threshold.grid(row=row, pady=0) row+=1 config_window.sb__speaker_energy_threshold = createSettingBoxProgressbarXSlider( - command=slider_input_speaker_energy_threshold_callback, + command=sliderInputSpeakerEnergyThresholdCallback, progressbar_x_slider_attr_name="sb__speaker_energy_threshold", entry_variable=view_variable.VAR_SPEAKER_ENERGY_THRESHOLD__ENTRY, @@ -58,9 +58,9 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ progressbar_attr_name="sb__progressbar_x_slider__progressbar_speaker_energy_threshold", passive_button_attr_name="sb__progressbar_x_slider__passive_button_speaker_energy_threshold", - passive_button_command=lambda _e: checkbox_input_speaker_threshold_check_callback(True), + passive_button_command=lambda _e: checkboxInputSpeakerThresholdCheckCallback(True), active_button_attr_name="sb__progressbar_x_slider__active_button_speaker_energy_threshold", - active_button_command=lambda _e: checkbox_input_speaker_threshold_check_callback(False), + active_button_command=lambda _e: checkboxInputSpeakerThresholdCheckCallback(False), button_image_file=settings.image_file.HEADPHONES_ICON, disabled_button_attr_name="sb__progressbar_x_slider__disabled_button_speaker_energy_threshold", disabled_button_image_file=settings.image_file.HEADPHONES_ICON_DISABLED, @@ -75,7 +75,7 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ for_var_desc_text=view_variable.VAR_DESC_SPEAKER_RECORD_TIMEOUT, entry_attr_name="sb__entry_speaker_record_timeout", entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_100, - entry_bind__Any_KeyRelease=lambda value: entry_input_speaker_record_timeout_callback(value), + entry_bind__Any_KeyRelease=lambda value: entryInputSpeakerRecordTimeoutCallback(value), entry_textvariable=view_variable.VAR_SPEAKER_RECORD_TIMEOUT, entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_SPEAKER_RECORD_TIMEOUT, ) @@ -87,7 +87,7 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ for_var_desc_text=view_variable.VAR_DESC_SPEAKER_PHRASE_TIMEOUT, entry_attr_name="sb__entry_speaker_phrase_timeout", entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_100, - entry_bind__Any_KeyRelease=lambda value: entry_input_speaker_phrase_timeout_callback(value), + entry_bind__Any_KeyRelease=lambda value: entryInputSpeakerPhraseTimeoutCallback(value), entry_textvariable=view_variable.VAR_SPEAKER_PHRASE_TIMEOUT, entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_SPEAKER_PHRASE_TIMEOUT, ) @@ -99,7 +99,7 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ for_var_desc_text=view_variable.VAR_DESC_SPEAKER_MAX_PHRASES, entry_attr_name="sb__entry_speaker_max_phrases", entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_100, - entry_bind__Any_KeyRelease=lambda value: entry_input_speaker_max_phrases_callback(value), + entry_bind__Any_KeyRelease=lambda value: entryInputSpeakerMaxPhrasesCallback(value), entry_textvariable=view_variable.VAR_SPEAKER_MAX_PHRASES, entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_SPEAKER_MAX_PHRASES, ) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py index 62ed881a..f245c387 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py @@ -8,13 +8,13 @@ def createSettingBox_Translation(setting_box_wrapper, config_window, settings, v createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu createSettingBoxEntry = sbg.createSettingBoxEntry - def switch_use_translation_feature_callback(switch_widget): + def switchUseTranslationFeatureCallback(switch_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_USE_TRANSLATION_FEATURE, switch_widget.get()) - def optionmenu_ctranslate2_weight_type_callback(value): + def optionmenuCtranslate2WeightTypeCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_CTRANSLATE2_WEIGHT_TYPE, value) - def deepl_auth_key_callback(value): + def deeplAuthKeyCallback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_DEEPL_AUTH_KEY, value) @@ -23,7 +23,7 @@ def createSettingBox_Translation(setting_box_wrapper, config_window, settings, v for_var_label_text=view_variable.VAR_LABEL_USE_TRANSLATION_FEATURE, for_var_desc_text=view_variable.VAR_DESC_USE_TRANSLATION_FEATURE, switch_attr_name="sb__switch_use_translation_feature", - command=lambda: switch_use_translation_feature_callback(config_window.sb__switch_use_translation_feature), + command=lambda: switchUseTranslationFeatureCallback(config_window.sb__switch_use_translation_feature), variable=view_variable.VAR_USE_TRANSLATION_FEATURE ) config_window.sb__use_translation_feature.grid(row=row, pady=0) @@ -34,7 +34,7 @@ def createSettingBox_Translation(setting_box_wrapper, config_window, settings, v for_var_desc_text=view_variable.VAR_DESC_CTRANSLATE2_WEIGHT_TYPE, optionmenu_attr_name="sb__optionmenu_ctranslate2_weight_type", dropdown_menu_values=view_variable.DICT_CTRANSLATE2_WEIGHT_TYPE, - command=lambda value: optionmenu_ctranslate2_weight_type_callback(value), + command=lambda value: optionmenuCtranslate2WeightTypeCallback(value), variable=view_variable.VAR_CTRANSLATE2_WEIGHT_TYPE, ) config_window.sb__ctranslate2_weight_type.grid(row=row) @@ -46,7 +46,7 @@ def createSettingBox_Translation(setting_box_wrapper, config_window, settings, v for_var_desc_text=view_variable.VAR_DESC_DEEPL_AUTH_KEY, entry_attr_name="sb__entry_deepl_auth_key", entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_300, - entry_bind__Any_KeyRelease=lambda value: deepl_auth_key_callback(value), + entry_bind__Any_KeyRelease=lambda value: deeplAuthKeyCallback(value), entry_textvariable=view_variable.VAR_DEEPL_AUTH_KEY, ) config_window.sb__deepl_auth_key.grid(row=row, pady=0) From 0ae206f4bd554100ae6a8432c0e2a86b66b3ebe2 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 19 Jan 2024 17:48:51 +0900 Subject: [PATCH 47/67] =?UTF-8?q?[Chore]=20img:=20=E7=94=BB=E5=83=8F?= =?UTF-8?q?=E3=83=87=E3=83=BC=E3=82=BF=E4=B8=80=E3=81=A4=E3=81=A0=E3=81=91?= =?UTF-8?q?=E3=82=B5=E3=82=A4=E3=82=BA=E3=81=8C=E5=A4=A7=E3=81=8D=E3=81=8B?= =?UTF-8?q?=E3=81=A3=E3=81=9F=E3=81=AE=E3=81=A7=E3=80=8148px=E3=81=AE?= =?UTF-8?q?=E3=82=82=E3=81=AE=E3=81=AB=E5=B7=AE=E3=81=97=E6=9B=BF=E3=81=88?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- img/narrow_arrow_down_black.png | Bin 5433 -> 855 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/img/narrow_arrow_down_black.png b/img/narrow_arrow_down_black.png index 21e2ea0bd1d35aee3e23166cac7768a50db90e3e..e53829e5dc93de28c2b861c0ee8dd1d281ea7c0d 100644 GIT binary patch delta 847 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 Pl*LfjM5p|=Q}llWi%qS6 From f48581dcd8149e9966b51ffbd98f717c8689a440 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 19 Jan 2024 18:27:27 +0900 Subject: [PATCH 48/67] =?UTF-8?q?[WIP/TEST]=20Controller=20:=20=E3=83=A2?= =?UTF-8?q?=E3=83=87=E3=83=AB=E3=81=AE=E3=83=AD=E3=83=BC=E3=83=89=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E3=82=B9=E3=83=AC=E3=83=83=E3=83=89=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ・モデルの切り替え時や翻訳機能有効時にリスタートボタンの表示タイミングが微妙な為改修の必要あり --- controller.py | 16 ++++++++++++---- vrct_gui/_CreateDropdownMenuWindow.py | 13 ++++++++++--- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/controller.py b/controller.py index a01fc008..a528db3b 100644 --- a/controller.py +++ b/controller.py @@ -489,7 +489,11 @@ def callbackSetUseTranslationFeature(value): if config.USE_TRANSLATION_FEATURE is True: view.useTranslationFeatureProcess("Normal") if model.checkCTranslatorCTranslate2ModelWeight(): - model.changeTranslatorCTranslate2Model() + def callback(): + model.changeTranslatorCTranslate2Model() + th_callback = Thread(target=callback) + th_callback.daemon = True + th_callback.start() else: view.showRestartButtonIfRequired() view.useTranslationFeatureProcess("Restart") @@ -501,9 +505,13 @@ def callbackSetCtranslate2WeightType(value): config.WEIGHT_TYPE = str(value) view.updateSelectedCtranslate2WeightType(config.WEIGHT_TYPE) if model.checkCTranslatorCTranslate2ModelWeight(): - model.changeTranslatorCTranslate2Model() - view.showRestartButtonIfRequired() - view.useTranslationFeatureProcess("Normal") + def callback(): + model.changeTranslatorCTranslate2Model() + view.showRestartButtonIfRequired() + view.useTranslationFeatureProcess("Normal") + th_callback = Thread(target=callback) + th_callback.daemon = True + th_callback.start() else: view.showRestartButtonIfRequired() view.useTranslationFeatureProcess("Restart") diff --git a/vrct_gui/_CreateDropdownMenuWindow.py b/vrct_gui/_CreateDropdownMenuWindow.py index 7bb233cb..d6853b35 100644 --- a/vrct_gui/_CreateDropdownMenuWindow.py +++ b/vrct_gui/_CreateDropdownMenuWindow.py @@ -326,11 +326,18 @@ class _CreateDropdownMenuWindow(CTkToplevel): def _withdraw(self, e=None): self.withdraw() - if self.BIND_CONFIGURE_FUNC_ID in self.attach_widget.winfo_toplevel().bind(): + try: self.attach_widget.winfo_toplevel().unbind("", self.BIND_CONFIGURE_FUNC_ID) - self.attach_widget.unbind("", self.BIND_UNMAP_FUNC_ID) - if self.BIND_BUTTON_1_FUNC_ID in self.attach_widget.winfo_toplevel().bind(): + except Exception: + pass + try: + self.attach_widget.unbind("", self.BIND_UNMAP_FUNC_ID) + except Exception: + pass + try: self.attach_widget.winfo_toplevel().unbind("", self.BIND_BUTTON_1_FUNC_ID) + except Exception: + pass self.hide = True From 9dd10f14a8d9a518398f77fb1f3d5e53dc6f8443 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 20 Jan 2024 13:30:28 +0900 Subject: [PATCH 49/67] =?UTF-8?q?[bugfix]=20Use=20translation=20feature?= =?UTF-8?q?=E3=82=AA=E3=83=B3=E6=99=82=E3=80=81weight=E5=88=87=E6=9B=BF?= =?UTF-8?q?=E6=99=82=E3=81=AA=E3=81=A9=E3=81=AB=E3=83=A1=E3=82=A4=E3=83=B3?= =?UTF-8?q?=E7=94=BB=E9=9D=A2=E9=BB=92=E3=81=84=E3=82=AB=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=81=8C=E5=89=8D=E3=81=AB=E5=87=BA=E3=81=A6=E3=81=8D=E3=81=A6?= =?UTF-8?q?=E3=80=81=E3=81=9D=E3=81=93=E3=81=9D=E3=81=93=E9=95=B7=E3=81=84?= =?UTF-8?q?=E6=99=82=E9=96=93=E3=81=A8=E3=81=A9=E3=81=BE=E3=81=A3=E3=81=A6?= =?UTF-8?q?=E3=81=84=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=82?= =?UTF-8?q?=E5=AE=8C=E5=85=A8=E3=81=A7=E3=81=AF=E3=81=AA=E3=81=84=E3=81=8C?= =?UTF-8?q?=E5=A6=A5=E5=8D=94=E6=A1=88=E3=81=A7=E3=81=99=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/view.py b/view.py index 02acb490..91497027 100644 --- a/view.py +++ b/view.py @@ -885,19 +885,21 @@ class View(): self.openCtranslate2WeightTypeWidget() self.setTranslationSwitchStatus("normal", release_locked_state=True) vrct_gui.sls__box_translation_optionmenu_wrapper.grid() - vrct_gui.config_window.after(200, vrct_gui.config_window.lift) + vrct_gui.update() elif state == "Disable": view.closeCtranslate2WeightTypeWidget() view.setTranslationSwitchStatus("disabled", to_lock_state=True) vrct_gui.sls__box_translation_optionmenu_wrapper.grid_remove() - vrct_gui.config_window.after(200, vrct_gui.config_window.lift) + vrct_gui.update() elif state == "Restart": view.setLatestCTranslate2WeightType() view.setTranslationSwitchStatus("disabled", to_lock_state=True) vrct_gui.sls__box_translation_optionmenu_wrapper.grid() - vrct_gui.config_window.after(200, vrct_gui.config_window.lift) + + vrct_gui.update() + vrct_gui.config_window.lift() # Open Webpage Functions From 3fc3ae02bbca2b5817e6ea984a5b863ec638e81d Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sat, 20 Jan 2024 14:19:14 +0900 Subject: [PATCH 50/67] =?UTF-8?q?[WIP/TEST]=20Controller=20:=20Reset?= =?UTF-8?q?=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=AE=E8=A1=A8=E7=A4=BA=E3=82=BF?= =?UTF-8?q?=E3=82=A4=E3=83=9F=E3=83=B3=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 10 ++++++++++ controller.py | 8 ++++++++ view.py | 6 ++---- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/config.py b/config.py index 54b057a5..c1af4611 100644 --- a/config.py +++ b/config.py @@ -197,6 +197,15 @@ class Config: if value in list(translation_lang.keys()): self._CHOICE_OUTPUT_TRANSLATOR = value + @property + def IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION(self): + return self._IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION + + @IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION.setter + def IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION(self, value): + if isinstance(value, bool): + self._IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = value + # Save Json Data ## Main Window @property @@ -761,6 +770,7 @@ class Config: self._SOURCE_COUNTRY = "Japan" self._TARGET_LANGUAGE = "English" self._TARGET_COUNTRY = "United States" + self._IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = False # Save Json Data ## Main Window diff --git a/controller.py b/controller.py index a528db3b..cf3aa39e 100644 --- a/controller.py +++ b/controller.py @@ -490,14 +490,19 @@ def callbackSetUseTranslationFeature(value): view.useTranslationFeatureProcess("Normal") if model.checkCTranslatorCTranslate2ModelWeight(): def callback(): + config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = False + view.showRestartButtonIfRequired() model.changeTranslatorCTranslate2Model() th_callback = Thread(target=callback) th_callback.daemon = True th_callback.start() else: + config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = True view.showRestartButtonIfRequired() view.useTranslationFeatureProcess("Restart") else: + config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = False + view.showRestartButtonIfRequired() view.useTranslationFeatureProcess("Disable") def callbackSetCtranslate2WeightType(value): @@ -506,6 +511,8 @@ def callbackSetCtranslate2WeightType(value): view.updateSelectedCtranslate2WeightType(config.WEIGHT_TYPE) if model.checkCTranslatorCTranslate2ModelWeight(): def callback(): + config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = False + view.showRestartButtonIfRequired() model.changeTranslatorCTranslate2Model() view.showRestartButtonIfRequired() view.useTranslationFeatureProcess("Normal") @@ -513,6 +520,7 @@ def callbackSetCtranslate2WeightType(value): th_callback.daemon = True th_callback.start() else: + config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = True view.showRestartButtonIfRequired() view.useTranslationFeatureProcess("Restart") diff --git a/view.py b/view.py index 02acb490..60365984 100644 --- a/view.py +++ b/view.py @@ -28,8 +28,7 @@ class View(): ui_scaling=config.UI_SCALING, font_family=config.FONT_FAMILY, ui_language=config.UI_LANGUAGE, - use_translation_feature=config.USE_TRANSLATION_FEATURE, - ctranslate2_weight_type=config.WEIGHT_TYPE, + is_reset_button_displayed_for_translation=config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION, ) if config.ENABLE_SPEAKER2CHATBOX is False: @@ -994,8 +993,7 @@ class View(): self.restart_required_configs_pre_data.ui_scaling == config.UI_SCALING and self.restart_required_configs_pre_data.font_family == config.FONT_FAMILY and self.restart_required_configs_pre_data.ui_language == config.UI_LANGUAGE and - self.restart_required_configs_pre_data.use_translation_feature == config.USE_TRANSLATION_FEATURE and - self.restart_required_configs_pre_data.ctranslate2_weight_type == config.WEIGHT_TYPE + self.restart_required_configs_pre_data.is_reset_button_displayed_for_translation == config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION ) if locale is None: From 548668e8d4257896b960e8b3ea013298dbabcb7e Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sat, 20 Jan 2024 15:14:38 +0900 Subject: [PATCH 51/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Installer=20:=20?= =?UTF-8?q?=E5=88=9D=E5=9B=9E=E8=B5=B7=E5=8B=95=E6=99=82=E3=81=AB=E7=BF=BB?= =?UTF-8?q?=E8=A8=B3=E3=83=A2=E3=83=87=E3=83=AB=E3=81=AE=E3=83=80=E3=82=A6?= =?UTF-8?q?=E3=83=B3=E3=83=AD=E3=83=BC=E3=83=89=E3=81=AE=E6=9C=89=E5=8A=B9?= =?UTF-8?q?=E7=84=A1=E5=8A=B9=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- installer/installer.nsi | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/installer/installer.nsi b/installer/installer.nsi index 6e246132..c2b3152d 100644 --- a/installer/installer.nsi +++ b/installer/installer.nsi @@ -64,6 +64,8 @@ Var Label_DescriptionOptions Var Label_DescriptionComboBox Var ComboBox_Language Var Set_Langage +Var DownloadWeight +Var Checkbox_DownloadWeight ; 初期化時コールバック Function .onInit @@ -71,6 +73,7 @@ Function .onInit StrCpy $InstallDocs ${BST_CHECKED} StrCpy $InstallShortcut ${BST_CHECKED} StrCpy $ComboBox_Language "English" + StrCpy $DownloadWeight ${BST_CHECKED} FunctionEnd ; オプション ページ @@ -96,14 +99,18 @@ Function OptionPage ${NSD_CreateCheckbox} 0 26u 100% 12u "デスクトップにショートカットを作成(&D)" Pop $Checkbox_InstallShortcut + ${NSD_CreateLabel} 0 52u 100% 12u "初回起動時の設定を設定してください。" ; ComboBoxを作成します。 - ${NSD_CreateLabel} 0 42u 100% 12u "UIの言語を設定してください。" + ${NSD_CreateLabel} 0 65u 100% 12u "UIの言語" ; ラベルを変数に代入します。 Pop $Label_DescriptionComboBox - ${NSD_CreateComboBox} 0 55u 100% 12u "" + ${NSD_CreateComboBox} 0 77u 100% 12u "" Pop $ComboBox_Language + ${NSD_CreateCheckbox} 0 92u 100% 12u "翻訳モデルのダウンロード(&D)" + Pop $Checkbox_DownloadWeight + ${NSD_CB_AddString} $ComboBox_Language "English" ${NSD_CB_AddString} $ComboBox_Language "日本語" ${NSD_CB_AddString} $ComboBox_Language "한국어" @@ -117,6 +124,10 @@ Function OptionPage ${NSD_Check} $Checkbox_InstallShortcut ${EndIf} ${NSD_CB_SelectString} $ComboBox_Language "English" + ${If} $DownloadWeight == ${BST_CHECKED} + ; チェックが入力済の場合、チェックボックスにチェックを入れます。 + ${NSD_Check} $Checkbox_DownloadWeight + ${EndIf} nsDialogs::Show FunctionEnd @@ -125,6 +136,7 @@ Function OptionPageLeave ${NSD_GetState} $Checkbox_InstallDocs $InstallDocs ${NSD_GetState} $Checkbox_InstallShortcut $InstallShortcut ${NSD_GetText} $ComboBox_Language $ComboBox_Language + ${NSD_GetState} $Checkbox_DownloadWeight $DownloadWeight FunctionEnd ; デフォルト セクション @@ -177,7 +189,13 @@ Section StrCpy $Set_Langage "ko" ${EndIf} - StrCpy $1 '{"UI_LANGUAGE": "$Set_Langage"}' + ${If} $DownloadWeight == 1 + StrCpy $DownloadWeight "true" + ${Else} + StrCpy $DownloadWeight "false" + ${EndIf} + + StrCpy $1 '{"UI_LANGUAGE": "$Set_Langage", "USE_TRANSLATION_FEATURE": $DownloadWeight}' FileOpen $0 "$INSTDIR\config.json" w FileWrite $0 $1 FileClose $0 From b0def64cf177848f7400ef26684d59ffb4ff5acc Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 20 Jan 2024 18:13:18 +0900 Subject: [PATCH 52/67] =?UTF-8?q?[Update]=20Main=20Window=20Sidebar:=20?= =?UTF-8?q?=E7=BF=BB=E8=A8=B3=E6=A9=9F=E8=83=BD=E3=82=92=E4=BD=BF=E3=82=8F?= =?UTF-8?q?=E3=81=AA=E3=81=84=E6=99=82=E3=81=AF=E3=80=81=E3=81=9D=E3=82=82?= =?UTF-8?q?=E3=81=9D=E3=82=82=E7=BF=BB=E8=A8=B3=E6=A9=9F=E8=83=BD=E3=81=AE?= =?UTF-8?q?=E3=83=9C=E3=82=BF=E3=83=B3=E3=82=92=E8=A1=A8=E7=A4=BA=E3=81=97?= =?UTF-8?q?=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E3=81=AB=E3=80=82=20?= =?UTF-8?q?=E9=9F=B3=E5=A3=B0=E8=AA=8D=E8=AD=98=E5=87=A6=E7=90=86=E4=B8=AD?= =?UTF-8?q?=E3=81=AA=E3=81=A9=E3=81=AE=E3=83=9C=E3=82=BF=E3=83=B3disabled?= =?UTF-8?q?=E6=99=82=E3=80=81=E3=82=B9=E3=82=A4=E3=83=83=E3=83=81=E3=81=AE?= =?UTF-8?q?=E8=89=B2=E3=82=92=E3=82=82=E3=81=A3=E3=81=A8=E7=84=A1=E5=8A=B9?= =?UTF-8?q?=E6=84=9F=E3=81=8C=E3=81=A7=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 6 ++++++ vrct_gui/_changeMainWindowWidgetsStatus.py | 4 ++-- vrct_gui/ui_managers/Themes/_darkTheme.py | 1 + vrct_gui/ui_managers/Themes/_lightTheme.py | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/view.py b/view.py index 7c7f0020..692692e0 100644 --- a/view.py +++ b/view.py @@ -884,18 +884,24 @@ class View(): self.openCtranslate2WeightTypeWidget() self.setTranslationSwitchStatus("normal", release_locked_state=True) vrct_gui.sls__box_translation_optionmenu_wrapper.grid() + vrct_gui.compact_mode_translation_frame.grid() + vrct_gui.translation_frame.grid() vrct_gui.update() elif state == "Disable": view.closeCtranslate2WeightTypeWidget() view.setTranslationSwitchStatus("disabled", to_lock_state=True) vrct_gui.sls__box_translation_optionmenu_wrapper.grid_remove() + vrct_gui.compact_mode_translation_frame.grid_remove() + vrct_gui.translation_frame.grid_remove() vrct_gui.update() elif state == "Restart": view.setLatestCTranslate2WeightType() view.setTranslationSwitchStatus("disabled", to_lock_state=True) vrct_gui.sls__box_translation_optionmenu_wrapper.grid() + vrct_gui.compact_mode_translation_frame.grid() + vrct_gui.translation_frame.grid() vrct_gui.update() vrct_gui.config_window.lift() diff --git a/vrct_gui/_changeMainWindowWidgetsStatus.py b/vrct_gui/_changeMainWindowWidgetsStatus.py index 3a77cb32..3b6eaf74 100644 --- a/vrct_gui/_changeMainWindowWidgetsStatus.py +++ b/vrct_gui/_changeMainWindowWidgetsStatus.py @@ -28,13 +28,13 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta if status == "disabled": widget_frame.configure(cursor="") widget_label.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) - widget_switch_box.configure(state="disabled", progress_color=settings.ctm.SF__SWITCH_BOX_DISABLE_BG_COLOR) + widget_switch_box.configure(state="disabled", progress_color=settings.ctm.SF__SWITCH_BOX_DISABLE_BG_COLOR, button_color=settings.ctm.SF__SWITCH_BOX_BUTTON_DISABLED_COLOR) widget_selected_mark.configure(fg_color=settings.ctm.SF__SELECTED_MARK_DISABLE_BG_COLOR) icon_file = disabled_icon_name elif status == "normal": widget_frame.configure(cursor="hand2") widget_label.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) - widget_switch_box.configure(state="normal", progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_BG_COLOR) + widget_switch_box.configure(state="normal", progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_BG_COLOR, button_color=settings.ctm.SF__SWITCH_BOX_BUTTON_COLOR) widget_selected_mark.configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_BG_COLOR) icon_file = icon_name diff --git a/vrct_gui/ui_managers/Themes/_darkTheme.py b/vrct_gui/ui_managers/Themes/_darkTheme.py index 15721fb8..8442b6bb 100644 --- a/vrct_gui/ui_managers/Themes/_darkTheme.py +++ b/vrct_gui/ui_managers/Themes/_darkTheme.py @@ -58,6 +58,7 @@ def _darkTheme(base_color): SF__SWITCH_BOX_DISABLE_BG_COLOR = base_color.PRIMARY_800_COLOR, SF__SWITCH_BOX_BUTTON_COLOR = base_color.DARK_400_COLOR, + SF__SWITCH_BOX_BUTTON_DISABLED_COLOR = base_color.DARK_600_COLOR, # It's not working because It overrode internally. SF__SWITCH_BOX_BUTTON_HOVERED_COLOR = base_color.DARK_350_COLOR, diff --git a/vrct_gui/ui_managers/Themes/_lightTheme.py b/vrct_gui/ui_managers/Themes/_lightTheme.py index 2261386e..8d2ff33b 100644 --- a/vrct_gui/ui_managers/Themes/_lightTheme.py +++ b/vrct_gui/ui_managers/Themes/_lightTheme.py @@ -58,6 +58,7 @@ def _lightTheme(base_color): SF__SWITCH_BOX_DISABLE_BG_COLOR = base_color.PRIMARY_200_COLOR, SF__SWITCH_BOX_BUTTON_COLOR = base_color.LIGHT_150_COLOR, + SF__SWITCH_BOX_BUTTON_DISABLED_COLOR = base_color.LIGHT_300_COLOR, # It's not working because It overrode internally. SF__SWITCH_BOX_BUTTON_HOVERED_COLOR = base_color.LIGHT_300_COLOR, From 9208bb3ac3b9fd6b9b651eed0086e0fbdacd15eb Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 21 Jan 2024 13:00:16 +0900 Subject: [PATCH 53/67] =?UTF-8?q?[Chore]=20view.py=20gui=E3=81=AEupdate?= =?UTF-8?q?=E9=96=A2=E6=95=B0=E5=87=A6=E7=90=86=E3=81=8C=E8=A2=AB=E3=81=A3?= =?UTF-8?q?=E3=81=A6=E3=81=84=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E3=80=82=E2=80=BB=E6=B6=88=E3=81=97=E5=BF=98=E3=82=8C=E3=81=A7?= =?UTF-8?q?=E3=81=97=E3=81=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/view.py b/view.py index 692692e0..20b9678e 100644 --- a/view.py +++ b/view.py @@ -886,7 +886,6 @@ class View(): vrct_gui.sls__box_translation_optionmenu_wrapper.grid() vrct_gui.compact_mode_translation_frame.grid() vrct_gui.translation_frame.grid() - vrct_gui.update() elif state == "Disable": view.closeCtranslate2WeightTypeWidget() @@ -894,7 +893,6 @@ class View(): vrct_gui.sls__box_translation_optionmenu_wrapper.grid_remove() vrct_gui.compact_mode_translation_frame.grid_remove() vrct_gui.translation_frame.grid_remove() - vrct_gui.update() elif state == "Restart": view.setLatestCTranslate2WeightType() From 59079def6a48e15112e8a7c4ca12bc807564219e Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 21 Jan 2024 14:49:04 +0900 Subject: [PATCH 54/67] =?UTF-8?q?[bugfix]=20Config=20Window:=20=E5=86=8D?= =?UTF-8?q?=E8=B5=B7=E5=8B=95=E3=83=9C=E3=82=BF=E3=83=B3=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=E3=81=AE=E3=83=A9=E3=83=99=E3=83=AB=E3=81=8C=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84=E5=A0=B4=E5=90=88=E3=81=8C?= =?UTF-8?q?=E3=81=82=E3=82=8B=E3=83=90=E3=82=B0=E4=BF=AE=E6=AD=A3=E3=80=82?= =?UTF-8?q?=20weight=20type=E5=A4=89=E6=9B=B4=E6=99=82=E3=81=AE=E3=81=BF?= =?UTF-8?q?=E7=99=BA=E7=94=9F=E3=81=97=E3=81=A6=E3=81=84=E3=81=9F=E3=80=82?= =?UTF-8?q?after=E9=96=A2=E6=95=B0=E3=82=92=E4=BD=BF=E3=81=84=E3=80=81?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=82=92=E8=8B=A5=E5=B9=B2=E9=81=85=E3=82=89?= =?UTF-8?q?=E3=81=9B=E3=82=8B=E3=81=93=E3=81=A8=E3=81=A7=E5=AF=BE=E5=87=A6?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/view.py b/view.py index 20b9678e..093d5e60 100644 --- a/view.py +++ b/view.py @@ -1013,7 +1013,8 @@ class View(): def _showRestartButton(self, locale:Union[None,str]=None): self.view_variable.VAR_CONFIG_WINDOW_RESTART_BUTTON_LABEL.set(i18n.t("config_window.restart_message", locale=locale)) - vrct_gui.config_window.restart_button_container.grid() + # This .after() func is for fixing bug that it can't be shown the label widget after changing the weight type. + vrct_gui.config_window.after(100, vrct_gui.config_window.restart_button_container.grid) def _hideRestartButton(self): vrct_gui.config_window.restart_button_container.grid_remove() From b381e2d8ba806d1cdb9d98d300c2c36aa8012ffd Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 22 Jan 2024 01:34:50 +0900 Subject: [PATCH 55/67] =?UTF-8?q?index=20on=20installer:=20548668e=20?= =?UTF-8?q?=F0=9F=91=8D[Update]=20Installer=20:=20=E5=88=9D=E5=9B=9E?= =?UTF-8?q?=E8=B5=B7=E5=8B=95=E6=99=82=E3=81=AB=E7=BF=BB=E8=A8=B3=E3=83=A2?= =?UTF-8?q?=E3=83=87=E3=83=AB=E3=81=AE=E3=83=80=E3=82=A6=E3=83=B3=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E3=83=89=E3=81=AE=E6=9C=89=E5=8A=B9=E7=84=A1=E5=8A=B9?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 6ed71f1a4aad703e8f8edd638b38ee0eef2aad26 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 22 Jan 2024 01:45:18 +0900 Subject: [PATCH 56/67] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Controller=20:=20?= =?UTF-8?q?=E3=83=AA=E3=82=B9=E3=82=BF=E3=83=BC=E3=83=88=E3=83=9C=E3=82=BF?= =?UTF-8?q?=E3=83=B3=E3=81=AE=E8=A1=A8=E7=A4=BA=E3=82=BF=E3=82=A4=E3=83=9F?= =?UTF-8?q?=E3=83=B3=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 12 ++++-------- view.py | 3 +-- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/controller.py b/controller.py index cf3aa39e..d8f9efac 100644 --- a/controller.py +++ b/controller.py @@ -489,40 +489,36 @@ def callbackSetUseTranslationFeature(value): if config.USE_TRANSLATION_FEATURE is True: view.useTranslationFeatureProcess("Normal") if model.checkCTranslatorCTranslate2ModelWeight(): + config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = False def callback(): - config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = False - view.showRestartButtonIfRequired() model.changeTranslatorCTranslate2Model() th_callback = Thread(target=callback) th_callback.daemon = True th_callback.start() else: config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = True - view.showRestartButtonIfRequired() view.useTranslationFeatureProcess("Restart") else: config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = False - view.showRestartButtonIfRequired() view.useTranslationFeatureProcess("Disable") + view.showRestartButtonIfRequired() def callbackSetCtranslate2WeightType(value): print("callbackSetCtranslate2WeightType", value) config.WEIGHT_TYPE = str(value) view.updateSelectedCtranslate2WeightType(config.WEIGHT_TYPE) if model.checkCTranslatorCTranslate2ModelWeight(): + config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = False def callback(): - config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = False - view.showRestartButtonIfRequired() model.changeTranslatorCTranslate2Model() - view.showRestartButtonIfRequired() view.useTranslationFeatureProcess("Normal") th_callback = Thread(target=callback) th_callback.daemon = True th_callback.start() else: config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = True - view.showRestartButtonIfRequired() view.useTranslationFeatureProcess("Restart") + view.showRestartButtonIfRequired() def callbackSetDeeplAuthkey(value): print("callbackSetDeeplAuthkey", str(value)) diff --git a/view.py b/view.py index 093d5e60..20b9678e 100644 --- a/view.py +++ b/view.py @@ -1013,8 +1013,7 @@ class View(): def _showRestartButton(self, locale:Union[None,str]=None): self.view_variable.VAR_CONFIG_WINDOW_RESTART_BUTTON_LABEL.set(i18n.t("config_window.restart_message", locale=locale)) - # This .after() func is for fixing bug that it can't be shown the label widget after changing the weight type. - vrct_gui.config_window.after(100, vrct_gui.config_window.restart_button_container.grid) + vrct_gui.config_window.restart_button_container.grid() def _hideRestartButton(self): vrct_gui.config_window.restart_button_container.grid_remove() From 3bdf179a72264afd0b5b40304e62567e5d690a29 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 22 Jan 2024 03:04:15 +0900 Subject: [PATCH 57/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Installer=20:=20?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- installer/installer.nsi | 80 +++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/installer/installer.nsi b/installer/installer.nsi index 1fe78f5d..549db8bf 100644 --- a/installer/installer.nsi +++ b/installer/installer.nsi @@ -60,27 +60,27 @@ Var Checkbox_InstallShortcut Var Dialog_Options Var InstallDocs Var InstallShortcut -Var Label_DescriptionOptions -Var Label_DescriptionComboBox -Var ComboBox_Language +Var DropList_Language Var Set_Langage Var DownloadWeight -Var Checkbox_DownloadWeight Var RadioButton_Download Var RadioButton_NotDownload +Var Label_Translation_subtitle_1 +Var Label_Translation_subtitle_2 +Var subFont ; 初期化時コールバック Function .onInit ; オプション値を初期化します。 StrCpy $InstallDocs ${BST_CHECKED} StrCpy $InstallShortcut ${BST_CHECKED} - StrCpy $ComboBox_Language "English" + StrCpy $DropList_Language "English" StrCpy $DownloadWeight ${BST_CHECKED} FunctionEnd ; オプション ページ 1 Function OptionPage1 - !insertmacro MUI_HEADER_TEXT "オプション" "オプションを設定してください。" + !insertmacro MUI_HEADER_TEXT "オプション(Options)" "オプションを設定してください。(Please set the options.)" ; nsDialogsを作成します。 nsDialogs::Create 1018 ; 作成されたnsDialogsを変数に代入します。 @@ -91,16 +91,20 @@ Function OptionPage1 Abort ${EndIf} - ; ラベルを作成します。 - ${NSD_CreateLabel} 0 0 100% 12u "オプションを選択してください。" - ; ラベルを変数に代入します。 - Pop $Label_DescriptionOptions - - ${NSD_CreateCheckbox} 0 13u 100% 12u "ドキュメントをインストールする(&D)" + ${NSD_CreateCheckbox} 0 0u 100% 12u "ドキュメントをインストールする(Install documents)" Pop $Checkbox_InstallDocs - ${NSD_CreateCheckbox} 0 26u 100% 12u "デスクトップにショートカットを作成(&D)" + ${NSD_CreateCheckbox} 0 13u 100% 12u "デスクトップにショートカットを作成(Install shortcut on desktop)" Pop $Checkbox_InstallShortcut + + ${If} $InstallDocs == ${BST_CHECKED} + ; チェックが入力済の場合、チェックボックスにチェックを入れます。 + ${NSD_Check} $Checkbox_InstallDocs + ${EndIf} + ${If} $InstallShortcut == ${BST_CHECKED} + ; チェックが入力済の場合、チェックボックスにチェックを入れます。 + ${NSD_Check} $Checkbox_InstallShortcut + ${EndIf} nsDialogs::Show FunctionEnd @@ -112,7 +116,9 @@ FunctionEnd ; オプション ページ 2 Function OptionPage2 - !insertmacro MUI_HEADER_TEXT "オプション" "初回起動時の設定を設定してください。" + CreateFont $subFont "MS UI Gothic" "8" "400" + + !insertmacro MUI_HEADER_TEXT "初期設定(Initial Settings)" "後から変更可能です。(Changeable later.)" ; nsDialogsを作成します。 nsDialogs::Create 1018 ; 作成されたnsDialogsを変数に代入します。 @@ -124,38 +130,44 @@ Function OptionPage2 ${EndIf} ; ComboBoxを作成します。 - ${NSD_CreateLabel} 0 0u 100% 12u "UIの言語" - ; ラベルを変数に代入します。 - Pop $Label_DescriptionComboBox + ${NSD_CreateLabel} 0 0u 30% 12u "UIの言語(Language)" - ${NSD_CreateDropList} 0 15u 100% 12u "" - Pop $ComboBox_Language - - ${NSD_CreateCheckbox} 0 30u 100% 12u "翻訳モデルのダウンロード(&D)" - Pop $Checkbox_DownloadWeight + ${NSD_CreateDropList} 33% 0u 33% 12u "" + Pop $DropList_Language # ラジオボタンを追加しWEIGHTをDownloadするか選択する - ${NSD_CreateRadioButton} 0 45u 100% 12u "Download" + ${NSD_CreateLabel} 0 30u 30% 12u "翻訳機能(Translation)" + ${NSD_CreateLabel} 0 43u 30% 8u "言語モデルをダウンロード" + Pop $Label_Translation_subtitle_1 + SendMessage $Label_Translation_subtitle_1 ${WM_SETFONT} $subFont 0 + SetCtlColors $Label_Translation_subtitle_1 0x696969 0xF0F0F0 + ${NSD_CreateLabel} 0 52u 30% 8u "(Download language model)" + Pop $Label_Translation_subtitle_2 + SendMessage $Label_Translation_subtitle_2 ${WM_SETFONT} $subFont 0 + SetCtlColors $Label_Translation_subtitle_2 0x696969 0xF0F0F0 + + ${NSD_CreateRadioButton} 33% 30u 33% 12u "使用する(use)" Pop $RadioButton_Download - ${NSD_CreateRadioButton} 0 60u 100% 12u "Not Download" + ${NSD_CreateRadioButton} 66% 30u 33% 12u "使用しない(Don't use)" Pop $RadioButton_NotDownload - ${NSD_CB_AddString} $ComboBox_Language "English" - ${NSD_CB_AddString} $ComboBox_Language "日本語" - ${NSD_CB_AddString} $ComboBox_Language "한국어" + ${NSD_CB_AddString} $DropList_Language "English" + ${NSD_CB_AddString} $DropList_Language "日本語" + ${NSD_CB_AddString} $DropList_Language "한국어" + + ${NSD_CB_SelectString} $DropList_Language "English" - ${NSD_CB_SelectString} $ComboBox_Language "English" ${If} $DownloadWeight == ${BST_CHECKED} ; チェックが入力済の場合、チェックボックスにチェックを入れます。 - ${NSD_Check} $Checkbox_DownloadWeight + ${NSD_Check} $RadioButton_Download ${EndIf} nsDialogs::Show FunctionEnd ; オプション ページ 2 退出コールバック Function OptionPageLeave2 - ${NSD_GetText} $ComboBox_Language $ComboBox_Language - ${NSD_GetState} $Checkbox_DownloadWeight $DownloadWeight + ${NSD_GetText} $DropList_Language $DropList_Language + ${NSD_GetState} $RadioButton_Download $DownloadWeight FunctionEnd ; デフォルト セクション @@ -200,11 +212,11 @@ Section ${EndIf} ; ComboBoxの選択値から言語を判定しconfig.jsonを$INSTDIRに作成 - ${If} $ComboBox_Language == "English" + ${If} $DropList_Language == "English" StrCpy $Set_Langage "en" - ${ElseIf} $ComboBox_Language == "日本語" + ${ElseIf} $DropList_Language == "日本語" StrCpy $Set_Langage "ja" - ${ElseIf} $ComboBox_Language == "한국어" + ${ElseIf} $DropList_Language == "한국어" StrCpy $Set_Langage "ko" ${EndIf} From 46aafa4a4d2ecb9e9cd2e0fe65cc3afc489bfa9c Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 22 Jan 2024 18:20:10 +0900 Subject: [PATCH 58/67] =?UTF-8?q?[Update/Refactor]=20Main=20Window:=20Use?= =?UTF-8?q?=20translation=20feature=20ON/OFF=E6=99=82=E3=81=AEwidget?= =?UTF-8?q?=E8=AA=BF=E6=95=B4=E3=80=82=20Translation=20Each=20Other?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=81=AF=E3=80=81=E7=BF=BB=E8=A8=B3=E6=A9=9F?= =?UTF-8?q?=E8=83=BD=E4=BD=BF=E3=82=8F=E3=81=AA=E3=81=84=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=AF=E5=BF=85=E8=A6=81=E3=81=AE=E3=81=AA=E3=81=84=E6=83=85?= =?UTF-8?q?=E5=A0=B1=E3=81=AA=E3=81=AE=E3=81=A7=E3=80=81=E3=81=9D=E3=81=AE?= =?UTF-8?q?=E6=99=82=E3=81=AFSwap=20Language=E8=A1=A8=E7=A4=BA=E3=81=AB?= =?UTF-8?q?=E5=9B=BA=E5=AE=9A=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 2 +- locales/ja.yml | 2 +- locales/ko.yml | 2 +- view.py | 47 ++++++++++++++----- .../createSidebarLanguagesSettings.py | 12 ++--- 5 files changed, 41 insertions(+), 24 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 31406a23..24ac7cd7 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -6,7 +6,7 @@ main_window: language_settings: Language Settings your_language: Your Language - both_direction_desc: Translate Each Other + translate_each_other_label: Translate Each Other swap_button_label: Swap Languages target_language: Target Language translator: Translator diff --git a/locales/ja.yml b/locales/ja.yml index 2b3eb2a8..4d972a18 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -6,7 +6,7 @@ main_window: language_settings: 言語設定 your_language: あなたの言語 - both_direction_desc: 双方向に翻訳 + translate_each_other_label: 双方向に翻訳 swap_button_label: 言語を入れ替え target_language: 相手の言語 translator: 翻訳エンジン diff --git a/locales/ko.yml b/locales/ko.yml index 11e23fae..ff30399f 100644 --- a/locales/ko.yml +++ b/locales/ko.yml @@ -6,7 +6,7 @@ main_window: language_settings: 언어 설정 your_language: 당신의 언어 - both_direction_desc: 양방향으로 번역 + translate_each_other_label: 양방향으로 번역 swap_button_label: 언어 교체 target_language: 상대방의 언어 diff --git a/view.py b/view.py index 20b9678e..3ed71a23 100644 --- a/view.py +++ b/view.py @@ -162,9 +162,10 @@ class View(): IS_OPENED_SELECTABLE_YOUR_LANGUAGE_WINDOW=False, CALLBACK_SELECTED_YOUR_LANGUAGE=None, - VAR_LABEL_BOTH_DIRECTION_DESC=StringVar(value=i18n.t("main_window.both_direction_desc")), - VAR_LABEL_BOTH_DIRECTION_SWAP_BUTTON=StringVar(value=i18n.t("main_window.swap_button_label")), + VAR_LABEL_BOTH_DIRECTION_SWAP_BUTTON=StringVar(value=""), CALLBACK_SWAP_LANGUAGES=None, + CALLBACK_ENTERED_SWAP_LANGUAGES_BUTTON=self._enteredSwapLanguagesButton, + CALLBACK_LEAVED_SWAP_LANGUAGES_BUTTON=self._leavedSwapLanguagesButton, VAR_LABEL_TARGET_LANGUAGE=StringVar(value=i18n.t("main_window.target_language")), VAR_TARGET_LANGUAGE = StringVar(value=f"{config.TARGET_LANGUAGE}\n({config.TARGET_COUNTRY})"), @@ -651,7 +652,9 @@ class View(): if config.USE_TRANSLATION_FEATURE is True: self.useTranslationFeatureProcess("Normal") + self.view_variable.VAR_LABEL_BOTH_DIRECTION_SWAP_BUTTON.set(i18n.t("main_window.translate_each_other_label")) else: + self.view_variable.VAR_LABEL_BOTH_DIRECTION_SWAP_BUTTON.set(i18n.t("main_window.swap_button_label")) self.useTranslationFeatureProcess("Disable") if config.CHOICE_MIC_HOST == "NoHost": @@ -879,27 +882,34 @@ class View(): } def useTranslationFeatureProcess(self, state:str): + def changeWidget_UseTranslationFeature(): + vrct_gui.sls__box_translation_optionmenu_wrapper.grid() + vrct_gui.compact_mode_translation_frame.grid() + vrct_gui.translation_frame.grid() + self.view_variable.VAR_LABEL_BOTH_DIRECTION_SWAP_BUTTON.set(i18n.t("main_window.translate_each_other_label")) + + def changeWidget_DontUseTranslationFeature(): + vrct_gui.sls__box_translation_optionmenu_wrapper.grid_remove() + vrct_gui.compact_mode_translation_frame.grid_remove() + vrct_gui.translation_frame.grid_remove() + self.view_variable.VAR_LABEL_BOTH_DIRECTION_SWAP_BUTTON.set(i18n.t("main_window.swap_button_label")) + + if state == "Normal": self.setLatestCTranslate2WeightType() self.openCtranslate2WeightTypeWidget() self.setTranslationSwitchStatus("normal", release_locked_state=True) - vrct_gui.sls__box_translation_optionmenu_wrapper.grid() - vrct_gui.compact_mode_translation_frame.grid() - vrct_gui.translation_frame.grid() + changeWidget_UseTranslationFeature() elif state == "Disable": view.closeCtranslate2WeightTypeWidget() view.setTranslationSwitchStatus("disabled", to_lock_state=True) - vrct_gui.sls__box_translation_optionmenu_wrapper.grid_remove() - vrct_gui.compact_mode_translation_frame.grid_remove() - vrct_gui.translation_frame.grid_remove() + changeWidget_DontUseTranslationFeature() elif state == "Restart": view.setLatestCTranslate2WeightType() view.setTranslationSwitchStatus("disabled", to_lock_state=True) - vrct_gui.sls__box_translation_optionmenu_wrapper.grid() - vrct_gui.compact_mode_translation_frame.grid() - vrct_gui.translation_frame.grid() + changeWidget_UseTranslationFeature() vrct_gui.update() vrct_gui.config_window.lift() @@ -1221,7 +1231,7 @@ class View(): self._clearEntryBox(vrct_gui.config_window.sb__entry_mic_word_filter_list) -# Widget Control (Whole) +# Widget Control def foregroundOnIfForegroundEnabled(self): if config.ENABLE_FOREGROUND: self.foregroundOn() @@ -1279,6 +1289,19 @@ class View(): vrct_gui.main_send_message_button_container.grid() vrct_gui.config_window.after(200, vrct_gui.config_window.lift) + def _enteredSwapLanguagesButton(self): + self.view_variable.VAR_LABEL_BOTH_DIRECTION_SWAP_BUTTON.set(i18n.t("main_window.swap_button_label")) + vrct_gui.sls__both_direction_desc.configure( + text_color=self.settings.main.ctm.SLS__BOX_ARROWS_SWAP_BUTTON_TEXT_COLOR, + ) + + def _leavedSwapLanguagesButton(self): + if config.USE_TRANSLATION_FEATURE is True: + self.view_variable.VAR_LABEL_BOTH_DIRECTION_SWAP_BUTTON.set(i18n.t("main_window.translate_each_other_label")) + vrct_gui.sls__both_direction_desc.configure( + text_color=self.settings.main.ctm.SLS__BOX_ARROWS_TEXT_COLOR, + ) + # Function def _adjustUiSizeAndRestart(self): current_percentage = int(config.UI_SCALING.replace("%","")) diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index e85e084a..6678ffd3 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -250,7 +250,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): main_window.sls__both_direction_desc = CTkLabel( main_window.sls__arrow_direction_swap_box, - textvariable=view_variable.VAR_LABEL_BOTH_DIRECTION_DESC, + textvariable=view_variable.VAR_LABEL_BOTH_DIRECTION_SWAP_BUTTON, height=0, font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__BOX_ARROWS_DESC_FONT_SIZE, weight="normal"), text_color=settings.ctm.SLS__BOX_ARROWS_TEXT_COLOR, @@ -286,16 +286,10 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): def adjustedCommand_Entered(): - main_window.sls__both_direction_desc.configure( - textvariable=view_variable.VAR_LABEL_BOTH_DIRECTION_SWAP_BUTTON, - text_color=settings.ctm.SLS__BOX_ARROWS_SWAP_BUTTON_TEXT_COLOR, - ) + callFunctionIfCallable(view_variable.CALLBACK_ENTERED_SWAP_LANGUAGES_BUTTON) def adjustedCommand_Leaved(): - main_window.sls__both_direction_desc.configure( - textvariable=view_variable.VAR_LABEL_BOTH_DIRECTION_DESC, - text_color=settings.ctm.SLS__BOX_ARROWS_TEXT_COLOR, - ) + callFunctionIfCallable(view_variable.CALLBACK_LEAVED_SWAP_LANGUAGES_BUTTON) bindEnterAndLeaveFunction( target_widgets=[ From f2734e726cb9bfaef92c45ce82b3976b3183d9b8 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 22 Jan 2024 18:53:17 +0900 Subject: [PATCH 59/67] =?UTF-8?q?=F0=9F=91=8D[Uodate]=20installer=20:=20S?= =?UTF-8?q?=E3=81=AE=E6=8C=87=E7=A4=BA=E3=81=AE=E5=85=83UI=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- installer/installer.nsi | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/installer/installer.nsi b/installer/installer.nsi index 549db8bf..7d3b9a94 100644 --- a/installer/installer.nsi +++ b/installer/installer.nsi @@ -80,7 +80,7 @@ FunctionEnd ; オプション ページ 1 Function OptionPage1 - !insertmacro MUI_HEADER_TEXT "オプション(Options)" "オプションを設定してください。(Please set the options.)" + !insertmacro MUI_HEADER_TEXT "オプション (Options)" "オプションを設定してください。 (Please set the options.)" ; nsDialogsを作成します。 nsDialogs::Create 1018 ; 作成されたnsDialogsを変数に代入します。 @@ -91,10 +91,10 @@ Function OptionPage1 Abort ${EndIf} - ${NSD_CreateCheckbox} 0 0u 100% 12u "ドキュメントをインストールする(Install documents)" + ${NSD_CreateCheckbox} 0 0u 100% 12u "ドキュメントをインストールする (Install documents)" Pop $Checkbox_InstallDocs - ${NSD_CreateCheckbox} 0 13u 100% 12u "デスクトップにショートカットを作成(Install shortcut on desktop)" + ${NSD_CreateCheckbox} 0 13u 100% 12u "デスクトップにショートカットを作成 (Install shortcut on desktop)" Pop $Checkbox_InstallShortcut ${If} $InstallDocs == ${BST_CHECKED} @@ -118,7 +118,7 @@ FunctionEnd Function OptionPage2 CreateFont $subFont "MS UI Gothic" "8" "400" - !insertmacro MUI_HEADER_TEXT "初期設定(Initial Settings)" "後から変更可能です。(Changeable later.)" + !insertmacro MUI_HEADER_TEXT "初期設定 (Initial Settings)" "後から変更可能です。 (Changeable later.)" ; nsDialogsを作成します。 nsDialogs::Create 1018 ; 作成されたnsDialogsを変数に代入します。 @@ -130,25 +130,25 @@ Function OptionPage2 ${EndIf} ; ComboBoxを作成します。 - ${NSD_CreateLabel} 0 0u 30% 12u "UIの言語(Language)" + ${NSD_CreateLabel} 0 20u 30% 12u "UIの言語 (Language)" - ${NSD_CreateDropList} 33% 0u 33% 12u "" + ${NSD_CreateDropList} 33% 20u 33% 12u "" Pop $DropList_Language # ラジオボタンを追加しWEIGHTをDownloadするか選択する - ${NSD_CreateLabel} 0 30u 30% 12u "翻訳機能(Translation)" - ${NSD_CreateLabel} 0 43u 30% 8u "言語モデルをダウンロード" + ${NSD_CreateLabel} 0 70u 30% 12u "翻訳機能 (Translation)" + ${NSD_CreateLabel} 0 83u 30% 8u "言語モデルをダウンロード" Pop $Label_Translation_subtitle_1 SendMessage $Label_Translation_subtitle_1 ${WM_SETFONT} $subFont 0 SetCtlColors $Label_Translation_subtitle_1 0x696969 0xF0F0F0 - ${NSD_CreateLabel} 0 52u 30% 8u "(Download language model)" + ${NSD_CreateLabel} 0 92u 30% 8u "(Download language model)" Pop $Label_Translation_subtitle_2 SendMessage $Label_Translation_subtitle_2 ${WM_SETFONT} $subFont 0 SetCtlColors $Label_Translation_subtitle_2 0x696969 0xF0F0F0 - ${NSD_CreateRadioButton} 33% 30u 33% 12u "使用する(use)" + ${NSD_CreateRadioButton} 33% 70u 33% 12u "使用する (Use)" Pop $RadioButton_Download - ${NSD_CreateRadioButton} 66% 30u 33% 12u "使用しない(Don't use)" + ${NSD_CreateRadioButton} 66% 70u 33% 12u "使用しない (Don't use)" Pop $RadioButton_NotDownload ${NSD_CB_AddString} $DropList_Language "English" From edaca7edc2d3fd62531443db1067fc7a2d4335b4 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 22 Jan 2024 20:33:50 +0900 Subject: [PATCH 60/67] =?UTF-8?q?[bugfix/refactor]=20weight=E3=82=BF?= =?UTF-8?q?=E3=82=A4=E3=83=97=E5=A4=89=E6=9B=B4=E5=87=A6=E7=90=86=E4=B8=AD?= =?UTF-8?q?=E3=81=AF=E3=80=81weight=E3=82=BF=E3=82=A4=E3=83=97=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E3=81=A8=E7=BF=BB=E8=A8=B3=E6=A9=9F=E8=83=BD=E3=82=AA?= =?UTF-8?q?=E3=83=B3=E3=82=AA=E3=83=95widget=E3=82=92disabled=E3=81=AB?= =?UTF-8?q?=E3=80=82=20=E7=90=86=E7=94=B1:=20weight=E3=82=BF=E3=82=A4?= =?UTF-8?q?=E3=83=97=E5=A4=89=E6=9B=B4=E5=87=A6=E7=90=86=E3=81=AF=E9=9D=9E?= =?UTF-8?q?=E5=90=8C=E6=9C=9F=E3=81=A7=E3=80=81=E5=A4=89=E6=9B=B4=E5=87=A6?= =?UTF-8?q?=E7=90=86=E4=B8=AD=E3=81=AB=E4=B8=8A=E8=A8=98widget=E3=82=92?= =?UTF-8?q?=E8=A7=A6=E3=82=8C=E3=82=8B=E3=81=A8UI=E3=81=B8=E3=81=AE?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=E5=87=A6=E7=90=86=E9=A0=86=E3=81=8C=E6=84=8F?= =?UTF-8?q?=E5=9B=B3=E3=81=97=E3=81=9F=E9=A0=86=E7=95=AA=E3=81=A8=E5=A4=89?= =?UTF-8?q?=E3=82=8F=E3=81=A3=E3=81=A6=E3=81=97=E3=81=BE=E3=81=86=E3=81=9F?= =?UTF-8?q?=E3=82=81=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit その他リファクタリングあり。 --- controller.py | 3 ++ view.py | 22 ++++++++++ vrct_gui/_changeConfigWindowWidgetsStatus.py | 42 ++++++++++++++++++- .../_SettingBoxGenerator.py | 4 ++ vrct_gui/ui_managers/Themes/_darkTheme.py | 3 ++ vrct_gui/ui_managers/Themes/_lightTheme.py | 3 ++ vrct_gui/ui_utils/ui_utils.py | 11 ++++- 7 files changed, 84 insertions(+), 4 deletions(-) diff --git a/controller.py b/controller.py index d8f9efac..ade5dc0d 100644 --- a/controller.py +++ b/controller.py @@ -507,17 +507,20 @@ def callbackSetCtranslate2WeightType(value): print("callbackSetCtranslate2WeightType", value) config.WEIGHT_TYPE = str(value) view.updateSelectedCtranslate2WeightType(config.WEIGHT_TYPE) + view.setWidgetsStatus_changeWeightType_Pending() if model.checkCTranslatorCTranslate2ModelWeight(): config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = False def callback(): model.changeTranslatorCTranslate2Model() view.useTranslationFeatureProcess("Normal") + view.setWidgetsStatus_changeWeightType_Done() th_callback = Thread(target=callback) th_callback.daemon = True th_callback.start() else: config.IS_RESET_BUTTON_DISPLAYED_FOR_TRANSLATION = True view.useTranslationFeatureProcess("Restart") + view.setWidgetsStatus_changeWeightType_Done() view.showRestartButtonIfRequired() def callbackSetDeeplAuthkey(value): diff --git a/view.py b/view.py index 3ed71a23..fa748514 100644 --- a/view.py +++ b/view.py @@ -1038,6 +1038,28 @@ class View(): vrct_gui.config_window.setting_box_compact_mode_switch_box.configure(state="normal") + @staticmethod + def setWidgetsStatus_changeWeightType_Pending(): + vrct_gui.config_window.sb__switch_use_translation_feature.configure(state="disabled") + vrct_gui._changeConfigWindowWidgetsStatus( + status="disabled", + target_names=[ + "sb__switch_use_translation_feature", + "sb__optionmenu_ctranslate2_weight_type", + ] + ) + @staticmethod + def setWidgetsStatus_changeWeightType_Done(): + vrct_gui.config_window.sb__switch_use_translation_feature.configure(state="normal") + vrct_gui._changeConfigWindowWidgetsStatus( + status="normal", + target_names=[ + "sb__switch_use_translation_feature", + "sb__optionmenu_ctranslate2_weight_type", + ] + ) + + def updateSelectedCtranslate2WeightType(self, selected_weight_type:str): self.view_variable.VAR_CTRANSLATE2_WEIGHT_TYPE.set(self.getSelectableCtranslate2WeightTypeDict()[selected_weight_type]) diff --git a/vrct_gui/_changeConfigWindowWidgetsStatus.py b/vrct_gui/_changeConfigWindowWidgetsStatus.py index b9b0f9d8..02dddd42 100644 --- a/vrct_gui/_changeConfigWindowWidgetsStatus.py +++ b/vrct_gui/_changeConfigWindowWidgetsStatus.py @@ -10,37 +10,75 @@ def _changeConfigWindowWidgetsStatus(config_window, settings, view_variable, sta if target_widget.desc_widget is not None: target_widget.desc_widget.configure(text_color=settings.ctm.LABELS_TEXT_DISABLED_COLOR) + def normalLabelsWidgets(target_widget): + target_widget.label_widget.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) + if target_widget.desc_widget is not None: + target_widget.desc_widget.configure(text_color=settings.ctm.LABELS_DESC_TEXT_COLOR) + + def disableOptionmenuWidget(target_widget): - disableLabelsWidgets(target_widget) target_widget.optionmenu_label_widget.configure(text_color=settings.ctm.LABELS_TEXT_DISABLED_COLOR) target_widget.optionmenu_img_widget.configure(image=CTkImage(settings.image_file.ARROW_LEFT_DISABLED.rotate(90), size=settings.uism.SB__OPTIONMENU_IMG_SIZE)) target_widget.optionmenu_box.unbindFunction() target_widget.optionmenu_box.configure(cursor="") + def normalOptionmenuWidget(target_widget): + target_widget.optionmenu_label_widget.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) + target_widget.optionmenu_img_widget.configure(image=CTkImage(settings.image_file.ARROW_LEFT.rotate(90), size=settings.uism.SB__OPTIONMENU_IMG_SIZE)) + target_widget.optionmenu_box.bindFunction() + target_widget.optionmenu_box.configure(cursor="hand2") + for target_name in target_names: match target_name: case "sb__optionmenu_mic_host": if status == "disabled": target_widget = config_window.sb__widgets["sb__optionmenu_mic_host"] + disableLabelsWidgets(target_widget) disableOptionmenuWidget(target_widget) case "sb__optionmenu_mic_device": if status == "disabled": target_widget = config_window.sb__widgets["sb__optionmenu_mic_device"] + disableLabelsWidgets(target_widget) disableOptionmenuWidget(target_widget) case "sb__optionmenu_appearance_theme": if status == "disabled": target_widget = config_window.sb__widgets["sb__optionmenu_appearance_theme"] + disableLabelsWidgets(target_widget) disableOptionmenuWidget(target_widget) + case "sb__optionmenu_ctranslate2_weight_type": + target_widget = config_window.sb__widgets["sb__optionmenu_ctranslate2_weight_type"] + if status == "disabled": + disableOptionmenuWidget(target_widget) + elif status == "normal": + normalOptionmenuWidget(target_widget) + + + case "sb__switch_use_translation_feature": + target_widget = config_window.sb__widgets["sb__switch_use_translation_feature"] + if status == "disabled": + target_widget.switch_box.configure( + state="disabled", + fg_color=settings.ctm.SB__SWITCH_BOX_BG_DISABLED_COLOR, + progress_color=settings.ctm.SB__SWITCH_BOX_ACTIVE_BG_DISABLED_COLOR, + button_color=settings.ctm.SB__SWITCH_BOX_BUTTON_DISABLED_COLOR, + ) + elif status == "normal": + target_widget.switch_box.configure( + state="normal", + fg_color=settings.ctm.SB__SWITCH_BOX_BG_COLOR, + progress_color=settings.ctm.SB__SWITCH_BOX_ACTIVE_BG_COLOR, + button_color=settings.ctm.SB__SWITCH_BOX_BUTTON_COLOR, + ) case "sb__checkbox_enable_send_received_message_to_vrc": if status == "disabled": target_widget = config_window.sb__widgets["sb__checkbox_enable_send_received_message_to_vrc"] disableLabelsWidgets(target_widget) - config_window.sb__checkbox_enable_send_received_message_to_vrc.configure( + target_widget.checkbox.configure( state="disabled", border_color=settings.ctm.SB__CHECKBOX_BORDER_DISABLED_COLOR ) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 7d44a2bb..ade6913a 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -210,6 +210,8 @@ class _SettingBoxGenerator(): ) setattr(self.config_window, switch_attr_name, switch_widget) + self.config_window.sb__widgets[switch_attr_name].switch_box = switch_widget + switch_widget.grid(row=1, column=SETTING_BOX_COLUMN, sticky="e") return setting_box_frame @@ -245,6 +247,8 @@ class _SettingBoxGenerator(): ) setattr(self.config_window, checkbox_attr_name, checkbox_widget) + self.config_window.sb__widgets[checkbox_attr_name].checkbox = checkbox_widget + checkbox_widget.grid(row=1, column=SETTING_BOX_COLUMN, sticky="e") return setting_box_frame diff --git a/vrct_gui/ui_managers/Themes/_darkTheme.py b/vrct_gui/ui_managers/Themes/_darkTheme.py index 8442b6bb..46592b4f 100644 --- a/vrct_gui/ui_managers/Themes/_darkTheme.py +++ b/vrct_gui/ui_managers/Themes/_darkTheme.py @@ -216,8 +216,11 @@ def _darkTheme(base_color): SB__SLIDER_TOOLTIP_TEXT_COLOR = base_color.DARK_200_COLOR, SB__SWITCH_BOX_BG_COLOR = base_color.DARK_800_COLOR, + SB__SWITCH_BOX_BG_DISABLED_COLOR = base_color.DARK_900_COLOR, SB__SWITCH_BOX_ACTIVE_BG_COLOR = base_color.PRIMARY_500_COLOR, + SB__SWITCH_BOX_ACTIVE_BG_DISABLED_COLOR = base_color.PRIMARY_700_COLOR, SB__SWITCH_BOX_BUTTON_COLOR = base_color.DARK_400_COLOR, + SB__SWITCH_BOX_BUTTON_DISABLED_COLOR = base_color.DARK_700_COLOR, SB__SWITCH_BOX_BUTTON_HOVERED_COLOR = base_color.DARK_350_COLOR, SB__CHECKBOX_BORDER_COLOR = base_color.DARK_600_COLOR, diff --git a/vrct_gui/ui_managers/Themes/_lightTheme.py b/vrct_gui/ui_managers/Themes/_lightTheme.py index 8d2ff33b..08caeae1 100644 --- a/vrct_gui/ui_managers/Themes/_lightTheme.py +++ b/vrct_gui/ui_managers/Themes/_lightTheme.py @@ -209,8 +209,11 @@ def _lightTheme(base_color): SB__SLIDER_TOOLTIP_TEXT_COLOR = base_color.LIGHT_800_COLOR, SB__SWITCH_BOX_BG_COLOR = base_color.LIGHT_400_COLOR, + SB__SWITCH_BOX_BG_DISABLED_COLOR = base_color.LIGHT_200_COLOR, SB__SWITCH_BOX_ACTIVE_BG_COLOR = base_color.PRIMARY_300_COLOR, + SB__SWITCH_BOX_ACTIVE_BG_DISABLED_COLOR = base_color.PRIMARY_150_COLOR, SB__SWITCH_BOX_BUTTON_COLOR = base_color.LIGHT_300_COLOR, + SB__SWITCH_BOX_BUTTON_DISABLED_COLOR = base_color.LIGHT_150_COLOR, SB__SWITCH_BOX_BUTTON_HOVERED_COLOR = base_color.LIGHT_200_COLOR, SB__CHECKBOX_BORDER_COLOR = base_color.LIGHT_600_COLOR, diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py index 3e6dcccf..ca75416b 100644 --- a/vrct_gui/ui_utils/ui_utils.py +++ b/vrct_gui/ui_utils/ui_utils.py @@ -205,10 +205,15 @@ def createLabelButton(parent_widget, label_button_bg_color, label_button_hovered bindButtonReleaseFunction([label_button_label_wrapper, label_button_box, label_button_label_widget], label_button_clicked_command) + def bindEventFromWidgets(): + bindButtonReleaseFunction([label_button_label_wrapper, label_button_box, label_button_label_widget], label_button_clicked_command) + bindEventFromWidgets() + def unbindEventFromWidgets(): unbindEnterLEaveButtonPressButtonReleaseFunction([label_button_label_wrapper, label_button_box, label_button_label_widget]) label_button_box.unbindFunction = unbindEventFromWidgets + label_button_box.bindFunction = bindEventFromWidgets return (label_button_box, label_button_label_widget) @@ -265,13 +270,15 @@ def createOptionMenuBox(parent_widget, optionmenu_bg_color, optionmenu_hovered_b bindButtonPressColor([optionmenu_label_wrapper, option_menu_box, optionmenu_label_widget, optionmenu_img_widget], optionmenu_clicked_bg_color, optionmenu_hovered_bg_color) - - bindButtonReleaseFunction([optionmenu_label_wrapper, option_menu_box, optionmenu_label_widget, optionmenu_img_widget], optionmenu_clicked_command) + def bindEventFromWidgets(): + bindButtonReleaseFunction([optionmenu_label_wrapper, option_menu_box, optionmenu_label_widget, optionmenu_img_widget], optionmenu_clicked_command) + bindEventFromWidgets() def unbindEventFromWidgets(): unbindEnterLEaveButtonPressButtonReleaseFunction([optionmenu_label_wrapper, option_menu_box, optionmenu_label_widget, optionmenu_img_widget]) option_menu_box.unbindFunction = unbindEventFromWidgets + option_menu_box.bindFunction = bindEventFromWidgets return (option_menu_box, optionmenu_label_widget, optionmenu_img_widget) From fd7aeebd2546708114136cd86c96fad0e49cc439 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 22 Jan 2024 22:33:57 +0900 Subject: [PATCH 61/67] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20build.bat=E3=81=AE?= =?UTF-8?q?=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=82=A2=E3=82=A6=E3=83=88?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.bat b/build.bat index d28653d4..10cb4c05 100644 --- a/build.bat +++ b/build.bat @@ -1,2 +1,2 @@ -@REM pyinstaller --windowed --clean --noconfirm --icon="./img/vrct_logo_mark_black.ico" --add-data "./img;img/" --add-data "./locales;locales/" --add-data "./batch;batch/" --name VRCT --add-data ".venv\Lib\site-packages\customtkinter;customtkinter/" --exclude-module pandas --exclude-module matplotlib --exclude-module PyQt5 main.py +pyinstaller --windowed --clean --noconfirm --icon="./img/vrct_logo_mark_black.ico" --add-data "./img;img/" --add-data "./locales;locales/" --add-data "./batch;batch/" --name VRCT --add-data ".venv\Lib\site-packages\customtkinter;customtkinter/" --exclude-module pandas --exclude-module matplotlib --exclude-module PyQt5 main.py "C:\Program Files (x86)\NSIS\makensis.exe" installer/installer.nsi \ No newline at end of file From 79733f62d5d3b91ad5d43bfdbb6b3702a1c106b1 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 22 Jan 2024 22:52:12 +0900 Subject: [PATCH 62/67] [Update] Config Window: Add description to DeepL Auth Key Section. --- locales/en.yml | 1 + locales/ja.yml | 1 + view.py | 9 +++++++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index bfa7c48f..c3a0b7a1 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -131,6 +131,7 @@ config_window: deepl_auth_key: label: DeepL Auth Key + desc: Please select %{translator} on the main screen with DeepL_API when using. ※Some languages may not be supported. mic_host: label: Mic Host/Driver diff --git a/locales/ja.yml b/locales/ja.yml index 54cde38d..1b7e05ad 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -128,6 +128,7 @@ config_window: deepl_auth_key: label: DeepL 認証キー + desc: "使用の際は、メイン画面にある %{translator} をDeepL_APIに変更してください。\n※対応していない言語もあります。" mic_host: label: マイク(ホスト/ドライバー) diff --git a/view.py b/view.py index 97157c8d..cb3a334a 100644 --- a/view.py +++ b/view.py @@ -282,8 +282,13 @@ class View(): CALLBACK_SET_CTRANSLATE2_WEIGHT_TYPE=None, VAR_CTRANSLATE2_WEIGHT_TYPE=StringVar(value=self.getSelectableCtranslate2WeightTypeDict()[config.WEIGHT_TYPE]), - VAR_LABEL_DEEPL_AUTH_KEY=StringVar(value=i18n.t("config_window.deepl_auth_key.label")), - VAR_DESC_DEEPL_AUTH_KEY=None, + VAR_LABEL_DEEPL_AUTH_KEY=StringVar(value=i18n.t( "config_window.deepl_auth_key.label")), + VAR_DESC_DEEPL_AUTH_KEY=StringVar( + value=i18n.t( + "config_window.deepl_auth_key.desc", + translator=i18n.t("main_window.translator") + ) + ), CALLBACK_SET_DEEPL_AUTH_KEY=None, VAR_DEEPL_AUTH_KEY=StringVar(value=config.AUTH_KEYS["DeepL_API"]), From 96c8d071f1e3847676ff72bf6c85e80373f2addc Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 22 Jan 2024 22:56:14 +0900 Subject: [PATCH 63/67] [Chore] Remove the code that is no longer in use. --- locales/en.yml | 4 ---- locales/ja.yml | 4 ---- locales/ko.yml | 5 ----- view.py | 32 -------------------------------- 4 files changed, 45 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index c3a0b7a1..2806ea91 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -58,10 +58,6 @@ main_window: deny_adjust_ui_size: "Keep it at this size" accept_adjust_ui_size: "Set it smaller and restart" -# [Deprecated] - # translation_engine_limit_error: "It has automatically disabled the translation feature.\nAccess has been temporarily restricted\ndue to an excessive number of requests to the translation engine.\nPlease wait for a while, restart VRCT, and try again." - # accept_translation_engine_limit_error: Accept and close - selectable_language_window: title_your_language: Select Your Language diff --git a/locales/ja.yml b/locales/ja.yml index 1b7e05ad..6bedafbd 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -58,10 +58,6 @@ main_window: deny_adjust_ui_size: このサイズのままで良い accept_adjust_ui_size: 小さく設定して再起動 -# [Deprecated] - # translation_engine_limit_error: "翻訳機能を自動的に停止しました。\n翻訳エンジンへのリクエストが多すぎるため\n一時的にアクセスが制限されています。\nしばらく待ってから、VRCTの再起動をしてもう一度試してみてください。" - # accept_translation_engine_limit_error: 了承して閉じる - selectable_language_window: title_your_language: あなたの言語 diff --git a/locales/ko.yml b/locales/ko.yml index ff30399f..4fee91db 100644 --- a/locales/ko.yml +++ b/locales/ko.yml @@ -57,11 +57,6 @@ main_window: accept_adjust_ui_size: "작게 줄이고 재부팅" -# [Deprecated] - # translation_engine_limit_error: "번역 기능이 자동으로 중지되었습니다. \n번역 엔진에 대한 요청이 너무 많아 \n일시적으로 사용이 제한되었습니다. \n잠시 기다렸다가 VRCT를 재부팅한 후 다시 시도해 보십시오." - # accept_translation_engine_limit_error: 확인하고 닫기 - - selectable_language_window: title_your_language: 당신의 언어 title_target_language: 상대방의 언어 diff --git a/view.py b/view.py index cb3a334a..34711688 100644 --- a/view.py +++ b/view.py @@ -1342,21 +1342,6 @@ class View(): # ※Below 40% of the UI size is not supported, and we cannot handle it at this time. -# [Deprecated] - # def translationEngineLimitErrorProcess(self): - # # turn off translation switch. - # vrct_gui.translation_switch_box.deselect() - # vrct_gui.translation_frame.markToggleManually(False) - - # # disable translation feature. - # vrct_gui._changeMainWindowWidgetsStatus("disabled", ["translation_switch"], to_lock_state=True) - - # # print system message that mention to stopped translation feature. - # view.printToTextbox_TranslationEngineLimitError() - # view.showTheLimitOfTranslationEngineConfirmationModal() - - - # Show Modal def _showDisplayOverUiSizeConfirmationModal(self): @@ -1395,23 +1380,6 @@ class View(): -# [Deprecated] - # def showTheLimitOfTranslationEngineConfirmationModal(self): - # self.foregroundOffIfForegroundEnabled() - - # self.view_variable.VAR_LABEL_MAIN_WINDOW_COVER_MESSAGE.set("") - # vrct_gui.main_window_cover.show() - - # self.view_variable.CALLBACK_HIDE_CONFIRMATION_MODAL=self._hideInformationModal - # self.view_variable.CALLBACK_ACCEPTED_CONFIRMATION_MODAL=self._hideInformationModal - - # self.view_variable.VAR_MESSAGE_CONFIRMATION_MODAL.set(i18n.t("main_window.confirmation_message.translation_engine_limit_error")) - # self.view_variable.VAR_LABEL_CONFIRMATION_MODAL_ACCEPT_BUTTON.set(i18n.t("main_window.confirmation_message.accept_translation_engine_limit_error")) - # vrct_gui.information_modal.show(hide_title_bar=False, close_when_focusout=False) - - - - # Hide Modal def _hideInformationModal(self): vrct_gui.information_modal.hide() From 204710ea5863542488697188644f612629cb95de Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 22 Jan 2024 23:14:46 +0900 Subject: [PATCH 64/67] [WIP] Prepare for adding korean ui language. --- locales/ko.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/locales/ko.yml b/locales/ko.yml index 4fee91db..601523ab 100644 --- a/locales/ko.yml +++ b/locales/ko.yml @@ -9,6 +9,8 @@ main_window: translate_each_other_label: 양방향으로 번역 swap_button_label: 언어 교체 target_language: 상대방의 언어 + # translator: + # translator_ctranslate2: textbox_tab_all: 전체 textbox_tab_sent: 전송 @@ -113,11 +115,22 @@ config_window: label: 메인 화면 위치 기억 desc: 시작 시 이전 화면의 위치와 크기를 복원합니다. + # use_translation_feature: + # label: + # desc: + + # ctranslate2_weight_type: + # label: + # desc: + # small: + # large: + deepl_auth_key: label: DeepL 인증키 mic_host: label: 마이크 호스트/드라이버 + # desc: mic_device: label: 마이크 장치 From 88a34e456e78bbb0ab7f7c84096b7b8beb80e956 Mon Sep 17 00:00:00 2001 From: Soumt Date: Tue, 23 Jan 2024 00:40:45 +0900 Subject: [PATCH 65/67] [Update] Locales : Add some Korean strings --- README.jp.md | 2 +- README.kr.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 +- locales/ko.yml | 22 ++++++++--------- 4 files changed, 78 insertions(+), 13 deletions(-) create mode 100644 README.kr.md diff --git a/README.jp.md b/README.jp.md index 51328196..8c39521b 100644 --- a/README.jp.md +++ b/README.jp.md @@ -6,7 +6,7 @@ [![Licence](https://img.shields.io/github/license/misyaguziya/VRCT)](https://github.com/misyaguziya/VRCT/blob/master/LICENSE) [![Booth](https://img.shields.io/badge/Store-Booth.pm-red)](https://misyaguziya.booth.pm/items/5155325) -| [English](./README.md) | **日本語** | +| [English](./README.md) | **日本語** | [한국어](./README.kr.md) |

VRCTは翻訳や文字起こしでVRChatの会話をサポートするソフトウェアです。 diff --git a/README.kr.md b/README.kr.md new file mode 100644 index 00000000..4d98d5c9 --- /dev/null +++ b/README.kr.md @@ -0,0 +1,65 @@ +
+ +![](docs/vrct_logo.png) +[![GitHub release](https://img.shields.io/github/v/release/misyaguziya/VRCT.svg)](https://github.com/misyaguziya/VRCT/releases) +[![Downloads](https://img.shields.io/github/downloads/misyaguziya/VRCT/total)](https://github.com/misyaguziya/VRCT/releases) +[![Licence](https://img.shields.io/github/license/misyaguziya/VRCT)](https://github.com/misyaguziya/VRCT/blob/master/LICENSE) +[![Booth](https://img.shields.io/badge/Store-Booth.pm-red)](https://misyaguziya.booth.pm/items/5155325) + +| [English](./README.md) | [日本語](./README.jp.md) | **한국어** | + +

+VRCT는 음성인식 및 번역 기능을 통해 VRChat의 대화를 지원하는 소프트웨어입니다. +

+ +![](docs/main_window.png) + +
+ +# 다운로드 및 설치 +원하는 곳에서 다운로드 할 수 있어요. +- [Github.com](https://github.com/misyaguziya/VRCT/releases/) +- [BOOTH.pm](https://misyaguziya.booth.pm/items/5155325) + +다운로드 후 exe를 실행하기만 하면 됩니다. + +# VRCT가 뭔가요? +VRCT는 서로 다른 언어를 사용하는 사람들끼리 대화를 할 수 있도록 채팅이나 음성 번역을 통해 대화를 지원하는 소프트웨어에요. +이 기능들은 VRChat 내에서 사용하도록 설계되었어요. +※ 지원 대상에서 제외되지만, 영화 감상 등 다른 용도로도 사용되고 있습니다. + +VRCT는 다음과 같이 당신의 대화를 도와드려요. +- 💬 **VRChat으로의 채팅 전송 기능** +- 🌐 **번역 기능** +- 🎙 **마이크 음성인식 기능** +- 🔈 **스피커 음성인식 기능** + +# 문서 (일본어) +초기 설정과 기본 기능 및 기타 기능에 대해 설명되어 있어요. +- [Documents Link](https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246?pvs=4) + +# 사용법 (Youtube) +
+ +[![](https://img.youtube.com/vi/rUTad037n8Q/0.jpg)](https://www.youtube.com/watch?v=rUTad037n8Q) + +
+ +# python으로 실행하고 싶은 경우 +1. 다음 버전의 python을 설치합니다. + `python version 3.11.5` +2. 패키지를 설치하고 main.py를 실행합니다. + ```bash + ./install.bat + python main.py + ``` + +## Author +- [みしゃ(misyaguzi)](https://github.com/misyaguziya) (주요 개발) +- [しいな(Shiina_12siy)](https://twitter.com/Shiina_12siy) (UI/UX, UI 다국어 지원) +- [レラ](https://github.com/soumt-r) (번역: 한국어) +- [どね](https://twitter.com/done_vrc) (로고 디자인) + +--- + +VRCT는 VRChat의 어떠한 승인도 받지 않았으며, VRChat 또는 VRChat의 개발 또는 관리에 공식적으로 관여하는 사람의 견해나 의견을 반영하지 않습니다. VRChat 및 모든 관련 재산은 미국 VRChat, Inc의 상표 또는 등록상표입니다. \ No newline at end of file diff --git a/README.md b/README.md index 5892ce75..e9d55391 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Licence](https://img.shields.io/github/license/misyaguziya/VRCT)](https://github.com/misyaguziya/VRCT/blob/master/LICENSE) [![Booth](https://img.shields.io/badge/Store-Booth.pm-red)](https://misyaguziya.booth.pm/items/5155325) -| **English** | [日本語](./README.jp.md) | +| **English** | [日本語](./README.jp.md) | [한국어](./README.kr.md) |

VRCT is software that supports VRChat conversations with translation and transcription. diff --git a/locales/ko.yml b/locales/ko.yml index 601523ab..d27438b1 100644 --- a/locales/ko.yml +++ b/locales/ko.yml @@ -9,8 +9,8 @@ main_window: translate_each_other_label: 양방향으로 번역 swap_button_label: 언어 교체 target_language: 상대방의 언어 - # translator: - # translator_ctranslate2: + translator: 번역 엔진 + translator_ctranslate2: 오프라인 번역 (기본값) textbox_tab_all: 전체 textbox_tab_sent: 전송 @@ -32,7 +32,7 @@ main_window: no_mic_device_detected_error: 마이크 디바이스를 찾지 못했습니다. no_speaker_device_detected_error: 스피커 디바이스를 찾지 못했습니다. - # translation_engine_limit_error: 번역 기능이 자동으로 중지되었습니다. 번역 엔진에 대한 요청이 너무 많아 일시적으로 사용이 제한되고 있습니다. 잠시 기다렸다가 VRCT를 재부팅하고 다시 시도해 보세요. + translation_engine_limit_error: 번역 엔진을 자동으로 변경했습니다. 대상 번역 엔진에 대한 요청이 너무 많아 일시적으로 접근이 제한되었습니다. 해당 번역 엔진을 사용하려면 잠시 기다린 후 VRCT를 재부팅하여 다시 시도해 보시기 바랍니다 detected_by_word_filter: 단어 필터에 등록된 단어 %{detected_message}(이)가 감지되어 전송하지 않았습니다. @@ -115,15 +115,15 @@ config_window: label: 메인 화면 위치 기억 desc: 시작 시 이전 화면의 위치와 크기를 복원합니다. - # use_translation_feature: - # label: - # desc: + use_translation_feature: + label: 번역 기능 사용 + desc: "번역 기능이 꺼져 있는 동안에는 번역을 하지 않는 대신 VRCT가 조금 더 빠르게 실행됩니다. \n 번역 기능이 필요하지 않고 VRCT를 채팅 전송 및 음성 인식 도구로만 사용하는 사용자를 위한 기능입니다." - # ctranslate2_weight_type: - # label: - # desc: - # small: - # large: + ctranslate2_weight_type: + label: 번역 모델 + desc: "오프라인 번역 시의 번역 모델을 변경합니다." + small: "일반 모델 (%{capacity})" + large: "정밀 모델 (%{capacity})" deepl_auth_key: label: DeepL 인증키 From 0a09297cef73e9a7f97f8de469ebeeb1b5b02275 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 26 Jan 2024 17:03:38 +0900 Subject: [PATCH 66/67] =?UTF-8?q?[Update]=20Splash=20Window:=20=E8=B5=B7?= =?UTF-8?q?=E5=8B=95=E6=99=82=E3=82=A2=E3=83=8B=E3=83=A1=E3=83=BC=E3=82=B7?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E8=BF=BD=E5=8A=A0=E3=80=82weight=E3=83=80?= =?UTF-8?q?=E3=82=A6=E3=83=B3=E3=83=AD=E3=83=BC=E3=83=89=E6=99=82=E3=81=AE?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=82=82=E8=8B=A5=E5=B9=B2=E3=81=AE=E8=AA=BF?= =?UTF-8?q?=E6=95=B4=E3=81=82=E3=82=8A=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 6 +- img/VRCT_starting_up.png | Bin 0 -> 12489 bytes ...rchat_chatbox_trasnlator_transcription.png | Bin 0 -> 134043 bytes img/vrct_logo_mark_white_square.png | Bin 0 -> 13408 bytes main.py | 3 +- vrct_gui/splash_window/SplashWindow.py | 220 ++++++++++++++++-- 6 files changed, 206 insertions(+), 23 deletions(-) create mode 100644 img/VRCT_starting_up.png create mode 100644 img/vrchat_chatbox_trasnlator_transcription.png create mode 100644 img/vrct_logo_mark_white_square.png diff --git a/controller.py b/controller.py index 686113bc..f9e9a5b3 100644 --- a/controller.py +++ b/controller.py @@ -876,9 +876,11 @@ def initSetConfigByExeArguments(): config.OSC_PORT = int(args.port) view.setGuiVariable_OscPort(config.OSC_PORT) -def createMainWindow(): +def createMainWindow(splash): + splash.toProgress(1) # create GUI view.createGUI() + splash.toProgress(2) # init config initSetConfigByExeArguments() @@ -908,6 +910,8 @@ def createMainWindow(): if config.ENABLE_LOGGER is True: model.startLogger() + splash.toProgress(3) # Last one. + # set UI and callback view.register( common_registers={ diff --git a/img/VRCT_starting_up.png b/img/VRCT_starting_up.png new file mode 100644 index 0000000000000000000000000000000000000000..9feac583b2fda1dbe73f29670db64e0424474d70 GIT binary patch literal 12489 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 literal 0 HcmV?d00001 diff --git a/img/vrchat_chatbox_trasnlator_transcription.png b/img/vrchat_chatbox_trasnlator_transcription.png new file mode 100644 index 0000000000000000000000000000000000000000..9f2e401ef45a56e343e7bc66543235fbeaee4213 GIT binary patch literal 134043 zcmeFYbyu6+*DZ`gaY}J7Eflxn4#n=GHK0g}Lm{|J@BqaNg;Lz9;BLXaIK^Fx2GF6jabiT7K2vJlIjGFWPxlB^4>#&loKpJrv!J z;!qvt{QFpS_~y>S^@UbHrD)XWE6H&J`U{ypy3Zv?Yu0c0O$CKRb=vz^DFld3gBw@8 z1KVotMP<_)-LIDYtVe$%Mv9Am{PgnDFMVJ8qp9hK-)t~8zMM5J)a<`~Z1Vezh!b}S z{%}nkQ3w4Gu4lF>QHRm}->;9Dy{{6N^?&e*X(i+M zFogcw&r96Z@8WLzay?1CvwmpjLmQR|9<)Zi>fM= z?ytYRSnv$(;PrmkAKWg2;D2MeBVE%q@zw^$Go_H8D@NQ!>wCTM>2RI|%+j#3^3I^y zjJAFcmjmrAcy%%P79`v7gWtg=aaD%mlW)sE=dyN%uC~J~10Ba3p0W-FE=0}Jr zk9#UDkeI`vW}D2f%JquRj57}_&9O^Ir&*r=2QYXd2Q)u70tAt*=TyD+pxL8hssC#| zF~$mgk`hswqqtVTL(O_!D9sSZEgFaZ%(kOAu^rGU?1*WiO#GzmU-WrL#3hG6C#4h{ zz_hA_{~j22r0sTi<_(s+>2uyz+L>G%U8$R(g@e`luKl2uUWsjB0Wlr1+O7j?3<)|c za0~`~JJw`NkwkxX0>!W5F!;!Bdjha;${zz0m=x$1UA4^us9Ci4tNwuhvqB3&cC@8L zE7>%pD(=f(0l`UQR7G5RRrbM=^!+CM*W+BgX;wMw%8j86Lpz&j!i6zZI}5W(h%|s% zJ$u$5LTF5b^zi9^*ht`bMcjiuC-)egJq*Z9itw$?MTg+4ihqyvoSM`H6BAR?{KCR$ zaCUZTYH?<&a*zo%AtB+%)3dW|rHWw>>)DR`LyadOHQVLMK0Q4x#scq6EF0dbT2f$8 z)Nu^4s5E#wAALnW-NDu+=!(Bf)=5=USI4igB#U)XyU;{3%Vde`q_$MnuCro8JAw%P z&j;xJpmY>`b~SaBT~l}ouO?&M`Or4!ks83*?i3La<1}Ta)@b2=a-2EY8)FhJ>+eH~ z9Js6)`akI^=2kWP!Ju15Q%X_(JNRr}MbjlbO_ZfozOW8*Cvh8@{DpW$^yHYmUYkXH z?Zk~U;Nwm3CQ2~QZO0%&mgYI0)bcEgF{ZoByVh-Zwb>V9UZ%W_ya7_u#+z>ae|hfv@sE>J=pQ+f|A&)CPUE48%$z< z&4}Mv3O(9aHhFob*EZX5Im58x8{p`wKl(dO@PrXx z+_V|CEAbXom4ooN`U0laRge4E$vRi~OkW#THd!O}$rvRP6p~|oXtC+`%Ho!&4$2n1 z7DeOb5$ES47|aL{eXw?X-7QSo@C%+);ZEC59;0kTmq7@mTU)%DGm#C(gz4GRZF zlB(c)#`LLs$lXKnb9CEPMAYHYK;gt&#_{XJONO*?*P>Rvm7`e+ui8@B9W4~?$eKeY zq+IMn8`S4dY1HwNwpX{qN#xJ%&zE44=W$8-=xCOmO~%N4`J9{^FP{g|_ENx3D55<1 z98|XQ1Z9^2c(CSN4mveA5qj0e7xWe%o*n7SUx@D&OHI6$VLCy6Hw(;|c-?~d-llP% z|A?(f=ew~d^hrlB8}Y^bJ_4!0k_epNebbxO$Y(L(jLQXs-e8d-Oo2 zK3#7@1l40BTkLN8ouCL(U&e>3D%%Q?z4j`#gTBu)H}@<#ur-z>zStFQ{n#EqD=Jd1 zf4$K>uO|Q3|LzEFwsg6l)O(Y8o3cHLrr-ABmPhQlACIWB{Lzz22@W$HZ6wM5Dcoab zH1t9)P;F(~mgYIHXNX)za_XplYP5P&!lnD<*C8Ju|3`kBubUFR|%vqPX=>`8i}x0C!l5u#$edq8D}#fk*CYmn%Dk3B3&*}bes!t6q5oD>u>#V zrLtz4`M9wc>fWxD<`2}ym;dbA5^4ecxTBfZZ&s&J72uzl@P0xY)rrsm2VnK^l> zus~oZ{(NzKoDB}WqrGJwSy_JBjF~!by-EL>qlRI^Xto!Q(7kP=+g?&E#>Mv6f!jnc11EcK0{6q4T@SC8FGPPK zS4FayAuDo$88pz>hP&4qP4F-?+FpgN9C-Bdq$tu32t`5kWuM3I_kBMvAmKz(1uOB+ zdTMHFLIDmme?-M&=U#)nP+16c-^?^To7hWcBXdKdPKKrDs1=EI-6F7t4q7Inol_i| z0;QN5j;Tr7HmWyB7q^|bPmuNdfkFd;u4FZqq(l}bx zE=RCA&p1}?TLqa!??LxLeI>unaV2ceYb7f&=98)i+(=}+FC&N*DLhThfc@~CYs+da z-4iY|s-j}uRrXC_)%cRPNAT4u_IsM&62IDwb6ArQ8RfALO(M=-@3uQFtOCflf3o#7 zX^AaABrcAWj#kOQYTm?5I7`@}Pshl`KRwAMxpT&TcZP9Fh-pbnYSROCB3t-&e8uZZ z`c5(=Bi`C!Zp{|L+ zJ#}?;ZVU_zEJ?E%o7>w?&$zh}SRq>dMnov%fh3?pefUsj)>+WVN?c+g-NEpDmjw%~ zJZoJM`ypKj_f}oPPQ0RgK`eBYv3X4OK*^cKVTZb*lpqj?6OMIRzu{e3NGvh}KWn}X zbIsMft|%-iD?79bi(iXlO-@eEi1BYFoQWAO36Sj)tincywgmE6FBO>pOL;^?lZ~@b zIvf`)%Xp$(9etJeAMLfS`Y!o=>d@QeLliyEIK&-rs5P5RHsmYP<@i+<%|0HdSB4 z*o)nq_atjpys5!m-;TG^Yl1?}kxzi38TRYELY#vcVo6oj;qs9#+xjcat%XI`=7rkO zTO{47>$7?-H5t0II;ySJI;F0&$zD6wSrod>)sb^45m+0JF@+)#fM;2)5h0j=4ZN(+mewPC-; z-P6c_N0Bac-=u zTS2}1(x$TWLx^ypg$)~Cn(6%1)H%-_C04(?ikaD3n?zcb8#!)3MRv*0p9`rm^aiYV z8H`Qh0s>AfG>@F6<>lK54-LEbS<^{v)j0{<%)a_cykQ_`!g*ydMV8 z_HuXs9hR;X(NIJ0D*r6H^x04S)#=GxVvyQ%PAzXM=)TL?qpaUD(^dOq1#L z#+2|ocm@S?3Xh4y&|$r`faz-uNd~sBB4($dE15z_5cwPQbCdhNE+%VDLK1|NYEXAG zC(T`_4@!)Q8+rO#yU}8N>a~;0=q>p0mr%k7P{!4}lli8AKG3^HRQbbnj(E`NFuNWL zJw=UVGIVV8^YmTDXB8AP1D`AZ{{7qYkm0iduO}HX z+Ykp|0x1(QLjo&$W3|ZnGnYI3Q%XvT5j_I~&;H)ta>SiYx5=k~q?6YWZBfj`6Sia+ zUD(Y#x+1$Q`%WiY&bic6iNf)HRy(E>#fdF) zay(N;_M6*_O{|+cbXHPxV;fSy{PWA1#*7hSn|(F#+0@D&7MyMA7%;^=aoq)#t;okQ zYne!Ulj-qsq259@xk}P)Cvg-Q&m=oBxa@s)(Tre>DTOo^17$4)NmeD73#2MM&2-v& ze}iZHp<%dr%E)#YrNKMQ}!5YvC4k8eUw#R%|!r4ELZh@WMn3R3JQqf9f!cA_zm7Zo$MJ1jJN9#jzb;HoxfdH%G+-@&ddie$U;|3z4))EtP1C7TsRzGQ3}hl@ zb`IJ$umf(&2}IdF?=wMtO*z1ZX)CddKI@CD8m=JK@jM@%z2~Wl zUiz9Jq&&wQO4giSiY|UJb*HkS?HyCtYqNKkI2=l)9*91CH{;NDZvTbH6bA9IQn_e$ zx|(y-HY7epTt@Sk8{bY$XxSh6*xW4-sK>XglE!UJ`geR@vR4V+(ws{}<*^=BCi+Mv z=htT5cl{Jlv+=*s!dDODk=qOJ{TyuDc;C`AebPZ|wFUX5ITVI&*WL(1F{?bfcqolC zkk91h17+E}Ft7#lN=xD9*Npyjd!Y;;3%Ia>EF{d{^)SBgD10V3{lZ;7d?f9qa-Qa* zc|tQiJ^k%qIBO*LCc}r4)C7w?^AJVM)1sT)GdVBf?bb#nDT!!GZ?@E0*og23P3yVqn^ z$z=c~pL+M`8`bm9d^CBS#=z{OE|~XR>Uv~g8}Jf#=^5hpYwEx^e_*2fr=PY21eR(p zwfTD${M$OpL;S&8pN3zG|AKjWEy=~{?TZYDnBjA-GjzCEWnG-06hHZwPK5s})DaLc zjA9p7UN3K2`vTHQAa>TsNey()8Excp>k^l>zJnn8PWJSqTWw(Y-1*|4kzHV2K~!=) ztI!0InfYDw%2{C=rcXfaJzKrldI2|v3a>sXN(73STasRfqT~VJvuECn9i%BF8i!Hc z2_kvqGYxq+kgb=O=xEdea;du5ja=5Sk?Mf!}62;i)pgtU?RHS9F=(qLr9fsX34`Ef6=9%@asX>n|NSiBuT|`aRCv-%~Pgy8QV5`bUWI$P!ip+Et3nG$# z=)pH^B@aQI;M0^F-WUW5pwz7{_5<5&Pks!3xvSl9N@Xi$%$}~?y=7hyhCZC`yXSQ= z6+3P%*pzBO`jPT>e2wZR-Au_*6-_I~yJHdkIZKvwr* zbl~$Q?`!^grnCp_@r$1jV{tiu@Vkx^YZbM{6~kk7{UDIr$=S=m6mIg252Rjr_~13@ zo>md*W+a7^y*&~u#$)F}^0QD*w$3YxWEDy0U0Nls=v;#*39>Yg7JH&{0SPht#Uq-B z&gRdCisczbY805CFFkwbqpnn(Eyuq;qtg)C831Q5##0Jyf1ZEn{OaiCvu5dvlaRA%%x+M|lMHN_%e@@e|Z$F~H z{Iqgu%rnH}k6Vlz5}9{Gxt5{uQ^_Sx>9Z>H4%$ zuQhm?eY1YCXn)OqFLr5%GeE94^Or#Dt1gK;&C+USL9y z=Pn!B>VU?=!V$2?wmC%of=-ptzkMgfFjzG#luLz=k5An7)LU3e0_Cd?&|NZehY))p zfs!zgRA~1Ktf$xHYZmeH^Jjg@&c4G68I~ZU3DS+zGh^?-%WnWWnK*QA~ecS zhWU<4%&20CFq>pXVN%a24|7a>b*v}X-X?GP_Ct>LDx7rFjb;!`H+VEN>!8IYUCNNg ztqc!ydG&k+3edDerl~m+6%`eb0)oDB4l7l=RW!ypJOFoBpnciDIX~axNi0QOVf<%e zW*nDSWaKM}>Q5k1IlPXW{M#|S8}=eQ)vXEQ589a?FnI9{9=}8_eaOqnSv9MntMB4J zQmCsPwk?)>MYE-rrfB);?6jyg62z>0O*=;!^!fAWq_fl0I`)3CP_*?=%HAAokC3a! zxI6lp*k06if0)Phws$GV0}oQmZE|kq!kb!1_1=pknm{Uk1DN$=9t>Bm*@{aLs+$~i zWfNcll&xKu@c;DxFqDO8YQG%5p6$D5wTPtZAOG_0t}NRj{+YA< ztPrtRxGSi?WWpQOb(2)Qyn9$)B2pv{74pu_WEFi+7vAM^Fwyr!>j420(i1~Z~|Y%<06|Jh;K-my); zmn>*iNBdvuj6*n>XHOT0$sCs$0T{Y2*=-w(*sMRe+AtoTwKQ@-!*Zj}?blB4?EC75 zE~k!I$e6kUv|V>XEtt>AwD102<9k>c^fglITn8BL9y3Kw*$f4gD$jGqAK8^iA69+B zH`P{+uyNl#7C%g{x1|sHMdqtxNsHHiiA64|WXvmAR8|&;O_{3@zGU?3mGFZO)i5-f z5_1081q}itOjrg6gF)N?ASW#FB&?+o{2=xTWqu3ceQn4XbWXgB%Sy-@PEAd{j4^i@ ztU14mWD(nH%Bqs-VT1GP2VjpR4i68D1D*sVQ=a}8YkBjhPoJormCDNxYBrL-2lK+m zoH6?|dV71@Lt_9%p7yaf8G?bH{)e=bRGgOTm9~q^$(yO9Huiqsq1z7KU50bcc}M3C zR(t_MU2B&bnw5|Z{XC_>LWi87mP3{FHr_;aUd0AnpRJJh0mq6qKqeV6hne0o5Qe=YK7uMp#qy;`+ow>N|NgHj za99^FhU5Sr#yEgKXyQw6A_e|(gInR1H|a5AA*<9u{h>scLTl-nKub`P8-P+LQh-Uj zU49{OJ(%K9CYF^bq6IYFqw67_RQ;#;X$A)vr0>Dt=0Kk9huWqSq~)kXrLZ-hTz3AkB(e`&+xt&ZYP%nDwe%J$z!!2hOC(cuV zoPe?e#|`s8y=?p%?$P_VC+_$1Vb~EE)7??pwuuWQDFfDF7je-rL{5!;+O{PS2>&Fd6n-8hoP3 zGszhGl2yL?Yjy|hIyC23ujupV4ggDNVpk?qR0 zE11SAtos7FqX`AtFj0R`feG1)x#8RgLz((oWG{c>|*c}&}H?p$v=3p>!w+4C2L`^ska;C%G9DGDhZGmT2;YWYu79XBNyJ= zWpMTOFpG(df9k;l0-8W$mx&#g5rH>?^0Umsz+pEX9~(PPP9GaO-7u%+QkMAg zw&{gjgvZ}lHjF7_$9J96lmYD&TL$bB+@Quvcd#l}om+5qW+H+Uo2o9+SSIpzHLyif#{YZbn$1eltBcJ|UUsk)e{nl@-Yba5@@qyo4j?+) zAU!l{eNg`=9_r;$iwdg;GQO+m&dSqzsHu0!XfEL66U%28tFb@+wSR*@ENJv;?3G$Q zjcD~;0V)sdu*Psko7QEM2c_thoEmEqxR7ykRxV-p_hNQapY$4+KP$o@UFP{XL~e@h zU?+m{H~Z8@Iw{+(F^*Dzqv7+5sce+va%a+CO^G$Ks%nN$<|hF}LprVK!%>j;ba$ef zqiX$>5&2VjfU<;oIr4uM^(KLtg*r_O{3tIk4`xA-j29Lb_N_4fb63m%@4`W4fDS4gyi9C zo-lE&YC-<#gTlXW2aH#kiL$_?3qM{NiFiWJJEDx+*Di~D3KG@8Cc=KVEuX#p$;2ez zb=NB`oOO-#qaIzK0EW^iAP;N}TGmh`OHy*mpD*oJH#+~5Eqc!{pcJGdrO6Pku5q-B=bNO9-|8)`|piN%kAx$J6P5r_D2Qm zs&6Z`KO_Z=iA`Z6ei4Zs+R&P`V@^19X*i>>BA;tl8aNmVOECfjS-)zXT|HDm{7nL> zqLI;paXk}ZKa!2HTvd<~I%ZGoY)T{is6WgJcRN+#`jQIXU75A@c|@4+fc3cJeO_xT z+7i-=vnB^ZVJ=h<9a8bT=WM)q-@xgfj$G0~5<^@{8XIrKaEQ3ruiwYnePJ7Un&l9N z(?|L=-G*4tnCXE^vPF!px*w*ICWxB2lOJ8|^{CPDPWYYh325}2-$~y0Z1liA&0CtP%GZyllQTC0_yOGF zAF_qplQs68IxRki=5I)#=EFx=G6R--?m;PXH4Jh;{lb|I5uwRze642Q7jdMurmsbw zZ1Z&_XLmMnEy%#q5r?={Daw}&EbRN{-OCe{|l)r>wn7&q`&sz~E)zgT7uT;(UfY(l}^1mEm&D z`1rWXKg!BoO_uJ?y1AhH{4x3VI9In(#o9R|Cs4++&d^*L!w??wTMll?;e-)96!yD- zckNmwRTqSz@#JBBdyxDtagDs}#{;xFBZLS0h8Ve_u7K9My|!SHCT?R<#Fx!b!k>%l zx{PuR>~*e^CsKq3s4NqZ%2N_KPpEJdJ{~>Ay8=jx3P51xdpvi_Ps1!hii*M7V*%I@ z(SYu1j2mnEC%JBPlWG~7GS-G{{t|=s9E-$K2cz5#2h(|ZdF7|2hXmEP6~t-$9*Sm= z0{(hH1F`$~@zW_TIjl!}<=TNZl!U)xqC6}dfu#1<3U|@de}tFS&uERADczw2k`V=KDri3JfuXj4*Qvv3n)n4 z^lHE;qA%Ygf0&(r_UZ4YQJ8g{jja%(rAWuokCZzYW^2PZ6=^UsM(@PeXW06s9s>Wd zd@p_xSJ()ac5Q;S03>qcN+-qlZ@zrA=uH}w+Onw4rYTP?b9x8inTeC_YyvStlJh&ecHpmytffr2%LC?R8!1 z9t_M{rfUN=HNycj>0^d~=(oiy0R17xwAI-nVzvKR2SS_y(k4Jm;{q@()sAd&dD?`i^mHm=3 zip)R26YOLwJq2;EVsDV^s~|YSKlkB;Q%~wweC)e?$@~kl=C@mL^jBotRLWuzCvIk+ zhJMrM=fQJ=p`fdx$|dBLR+wl1OK~>#IgJYVr)>`H_rv`M+orFwl|Umw*7OiE1x zgG~M)jB#YR@fZ)ctVfpIK6dDUY!Gs%QK|zGR8od4gPJ`_{O6C_anZMEJi_#E-)?EG zu)Os2#zR6vu!J-v$mBglzdDIYva%(mzkmOpAMEHzirRH6tf)Ai2do$Q5Pyu%1Lj!(qx`OI!g4pKx4*X`5&jOI>eeg<+bv-1uDx2sAW5#wst zA`XtCixCaf3nX%j33VZ6w_{+hImh;y%gi`C8kXJcdBB~a4EQ0iAx;On{+^TF1)ITE zk6wOZ59)hqk5Mp645AKdkFUZAo2i#FBUY^u81gGfSK`NL#6ILCI0dsgh<8p6X|~_y z)Oi8zX2z!dk*yIR9J&(DKG(K3XMuWt3s7%N#a~QC>0MQ{O(WwslOwcE6?OYY*HZyS z-yghm+kWYB88&ct0+jJG#RN15tVNk)y5Qx<_pirLgAD`$C{xZv7ilyO02N~10(j68 z?|YMK^h6;WO_0CW-vb_ssnqSSBur8jvW16F0G8r{*$;XuhLJHgEPj%Y{!*{eIrp{M zjF@!x!Hi#M=hoZNmFv~%;Y67EumFTtBG-Se^K^6>sueJhEoUg(ZoFD8rCxIQ*=<;`o;-B_v&Q3my5{k&}Pm|p+G!;&}~#G592)Ok9tCLil|8oLJe z&75-I@%%?7FoV8$_m4Nt0^MO~A9{*?`RAV7Ng3IHq&q@W@puh2)7G2}h)1J)QUFlYtV>5+Rv^ciQ z669~lt<96NfplWy(sIY<%0ZsyWf2tGeqQs~1Q8|T5=j{$$TDr);(yBZ^q}V^{-jU3 zPe}tSjoWr>Cdb8H@X}nB?5U(cXUlErDxZ>YhVjF1B%% zH@?(G7wP6fhN#mBUPKTdNca7FDT+E2{;X-VXDHdPf~9uR+S1gzP*O9na?R4J*a9($ zJxD=5X+B779k=Gf>tx^9R@}O=4#HxrYPK+lo1#wS)H!DpyEvoTG`Z_U@skodj$5LS z{=*Wz`0i%H;bLAeCIiRw(}uRc2v_Fv*$^#8K(8dIGxEv_dF2-%Uk``t$o85j2M(dZ zOTW!_sn6@aI>sqpm*;Aivzq}UJuUM59@)J^EXGgjEH6<+)G2|NE6#Fv)mm|~NSctHYA@5- z{UK!M-nBIR=*(al`*>Mlq}g_F`xfqXMx8adZ!Z1Fqm+-Gc@fb`mT*NY*7yvxnXqV& z(T2L13tm?9y#kM1t^6FQJ1lnV+k@PYthWdTtQ@s&ti8Y4QoGJoU~?6Umm$kN%cg|v ziAooWT4mEjfFJA&joieq5gA3tZ&=$}*{EeInslN>NwT-yW>l$Mr)RGMH%mo_;3u%Jc$phb?S z52Lc12AUtg<^;LSzMHd`jMP#vz<()Qj9k4ox`oOOy&U>QaYF&3Ic{(zx}g5D+%DQc zP=M8;N3b8ZEO@ELO*j#y%udp>w_0gNY@PFW?IOd2+q_k{DdIF4-^y92&WwbF(rJxPTno_6D&KJg4 z3=Ow)$8E9K;uP6J4FovodN*>*=AY3DO1oSW$s;&;U9`U|Okcohwv;kIC3|MeuAW#k z01m_30-e9cdj_$A&6C%}^_i0ax8OS`)WWR%oeG3k$CJ>>#L(omsxMfvnNiwa{Ly$bVvB6;=0 z2NArt5?;3<8fuIek4L&vDP0^4kq1(wOjLG1olTE!vCYzwLGOHL?O*==-Tc8lOof$d z;b-}32n~)0MPxSvbF%bMoNy{qc)P_<>VgiR)2gc?dIl*LDDkK8aHGy^B^oDen&CY1 zT+*l5t;zYvJ{Q>CH0zcBol6ybNTfWoY~+8>sHb%yte|G@8X#@e?HuUhhps!;!A%4< zK52}ty?rySqPF!+4Ov|x@!trajmN2|>FIZW#h+Ii9FR$va?DzYY>4kj2nnXlGkJ{6gXdZa{#+$k z-Y&2&GD)D@)lM>4V{1dhb#%$3zr7!J-UXaZc$fO7DCRAh3QeVeX4=DA78&i)KG&ok z*5k&pu%rk>M-;buFKkmOA;JY1r^&$J!!9|_Pqx@}?Yr=HFi4~8Rlxeltksh#yZf?P z${?3`*LG#oUJoV^$k@eViQ%*sVZ>|jav|2ceAszp#j(P_i#vib)a_&QhHSUE7p>8+{=}X?Sip!%=5k&d)nG(w$`Su(ynRP4Pkd! z_+J?$BFF(!{peR8<7>W>(gkrdkKxPc&*L45JuGg2LAvjRQKAleFT>9z{ffrx;TU)#_^!5cbK?KA{oYvGnPJec|U0Ll;a5)4Bqb8cp;UkD^45BP%6fm)&~f+ zHc#?Hu=Cr9P6O~1GiA~=zKSJEGJLII1m*AI)Qcjgi`rgBmtoAsuAqBLwn!b=IfOhz$y;{h)1!r|9 zu%S<(s|CSpHTXYyV6$<0^P7K zD|+z$7T-tS&dr_2ZFJciJp33M^*2D!K29f5kfNUeVpDPj#tQ|X21xx zaAvhwF#5P6#okC47yer3#_-;DAsFl=yY|2QswLgwHsmduA(P&F0cyVa71;9YNy=;l zyxqCuWq|$An!5NlwL$tIwTh@IfGweq02-6w zj1svOH2L5MBQ}h4PFGBw<*UW5 z*Vvc)R5f(fW)U>o+uKJ)VAYx$I)Z(MDMZWgsz;){>4FhXNW+Y9qj$ooJ&>-t!B93P zpVcZ;&=8>h(rOr$n=sMY_M=mwnN9%g8DBWC>j(9}BN#ifSQ!J~p9M57q8UXq5Z9a| z;XHGrKVOI!TD3?YwyOUU{C8XfxZXU=W~+wO73VIq6Rj%gBoL>? z4=tKap(`nXn;UjOpaIBPscS-TsYFP{B8Hne8Rv&VR;p7v1ZC}$XwV(&ZST|PQ^A;m z*WE2HLYVjqJKq4G^b(j<9aS?fP+@&G=>o`OL=?&8GwoJJ1Tyk%5)VTIEtQp(pH8y+ zJq~F(TA$H<7}agwugrNeXtY4_E?|F@G<23m!U>a%ktXE%>F^;CA5Ian$ml^>XJ_Zl z@s(1_;$d}5(|%%y`93lG7kBrCJr!SF2&#&LX`$E~;H&26igE&elR-pyW`3heZJW3? z46%`?)R6Y0p$J=(5ci!t9iIj~5bQB7h?-x2iO7dkKvW&8jhmwG?>vbKwd@vag6LZ^+~2Qe8A%W=`JrnVLS`nc zds`$A6(R`YOGssae_y$anunruv%~o_TnRys`^r_XhN?%*x}XEJq8TlljvWssAtrKr zJ0y-?tb_kR+txxf{j!TzcIfN$+JU0g_lfg=*TG)hr*m92l>%LYzBxKHLF8UtgB?ki z-ZHlgj$wthwHJhof@gexGD>E*efl3W66B2k?M9!V<~0Db^IkFE-r9n{5L^g-qiIv% zMwSDOy2)qUy`H8q!Fgy$p$K>$J1mGmnX}>v=~q4F8{4}xXOUO&hFwInATVP2X9lAY zYRzS2bCxF`fH{K;=TtVvrX?GrP?Uh8phG&_>bJCnafMwcI#t>i<&(eI(Qn1S{w>+P zWU&DxapHWDnrOQ@)f%32;qkls5tq)5xfSg3EQW%~Q87FTI$C+5C7~Eh(go6x52kP7 z3&orkp|9l-J0yUDebOk^=fWPlWlbKJS0a=`KrYgILj4JDH6G>=_EdIK&b56VJ8_r*@ z6#ag{tV8NxRs!o0XI*`LcJK!=8vcPNrUgl57{2FxQ(x3UpY<9_N&sGR0Q7xr7D-9w zTO^0hj5xzvaUt{c+B;TrC#8p4C*G(PLL#Dam4=z%*WqnyOTHMwt3C#8-iJ9pGb=^; zNZ-;9>m%YW_J5G8Kci*d?MG2C4r(EooP}!ik^oT!Hp--@n0CMoD3|!)2wvLmI)5>i zEuMh3&2ra=f1cJ|A*jA{Z02LZZ?XOoEd(nVoe<1_Gosj(JkK@KMV~{W?LS3h#MfVT z(*9CMt8ZlG$uC5^?-l;B0}fJFl*7V!QRKfkS8aQ^MLoMDeq|%SvSVXx#Qf=5AK+D| zS1>7L{-yO=?llp0ORDUU^jhDHa3S^i`+%i*uYP;>xhA80u{{qDooV%I7}rokKF_^F zp`Y>m?C-)z=3P$|d;EONv%ekGL7Tw9V#B5Yc8ydO+M}hrnJSDoq_1FWYxR)hv^xr!%LxZ=1ai;^#+^h6T!SL8 z-#*MXGl1B|N;qQ-LA@JEW>b(5d{oP+%(l&^GDzx$XC<$ z67dkee~?eGdmwDP<`r|(12YO^c(C5^1Y4pk_1gl!hHLD$RzgL?Ypgy`cFkp#_(UAA zcwW03@gpAwRnTO)N2tdFZkN(zLEn2c(9iA=!_D6ye|(JvZV%a!_ZO2v%_`CdI9RN* zTgmfwx(PCF%!u0GkyxxMVdwBWd7*DD=;)w82!5y<m;g3c!`M-)b4~MI{TJhZ$`vu3vdV%kH}DzJ zqRA{G6)I$~TOLmNT#!-SP5|I`^y2NoE8XS5*lq%brY7UDPcQgSof#_7hbPZnp2XK$K*YupK zbE{=oJW4S54>?YmdztiB*Lh(bj2c@{Of&nXKCCdae~LJ@kXsdz&lp?SFj`8yO9#d% zUl?Mn-+`aZvBk>p8;NO;{*;3ML+*)cFL!wbk?$2(T^uuz9Eox&7rL3#(BaTxM2m(#mkH0p1)f$Z+__? zEx$R$neekN4X7|_Wi;>QcYm^fH|t>$YvOq886jb_GDWBHELwBN6EyT@0)Frkn4zmK z>-;{VPS(bVb|qN{0vMw&Fy@$dLhNK?mTuC&gGCiLpL1x;Kl#?T*{z#J3_+UcM}r2n zWz&l%>-pe#U`IrbGCbcw1w)IBhIsxYmaP*%oe!ASO(_JWHn|lvd$YThXujV16?fgG zdKH-g7=O1>1H-G%m;^HOiNECa$q1TW*pf$(*z?44F1M|Q*^q6g!MQ<(7`)>XojIi09?;V!?3=(tKgpddQ@`WmfwyCW2VvpYs8t2Kd$!}Tb6 znW?cB$@Kk>H9?$gLm9^DB2((tg7&JKBH;zvl(_ zOk3_%k@;Aj&*N*kBz)Z5-$n(jSb2&5uG&-!Or)welVz7Z0N;@~uv^8Gp{#jm0^6@( z{vpoE5l~bB$AMzg_FAQRzW<0*o9(FW2Fk3~%R%1Kg`(5%_FZ!UEAYY`^9?3r0Z5|7$4nq9f|zxez} zDi58A*f`oUj-0F*SQ5+#cxo$dX4SP|IK|}x&8h2INu8_=)*k6O5#O7tyQl}Bo?>k4 z&~!y?hQ|4f%OFlmwMS=GZs;|yt@cty+dlq6bf1nvT#NlkByOic+V90e8)HzX6D>%q z!m^+6H^st3l0M^c%^#lWI!XGBfVZA=d-1*GQnT?wZA}lDwN2x4$vf;1{29P_@5Od)2QT|3x1|Ij<>=mL#;eB8kZw@Uk{|& zg_kJT_E{)Z8E*(>|u2Zq&CR3b?yc)koiodHX%j)`0G6mDyYYl1AhG(96bf^U8?q1DXOo0aQ z>(DnJGUNr^ztMPbld)Hq7}x%K;|0X4*W<0}tCSJFnSu<2;dWJQ|KQe_n|CV$sbv|< z*ipYS1Wx~PQN|S*O@3;XPwMhmV7piN4DbQCoW`))02hLxxrYk}HFQ(4=p~?5_<4Oofi!zZIp^h91N(>kv0wP@k5(5Ddk?tA| zDxH%KDFG?znt+nhF}ibf!^YsbKi{98zkt}i`?}6K@7L=zIv*NlGM88`uTnb6ihXKa zA|T6qtg>32=+aL%o3K^$Ylk0Y^^MP$AHo)FJPXTqV}u;tUi%fR_2vxp z27vYiEo4ZMhWw0;qD=Rq0N&-uH`I^aJuBI6Er7M+3sB2vgZ%FT7jO=Mm4XDataPu* zM5lo^6Cyva98TMN6HO*XgwMTLqvZr=qfLSirD)mgvGvIm>8s+viQ063<_{ne*z3+9 zXGhR>E+0H1ce-QG*AH^NV^hj$`B~CYHVjD-%z&DYX}%${=?3+0h5LHb^ri`YR)MYV zL{ankH79L^IuDCwtE{e(LU)fCe$b%v;(d=7Cw^>M5#hA{rbH6NqG7E z-*l1#qs!K^%xAJu4W>`h-GmvO%$dUSieWD#%4!fhsHuh*p9-d6)`INBFex>A|6`xy zS@yqKA)e;bU9dIxsrN2%ptPQ$c!=d^k8Ast_ru~D%Du0RVdn$iE%#`>9bZL*Lc;^o zxK<^R($8mB;ay>H7+-{D)1WDO{=B# zR3=c*$~=5d`{QDGXV-@dmT{S-9&4HW>46i?vjcs_HDS--! zW9#@)GeaCNw8ZW8uKi-M?bo;{?tNGavbko$D7Rq5gKX*9zhm4H7eTM19Ruf&&LfXM>SeKrG1Wx+y>7{R$LWgDV8$G;;_OR7$r6UeZl!c?fds#4UH zX06|NouqVBLM|{_FS$xg>q+;gJw-ZjE>RwOQzSg62Vq3q&Wt$BwzHJb^?Ui;d ziN&`&{Y)sA?(dZ_(rs^=!Q|k5A>$}Gu9NSZLiLmj-O<6`;}V5T3b$#9eRqf z<3L|3Is+pZ6cCs{R-6f6MSQK>Ln^lXCdxrq-7kR{F|kd2qpz1=@kO_bM9KQXg;u(>iC+P9WW!Ut zY<*s>RuHQ=u;+IwTkmy-a2O`*tVxJI8gra33wNSI3aZ5NbNPZ|WVMjyEhKx(0T*RIT+U-}%>)-wx_5Czh! zJQxKp?UCg(?K62I%%J(I@QH zX*%jX%Gd@E^u_#GH?R%FH~qXChe^?8i!MW3*0cM#Ewvg>b1{K1wU%i?%ho4GdZ*ml z);ss!w~^vm1n%F?Ed)AS(V8v#8ag^vSy@@8)PXGfpK+mASD~UjP4wKKtgY*-fXomI z3V)Iy2h=HVNm3m$KJ^B?9#E+Hk2apQ(0TH|iqLa5`kB+aX0;fWSA{oYUPm1^&_hSx zQLYdYfyn#FqUgJLp;zwyY7zAPid;GqKubW~Fj)R2$@+w@k&%zcrPRdAOFgv})(GOy zA5E=;{&=*jegN1S7-KjgG~SzjpmONusLOEzChivVsu0=gk-kuayGDU%mYv^C>#WjX z&+uq0&(N)q78-ITGJ3PAjP?qtobTV4^@Eur`&ORQ&26!OI=etR`^-wjZM+~?$xt}c zXSX_um&gPlCn@UN6MK-svMCqJtBeb4wPyM(S=U7_{rx)&llx~Crad3Fw(`p;=9`0C z^wpGilDdfl|$;T#f1C@a}*jbpB{Pm=9 zTsB=lmvJR&bK)g$>XD6nnaI767$s_yY%{WcA9~beq`K;*gkj*g2P=vYZ zjfGVd%_gcL*g<_0Mb#)dtR(IfM$c4HR;VlwNQI~Dq9f~ZLc}B?S`&%tx+bBsT$M$D!SJs3M(z%Sf!ynms z#tCkGy-ZQ#oM9XiF9LEL?s%GkpM|@c?GGzUd%`M6<1%#Oy7x=VFt@$?N*7MXFfLE1 zz3c?HPd85pz(;8H0?`vfv(6}W^q0062qaDpPC4E04aX>?`9EXf{^GfM%#kBWXoDxI zLgsfyTtb!X{k|V!AA%r!cHTl<2@G40Ax&B)O6f_yyk(-gCJ|iF65NdLb>wmNzi*H^ zD+r)Ea+?&)Fa(`XcBLJZFAM*P!gs3SnbqB6Ap_7Kax6PPN2RF0Q_<}#wWeUmAan%&&34?0H*!g=vk4wVbf zlOJOfvCOsdWb_4I1^8ENIMwA-QBl^p!EvYi1ItUTPpl!??)G;(m+)x<2Zvv@txY!F zpTo1y+v|h7LgAKyRmu-dWkyQO-bZ(gWumh#4ol_0>+}*)&PvI3zT9Di&PtX`UjYZx ze-6jhUz=N!Hh7{&T9W2NX)U=8nIJ_|gtfFSOi4-1gnvRb+^Zen367Z3=4dra>xqum zFdR8JE6Ys$XSg`>h;p*!^21LJ|_3vK*CZZp{VRQ zC<_y7Q+PD33Y+-XJ_%Bgmf1a7$lc-n^5`@vk;X3wp5vjKzLll6v``t4pDHxiP;6U{ zVJsdXdd|%eiSX+BtuwM_uNAe8RX9hewB1(E;w7k*0@TW)d>z(*|3e#pCmJeK39!15$gkK zq9K>-1>fx|(Hon*tb6TShHq&)eb2?ZUalxl%7{uoFxG0IJX`XC%GDp>uHYUYQ@jE( zAn^K-SA4`K71sg7UQ5u%^FSjARff@q@cgkX3UiRBbfnsrt0eSqt4lr(qb-$nWdp1F z^iawLkXjs1GSOb$?<8e%>yM{BeOhAq2yc{jl~gLde~3Mdv=};yX?hftzTep^?$8vG zI$K^-Sy^STDaNA0EBU{?Bp?^X^V@qfry8kLOVzL40Rmr`o z&sugcWwL!-Tg~bB#~M~29js)-%`F4k26_0ZIVk@}H4M(o_#lAcz<&|u|9b7@xcO1( zn5=hQGS7X~kiHKV87$RhvYzJzx;MZ3 z`}}JA74!`l#;3Bre}UF|`_}8OWRf0be`o(Ym{Zo(t7M=X$1*=SST4@QN`ut|qM=0R zK0#FIi0oD2g&uD9j}riZrKjex9IPx7@vI~HzRt3%#f=9zp@NEvr~5Ztf&nQ<609ts z?cg|h;6D<0Wa0e_J+)v&RGmz`xkJ>?jk~+%DGM3$9(+r4ozVO*PQ$5A`RWgZ)0j2I zF#+U(5V_1USRC!7ev7vhN<>3ilhR_?-US+K=zX6Uo~+Rm$?-znav{Up>R-0SaFVGGBGLKDl?j@{{j z{tvElL@FJPq#5Oj`D2SZ;#RdCv`HBJ01^M8DG(q8WFh=NSYTi=u^2$afFuLmXCH`d zxuTEmv{!q0$DN}vGgU90wM4_MMExUWluU;8h6_;~`YM-;%-}}gQ4bMaa69zIkkV2K zNGkiD?9s|?k6nM^CQN1Z~jZ32~ZVb6|*+yPgQ?E*w*3R>KC!ZR2rC&iehrn)k%I> z(FngYHhsbownjGn!!6;us&8;>>$!LPh@d+wPH02+*sGgeK1DKb7vtN1Fd>FE3HI)? zcUGh`{e^5TYg$Z;L!NIJcfn1<%+`+{gsmv~$t`R}Hci13<<&p${LPMK^v{~9!?+dz9QhIIxjv&JbwLSpT`O|w*`*c`Ms*{AaKfTO=c2M>^8kEUAgz7{;OJh!S_qu zU0)98wd8wxM*^N!ENLAS7)kCp)bOxzyh6m(Tlm_>2?MQkQNy#?^Bav8X`fr- zpCA3%d2(^IovZw7h0No$4fGx%SGw-()$F3_{b-q)N|}@8!KlmcyeOu@lMW8ckR$7> z)hQ+8VsXE9AT>XEu!z?NevbWXUuEU8W!^^z=Pgek`TQnIX9mP7hePI>^7XoAD8!p9 zH+obOE{hlM!-mA~SO`?|Tb7$%^kwVltcnbhKOtl=mRssO@+lGzE zp2F`KTJ^g~z-uF(Lq#Cm_7lIwBPT$HHnxOo?GTyyyS<)ft#1k6qfVqQYe(e_p==Id zO`$mCeMO&xscZfTZM5IgDxcn`R|;n|$Jc#+nY9kE{%$K_+YN*;f$hr1J)TivA2+ii z9V@=%dI>M&ALihR>2K+}r?#sc!`35$Zprg}IMsfLfqS@P`)TK-pO(zgqjy56Js(Z3 zMHjtbzThZrz098@E27e&nM_i=&&`L8s_EnvrS+?Ck9N8quz8id;IL!$rKM6*q^X`^ zXP!*E=&b3vxx3R-M6~78#eFUWleW-hBt`l9NKXg6uZ1zkRno{(N+&zbAnHJ%(LKG< zqYo@6RklhLGd*CuPfFtKc#~Yyn6q9#Gh&y1_T{W#Ns+I&8}J@I%Ff&}l`c&6LX2g8 zRt-8jvt5A`nUNd>_1iusu2TO5u0sE=%Q(!&K`~AkI&W1n@r(fP&_k^MnJ19yZut@h z?rssBDJ=T&z4OWvxHnwJHA_n+w<${u?^Jj8E8X8@jYcl~OQD@h#Dh<_f}#-RtR2mp z<3Om^{^DB&bAA*YXHZ4W0=9tv;ZX4`Ej`P- zMl+Aoshd8tk3Uy^|Em46Pm=kYpbAEwmSUZ_KhsYqhH!j_M8T@3Xw@FfJRA z2Rp9aoUsdGT7HORWij@;kK0ky9Wl|!sEfj~?HN8((OOLrXlk;_U~)V7VN1Rk8llZT z=T|ml2$NTIj5v2NG1St`+@gn9dHryx}~ZeHeei6|SdynTb7f^XuQ)N__Nd9iaoqEe|YsBr8*a z3&f9cGrQ2a?152&?b|`GEDg5clK_OWhUbSScPV#)tN24_%;?q{uS(PWk5czxJS(_K zqWV8OYJJ^}nvIt&<-GsI1Y;{qrV87mJxG0{tgQED^~(|{S03S_w~G1-zDu?z=pkgp zJx@qB?@1_0Il$P@Yvdtm@DEb7ab;?j?T5d`J*4)gxWm-<4dkhD!kRcg{%~tB_-vcx z__~MY^!iz3ikbk~uwjZ^t4h5oI_t$ML{p*h%ai8Yf+4;DYM9v}{*ivXH5u8b^Xg@C z%-)Opm#^#>-NJcyJH^jM413s9FF;%&Hjal{6g?4$ujDG6vF9QnfA(~o#(Va}#2ldo zi0@bSAXf6Q;pwHNC1=2Oi;x*a_Xyoqp=72^?gE56$Eh`+5!Vg~s|%?f1xO^0dcd?p z_|Tywn$GI*@tq8}C&RJC>c16u=ccB->{1XnvP)g$KeAPsS;#%V19N07^(FoE@9v+l z>WbO6V_B=K8`lPP4_;nC2Jk0dSsSs4&|~?iuITfaWv2feDT{vM@lFrxI-orarw1J8 z1MIKuvD}J3!=+q%zL~u6rjs+s406f%Qtpy63&(wiu}^<~|E&Re1LJesvYSkC%6j+b zKOOi}qxIstIg;R$_qtbRsTmk1XG{>v=T%RpSmvt&kQeTGa2Vvf7ug|>wSWDm!zm&R zuia(>tLrWo%wX2qbL^i7vL=pn`mSOZ?Xov4Z@yG4@0PblwLP9qo?)ih=xzTX+VX`R z&sL>QT&+q==^6&h!>Q@03*`wq5z}V+MttJGVb(S_G^Yt^y7^?HUf6m|11zZE)A5OQ zxQf#2hy**)3^?~H`b6YD(X=5AQ4~z4q+wn3{mdnK#<^6B1OAe7eyimWc0p*D5v?bj z=8P_J3_Qg)S87&a&A>DD>@fAPh3U#4lM9flW- zIq?o9%e0(DeCayLXAi#z5Zw!?%uD;xn=_A4Nyfqm3 zfQJC%Wqu2mvx~#Y?x>sH3`@E5Urfn*c998OU=~LS)KOY*Ig6Nrt_Psd5~!wbCF^zG zr)1Y*te9nX+iA*LYj>BXzSq;vs;u=TcYLr{VlW{8^H|@bTJw%Nip2P+`zLqW-ucC& zx9?;iumXoaVMgT#ed&mjbHlNFM#miJ*Fhnx1#=DMmkD65LWZZqu|Fp0(!%QXIz@DG zMh>KazecfSuAZ%T;l^J7(HozL;mKwUR;`r~ix0&feH+&wKDc(x|K zVnOyVzFGQL%2>Z!{{6&7;M|TRi4XZjcqvU16A&e@E$vQ;d@=p1^QeGJUEo@O^%QHf z^)^L8tMeO;g={G|k86uE*`Y`Yu_caSMY}VLB75i$&$ zAUU#}7RcqEINq#FrM9Ot?o0 zlzGvd#`tcmR7*;bY$)|D=+0FCRp2GpT$zZ)49-Ya9`22y>ZO{s9su--bp53}Yd1qr zGwZPbuSaEqm^Ipyk0!yiAVmWQ%7U!sq#9Z^%UFSL50vQdK+m|yzqYiZaDPG+4BgP4 zUMCsu?=9Lxxzk)UfHQ+xjal^I;Kv6DeO?2kf0NB}G?nJ`UP5Zu2Y}&BCt>bApxA(K z39crGsp)AA6uNi2i6@7B;ZkglxO!uMcKuc)XMI1BWOHwU z{AP7cR($#~B9VkbF1{Mui;k4^GLyS*?~L?jM6dZENr^q2biQtJrAEo`b2c$m;RHoy zpN>%D=j*m!MyBP>vKx@ZbTEm8qsIw`GJ_0g`1D5!nyy?^4^)Tps~Sg*njYLoA02WBaD|}lzS4Y z47=2G*fhmQARXk@H(z~~U147hwzZc8O>4bLyWcMlj%h`Cr`~0|b(+}XahWi$#_ar0 zm|NHr8v3CuV&NMUS0CC%(UK58fcmSSI_KrPtFYmgf0E)rwN?daf-aB0nP+qmnwz$7 zT5A$cdsTaJF+u~RX>(7z{9MvqqTrTaycY!Zn(^cR%EP7Fev_7sbgFtRRaGp5ug~}C zCFmc)J|Al&d7%rP9!Fp`6H!zvx!v5Q3h}=jhZ^T2#{Y2hk zo5hmfHmQay5x@(&Ud|$q+x(ae?}Ve*hZ$a~eKhZ%ti=EPMX!JpJMP3&1<&Z49=;^o z`=$9_U7cAt$yw3h%~homD3M|08RahjLz&?}ua?l)R%teyv?>D3cu*a?_3I?-Lx<`| zLTXD`|CzGk&`?+aMsKTO`QtjCSSn&bu34ho5)FY3Yx&=KYwYVou`UR==>l*f7svavo%~e#_(iQ&eS| z!#bKscq5Py`mD(z1c>_90%S09_AlAv*jGj!UqtD?wUBav?mj2av2ZxdYWVSEGoyw) z{_H|wqNzzTnQkTFacW4<@mJYU%H|suPke5YXxw^6K03{*XNQQBx*5=Kvb?fzJ*=Sr z(l6%LgZ!63_8syaN%+n#9(%@1k{Xi{P#43?ppijj!RyX!Cv5Ct!o#)Ez0L1)^W}-A zgz~UPE9=cvoa)3V9~~-@CK!y>kYq#mshY%fP-0HzpRBov38S?{9DyOt*5ODK@Lgu7pJ#*B0-l@R;Xh%)ma?i`m zv2jQ%xl)=SrpYBE!gvyk_#>WD;Qop=GlJTk7AMOdxzYL8;5d%A&@|%va|)}otenJR zNcpE&kW6mNZ&%$~dBODfFG0uI;Z`yb%SE!K$~lx~dhn-Rnd|afm}B(~J>P95wfd;} z(BXnKgBr#10dnzy?PA-xY0a{A*{)!ex&gJpTfD-j-N8b$f~0iAx3lSA^$H#ZPQ1UlHOQ z`4}QLBjSAp>Wz<2{FXEdx7O!oD2tS9FxdMRb|3P=fzwii(bt8{b#3NLrdOfQac3Sl z{CKb>zsA|P_1Uja4C_+osm*^6;Tein%6ZGO?Dqr0j%qQo5u}oJ-02 zHrT_mezOlVV5oak8==OzMS)B->knQwD^Sx9aM|x={1{eE)mTg%=TYdO4LG zU}tOllCMce8>>rfCPux+S#ypYd`e$>&6}nO+4`ZmKX3Men$A zxYn!T8snsysxaMu1#tdFLx}E$7ioNI85ix8YTpzL#dR`QFJgs4!uRIT?uS4FCfG}% zr`8qjwTM=ESx#?RygU;cOEIbj9MI03J~u5|M=L9Q)ba>>SEcJy$j%|;(bn&Pi`k|S z?M^Cit0#Z@E|&vTN8&4wwM2!0xd3@1MWdkKuXp%yJ}>-hEsgV&2u1X zHGLeIUN=G)EvW;Fm>GsLv~7Q&pG%GQG|nZaa=sGZHyMs8p-Q8fp_L5A2b9VAxlrL^ zPA3a&nr>}9@jnzzxBL}1O;gU>z$@qiZ@LTwYp?vSfwkg;ske&o z)7m0?9lLoaOT$3J;U9PQHJ9yi`_1F?m+oB!r=;f z4>Qfv_UW)~!V@aKH>;BjYmTMKd20g!?Sb#%VPY@wfG&^pZNi|R{=VnRm$TO?5NS!# z*wcV0H*z?d-S3azi6dZ;;VGDb=Rcdg4muiG?$&2|5>KEO=A<>d9cTZON)>=%?(J9o zDh(0S*oO@OHFp5n_`R-eTF>rUZhYc*_219Am~>Y#A-DLoZc-2cWbNY@YBGr*o8!_Z z;J-0G?b^!9y|fQS9*QABkll977ZKkgMe*PS2^E3Se1M+a~kb%xyog zd7ZhEliSeHz&iM4&#Y11j>RlY@FNhgpun^cWBBFnLIj14r?S>~ITc~KhHUV*gK(8j z?I-{@EIY|#p(uS$gSmXPQh(v$+2-QCv; ze8W;{cGRhgnr_m#2-#Nvu{uFc7VCS$QxOsxr&F?HsX^I+HTPb&C;#)y@0Xw5ae*d? zF6&?A5Ia=y$yW)NyBemXruq%*#V2{PLO%!N8QR`0XKAZ2*nS;Hr?quQt4*80E{K?G zdfCybb%nRD1boFWm@)8w)vc+McA6QV-%kqJ3YC*XYNl2>?)9nbd#HR9k+E2x$!hJ^ zu^eLHg(O$S-GIEs^@ z5!OGXW0}bIJk1jfb4h4l98O*Q`>xcV7E$c=8NTugFPoyXd+${*Bedp)5xr6Nud;Lt zS`^Q*(dF09Fd&D8|(LF}v2Nva_-cHE855RCLo7;r?@eS-pu zKC}Dl%Tk4Zm}BYjU8v}uH{@yC&UrUpb}k%hR>D){+K&D z6-VjuUvB}T`uVucFgqVgo|6Z%M!YVu=eB~Zc-}ZWv6@FA#UE>od#a8E8hpi-GD=D( zkwYiNe7vOyA%$h8=v~+kL^SMdmRst$Q z&P<|JR1l~YSSB!DeWGbUk%T>vG2zd5n=;%{`@kh8>e`!WSU1?1QzEz;+*(!K`;hDe z*8FtRWlPbNogtRkr=o1%nrww_dXtc;k#z6gbnWrx%S-H3?fa{DBMlN}TnLk2&W*=p z^a3KcqgJ_HZ!DJf#NcnU|J7Kf6rNAF-JpqOWr=3ektszQ1jm`v85#bzDd%4`xWNKp z1oTk4tC^+9rC3+MFnQU0_3f3yXp)gh{X5~U-#~?R9sHpgWFhh6aC5Ix9g8^JIcZfI zr|t{NT)Bv=@fXqF?FKC0*A~O2-_dc4N~Ejb;Vna?N|}=_EFl+RU9>(W&os}?gm}eD z1c9D}(WhRwWJ@Kh`%JByqo%T3WILwZ6MG?woy%kI>))&59y!qlls8Q*1_%ty{&zF+ zGuDh05s(A4FRbOIzL{@qw41l+s@29%%gyDfHx}5Nq3*~xr97s%%dJ{bP8%_6#r=4FU*+Bp8{3hi$tut*_7R2X*@eKAB=%Pl ziqiE=RGB5uqLV|{sk}`X<=i{JsC=_KABMM~p5DPu07;dzR7x;mUV?2VWfDbq63h%< zIVa!2M1Eq9Wy@EZRzRDC@MhBOH(d-)eM(0o#0G~JYZ+wmH+YGsItSfutmb#F1o9`} zTFbP4XZVmnc^(3E3Hshk476-lqC}1jz~A}e2A=Zri7C{5?3@=H=UWzbC?V#T%g?;n z5KJ!G-RF8@Jej!LjmkVraO2@x(Va3|WA%MdLn~@LMN2@J{WPlZ+zhQgE59ZqVBjhW znYzIYrfv&Cb_TjB07prcCXy217|$Q|TU_S}Aur90J|u|mgSEB6=Rj(LL9IUjDVax_ z&yV8sUy(X8M#?bOI}AiD!ZAGsitNN#C`d3QVb&Htu0CA+(&i;*Jn!RHgWCd!1#c15f=KNNkZ`-E+&<)) zuxJoFADd?lE7f2cRw|&Xw2+f{;MA1PV2#&~BC%e25wqva@j3O6Uc`q@s^6OVMU*dn zE<31WLWH50QbVfiXC!`H{q8fSW>40XhLj$Y4%$OAsHxGXK`ouPNmd1l_QS9_t)FCP z<3)%MBWLWybfPzHCVNS^#MIux$xd-)Rl*ayr~rl2MQeLAcl$q&$v~5K8(B9;(uG}z zl1Xj7R|DW}6cg(|y6&10Rvb*wc;hVz~Bh*b*Ob*#|tvNZm|DgZ%8aSkpBFoJFc?HuPxpHka>hKWG#U-67o*r* zl@p+WZvNKNgC#qQNv%uonLRV&I257uTqa7r9V|TnUcLQlU2I}Z!g|>l92f6UO`B{z zshz#rav!tE8G~{3^K^F76LUBx?~1plhTSpif$6!v#Ig+E58JuEZNY-$t1(3Yjv}J- zw5(j;A5{_U8f2>+4T0m2tNIu8K|MU7-3ao1^Q(*}mvg-?W zsf+l3t+mBNkLy&Y!Ul;tp7Mii=Redg0e)=xH1>fox3rnG$f~daTJkzN3*bA8o;0qF zrtSUPuQk5Uv!}z{QNA7j(_!POR!6t#+lP36jxA+h<)c?9!JO41>o~m1N~Tvwm@yd$ zc{~yXRSUkJ>RC+%PaL+XP9#upzJ)s^$C&F&8nkMb@f5!lI3{z#X^T82S2P1|$eHKi zs_Kq`eF(s8|E^25^Q;OSdr$xDyT8kj_Qfg1tKum1X3*T9DB5|63!HAw**$`BQdO*S zJzcKTMK6Gv$Nl~2V8SFs*w;0AkR-_(L368rMOshe+|~p$1Ukzg_GTw_B5A#hdka$>_((F>S--TOaU=1vI#;Q{rRrm{jMYFpE}LoW?s;MW;vD!@DH+a zlO+g@^ubM=T67=B&vsE9CYw~<2^o9thf;c9N88}&=j_Zh%*Q}5N+BFVObnhNpe(m+ z2MFsbWNDC}E_6!Ky|hRFt{TpawxzH4dgm3G)A@^$DgKBH?0A`=iEh19C)r-c_Hf>E zjf!`|r4^Bh-tN|-GnK;bR@v1NmtXgrbSh;hBH*vwFo$=2nPGvvsOqBGd~Z<9RtPs=~ew{OCAD~?wT%*~6msXJ*7ANEPl*R8Qd*>d#xWZxaxLAxi+=Tq30 z_CUTy*-YXVMV2ko*jk$Om$9AkuwJmm1EeDZ8x#`h665xA_+P~GXN|x=^eO01?6wcm zr)h#E)?%Jr^tZ3CPr)B!feyR#Oae^7I396K%mOwrIcYny*Z);n`ObT+`jy~`QATFF z-gK-0ck1BsCGm})1KpP){x~9#{2FX-ZvI0?GTW(HHMmzE$uj@5a6!+&IcpYJEV$~d zPKcwF1^S_lRGL31WuK{M_XrVpJ&&b{E%-ySy72VMCETvH!lYPWNleG=KToCD_-v-2 zssRDmEirLD9awYQI?nO!DA$_oQ7mJJCyzFk#t>Pwku? zF;|itwbLqkb!LXI3C<@kp$TRpLzlEQK3G+H+s^aC_&@+|+|dxeV1fG>KKsU++h^(; z3VuuL$kO|gHM-#FF|-{#J33kw7@tJHi}~VwdwcgRHshWOvq*JcR%x73Vfb73rkYwH zer=I^5nVn|Ybs%7w?whWH~4;^PfO^4IZCunMdB#C#w6QEd%116m^BMN@|2rCC_Kw^ zX)hF@S>-EIy&`Z?Rq%8BUAgWy;ejPO7x!zx59+vEDHyXci3b?BCFUF@xxysjrbAQ_E}B`5Ns z>ZlmP?^#!T=Tk5*B|8qAc1(N`$^ma$w;=vx6>>Oq_(nIM11pwSMcox9`7(gdq5)K9 z!V_PF_g+-w#oa1c)fJrkQ^on3Y_Iw035$0$tKVjM%87JH>RVF3=_F$MY$IwlS|(>~`eC-K)6P(6!PvJ<;B zev%)6>ko*JpaeaK4x}~}e^*4kmko{WpVYJD@uG#)Bpzn%%GD~&9|707|6WK!*PDdTg2gGH2yV&#-!7xNG{fkt~QS~I8%lo9%Xqn zjkY_l*zO}*HIG_U2BLp(-!IQT^Aja|oNF4+x|tT8Ha%xx+iAd>Ki(=cdr5>VB6$v%I*|I-uK;|Z%pM>W+ApC<%|cSno4wB!(k zWD{;6dhH*Hr*so-!`gvjM$z0+KmVs5U*5C`vNp}D+eaZgay{I1VtS9a^I0>EE>Q>(W>u@5K8KHAXgmWZVc0Hu^=6 zbm1WHbOtuMp82JRMtQ#0%W(1;tyG%2rQWfmkJzBgFIny_Hn}vb>7*`*gs1F~uan){ z2`aXwO1G-f;koGN78C7f8=iX{-|B}<9k89E9Ri(KPDCvmqjdSNZTz5@uR_dOdqv97 ztH64ktG9-Nlo?H+0&(M>;8J&`3CF86$Rs2p1;PeeoyeU!B_5=0hWcW^oSHSGY>k1% zRCz|s3@Ra~jy&k1zk-3Iz)Nz;!tV|1(J1Ed8v^deRZ+mGb~~%(b|Fo zwkheoU*`9UEAnP;LF>OJh_m&bGH~t=-i`=Wk`-z`g`d9lp?#og z|CSNtaCPOOi8Q}?!pU2;jgZ;cd-eOC&c(KwHN^kSH%QsxZ3&uwr~-PL`kP^;if^BF zmHEl6pa11Hyo{BqVp9g+bb9~fu2>Y=k9eW5bL0lk+P<9MY{GARiHJ(N6!*@ zd>E>T7bn~vZhN+2nlr<{)Uy0>V!6jC<1*6akWb4!r$ta!e{SW5?{KPnH-q1rQNzAb zbKtX$=VZ(&1sR9=LV@?&!u>0q54os*Q5j^EOgnBYL55I^IhkN{ME|Mfgs4k6{R%_D zt71uyVD0p6cOR$sDNUk`t*8cCX55`^-F~XKWH&vRmbR-e?@$gyY5&r#H|Lok6W~ox z*Xl?R{DX18>WcfTTcspUSFjn~SKq0St*dQPBxVo3G5+u_FLT$dnW4W0jS9e12O4P< zpDhLriJU&q;VMtcb|v*H_ANCU6b>o78(9QWGN^OpIv;-X-xGgXoatdo88j1CTE#U zA*HHVt6|)fde<{6P_nY*4)a`Ly?Hw?^U-MQS~HtlJEl z4P}_^sAlLDk#2m+245YVmTn9bSq2_y-BPxZlftD9L z2^+qBz-ahih%4W(x^gJP{!Wkz1X5tGjn{Wc|8!TzAXs3;NU#$AC|)FF{oTc@>v&gM zaO3d-E=Kip(EKICCp-v$OV&V&?qew>I-lFjChqa?2a37=8@%vObS*QM6_xyR zn@ott)$!kGeQtZuDceN)RJ-%D&x;CcO#cApxhOb5d!yOv@mA$i7kaLshN9NNhtYi$ zz6mN~co0l#_l8uG+9yaqWZ=V|??Li3uw>vSVQE{nX9*MkI7+heYYhWbCEx?{`Oa|W zdfVH42@pbSA*4e|%w>SXa^)~@*?rh=>D{fC zT=rDTh@8qCYzWw43D$cn)v4^ZG{YfPa1Uk6#_29$W1s|>GH;!U1`~Rj$4g2)K20at z#2^VyA*X`!;Pb{$L0d(;o5FuyAelCR!u@I?Ium(#1*G{qANnQZ9VEjLO6O&)en)7d zs8g76bHq3u%)u9Q7z673{TKW8tpedw`_#OHQU6Zw?f+Qk*GiHH9Hrez$oIe%C0DNc zdqhPQdv*Je4blvW$oP+hocIb3_9KjotWT**&F<}*NR(cwN31QEi2Oh@GWgefiD4TD zXSxZgf=n~ZJalvslBtYR)*rdw@NxYI-*DLM@mfDAN>_52;B+Ng`1pL*Vz2VR4e*`=5p_&_H!jS=1*A|EE{p!h2=9$PPl>=5Crz9vH@{ph+U z!DYWD^IpvK#0-gqgBe4KI-_jt?WI}aiIrvdp5Fe*au~aNWMsq*?4(#xC3Q$JL@+f} zl`d0p^%{y?*!#CPy-fNNFZaQN#YK_r>wiPdo5+n;QnjvtWsrrK5(L}s$;Hkz$S`FC zYxZ1Wo zHn*Z_a%ZzZswdeI=-bUKuY8%HAB@AU{^bGnVX(s#ib5cjin!wSoo{Tg*rzieM!Cib zne3A6Sfm~xxiJpLlR-@+$Wpfzi=;Fz3W!M+Sx*=P?lLl(L^|Z69u4-l+4+VZcIG3b zamtDw#W$vez{vBxDX4X^+5x-GU$3S^T!` z4NDiNYw=6AL95>Z5Ml3L+l=6oz7q3~@hbu1kgg*$Pbm7)5bb;$vOT4zX9~A6eTT=7 zk)-Wo`@VY9U|r>o-uZ|7n=xL2O%-V?0U)EL3{YnMPLh?z8*!Wi>VKQ4Uo8`p_w+tn z&;h7=#te*~AuyXL6X%%!OG|z%d;+wMet|h?3l=WpZG?zc7;r{vg1;g+D2E|up)OTF zeQfNZ>Oh~=#pLLm%Z9H;&p6WO6@spRiwrh-)X$Y5+1@j6Y*ik|W1JhI51o2^*n1~) z#jsdU$G(5l>Sl9m*bGYQ^f%**B4Yx_>pdl*kIG)Z-6Qu=aw&N&Hw3`R8&%6eXKF3D zgKfd8N7}tB)TdJeSW3L8MixITKZ*57#YtG~KOyLwof$WZz>f?+SbVdiI~GD=Bn(fv zr=$kP*`=X(DdrT*rT7m{Y#rt!3{S{>lgqA(A-@l2IKw*I zyyX}PKTQm}8^W6aTj6*>i%d_#d)=BBOA~MCT-Oqi0KGzkJ?I_}uuoii&%firo*XlQ6 zO*)uAxIbz(MDbuKF6(6aK)z1BAtCx{MF^dh5=qy4uz}+<-M3Ve(O_56M;9B?s@F+* zd3nZ5SP1_7X%cFjMRFSbOeoWQMn;Es3afA>wbV>v3rlegO=R)DY)pwBziu!8nSJUf z7j7p;?gy`|!I6D>YaPRPc@-dlraN55lW})a9Mu1Ne((M0Jf6Di{OQLlt?$%y%8K`PTVjRHLiUEaELM z<6^HKdKLlLyubcC_r2bx0$k^lm_FT3K10gs&W;`9R27#`o${$1+LD;XV_=5{NUg5d zf|+6ihX;WKuWsbsMOj}fUiT+&9)4Q8fm)doUHXiw+epQex6LT1{qtxa%;5xE;=7=a z&op;5s&!J-(pNo_9ZgQ`nhT!P`I)yy3`C*-do8%|(236y*0!Jw@sPc*#!WM$k}3$S zAB-xD!nU2+_QL((&P(*?fTVi@&leklV}a8y361HITpTY8FOM%6Q2QDsxoSR&Kz6rQNHF*`8XQ8 zLNu+(t__LGTIySlYu*MW$6Z}2d$R`XqFIdIeR?Zdr$2`op+I3KnBrugGR&;TG?=0K zgkSBI%=D=pPtzwI$vXq?U)vi2AoBd|l;2)ihX^cS3NO75dGgrlC>`7!FbeRt1;}3* zy4*4cSoIsVr#21n2WO1vPd3jfyWV4XD*t}|Ols}o_plMYftL65Kiuapd(0YTk^V|5 zY@&c;QTo&yS*IPMerV@aN3hP!9EQ4;vuqFdOs=AXMR~?l!`gdNuX8f84-n(aPd_z7 z3s&@cJ9|NK39%w87!9W!9PdJU#+|vd!5(8^)$^R4z~RSUy2f!%8e4y@D8zc7Pxwf8 zb^bI$1?@Qb3eYPYwPeblq|mv_dVnMzZe6+cT77{b`s0;JY$N+wpjM|1@Qw+~ zh{BOi>%;1UJwAANDV?Yabeu7B*=)-$c)SW^E zDCiTL$;4XsfeXeM7(d%tVWfkhd>?S(OL@o6HO!%^6~8?Uy*d`9o9M}U%|R^$;)Uxo zdTuWZpT2evNg=|I5Ll)oD}dwQw|8bN#In-X!=-X5+CHD-gj%n9AWwu3pbQBd` z*dFw^-mc7RV(Y`d5py=3mM5oRdQVT0Xlj+B)-Wh!W=|>L2gZ-TE^;=*zxD^7g#NsPV#fKZq<^1aqWgb zKY%$*o4rHV%XZZ&@yl&7-8$=n0r3;8CDbTB=6rkf^y@Aq74{`Si;`imZ}-_{mv!@Y z4KXq0W0e)MedCs|F>Dq-;nu81=abOGGHI0_>j1o%H8!VZnn0)h?%g-&GWBhGa#FeC z|50=pQU}~j^}L{EYoRQRR#;vpssyyZmE=#_Y}u{RUx5j63wkZr2J6IH`%llu(VF$Z zPbbbgvvEp$X{P1%UwiD$Ap7g8g9KXltkg}3wj)0?|MiK#GI0E-UAEt{2EckY+0h_% zI$WJ4E$Vvo?~z89ZGvb>)Zd!w==!xSh(669viHa@*)*8yXWg@@{`4&UxMf~p6Xpl> z)O00X>T4RQsTNP*x@QKxM6p+$^{;BPt~$U%pCg_Oke(NRGHvZp{kPW$rS2``_oY-Z z&O#8SwmnrBTu_Xy_2D!NMuKDZ}Z+-BJd3Zo|j9hFR% zJ8(WP#EVZO6U!5X682IK&c*fZ->iz$Q5+i7fuFI-O$zU~D~+sKtK=i)wlYH+!aYBn zXV|W%4z#$QF~t?Jyh}93JAVR$ly>J)xR)e-q|H@L9v0a9b$?Nb=geAHIwq<|q{wu) z51MV)J%B}8-?#?l#-H=ZRmk{9{~J-mTfVK;I_ESxUkSjwv-_UqOtnd{W^IAIXS{H#xAP7)>6MS>L$0wduwhrJ>Ki@mZok_DbGJW+;NyEo74L1hk)NNtgJZ2ZPf9LdnG3y9^pbj z7`{Pt6AXoaVU;IVq>N(fr^ zXcIoE^4!e!0Pn$(?mH}yrQXi9b*oQhX8HL5aL566kcboO2@OCiYIk6DO^?ak8$qhS z^EMy+&_CQ`703rCZY}2q*pHVfujfA&h_2r^l3EQk1mLR4fE`ZYi-#-ZPY zo8ArhO&iJLyiKy&OQw+4nC}3$s(YrTU>|7HaOhX97(9aX_Hif3hk3}`Q?X*%zr&=W zpk3q(`dwVKj`sq;o8%VD&)tf<4hpi&DO&1@(Q>4`}PePgIxN6l2v_|{Z9@Uw8*!b;9G!5Y5sWuO9?$dT7g{4q!P6x3*EOU1YX)k?SK% z6$Edqz_aQ(FDzC2-duin;O(keKDsP*5p2PIhhN{bKQCJdG%v zpBIpa?w44(FNpjSl;UR;Qk_x#YF6zc`h7hekA(to;zs_2!zG)w!oLe|<`>+i?wZ|YvAsGSK@G?PIEcPc3ZH}&ARu$Q-l!l_f|kom*nFyPWv=+ zC7+@W9YS%RsDgH{U%*&}gqQVc_zLCMKECI4xZz(73ci^vajRpZg5W(wym;&1y$dby zlLGyq;y)a9@8aa^$VUooa`pNn-9T)FovYt$nG$69u$^@r~oo#-1Jlz%e4_Uh?ObG`e@G_8`iFf~roq0h+y@;P~LJtL;Zyf_d zx491g-&>Wb_DR%3Jp`IkM)*0}Cg59ypWl2J%CkQN0*L!MAsw<^{Gl` zyu79D|B*wCftTsnI7f;NAH`cmBp;)g-oQPjBgBM z^B%=XS_%F`4#k}R!*z&##-(5N)crwgSs8kGMxP=KNr?bE!|SL>L4S|zj5Y;ak#s1V zckl;=i_+hB3{2I2-h zCMmL|#~vOYr92lm!V_;tMMgG%H0K`dprY_E%TO=NoaC#hh%P8CrBCkyfYWvWupAj@ z&U8FU(o#yT>qs6?c#|~J0Azt{s}{oDB`g!y5_iIyWDH`9{6hf+1sY(Hhx=x~5xk2v z1Ss-kuJ7938c1~$iIoD!Hvpe>8Ig{C@#2M6FJQxn5jpGPMOlIFzbz2sD|Bmbe0=xZ zo_o8WjPiIuDZJgRNTE)pj(8#5w~K1lU;dCBSM2P>(O;ufp4_p}4V$F_58D0Sh#9BZ zywJFWxWaiqAexCVM!|OCD~(e~gI3v*k@ZfOV#)3Pm~OpJaN5!=HqB(qto7V%wQ_pz z3yaa82>)`QdpQ1&Ea>@pXeiUnr%!KOQBzT!cwzxy|=tkb;ecWHUgR zQ>&<&^5tC`_&hLo{=vZOpJou}Vs)2dI8QbO^qmYiD5jC5A~W_p55+}#sq{_;t}LlB zM<@~~jazD7$=~eDk<09Lb@xb5B)-UM)&*T~?XD5q{>Qc|-z%B4pfT>^XjA0$gM)1+ zVaSKF7r`~U+@i9jR$?2BJ0!eVcxFI3W=c8aCIRpl$fm031O81wx8W+RlF6}!GG zRza}oU~lE^SNSF>u3+3(>ZOBHldeb(Cq9(&6!``(2?lRja7_pGWoF-@6z6srkI~2} zh2*4@j?=COMg<8e;jy`Y!af{_I-+PX6I|CnXC{(+1>eJ&l3zh}e&sg{l_Ao_TmYRk zAaZ8m16AAB4G-n+mk-{4G*`PmF9N@5V1Vo$0)0XG3Iws86+coj!EZDN%#5rg`=9W2 zR}d?d^W2(}n?Cpy9tO};DyuJpgU^6D%;~Flt!&wx%+XwHajH0r~`+DFZu^QHp;w^`TX&KUjr%c^&2Tdog=2}5h&nX5IIl*;flut zg{>-U%`P*Z?PweV>_L9CgRNHf<#fht4$?#Qb+ZP~qq9Xy<_8WjoAU2A! zl$wY7&4YAm-n%&b%C=bR7EO*|E%dT)@ZWOVA*rePpK-H6%myBjIeiqJ#A*EzCt~Uv zV1^zQ-41*DS)lSLOjm1mDQEw2yZXXck>hUt-WSS^fxdx+B?XqyCp3OF<@@(uCj$Q) ziO{WowIe-qy-}gA&^5@ly#>a`Ns%F0ib3Z3yWhOpJ{7rrJK0z#t73yP%i2M^SLig- zr`lycxMk_VH-&a4>e4r0GnAigz%L0trd|zfx@g_v2FvSfX=6Cea{JCdMukPxh*0N< zQ0%nPt*AZqZPvt3Puc+-R;=|`X$l!}M-81pLP_2wdeO>Ttxcjb4b;xmFj_K?1 z`P(Hxg>w|h1J0)a66)g-@PhYD8rcb>)%5WfM*}|zPec6f$gI++ybRwi9z89L#0?BiPQz3@`oDKh-ld5@0Du7;f2R zsfVuKl{sNb?^+hLmB}o7c7+LX1=d|SG9V>%wJ&31)*_uIIBI=0d89q- zKQRm3 z$Km!KAGA2f!LmwnsV;sVq#OLgrZYc%m&z?PeP$l8I3aZ+bWhLst1P#HE`E9W&R=1F ziMkpu?*PC94X)@N_l}fxEBT4A6L-%MW&SHuyAW{Vbpk@%Z6)0-W%c_P0(jWFOuv!KwNS1i1SL-PZX{4EP(Xv zdd1G=QGg$+KpZ};eu=wjxBM@MdJ6c@Y-55eV+UXs)1o;T!&$#x>UWHq2%YrI#peb5 zGFQ;9s#RdYW9HLQy)D2ol#+5ERBzQ=;3fFYw^!TQ0pXdLAfA5QE_Ej5xcm)LadO9a zI^5P;rL;@#LI7y`zA{Y%&*;CFP8+&~yAq+f_{n{qxsdS)aq32Or52T!)>f|4B1da< zk*gU3kM^|E7JYcHCADVrzttx4u8m?J{NJed(ZTF9nrM-A+D+O{_CExWJ80i1)R3b~>%!n+V*ok@1Fc zh~KkjIBSj)*V@N(>E;IcFe?*AqHjOBJN`)Nq8RIEQ+fX~`-@&j>>faXgUfANuK3OqT~ zDWc+1ac}4pC|hcrUtiAtIOK-PJe%5)l##XdJK=NVCq3;be7}&A&~twNKhGon>(L#p z_3wL7z3m_=V(MgCI7|<6P1@6G;YJ5@N^!w^QlW zF&aJG-9r+8b#v4m^#+ocM zwA1sh)hs{8^f_4h5t3{W|7%iQ)4K)X&N}42=h@9TYvWiB&}^m2bCudsmgl7jug^o9 zBgMBftWJMZoUJL$F5tAgIaN_%XTie69|6;2RKvV{)0NPyhmiCWD4Rd4Et1W4xW4$z z#Dk%AHIqDUMI7LXF=$^i@%4=@2}QcL%(JPY+c*16LOBlfaHNHDJx|$kGB{zP@f69Q zg*$R5(Q(dAbV)Wc{sYMi`PpfPcRxWL3$tgZ;2}d$%g$Y8JD89^bBCGkgWH?t7da)b zcr)A$!~6Q6&4lVHYDo-tcd*(P`u9di@T3g(f<2&p`!lmCu2qSA4B&MCiMHe=_yA-f zmXDu~7)+8Ia?|()rVeAmiKw_^eLn*mp=&1K?uTmj-KdVrqTAGq^+H|bx=DS{|3@{o#8n3b0=TNyZBrGgr1ev(R zuGumEcBF`Bo{UcO&^&>jn!t#T4=)qav|uo7y#s98_FgvYYQG`< zO~!#9=)QagZE1X$xK{inCH`|8MWY)p`?d4G?ypjl?_FI%nRCW9YdLxQ+vYyJ+%K=f zCI?rtGJeWA4b5J54xWfNu^|G-J@V5}(x;esVL|5e82UM;?E%lUOzB_tyZ3Jd&sN%9 ztzkxe=m~j4^F}kE-)8hGK3kK9_e{s7qJKk&Sg601nlsK%j?1JkK;ee9L zCUV-58Ja0%A5f4V`l?|e%>)1KvwwsYB@qX)$tG-+QQo>`hOR4=8I0Z?9&r+7VR+rq0D2=2%S9y=4eWSAZMZhiA{PB# zB=Bazod3lR*-?X0Hzo6`%W&f}x?nG^nBpXsv2Fi6^cdB1UGwN{8h(i*sT~_;%j8H9zlY&88}Qt2<$9t-LB!BmZJNkxPxe9>v1Ho>8LV49&cbw!ord} z=U01A)nrIurzG+JBYvIkdW;vlpi4_GhoY;vJKDV-k4XE!G+t|WN0F20d1K>tT^aE8S^>tC((n6nr@iY3LZy*} zUoW<=svcXS+u?D9!AeQU3B=Om0j5yvO3N@((cn}T)Ascu&(YsZTVFDI9Rtqu19jv-=GTb0 zn%Uh*Uu?U`*#k;e2+U#j5`vk{0#H(*)JTL1VSF&Ey&8RC{#EC?jta8J2+BF0>iGH1 z0c5?*^Z2I2Dj?w4h!f(U3$)uoxFkJZ$T)N-Ig{!oi9dQrAKQ%7T;sOD2JFTOUyfJ# zL1JZpPB*Aa-p&F875c5cY46eEzpA}02ZT;YmNXuZ~|bj34<-Va`b{u zyxnAOy&7^SSK!^=n;#E;3HuLSKWCE%q@?1LzuN90rC05^Z8S1 z$kY@2IZg@lZpCro53_NZ&2l^&w?&BFhpg_+maw*K085Ha4Dgp`-cP4Esg+eE8Y0u& z+zdU{WbR$809L3d$@%%!c?oZ8a=E3M3sQO`d6f)BAJfs%QCA0Gon2$ZvdUy zGskXlq&@lLJZc-JXPS-r>1EIK)r^XCPgVq9bg`0}n%ctbN#gUcdyGsK6(H&)?yj>_ z(AY|{*W=%vQ4MjJeb5Y%+G5&RPj!^VyfL<-S>%x+r;*GVP)c*}-xQ;Zl~~<<{<4!S zUKp}`OfKS1LA=*~!7+0!KZ6lD-E$MF*EeY>aAKr<$qQ5f|I*I$iPA@dpzdI|lyVpY1c;`#T1XN{ql1@S8Jsn_)x6Vi5r#+k(elD~V zh+adAcs{$7Sqk{cmxpd63SogccP!Zcd(lAfBGYQRgDAutVkYMJn9y{sYYNRJB(T_1 zvk%B^M1|M`4X{1^Lai}%K0T~*gqOi#NV3~F-G2^q_l)b~pmW?E`?|KWpY#1k}~M8K#`>zuLhwVsQhbldc-zH6^2a zR3i|@-bCBPBi7B|mv@ZeA9K(*L56e4eKM4=0P|pI&NMV!!Qt7``(C@TdZa1QGs7kS z@uxo7I{JK^0znj_^Q(0iy35_|~nM{m^yfNMmIZ33r?0w4o2*v=`rK!}4 z!d(s@mA5qE1~i=r7aQ$by*4b)Xuu(-$lQz|C|-=$Zpzn6B%Mf6pe{sUrdwIy@?z(# z;6uZFey4Rs)wjY_%N=OR>uMR^*TyXYIP~ihZ)MLLL>T{*g^fqDY#Z^97JndP)3PW!HoGB-dpI%C`Jxgvv-*?8Ff618{bV8&~1mUCVHbKNQV z(cHb+dHIY~V4%3**_?Tdmc@Xz!w?OUJ5w96%-5?fz#Dk)xs688Av%h&>)08_ndHR| zaTy1KBuPK#oCb=5?5EJud%z5Lz1cq$z}#X3SH~7B+ZZ&DYcu=R0j1tv;NX}7b;L1A zpdNd11|74qY<#*3a!9OWB%iR^qF2U|bo-xv&qah%PnTFR*;Xb%v}S2*hyZDJRIf^a ze|u9Z`WhwxFD8IMZ1>KACtWN5J2Y<$>#Kog8<2-6y{FHgFVg^16tmROBXL}R6>#AL zmqv_sXG?yMu1>7jrE^GJ%ZPvmb?pq#gCM5f1CPZ1uXN|kljN6O zluxzOds(oyGlxvJ4Zp6t2u!HJmHEwARb2s5`%+PzC@yvqcyHUb~&Z8fe^#2|BK!)9ptbCG$jh)+`eCfi*bAk}6hQm@) zSggu2B)iHuX;*+#>wxW)@V;$3eMVU8g77`6^h1sSoNMpFXb;k6X;jlLfXUWB+xxQQ zKm&EQS75DjO}3Rt5YBpq;up{qXm8AeBOmH*S(1b)wQT8y9Tu#VYY5&*5wkcB{)bu! zSZ>@B%1H?rOx5W)5>+7V zPu&OG3GlIV5~aI2_T2APRpT~B2Tl}}K>*Jdn%1nJ>0kS7b^Z#3Km0L#?z)SqvgC!* zVOz0YbzaIw2IQ`&n+s!3DnFa`l5-k2+aoegLLakuH6fupR(YqhO);pwgzG-xf~_6e z>8JAny#~B(_BAQzLTZs*RLCycmSU^A4o3rHjL4N#;6~sQ`}v!ysic}%^kK;Suh}}8 zR>b`SZ6*f4p>q05S$OmrEki+s1Kry2$WJUr0b0GXof=e5$QFC-%1BH5?O|K&*wx;ES>(;zJ6Qk}J3?o`;9(N4k z^3tcH(SzrhwZ~<{5Ap6;vIVKVG`kUSEja%Irbl;!X+20Pn2&!n7cMy&==_64fHKIb zFe8Km!eh&wM$P=UGl_nhOLHD@>>B-!7^8e|(^7hpyVf4-Fiyr{Le+g*Y455F4CNM+%*F z4zOE5`P^+PDVDT4*DcaN?JsapgH&_NSseuTG-_0JH)UsMGi=GyiJDNriWQ`DygVv2M%m=29!}SdJM? z8GX(7w9B3udFKjifcZ%kq#$$ao8#7v_7UDuw4lY9pQP#!jcMjwe;jtaq{BiRPMNp> ztlGvh4`djud@j$r8^}v`aziby{-B_sp@kz>g2Kat3(|XEG&yluWe(y?f0Q;UX72ZT zb5zfe(r)wY6$=t6q=e#0C})_3Ja;@e?@UKSbgL!4)48~Y2XGZjzNUnyZxzp`IKCF2 zw{lGtj}iZh@1S}owjU|#@>-!K0{*>cJ@sNdcI57nK*)EHtryu0P0eAjM8c?$jFeu+ zdz%}mMt=DSZDqD6uN;l72dMr|i;L|s0Y$|9aPamXT@_w3kA068Hp5D3g1&xQ`X6?N zX?nuPft>B|SpDC;MK^<^^@#3rt02%Xk_6C_t{u5a3b~IEYD^@~q;paweeVyonadp| z+d7HH6BDsA`4UrthO!)5Sh{YQJy6RGpywX)D$C$b@2`~YceT||PrOQ+-8I=@>~dYE zbG+pkb#QL$Q+6%-SdUu4WJVEjuI;(2*=%7Rw)vG}`k~O>=KEVVa6Z#>PP{~U%f~Vy z^P^pXtbZmv9xc zBZA-h{lv}Oct3(;gT)vK+|aw?AFVdr+gL_~Y(6pJ^-#1xSo>0Q&Ztrc$4^)b6&EC; zqx;*XIhk}vO^CAA#!e0PNqo?4^$PfzIV4*!ydf-N*YdN!JMau{OME#8Fea{>8WaAu=@y^7_CN7X4?sy{y|bj%p^cKKbCk1 zpS%B_5#U3`!Mt{gFmO5G6jlxVKSFRMAGxGAM~kl}dAh}va+pDeBJzf{E0|30QDCB) z{By}64`09m2q{zt;-%Q&(xcden_g^Ydb?k!1xA%g${qMiB)mD;);NazzUVhywu>YN z)d$1r>X(5%%|EuFFoi|RM>b`n6rs2OArwSE_lHB^i}YB;&A_z|k-hLuQy`dXGml-q zLha6`5?CN0O}VpJ^}IA!fzh0maU?cbNTa4(rVS*YVJzxL;AxpW9d{`7a>Uwz<^(DZ z?Y8;7GBXYbmiyzjDXojvGMjt9yz@5r7u_;g7?qvI>;FHcFjr=X8Q*AaD_+WX&y!&+$OGi^u?2;`;w#W@oBE`dI+X$ ztJ&imr)2lO8m~7@<^F&LKppQEUN{r00-R3&4Y!Ti%YP%oxPvEkpJgjRvI2&$uw&K6 zjoxRFYufBXw55SElUMT(L6epnHPscFf2 zwsP60FGMe=AA|1?kn`Ku?|VVN!U}ED|Lccy_mE#xox3YF3I~EG%OZ*6pQ*yZ{3kB3P3yoLylD(rA z=7*ipO`F-jF#h=Dqa(K<)Lh~6M9r|!QJ^AbVKmuu7?ZW~F|K$X^Y9D@5PePz$K7)J zVDpU16pi&C-F_evt1~c~Y-e=i;9g$oCbFhyL8NGi+E8mdh|4O20TZMc9u3ygCv8P8XhtNwNf zMIrN3m~Q-d=!Qnk>2=nrdNXn~nx_R}+y16z>e2MIC3DiBPu4{F2-g7SVa6kVn@EE< zCWf^YBMu3$>R$m23u8JGOBB~<21?_g*L4Ii{I$|dsariMXD1KE_X?fKV*hs;(KqRC z$YeD&r8|-3yB1ccQ=M{uBY$9L0EKj^+EY5umf&#yC09VW?cV9r^duQURX?F5C23XK zaP6jECp$YC?Kg@a+Tnqg5V4oc&%)fFaD0dGx-QGf)`^cYnx+~>@G(m zwafRVoaxxuSX;`UF@sNvvWvJ)Qdoj%1Q@gcT=h&A@aHDU%(!9I^+_~OcCzQY{eQ0? zT7Gu&(sC48+IcJp`TTf6cIty2AtOpk(ue6N&>T9_JR4&EPA1B-ebQzid_6UtzL`;d z^;uV0XREFjE~XQEu|@cu4kGI+n0-04Cv^=C`)iSkw&_wE>z8E9Io$3gQkf}~9dAgv z#dK>Eyl_`oIyL(76;Xg3&8uW%BcqZ)^hzeRyT3kQnSr?k2~S7sGxA6qm}e$>Np_>#CqDkh7HYqpgmn{t8wwc>t7;OGA>6kWEATYAtj^bS%D#^--|+4gF!Q zLFLp|&y>U-S>um(FO?rcdJS053)19#8YDdko>NZPOQH`O*M5MN-f)xJpJnOyo|yZ( zQO=g0qu9*5D_gr1&+?(keXGLsF&5*$+<*0ct@XD+3O|%NoR<{R9i^8f-pW6U9(%GS ze$rYoEV@l=qt)JCA7e>M|AWi4Xan>cF{VFsrVu{vk?i>;JO?}zKJL@mYRj75LX*qN z___|Cjt*we2y)JWW(DEqgM7P{c34F$=K2kqE;v2FqDz0`)oR~vVoz>gcErilO5P+K zln)Tny4kcG?6!YrOdE9w`?bu<{0#_Eiet>1Wzp?S!oMo}Rau_?W!EJ@22@c?tl;xI z?4%$@gJL^5hpps}=k{wa`2G7;6!7qdo7SBQZ??m>Rq__=P8-K#BH~pxDWU}_R!_nY z4YjUB+PmqD2r0v)6Ezy!deR}DjqxwzMiiPEY+P7*5-)^4tAVyu6- z$L6j-BR|cu=XNHg}pDrDhI|+BlbEx#yzjPo^Rfs>V!6hqZi(pc?-_S$~kdE>fv6~MTN~Lb#BcA zJKRDHz!6WV@FzmxJxnaIo;`vi3dR>Vqr>iRC*B*(MEoDB>Y5i%Z;$N@Dv`mpj8KTl z(qo?v2u6X^@tD&76leccznI9qzxvYY{i-O^jS;t1{TA#Xvo5>jg18LN3l}> zQ_vE`2Urb}>T*&#cs*)3*4drKS_iQ(VtjjFZ;YP~E3@^@?Roo^F?;@6p0!Hd z6W33orP%bLft!EDJv)wnf|^@?zMbKkP_%#R&Pv(sdYF4Jpm0wc;i z*^g`ZaP!@?Ng{~MFS2m~?Yw=7pT^6)?8w!(z4x}xc?;T@(=Dd9FWDg${`UWlo;GnX z=qO1%+}^~`##Fsem$D-E#SBq0zuc2BN?rRHdM&<}Sc%^rP6mV_i4tte%qHWW%>Pot zjw9nkgq$ZCi<8oO`8G03jZmVrmPa74E0eL&YiZnH;Hr2cPz7`wO8-8R987KW_*Z-~ zu4hu2qMMbwP`BX*!`L$Tjx!j4U(|F>^NKkX;N>)(-m}g))@dt!y=d&Osp@j$)gs7Y zPl078?E@$4t-Pl-e}6pz?~4Z~_~6|!7gy(jhGzKX8j~{P9N9tasFZ&;^z;Q>TzN{3 zg$CaDN7znHUDQ;~-(s#5|6nVzJ-OPiZAqo6YIPO^iWU+=FwJ~lLYn}93`E{okH#+r z)vV~VyG1{IIZq8FvEHy3LIm%DEw+|Vxp+lwWky}YV-rDY?NY;V;0rR+W3lg0tFv}4 z1?pE6(ZL^(0bwl?7ol2huOkYFIZYef%37oL>Lk^1(a^20PU{O!+(9|%Ek{i}nXmK!$Q4*49?7xp)6$dy5qz`8l)& zmF+U>blBDUt0J5dCP}e+HUE@hrKMz=ZLSoZLP@km1-NN+)r+19%q7`F4BO6y0)#iZ^rCRI0Nt~IOD^K)dN z?WNVirM|I4(U?)R#wzZcyH^3%uWuz1P%UgaQ)2}&$xj&7 z&dsC|2i^_)fS2Vr#Agzzl@(^o#;@_y0_^B$|3zaPUW888f-8yg$2 z!M!pdBOEZqyrjVoELn|K*B|%(KUTx`&!V0d{=rK z@ROYZP8M(5qD7UF%96z+(aalctC*>y8C^i&N{mDFphB`A)p@@lUs)2h?ei8&(IJ6_ zN!Q<7awp%qREnWC4{%yszMr;Zz9BuusDsJlwoE8>lg;-^%woD)Ea~vfRN&eiu%8TP zntf~TJn+M3xM~a|Q-B1Fx7cx))UI%nweJ#|1!%Ro{obBmE=8a)%lo7JeI~(Y#D*b{ zH#fh20!A)6KQie{>7a+ntq)SuD7N>WwJP9IYeA-MNFw`b!BeG6TeG^J=T97BCxSJS zp2n^DG!m1Aq*`>9Xj!%$+&P@Q$jUaec@9bt?={4FxQr*N=`Oq|b#CPVy~*5f{HWO! zaY}EVYiE<=@67(o*#1DN^N^*^Tcn>$a}_JHiYasyqtQKC+@eTZV7IF+lpGN7c-5}E z`f-1{~4oKL5KAh`l>pl_a76aYHf)Kjv?hmsy zx=qhwHr^O+e<(j?v9OeAAmg*8wa>oBA*vuYoSWcrEqy|ku&Th03-xqJ>F>BFL=%9! zk9zx$jm}&DlbnDy$NEFo#*MeBzdOL$z(OahaY?1Qn*t8PKVGn~4`pQk!qRu}TLj0G z9Ta@*!fEmQ3)4fxdElf`cdu53H(K`njw#T@7@%HsIca;}zLc>pm--LYf*;TJK{b z*F(jlH*T?zcR|g@>Jqgs@xWynbf_*mN%wrM(71UyNOx1M+AzPS11vgnHcNKWm$>#F z!JsX{kjFZv+43cPlWS0|$)VZtYPaw*OfW>T(iMBR4*g;5G1^`J)2EAjlpCc;hnbF^ z&Ben+w{y8 zOs*{O3h8&&Ocn)IOmFk#VE=(^Mu+E{68qHY@Od+gY2}L^VUb?l(C$xKH9oP64-yMq z$LxN{tQyVPV^_Akn=^wLOtZ6_U0hq6Qc;>ab|KOQF0E8!`&rGcTZqhQ#=L~BsQ(P0 zr>yR>`BgE8Q{v(oGPcKfV6+H8eyuJSKsNZ`A2sZpvxR5QCt95{lZ@^@DY%@c{kt+p-fu>6ek zmW{Lw`#)=);eGYp4f?j1z@cdt_-H`#R%)ap)$YT|TMhb{R{HHiY9(IN)O0m2n>^p4 z)O?4zlXIK7w>8e3$3Uw@)`zg_dY*oNQFcZx9OdmGT>XpoHN5T07?myeYGx{3HbvqW z=Mg|T6*j^9`Z{SeFXu4Bj6`kb8!JYaN-Zj>suf3rM8^86LbfE_!=YenLXI{)e0l}2 z#o%TTC&AiRD^~NGPEG9II73OviO>9U9@2N`2b*WgTDt0Pr`_oHtSfrn8!XK;)UjA4 z>;4S>-+y$X$t2LTUn%GY{45#ui#HLfZv5i=Kc(n5#>9n8`SjT?`b&`)d&9!{AGEF$ z6ThF@eASoSOSXDJ|6i<8i=mdu@Y#LH2)W_xlX~|{mTTYhStY=Y*Py|DoVq_5FJWc^ zM6|}2Uu`upW>^|tJi6>v`_~cDw(k{tF1eoQGnj#D zLm3gLh@>lwetV6a7;n6S1qn(o8lN9XAGB2>;KR!XoQM|M32{ZxYdeFPy9Mo&U&P5Q zoW3)epj_l5g#}a%IkY)uvyzGrnh_7DyU83YRZiD#R5fKMdbYDb+^rRS@(OokCrUR2 zg{AqiKrn^n=cC=5Lgc1qc-a<_-+n>v`|3RDs@dBH@4_vW-Y$j1S1HX<8;ph+6R(+q zqwNQ@&a(OSB08Sm-Qult5FwURzAJ%^;MGAs=Tal!F?lN*8ZIXiR9H z{MqDQIF_B_;_^8_86IvZKJ>2q?a9q-yDB|5fj|ZZQP=I7 z9u0e|?%fnt*=@QEY{O49d**2g=@-P``?&*^mae*hUl z=Dz9bKrRtlXP$d|=d9i)J#f(b?$7`8Ig>^nB@0y{H~%<-JV=7meoIej(Ea=^>$M)! z6H>?E#QrX?dVX;_4^lXR<40$Hei7f?giYo}+q`VqUp*(!FKr&K$w>%&qt>?;27HiG z-^+uE9^kbp!%XOTre*W7Srd+bWXYU)^UF#~WkL-c`vjs_a6$AVEHc@NZ7zJ4MT>li zBEmymzR{0w*1|b5Azv*g>U!u&HBA4)^3UFepw`PMEyBLq2(|AfCsm}aD6&&edgr%+h%=n z0Vg^@M(m?yyG{@m1J5h=&|SBQZ@IhqjDNg5$i%bW7JB6%D#u9jbZX0gk&aITwG+}N z%3`C|mdJ~ZI1%o)gKrO(Nv4RV3))6ULFGorF$vmZu_4rZwvAVuJTvnBAZ_z6Hm$o0 zdEo>D>GQ);ZW+OqeJE-^diR8?cX#uJUELxJanAqJIaABV@_7Q^9xQDXe6RjvTg#+V zA`q`}e#hVX9pelK%8_~@Z7v+g_6p&O(8v7b(v55GzVOli{YvHEt1shkqp-SW`$ZMo zWTFdSJ|L4_P)=^IVT2%p&4IqrQ-#Zgx{oA9>vL7STHWI!cuo>!-;xWFOB02=xo8g2 zw4Hi?r|p^A!LHKakEfq%JG(lDFi+l#3ixLK6;;t+RKUD&5#WLorpW?eIGyb3?*n$1 zmZ(U6c@5-)*OGfxzPX;0-l8s)@0EiK;xd&V>KMGVCh8x;b*G*4?L|{R%efRR3+k5M zYh|N6crNW1Nm+0b;LT_LW2CR_twB2knaIm;;Mm@!1d(qb z58HvD_GxtNC0p>G-;*V_+0T^4P5ep_>ze#31x~uXefE6bhwY&J5#=A}ih?|2hff<3 zU-shqw7w4H#LTqYVsP{x#4%tAMN@Dyu&Xt$s!PE6Z*PbBVP|PQN7LuSVrl{E?aVEnR47AwxasQ#p&4j(~kMXlXsu@ z&*$(fpvbDfPb<$};Ac($)!{laKEpmW^tEZxR4xquN?MDd9z_3`FX_N}b$ll6eu;uK zX1rvtJbC8LXyd#&J4ko7%Yf&UHWt@tDJwZ_$9HjmBYQ%^{vXc4euv6AkaRLuAQ^^& z;bcP^|F-=6{3j$fByDYNGQqf?3~(oy1{4s}!LNUWerrHMLBYc~{&mU(E*r`i29aet z$>{$qL<8y8$wK`4N9X|nl;K`ogaH@Uuu$&2$ruLRE{7lbp#ZL34bMNwY-AsiGs{i3 zG6W}?#^dqdqb%8J)(*4kRJO`i&rKbYh6}c71@tQP;1T384z_SSf*G^VI492~b{1)i z@y*e)BLX;nYjJV$d~}NV;EXqjIzX3g8521_uE%S!6Vk%Y8GL?qg^NI5kzZw)j*jfy zuCNs$p34X($rqL0oQeN0mP>B9-iXg9uzAnlpI&4@Z^6%=Mb z25I5bj=q;jBm#Y=Hx;xQT-+@WTH$BHUP-?SPA2%Ywzgi6^G3R%N1EhoQD_s{?nt`~ z$Nct1n?Xd*a55O~uHOh*FGgP>t+zV#KwspmJ2FxJ{gQT+OV+OI`i8$kPzIEs&sxt( zU*wn9@cM`dH^KJ&3VoN^*pBp;Gv05Z{hEZl$Xj1CQ1^Mfyp#cc4EI!v;3B`#j|*H_ z&k)oH3R3Us5bjGIlL0;WGO&$sBka-IFkb^oh4zpOa3FvGn~lu;_ zO#Fk3)z$p&#*p6M#IkYv@Hw0rVs-KTMxj-h&o;K^+h2^LG6nPOFjA*qXq6_?8 zAE4Kgw;sZ0ThO>*^3-45J@=nyM}6yI^`5t9RBW%}S84PsAd%{L3X$c~+X5+lyClkm zZ=06B7JSx0*8Qy4{~?IHtPgcbChEX1tZ!)^y=?Qdw_aYk{Ka(ontqnOwEh|>A zXqR8OKI)4Kn3ucfUO3Aw0@S={KKi08wT3x~27c$X46NoHR zr%H0&^mDG|;&ueReiEGAXE~QGcy4d9{j@R1JQZc~aq?vaZ~0$Vkd}b{|Lpw>oE=q} zE{vPqd+$!torFM=j)Z8^K@$I_I|@NbxTuhv0i7Y?oHL>%2%hr^UeNgsiUbAE_X)~H z2S*(*z&S_I1VztpNTUPHkbvA#2uL>`5;Wat0MkuELb^NY-n+Z=f1bD2yQws})LK=0 zSJlpb@>^M}YWJ#o*Lz!+S{L)2O#9ZCPxkHv$LX_<`#G{xuY`w4my>@AeTV;>!{9mZ zT=2acmM&k$N&nu@9%K=~5?TDgMfm(%?SqA(6_1iRe!0iDbp2hJ(}6BsbjIg+A_0>0 z4>~TV`dA6@J=#|!KRG#g&*GWQYp+{!_VU?Byt(hcKX7vM%GQNE6_3xc;jhedGifUv zY|PW_w7=Z=UKuAuth!n$|9c>?ym7w&pkTMuBqM{!1AU@7lc#LJrPhU%hi*1nZhpvE zA@$t6yD+_R;vc{IfiqWzIq~M(kFEb6PXIz0*e7t-3II#S_b&#{>EO<*HN9S~;P995-o{4FhpIin_=Kd=KBpKs%5+XX^Nd&4=3T=U|Xc zI)3|`@1jinW*o^kSq1206d8a1@uUy@!?L-poG-z$Gw4Z9<(2{c58oqgmX@X&4=z8h z)!Ofn*UxT!`BWsWzEkjzlMHdpxm@mc#MYBWhK7bd1R-)FuufQrMEM+08i+!e1-i~M z_X7hw_!)VDq^?qq3t|u`2a6;(Y(ZAI=8X{~SQqi+Mn*=OAxG;0M6;MF>`{ZH;Rr7o z9C#la?K|;qUY6Uj)$j%)SN8h)`m3_p>}tqT8qA2b(NHPimEHn9gd!&#%3Vtg4-eBv z>&Kh0^%0-JAdNHE9eWAb3Qzu6j!rLkvd|+QwUbzB;Pt|5HV)Z1Dcd3tJkz8qj!^yx z{E3b56hJzl&}A<0d6&5t7|8b*=!G_hYxPD-f%@WJ)Ei$$QU*Hc&nQ=fO8GSE4&TT3 z7D3OiTow+Z-NoH=rA?3LgtdFAGhiQ5XCO4Yo)`sEbnyoV7wN0XXVw{U*bLvvK{VOz z3+X~WpZ`Xg7J>K&xN80(7|O;c*!YFeLH$rl+0))=<9uf&}?~x{5 ze=@vx8qkAZya??F?rh{g);E;@1ub}#^o2=d5q$hUbk}L%9es(#T%>~Ml?EJanUqw9%EfMueMW&4YTqqt*~ciSFZ{J{ZyJd1Un-`*+>kWlKY)8`IwqO0r9P60q3 zQeI(YCi(^1B@PRI_~$O~EWeS5j!#uexA-1ud*J=WMKha!-7@X1kIOF>=;)CNFgiY^ zymkV3%epADwSSlODV!}$Gaf}bM5hr~l8*{$OBh)e8uIn8e*eQC`G9}I*ou~gH>_+u zk&{+9UPgJV{Y64+2;B^$)6A=F#N3+ap8S&Bb-;?26TbDS6HnuqB>jS%j)L29$dcfD z`F{GT!8u1xefqkk%gzaM5{NBOPLrxAN-qteH>dU%zDeIj)O9&}R7J*1q03NOJr}{d5>Gf8_eI z?6}rjE;`{9_615CGJp?}`o|hPTSt(3)&wpNs>i)?cRv5@=Pztq{q)*R9T(wVr14zWBtGuQd0q(6#fm`N+?4 z@vwYhe$n@?9owun4CE;?(;Fvtfe!Mo(rr!E4ZL3ZR`e5v=E+l@_AdfK+xYdXPCN7C z(;FvnZbwx1M}D#W$qC=Nn>Rdqjofv>^-H+W?Ko~4Bx5J!C2p6>#=ofq@NWM*$s^jw z0DRXUzyG0+eBe+Mk(!_>i%1Ev$! zh7%8NAi|AAxCjfEQWqM#6@9O;JoqlYSHD#bUcB(sD>!B&zgmKuHmu#)@hK!ZZp1mr z>QgHQyw{{`kr>sBO8!w7h+{P3>sQ^^cBgmC9$fsv6UXqZ(PIMQk75A%&A>?a61x?eu|u`OL^<9YJU7=dl*MEN$JP_?xAow{ie>jaSjuA|bwLxJxR z--A#*uXW)KF5|H6ufC$+D#+tN2>gdJK$${8yq3*otyiXVLIA?4R6Jb9rgV6}OfW+- zJ2W(Ons+xiQ2s4-b#)IT$xebcqAI1F`vr}IMA}@64FNoN%k_MLCtf686-zAcZ5a1+E?xu|A`6u{QTArdu zumu^oKYv37N{K1nydhi?>6v91MdyNEm zKW&yY$VyR!SI3PT_i@rHe-ItCjFtA&D*rG%X5QFjlweSntTT>)K3ks}1v_{Sk?@rq2kikmfnk zp&uL^be`aYvV0zO;0D$i;5Ye9{t_&6rTI+IUWM0)*;W{Y4qS`2gY(WN2S6NUBcgIf zm3C@ndmk6#LcVQCst(f5;?h5bP9CJ+;Z9P|fX)lNpX^}z^z1|Hjv}t@Mvx<75W7cP z@zbzdr^Ujs`)`8h{D3GQrx8jY6%qjQo^5o}#Z%Q8KPUMq3%#WCL!uOE2)eSBNT#i`vfquA#${-S72br5x z><~hr%8?Po4_oIikdF_#>r|!d3ECI~Z8W=fiLSB0BIrjJ&JR;B!LD-}~@?hJ5%(P~DZ}ViZ>DRf-J}|Rd~0f$Ag6!hGB&*i$WXpt`xATqanT1q zwPg0(+u={H1An=gOg^Gf_n85ROy(6;9s`tvdL-X&o_P7izjdAX$J20Dw7i!)q3Qy) zFy8>8g0{~<4$~OWd z=ZgE_(Ets6zxWUYnP}70IN?uUeg9b(xab{sKD=eaBiDdudNLtHM2Ln}@0&5}-!EQx z@)bym?IW<#|CLeC31}ng4OihR3esqR7-t>8ce5SHewwrcd1_7Un=f$%KqViNj^d^dx%+sRYJ1Dqg#UdxI5{eCM8-IkoayfHtv2bWR~ z1RFl%HPN{ZkDg`An;IH78~T-|IQcpnLJR1l2`7oJ$iXqfuYTaH^BeQ^JPDQc4)uz< z9+%G$7#g%L9N?IZi!Ubb+Vt#2JTVqY-Hc59Du1g4K)>h`Qm(obcfBp|H+)M9q;3UG zx%)P6T;b~H`TfOr!jraZli-i|ew{bM_E3Q*mfrB6mM?Q%1aiyMk6z6UzVIxmBfYz! z5E+lSh|!!-Rq9(Hj>8A>o#goteB!nxv+aFOhj&_=@;8vpL?Mi_0m6Wt$x7m(y1Kf% z&21|T4Gnz=kxmwg#gTx1g72>@6bfJP?godMB$8MSu&oFJdcWS%v0v}p`0NDCRr;Bo>8C;jMqQze9IfYJL)mSbSEYj zd4i5o#l?wma?=8w(1(cj70N#w-m0Bu2OUrUKzVlYLgDptMB~QMW_RerDar>_fZFV@(1MM-^oHIqw@EdAvO{hxc3rV%oUY zewXca^nX_%$(>cB8qgyk25;wl(6qEH z-Siu>+j&oa@9gzme{^bhEuY(Rv+kmWz_O^V#SO7{(u7$0>s+LAs9h5+q58mgMWv&m zpL4^~Wy>4$^{*nSyUq(Q_**3b-ZSERWqfME^kW|4BrM*v!RDRYTktGCEEI1HJ!882 z`>e*L8tdy{N5uMDd025?`k)^NDOcLtz!w&u@&BxBec#uZro7GXQTdZnnO&F{$%d{uM5oJnD_ye)_NsR(sDY+!R1h0U-}V$qY2{fA1m$IML|~i{vCMndkxfqV$b5>zgT{;1W)9J+5G!*2S)93*>1e<-c_C>7ZvqY4Jw_ zc}s{cNy7RBkc^HmMO~)Id1Mv`ox{@wj6A3xlt!wFIP5J2igge<>V-2U8CPT!3A&4S-wkYt-y^*<@&feP}B zG|^WUxKQhVeDJLET~2Cczxd8g?Vm=Hn|)~?-Brg(m*x$nr)>XSyzo?x%SxSq5Vsuo zZu-IDMKfnvuXpI$v5kiU==fJ41pdRAK!!-j<#G=}d8}6;4Gj&Q3xhopNf{h84xwV8 z!$8DPH|(88V%>SnWHOJS>>Mm5FQRg0VE|Ix_{e;Kxk@ff`NZKPrcrS~kBdK66$*t< zB5jRqHv2Qm5lQF9r}A%PWW+^}{(`(o(V@c*QRuyyxTPP~sYjG&T%aB?kcMI7r>s7( zqa4&@E@mQgG*|=gRePn6pH08-{bC1B+`^+e5#(bn;<$M{8sxV$5)fn&2x;aIDTm5E ztZ#DCpDKsi)g-yHKwVv(4CuiQ)Yikwr-ooC@f!Xt6wf(HnLZ6k9gK_HpRqhrXMmm} z+C|or;=mFfcTInc&^!*QKVcB*JB<#?pb|n7KOPWb^WeD*tfEi<1JHf1{9*x4$VZR~KM3rf*~$>? zDech7#ljq|O9(Qf>_{9+08fxiYB9))C-6I|HF;Kw@g zPiWV6x}FjP+9vJUv*$J>ry`Ul;uu>$82CH&LQW;%A`+K{LD1`)c_Iwn=kKE>Q6~-y zjM;0y01mQaq`I91*^!35AS{%v@#EX4PLM%we_N2D(x5%Eub#W}`QLxKyMLGUcoqln zFFN5A9l-b7T2kOC5%`S2`k}xN4zrJN+evFbh*U3)&W8ebHk;NkLHimEp5Dd-6p1Dy zgQnDt;pOvMZuWi(j;akBmu>qk^e}suLB`6 z{o+OikgLq4!1Htyj5s0fA<#37w$MYilPx*ncx&ITw zl8=Z7SIj@*TJ$+e<0>m!7T&;9Oz?XcUq_xYPiRBuDL*aKj(!kH(yMLoBtqnEY{wEK zk@Z>j2k;ERc2Uay@yQbwU&T|f@XXQUA>}K89h4Rs6G1$9>7vs==dvL3{ulpvZg*er zY$SCP`bt4$1)V@_`^_LR{+l4}8f*dag&P0!Luuebp1S|o{QL?oY>;0pV7^&=1xfMq z2uW%Ojt1!(fiB3r5zyYrlUlu>>~Pof&tB9!I5-8*(M=m&QOav%>kOX8hp4o1qbhxz zARUOL{f9csYSEe3{(D{9e>!%3P*l<(IROlj#s&&CoXuwM@$Lo(@=Y2U894>948#!L zFBO5lPX>02#o|Z3+rhz!J#}?;+>Kr)NZ?l7N~-)LJ=U}^4~t?h6A6#ip4<>PfrO!+ zyMemy6pP0_m&@hWAQ@&LL&ze12rdgi}N$AM1Or%35 zZ_!`LT?-5k54WI9fhw#+Kt7(C9g3|%+7Z^E9R9^0i3E9VLV+}+juO84%QinxPx?+Q2UGX#*>k7&Q*huN)Qc}8*7_jn zI#I*HLD@y2+hO7PEXQ`B;=I;{-_o7=5M;2K_bUmMgY7mZ>-q|VM?L=XpU;vXEOGtP zWy=xChZH7afLwM?7wsQh4o%{yL|z6N+5hR@_f7+&bRli-U8mW6yB9m1A#G~`)gLm~Ck0n#2o zoL$$s(fZpB!5yrpPnhIC2~2sEC>R@I-$8uTiUsexF2Wt4YDf3WC-W2#Jd168@-cRB zF-}v%L|F_8nrQo~Pc=jj%A9??Tz6mJJ65f`>n_(5bi$(Th4xS0{@hcmkgP}Gx8p?U zV-3j#{DVo^f%%~Gl9U_qqC|shc@)x@n1AB+cn;emI;I&%T~=xX-q-1N`iEC9FT4)V zk+DL=iEINJkS?|xigPAUeR^f{V;u*A&?Npg0*crtG7BM-ei|Za5;-zF^(AM=0YfO~PJbjm$q{V-O>gwZW)P%a0Buq0i!D^osT0Gk zO>cD=2m7m>0tem%;%^!TCQ1y+=kq_3JC-OE3KxSXdi)F-6H56kpzos7IC6M+*!n|f zE|+5`AaM;B@Y2ZugDPeW5Ltmf4kF4AUjqb-bIhON<^JP5|$nbssX@0$)J?m&k?#Xp+W!Hk-9Rx@u%(WIiJ0jSG*Mhc-+9 zo+}oMt`qvCqlio8FYP3$wnDn3VTN)S&5rI-Oe)dxI_s(<<#f_Ph(2 zPuhEkvuN8no&LcI%9%`Nv*B@~+UWa%@|<+?HzyOVe=pbm_4jcLh@0Z|!{rgV=v6UbHo_+E<*gcP?wFY?3 zMPEHgs&7p!zej;}Is2fbxqxz9i)-tNejHOlqziDO9!HX0XAl>l8`q+KupS5_&=2?| z!e@}oql`-D{&a=+82CH;{dts+i$|R;ZeBibzV%5I`+=61aoDmmKkdZ%Ib2+X$UnvzmZm5HlJ<_YHqCg%cJa;T z?b~F03h-{Y!8tSE58A`@b;^w>^zTsM2Zu_QUl;sjJ9lnd(9vT*A)h-~BXHBF(nm&w zB-zB9039ctLk+q7_DdIiFp>=dVn*PPp?)xrFJIYp+q_HKpZ9fc+Za3 z^A4%AW5eq9CofyOvHi11^82x#LK&-0){u1Ay9fA|B>5DrFF}@dmAemCw4V6Qrbe%S z2m6U@au(}ltxKF20bjpFb{h6iG)wB35P6IuOIaI{o81&p*h9K?rGTCz4M3Gy1l{uby_+M=n18lrP{~PDDpc`*P;DXO`i3u@dzN zBH18@!MmFV*qaXcEsN^nI*OMr3C1?zU5TvMXh1D?RFwh4da(k8rXlXnUTk_Y=1dniQ0vlRJ8veJT<#^KP=(BC4qnt+M&Zpmh|51Fzj z<%62gfU>wcW>w4s1;QUQ_&XGS$Kk)H!T&e@F#4YVZ|RU2ICv}*KoE21(6Nx)>AQFD zZYvZDpTvD9=*bO8ieV#=hhbznW@Kc99W5)MOmYH<2{oh$@OzI!hq{oIkL!?Sl4K}4 z1)7FIXS;VRIH1csnLusPrCi!L$uq)#SWFreQ13TO|ta1hTiDM1{* z*y)|1TM3UtW@2od{hHUmoh z8@yH~39(;(rrfoFkLPK!`+Y#V4ei@qP6C4LW`cKIfD`qY3i(L=AZ@I-3ZUfH*^&Bp(o^ zZo5KgKzYW9mvh9$C!TU8BB>+rthjuZlm#?!kh*T|^X(VOT?_C-;`s~S$AxQY^S`8% z{4bs}@7+ISIlSBMz_Uy$H25HAeP>yg%$|EYk~(24NxqNelD-t;;YBlNS$$a9T`X|i zo_x^|275t?m9F-99~)Ueh52xnY8csBLwu$^M9zTp4cGqPb}n|c#>$oxImt{WJ0PT? zEdX{2o7smbwlvLn$mKMK1VQZHOWJ|@W^}G1wx+t))89MG<>Uh2g2=Q?t{Dya#*4T( zZTy7JWpnKpNOF-@@8IB3&|&S9gppk+xpM+DB59w1o}_)RsRmF!v>EBQA>_OJdf#!y z`n&Gz+1+P733GpFX?pA9@0&5}K67b}o1cF4>iar3o{yyTJrNGey1Ryh!lw^Ts=+aE ziRCI@sE-3l?(R5%BDJKj%Ug_ zVcno>c{na_UyUSoKWBdlywW_cpZ|Z0RY6#as+MU z9fQkm?tHK6ev)*lhC{&T3zD=LOhHs>kdX6B(yI8EdQ zy__h0JLu3sFDZ}wm~4>7Qp1cV#IP*I{BFmk-d0lU8R4pqHEozPGNfuEX341Z3f)DDp;w74Tk(5I6~(ol@)eRDFH@m3U6+ zBR_7X#ZGjx1qZddxIo#v?FPfk*nsy_h0K)|;v`&XcBH!mwhO6)cn;-B9ZU;YaMCLa zkxcv63b5OAk=J@-2mC$zhjqx$O&P2g3xigk{*gwWE695WY44)#`;f~5oI;`SMda6h zH{0J)L5za5kRBYIo3yLAbW`jNyv=s**tVeS_3b># z%?g}ciTqLy1p46A*cs@SI3F$@8sgU)J!rR4WoC|Iey6@WZ}87k?bPYF*n}mz`Ptpw4lZ-|wCIU(dMi^kx6~tEZj$iI(Z^ zi{NhA@aWZ>cWgTzbn?40X{DVfs$WqZrDQdCIckojq*5R#ex%Mq9~H= zPw2$mIKYYBW}|}Nf6Cko4AuiHT2J6up_~YdTdAp+L9=eqQ*4=b^h2Re0fGF0l5T`V zc>QV-{ZidZnoL+e`6dR8Olz2E)mKD4I1^L45iP3&XOC8gmK8^{)J=Pyfo9D?KD_m+HHRw?Y5h?iX9-*@5Qcmp!1@fgpawY&|eoEEX?9K7XV1kjx1Ic{bif{$@u;MrI+si1%!OvOa={ zN}9yVQdePF0eLt$%f%L4;311G5d5(O{S7hiYbgC4o+J4Y_^dbmjmhu-UGwe}^!X-k z8X6ipW6z#FzwYnv|D($;prbK<{P<;=OlBLB(hx-mBS*=LvO85CD4#@fQ9~?%46KcY zle3^hFGB7~?!=HToy=>y0ECqm{ep{6W@A7G>JU$c{aP;osjsgey;EP>Y=ktkek|}L z6YrKYSoXNe?_4K4>ds*USN=e!qFPW?g9E^SiCyFeKg^wOWuO~z5`CKZ8q{s{UDkSr zb~TT?HdofZgY^dc0-Ol`VeeLOWV6{{;N4w#hJNHvWt{=qi}`&1hoIkSa`h-wy6zB} z(>8p1kOl~`VQJ<`6tHogTBZZsPW^(o0GdfJZHN7O>qQ7C_oU(B;Z`Ks&kj14f!B@U zf3$m-Q-AK@qA4WF@6_?!2ihrVr~jf4ux>XZ-+Tmq>|@(I%z)nhpuU7WEnlR zZClVb6Mg!hAjvTR|0y8;zxK``8!(*FJn!d-O1{taIc0?E{76&7gulog3vBJ%^>&ja zpIvw@@2C8yHBPjC|C)~9a#11|FzQF-sO#J0!$G5bdT5hd#v&2w9^2CSx841_tUvg! z@J|1b#h}JVIZz)9L`M;`-8IBTEUY`^7f0ZECRK6Z)exh(a6l#Grv8LFSGV^0XRUV; z=7d@JH0_g6c4-B9%{qOk(Q%^9`p%6XMSh)V@1l62m6ZzyK!ey1^p9~3(S!1pK3jKx z@0?ZZ+S`6!c0c|BN%+|J2Qf0z%@sZh+ zkF=Ugv9+&PP5?ntgA0VY49!Q`O+e&k1#36SVk!1jrJrhSsM?@5N=O0HeoFV0(?@god|Cw)4?o{~H}w`_nd%Ps>=AvT`KdB&~7rLOFd} zH%sJm(jpH5c?-}+hg?np;pt&K-4oBDzo?i$^lZppZq&hqfQ)x0#%B|*;JY>O#B%G? zfVuc-!SopqA}QZ25K^W4K9Fu*3|0KpiKl)=?plC$u(fa3Y$W~PU~S?2eS!5tYtxK} zPzGsV;a1Y>TqzgmlX2MQBc@oj|9ZRyAoMSR_?o7M8F?a;MO=2QMczq6Lqn$`f0)6+ zz`*PHwXM(gZaDCHQvp4UVy;(!ok78&kqa?{HVXb|3QR5A^oqQ&3ebL{ehEepl@>2 zB=r_?7#WtFC3M1x_t2*;pu=3%5C_12YaQUkIOOAp5v{P2<_id!1dA{NJ+u12Ju))F zT`RQF>7tQXDChtA*P{bUFB=?jIzjk2$RM> zL^Z=%Cyh=g2~K*|n9IM;-Pe$0T_OE+@0W^0KjzTx>YjMXeo_rTnuGpQyDP@BxPoIO z;wM;+r+VOE>pp$9;S>Ls zr+?Hv{!*9qnBtOIbMMdz`FMXSeH4PU3lWOkF^@N00UnrCEMP63bpFfuH7AJJo`^-? zd(wr{dc9j1oZij}YnN z)HKT?sSvq>z0z;H`RPZ#5oYm6%haPEyLH*h6D~U8lvNG6`fkX60JjHCJkZoQ;ZN5u zUAEliL>fXCf!u%Bojj!kG;9Bp^AMc$tA=WYn>s*GlhWR10Yd3C7n^c|KFTEhJMV5R zU^&Fk{As(}uFq%8o-5HwuV!nQASV%vpqB$z-|ySR`iZ>>*iPiF3CwQ zl!fo~=`@aq5T$-8LeXV&g8$gOi5niV?N2_IQ(!75@;U8(g?x7|18vD9!+_6_@5UXk zzG6K$m?w^b4t{${<>IH&pdGL*+#JUESu3pTe1YGLT~7!j@5DzkmOBMssT`wv+I@uvK>Msz5BILqa7P9M#R#p1`nm~x*2BF`ld^F2A21qCQ0 zkE!9HZW23>&W1BIH1uBv1_u6V&z?R1JUBS`okN80?&qFA@b^Rg%{~F|-X~9?Q1}{X zI(KAbWWFIW66EvwZy|gG@{^-X5h7`X;(4jAJPuQcpK+C4CX?Zy1uNa8cVt*+v2wNE z;gA&;gSoT;JK^GMVaF$Xw}PX-zW!T?bn!=8I*Ow~nn}Af%)(6OMSecZMGi>-X-}j4 z5)Ig;Nu^dV(hB-Sle9x!T^%Pm$sjJ`B-@1Ato06i;LRk@_1gn^+4sqy&N$7x6C8Nw z7V!6BB&E(UJ~MS7-GI~^s53Ze!3n1PDu}f4A`YL%cdFhy0X+YM>L7)PIBk4ayM8$LTZ=WS0Q?rNNG*Oh$HDfKx0M zKZLyEOA*RpOF|z4+vq&ujwfwc?TVs6U;i@r`(;S#POO}S!;nM&e-3{CBGOhM&!887 zBL8qnI!SM;^`c53x(Kj4TgBLWocc*0T(`CF^;x!) z_Ba7=$?Um5Lw=c*f?KJyGox3sVZ)N zNPn>~2~ikcLsTO8k*3B8e|C}in)csawt2_4_aMpoi<4{gTSl6`<0Ks_W0{oS(QW_1 zciB8y)FmH$6K)d+gxU&{-sC@r54}E+r&0MSThQ#nzPp^X(}&AMGMB|h{GiR#GLY2C z1!)J1L5!eWuPvWvKhdVW`xSl%@b?L=fxMKuH9tAAZ_eZ??MO;{OK#Q>C1{J3tMuDe zxBu>n$G5y_y^#aKI{2dFPrmBd=7$4YC;fNOwenMO#+19@$aUkJ?cfCbZMJcJ~DQR=@L18N6eo3Q0X>u<)!4%ok>aKZ@4nwRWfKnA2; zz6W@B69@H?TmWJg`Zk9zTk%~pr2NQ7Sv0|$S2np5MPA+^tGJSXZ^7@lgV%w1`hI*_|zH)ctFP>jZD`C zk_7TgBo{Hj)wh{^PWDyhpnZhmi-TR(5FTm=V82Y%HJ8DF_A%=W>F6LNtqrLlZ77=f zE(WUs6=XZcmM;e6DU~`iGBRTQamP%QCU4WQDDz+-?F=l-4wpU^;V6UH;i-V9gG^Kq z@;<7)TsZ*!3z=kQLYj4ubj##X@Pj9q@dI68AkQS&a0`;s21Ga*Pigl2or{I7CtHCw z?u6{;cUpO_pwHEbyTfP;w-q$B#cBvD2h!Zu)?J0(WBN zhshjb?x>#MvuDqp-d_X~<|3KQbzO5mCgpvbfsr3Vq6q!2N*P>%)>7cjT zdMtpQ;q{*XZSQ{N6z^_uESfo+b%V6K5K>#`85vL(!wtFoYfEMycZc_jHCX;Ac_8g@ zlMamLqvS*lDHB4HWdarS7IsZ*Xne`s3xw{zU2iib!~S( zwx#PFJZ3CzxA4v0RXoN{3c2Yu#?7&4u&ZEJD);5+BYDbM1)I{756 zAnmkg*;Hm}^E~HMK#UCH;Dx-?MRy=Nf2MNM4)Vk?)WNQ;C$cY}+jw@&u)`@9<7nEu&JGc9S+EJxwJ)9ssg)ShrcNNGdfl0X$FG@5Rnyk?>ZN?*+Or~Ui zCd2{iyabne8SSN?!M=2?kRgo_-lH7!fm{guaEQ$cB88`4_I=galc#i`EF5d*Wn8)y z1f*^6H1`RB3KyTvcS{@0Rpwngx4-N1b!}_6_VvC4m-XPc)+_#YS(@t;r9ac&V?UPV zYq#q+#2O+3_0^oRs$-r>w)6eLnqFUqve*=PxED=ta~T*M5AmObl|osRVCoF3{FO}BK=ZBZW5CDg*F5^$c-A_02hCZ9Xs~ZNDhKW(!fWEqJdvxHhEveVFOmKI}y|mBv$zF2lUM& znM}rdH+MkwJFrus9U$P9W4QRE1!=2~wVd#Z(1j#?4yC)hySuw{!J>Q5=lzcTkKY_H$33q5I?tgxpTLYV3`6be6}H0FeSsk`7+eC) z;fiXdM4V;~H2oeIOkmja{`WU&2ewe<>ZhC>YU>u>+qhm4PfeNB&bI}pru4aN9vh2y z_Y>HLyCn&nSTIoSXZ&p%FE!BPBGDjgGF2NqJbVLnX2?iY*Z^TfpFLImdOmH|gh7NR zp)Zwcnqof5tb`ky@!h2?jC(#3rSXE&;;+XIcBR(UzA=x|O4D1RHCaWFL*mGWDncUa zFh}dXt#kUaUeJKbmW1ZgaG+=cw^CZnZ|Hlo0Mq!WF_T1+JkpwlqOs3e#$%L-uE|g- zCy%4|4-9sgEO1TxTR(lh{wuqmoS%3jXYyk;xEe?K%uF_GO8gOf{3;*Dhybzmb4R7* zg)Gh=uc1bN7_0s*XKG_ZGlruB3zi}z=X|nTA!j9^rDbbKoaL-A2ehVKJg1Ppu1Y`5t`n0k>O6-}kto&$VPw^#pou{qLXdJ;sEKI59$M#^yH?9eg z@lG!pm+0V22{4?MXC(!`g|&CR{A0c=dnGA3YU%mqe-b1@ugblgd% z)#~NE-jS+1l;9RU#dDe-;x3o@B`-16g`K+@h_yJNMSt0m{h`J+1w#cH$vxG|R^ZQ0 z?xPX!d38{5y{B}m9?u%R#!ni8AeC!}FujTZL&`@RcXhSfkW0`f`;h;b-8%WYw8hhb zWuj$pla;s5igyMX7>j%oHEq>038d{9P;j~$_4}sE=R}6_kTV@0W3PdwA@1@wNWaP; zzusa$y(P}zDa5ieLc-q`y4C;qxBxr|+!xM0FPauF6ejtmyKCR+l@v?9k#MwHRaV!* z$2SGAED_(ww}~M*!ePO#WHDUqb8 zf@f0Gs*cI}uNpD4)^et3PSa@d-z=ZP%`JaVYswXt4f0Xee?oi>ahkT6U*i&3 zy+`QYx#L`&UfWo)fhH%k_mC^L2Cx5C&D~T@KEgArZs}l&sBxIRYq0UL)rqieTjh8m zencXf_?#{Sy98&JqZ>jrRtt_Bp|bOIx-&nzn))88q{Jke$Jyb92b1wqcgbk;MD%tg zxbY&>endz9_|AFHPlU)gPlP|p|AUy~7x$)ne3Z?cd(V~NPTj+W zSw4Sf$wJl-@MZ5DA_LvdX6bDpK3crCsydba1!>7mi?1^R{x^Z{yj4^gCjElx?NVvH z0zYNQ_9_3O6}zAa0tecNT}}jU{%#Z)o zO+$ytR3JMaJ8@7^T2ayQUN-Z&3z4cBT{%%c{$MIxOuAG_3x+=dCwKOaG@z>jUi~>V zo&Tfy4Z|0`PoPppjR{o*9y6u~of`C7f=z2Vyf5-Hf`ft|(TDfBl!+RA9X!D=)B#P! zM)Q?hUD4N;&x!JMjEL1DgKLmL*wV;UT;q1t;5$MUVcnKzIcTZ3#uF)Yqk9 zm;=4ITmi@XjL>oHopQUMFGqh>Ss~t2kRk$aupIs86yIY5WviouuhfJFF6njku zPf}6#={iDt3d9{2`bTvsyO}2hPoYhx%Sfcvx=kOmc|TO1%}{fauEjz-$JyL67qjDX zxb2JKxapEnwe~VYINUBn8n4aS+T3b=(F={UAY0@84gg&zE7_-CfOLLNZ$ zFOv}I4AI0^w7->JdB%s`)dceII#~mmlKIV|4lwfuWnp_4Z0$AdJx&ap@=JXlNjFY*B(^mq!;JO9w<6b7q>M5|v3lUnB8Qp!uH>k`Q{T3X*;Y}3B-tNC{tyGH{&&2oL#Q;qyM%&Umo{pmn+ zt4XdS{HLoe07TI<59Pc0WoY0!_gF8;YT$lQ0l88{APMCd{)Z@YQbbjXyirTsd$17? z_qU|HgN-j0l;P13XvDsJHYoRMRcK^DeB0?z zkS+h(%ywo-V#@iU+ol_b_)N!OyFvOaC3&tQRRG~yV6MaLFEiD%i{Mf8R@FCszlYyh zWGNU<7Iyy%UgShZlA`m)M!KB%>rk71BYAx#tDNFaLNywjh>OErmek&@Y+yH- zz&j}wZNb)-I-4y(r18LyCO~s9aUqSRpr8;zjPt^wuAvDzbqH-NS7z`6KJV|FbfW{C zn@f~b1V3?Xm0+~Wk$1u`Vb%g6oPonfX;j+eaSl&9UwrbQEf1cJ6AmpEw^6Zlo)++_ zkxS7sX7t{S1H=7WGA3$?B3~u?P3S4UA6GTVF1Cw+37ag#SE^E9_)Bju`c2ZjNX;)( z4MIOKy&3jy5+W5h#Q@wE|K35jFRlCQK{-rPAYRmkBRzo-*fYFSUGf0`6IFhwZh zl-I>nCg187JITN-GT$v~D&RLXxost58kjqnL3a!w?kfhDoV7yu!@C3Jhh41RMGsUn-vf z8=#OSS>3K7ZeH-q;arx5PR$l@ckIw#sJq_0+mJ-fa+zH1e|Fmi#+K9TjsnGAZJ~cBU^e?sZg;opliacovRRHht zmO0J!G3DJ(oCp}KP{Pl<=gSs-hR&Dysv4~sWO>#TgUc3JqSiz%xp*X+CB5+Cku?zQZLSQg8 zn#~harib{Nw~Qx{`W8UP)w=FA3V4hbqHQ7KJ}CO)6 z#~FON`2N9SOQx~5d}(7MrY$-QUx5fYJZzx+5m_(Sk1b<@oP@+u(uP|mn8Q}g^EJv$ zViJw>({e7HStmzvJb699n(@Yv@t(uSF1<9)?ga6F%zqyNMyb?A0(4OG8SICpiWDl^VFCG#(%GZ7nQfDPLa2Vlyw&kf<)R}juSd_kcb(cNncfe5 zYM^NC6Lg)HtXuM*CsD-1^{$qwEsuh;jFyPDN>GH1n{rf-Fr3ntH#fhJbwdyQm_;D} zqy)M+!}j3%UFXia@@u~3v5ST8LE+}Fw#Rws1?BS=4O>Z<(C!u=^3uqN(+b#ANqCDc zlg4Fl?s8{TpJ)VAvdxuQ@xt$#dRm17fLj#pyG zLXR8m-Bd(LI$fv#fNExtRb0`qS$^G+tyA4pIK%zcb7Whg0Noel^dp1{g-@zlS`*Mb zs-DTe5nvBld^~IX|6-W-OGm{@t(q;lyRw%X0MUGvI2b|O zj<%4IHI{9lvSicpR1LruSBH|bx~%?{dW2)zYc~-~N6(DFCO%1O`r5-cyFC2N^?dbv z!=lCXS6_?K1&Cj}nodUHVR4qt@d}{NnX$k5 zZa}Y1G#c(&p|FV)9eg#q?Gb`YEQhRwa1c|$CmH@EF)^Pxmlh%7Z_Mf3B9q#oKpT+$ zZ^ajnv(AhPt+|nF#G05`#0U2i`iN-o!xxB~hXX7~DQmk3aGCkO-tAWiyWE`tUF=o|>x~mPqV(l_NO|P(85}vgGk(YUY7TIx_G6VT3(VK;acnR}LD>C~2oqWcp*MK$p z@!qU1IC5|2;QdE3eAT>6xm1)D$CdN7HqNQn{riUKx+Jyl(S|G1#!bJ7rm;9JH5&Id z9wLv&p+fE~H}MrNY?2v$to+ z`}XiVhAl-btE(fSL-N&x7#R_;4SQsG85Yx53jLEeEsTh9h&T8gw!Jp@VKNLx&tW<= zE-o&0u)vM5!0vr?_GoKRU{>y^0>=?U(hO2``EhxBsBaC`5=50mBQ>5W69r6*8~Z_-V}}vG!Qe!6kI?k%a1Wfl**?m-a_k}`)4f?c z8mT?Gph&%8jw+wlO#G2i#}s%PlB?wMjN60~a|MAfk0&zp;)J;3%X(u81$@k{+yc14 z6PQntm5>UAP8LgezC&e8-Cz_Q_35q*wa|aOijOlI8@}@cV=ppYeN?~^y12m7^hAX1 zm1}zgvG>}3r34n(3;aUl2$1CK-#hZR}p)k(0A^d^+vh^hGRh-W~G zkLXZx!WY~`=S6vXw8+=(&CkDcH$oBT5cfc@>x$l^mew@ioJ&G3vT=w#0wiy%I$U0D zm3%Aa8YnVHtE+F?7HEecwWkA|jebr^x1`3mu;{v~PA~C*^Jj2QkQJ`!vFoSZ$!gV^ z2b~tSblb-dU$?jB+x67(QujUZeroRjQA{Cnc~4LB{+bu%)5d4NYnc6ExM@A5pg&3d zXirFIJdB%)gZ*IDystspG-su&4ueNibp`4wmRHVmwybt^gQPd)9`Z3hq)9IH6<_52(vA+0`};jG*^;vn zw(QuG`>1&~Fid*%W4gL%+BV8z{cz!XFI?f=7Y|5Pg>Tduo!IxUwRyT%38)?~#3zVU zPEN)m9eA=jHL}3IZ`gdXSpmg?8vpv+i5(+3w}25y2!IhLf%o@f-&dnoPLue7`4u5ed?NJv}}1($dvTVfRL1X7aVk)3L-@ zAP4D}{|pZ^lam+jOE&6CFPqu9EXyBxpQE&`GY)z|c@WFmCoBI1J&E)1TV#<~MU4T& zox5xD)uFW?(&mb}pH*Em@OCIQDHQkX&eL%&aJ|AxFm|N0k61wYvaoBDLxup9n9ej3 zWB=hNrz7TG=xJ}~CS2BNJX%hS2)fAXANBe0=+>>PQb$PfNq+Vj~8F4)D$m$I4dd zd9Cw-o|_{p81{^?1aOx7;c1fB0}E82UmaEACJs7KS5_jOpx&n^8f7%9u+P5^^Akn% z%73d+!YMxTh@{*#JeV+}+l0P^$-Dbz&A|!zOlBw>bifZ;)$k`5?7A03+1F}c6SJ>+ zMx<8~jX_@1q41_(RzA9=A(2aN@NnDf(~JHdt`UY19g zV<;rSx)J9ya9PceNSBUDLzfZ<9K8)nTZ+phba@4W^m~@ZBuYUOD1_Y>vX`*934Fo0GA=o(#vLkmvCX zQPyU=pn%eD<719*hb=%An~3Gjwl$LPxocl?qPUW;vFeP;8D@lp(GPMgxCk0$cNnpy z$oft06h)AG;iYT`y`#R7ny_neGcX$A- zNNwYO8C|*smm5Aohvh`I7VLI+A1J-rY6vLt^L8X50u|k~4!3Uuk|`BBEw>U4Zw@Pa zC(AzG1_%A3E@d`_(yV^$?1DN;oXQN%QpB0UxQl33Pi zvTl4A)lO|tfwx)mb>VM@#Ks`v{=Pny%iV}E98^VuA7*-$@~o-wdL+tcnXb$T`YWqJ zB*(QnU9*@>{;I>O7WPwW;uH`sH$jfHc$}PeKCFvs_bf_cpycs}GFhm?V2H?^&*y)dYVl^3v*6R+-8oQJvJKC1X3N6DB)_%>f!@3 zP6-Z7UzOF7v;N6m$5yZTi#ZmFjAaUl-H)<=T75)G-`E8%m?b~pA9i=J4A1E@JH)y; zWZOonbJErEa1|)_B>#GLKW@;Sq% zwfr#G0mD-agA^!t6Pa@NHr?C(!TsCxu&5^Ut)anm;fN#}pWCP;RUbha$Y-Z`X1vue zB4c-fLjtr^s=ZKui)U+{*#zMG_S;8P_p+eHL-AN|`lZIlY#fiBV11o6wB&ZB%n1&+ zyAHd$9m=U$s)V1TYPn~fh)j#?M>}TX4u+Gf@*HXxLsL?|HlpnpOD2!xifO6)Lpgt* z{GPJeHSF6w6$~65;sZNAJ-`sni`%OdK~Rw%h9p&hw!%uiA36zum3Izl0nJz^6G^RS z*d{46&iCk>`I`Kw2oO#0DO8Zb-iv9>`Kul$plqwZt!S(0fS=gimYZ*3Y6)SBm6Z?U z%n&rF$r$X#!(3G4XL^>GkNwdpr`-d7O+_FXdWP!^Cp2iU6o8<`A$DF9XVUe3Rvyiy z^X97*4T?wb{ESHcw7N3O)@A24IZ^<%Jp{Z_*@j%5UG6xZ-%a9k>Ytn+YJ`zuoU3&3{sn8IA%B1LwCE7v zEQt=}+l(de)C=oV_UkoaV{Y+o>V^`p?D+2GbE4!64=uLT23kL$CJ@EFlr-TG#|?G` zSs~gQ_S)bZ2EQ7|*;MAPelm&J_uX`G)F0u1PKSf*SPlfwR?IE6@iM?Y!(@B?tXI?7_{|$8+n`a+`&8XnVq`Q z$i(2FxOSutV?q)m7H<0Y&dE3M0PC6e)(C!w^(<77^vcs$kljfy98c=*PSHBuu#Syv zpHPrev_)1c=Up>%f$uc|lI;L>zGA+7q;4@mu5)cs5hkYk4{6}qRWoCye1C6Q0jI_i z!zW68k!H5$o$-J5bw+I>r`+Q7cj0_c5usBJP(Kz+*2?1x8=gsv``!5SW6zt94uH6E z=^mr%8V#(ez3pAMjgzk&jI8-tWVl_^h827F#PLx>VRb#_-Ynv=uo$%mpA@vZC%d0# z8~E7!vz7EZO^}a579NTbAI{)p zWCy$e@QYdqsPtB|kZUeIuG{OnJP&E8S@pn4E{vZ+w)+vY+Mc43MW*T>?@CsDeCJ!+h|jN7Cqc7wT8?3RL%$vBP1bucIx%4K@OG3tI>{ zgRy(EX&3>bC--gYuGBzn;^uB>qWTdA5bCfEpKd4WsW$Hnn+!=)+-kC1TMy333dVME z`WFY|+j;SruVEcBo^JJnUI8@3#<}(Kt_TO^rvIWJO$c`U{+~oHQ&b+BKcZ=%OJjNY zxe@-NHZ0)P_-I&3(O^~KXYcnw(g^2Cs6-Uk0t>s`@E;vPJCBGctF8>rDm}_i$Ki{M zHj+aOyn*3>^=t`lO;TVSd+!w(D z^R}+pM?L)=FgDQKOa3&z79SCEKpObud?Me5jx(2>+9v}$U>RkQ+WN{ z#A?IC1A4=4y9g0D3emfo7@-=tNgk7p9BFimtVk8yK>8iNM#>_?x&M(ym`~JKQ_NH) z)n@1y^VEX74vBMZN1=jX9Hq&4Etw7plvOBK>ce!0o3=akb*+369jOCCdiyqpHp(-M zi?EwNm_lYB?0cdl7jQ6lR|IyyE=;{>lt7MqWe2$4$ZSe=y?M`mTD|D~_or_?A!7H+ zygqTeIi#xrc9~F$OVk^~wQdyeaF{m7q5O{j+O^8QjUiUt4a>zFZ#7Nsm$#M{*+X}4)TT?vOPRMNQjzl7hmO5 z``^C%7)fhHP4d2N*oT^oCD7OD-Qa(+?KNNi69{9wk$*sef7#LEg=8j%(?fH;!5i{p zakheDC4#q5TeS_k*o9ZPEeMI1o6BiE`IDh5W3FKCu2SNo!ADWsGsP7K6W5O}${TmN z+9`stotHSfQ)BN+FjeSL!Z(L1)L$#y9X3%eByXzUKX26luGhe!Wc;S8_xXLn6#GV& zx==LiHaZHnemf>V0kLN8-z#%S%tSkJmY7lM&ZR9+kJ0S1jO&kv0}H&b%W9xcsK-&g zPG+!~FmN5cqoKRjx*4SmqWzPw&W7d;X=B>HY+~z@|KStVx0K`ua2$U;Pk!~~-mrWC zY}iyKI2f}_HDV~iulfD`{X1l&r0Y?Yok1$39Y<~1V!-3w8jqL| zLD= zkXBO~>gL_$MfRi&=3Tvh3fmkP`qYB)?DsgOM3QjBBYa*G?;%3EqQaC#B_$^pEP?Q@ zNHI|<OD%nbD~V z^gYs}^%&XmE$n^n9kI?8=ER;|%YL!?9XZUf4rWqFWQM4s5bkXk;OBnZT606ryi@{4 z_K1nzzjlwx3BJ%A4&%|PpVkI&;M{h)=*KNlhupq{mhAxO*TW4x22JKx+rk9$`7aM8 zn6uncQ8y-fPbXhdG#|)XsuOW@JkL^lveA$_ke}lU!~84E7Gw9nr4cb;KVNMGm~W6V8OO70;(j3=1~=D zI>nzv*X=E>(hXZK@tf39@49@Kj3pQbbysG5qTqxKW+J%yjM*ik^*6%!tk-$Dk=de z^E>C)&4B1k4Z@B4#TT$>(}`nI1hbwPMRoMJ%UP}9OWwm^UZYkbi$l?OZ~}tEdNev! zW_(Gwj?|-1y`{o5+GsxYc=g~N3JVq2fZ(}~hqiBo3Q$$N0H0sZ_hQ;w7YieltpN^_Ca$I!RGUGsOamIuqI&X$4INd#_z zZ)Y;OC2T`h{V19yeRu389NlC+Sxy&W*C+}su6iU#)xndSN11rPH~4F?O@2JeMG)BF z_B|zF)uX;2J}UZsImhGoh41i?%vV9X%s|_SZ!8=t#z}ciZA2zf1^0_K5Dx>O`lR>k z3H~$52QfsLhlvySRl~kz`Ui3?0TU>T{W4YB*ega{BD^C*kp@gRW%WqqC>j7Qq8Q8A!Z~fJKs#c7FM%*t z0dexoQWQ7TxAx431x#eEC7TyV+8-WrYnxl~=yXa%RjJy%n$Tf|#HJ=7R)Hl#MJ>nz z-sRUq&rvs)jv|!Ke4@6)q#<|XP;^*tO3*Z#Q`71jf^Y}-U73ITjWQy-WOH<`}&Tqe4En?GgY$!z9L#-&i=H{{fbs5EGa3VzXl zN(Er$?7zcX*|KK)m|4tt))y2cE14oj!21JlTQTWwN3!N#EaOAmz0Kna2l_r6%RJ|` z%K*nb<5)t$bS~qMTOI(3v6g`zzI9X^MAt0yPsJKC3{3qIRXw4=3>IIJ$R`=1aB>$CAC52&ET= zg4CnJ*Q>zW?O$AjAwtd^NNgb-92!#r)zY5@f!%XsvZpRU2d}xEK$9T+_OGZLziKL$ z+gjZBn*lZ3IZ*Hcd+*@gy;)dquw?2FwHGtNh%ud^zS_$04V!1#^Qs-u(uk|J%HqXH zvKs0XqSz|I;96+uLFWyTH}c(e+)|op_GT4${>e`hX2#r2SBYCvWu<#a>Tt+v@U^>V z)!Xpzy%$XPeC~JW=NLXFG5%RkYV|4tm%+emBUcZuj08?7e9W9hLyXAvL_9Ksl9H00 zxWDSe6cn#0xwN>ZeuxuWXW5&SoP)S^66=;U>Ui?iOH9Z`&9S?ixFhd^FGj-Od565I zi&BUq4LN9ij*Sjty~up~;j#&N?U*{563b$gzS_<>Ep7cXvX>q`&q^3U6crgM-{s6l zT#$jnXcmrR13P2>?b!9|?ZtBg~3uhVOjkyGOj zFH}S$HNftBskUcG&@2%baV;+e`<)FN?u`_BwRHxOMw@$Mzz^Fo=PYv29LsYveuxy= zy5`jsc9sF=4M4-x+qf{(Ykh4Wlp{Y80FNahq3(~Ap$-DeLX@FJx4P~+8{{33^lovg z`Bxf@tlzvWA|ovy&1*H*L+-uTZGUE0yov0xW_b1cxhVKz;fbzCs;-B}w&V;Uj+5kt z92IDFVW_ej7!TvJNBZz%4@q{V#Y3)H&Ndg3A`tD&n9de82=7k=*gFs%Mt|U3b^`pr zPxlb_LWa*Rf5i9E=1^aJqQ;ryy_5O!woCR@-5x!4Ptq{?axrDZ=yW9yX<|fd`Ispa zjYqM6$|UoIc&J-Zfp51t(_Xk~+#FTig+g&BHU6y#Hg)u|dmKYnAsvxP(;1asW%wDP z@Sh1U1WlG(-kLSfH4OnLg@7R_QJSB~m*Jiwx`ycTOGr27o8`mO|6jPzO$fUpX7(kB zJ{Q;``oEGNaGCyi{JAmSyaLqx_i3ZI@67C~wdOX4-@)-XlzL;kNSrjxj)a=Q#w%k< zgi5ISdCk$pe8u^$EEP$1rnLJjp7K)`+e9hP!zp|cUIKmeJ1$j`6o2P3zKJi7#f&-| zKgVKH%J^QVB?~~tGXZaI3x6=J1u~z9fi@@K>M-r0EY)d+0Q`gP0opmXT3I0V)+m{l z?9q|$^1Y4sapBh4<`F|%b70E#UXcLl2wC-;7EB&lx+sBWjsfX_wsS;8g}sboVhkj;|O zkqbwO8)X8;c8R`D9wZr04kLKk&PVja`4^sYg&;QlBm%4^H9&HZ%x&k(%=HxAB6 zJK))Y1<&JMNI?#+|9P*}&v@9^SXNzxZvT~(-G7M6=3Xe(mrBsWoa)MXYm))lTgQA> zw4YJiB%ug4=JDoI-1lNxXU7c^;x1$ys8>)v6pfbJe&Dw*f1F))W))N;6p2L<#tNN6 z>uQD!9E8kPadjGqq*Mn;Ru$%hTq?lhx2WLrF;ZgU%_H*hPEPuBK!NH@J2WQdA#P)f zEAvB?BshMC!5|*N;ACp^Fx0qOAvo*1wYZBizS-nKP`oXiSZ1Ys`5s!r0(Qot5oVT6 zJjTZ~9TLX*_3fX;eBaB`h#gbr*2-qgOFNEV%*z^GmmUVPc;9;M`}w;<1QUJF&kcks z-M&2?IPeof4|e}lM`N45-~DcCn-){^vvdQ|Vo485Phw=NbEUB?%?FgRzGsnZj!-LY z1v7e&9{k~zyhxc4bH5#Wm#6`CpVoIOnfYC?yr=Tf!AAN~SFfUD_*-Gwq*reu)*?y= z>^F*}c+@i$9zL7Vg^tI$yDU$i-#;F2|Fd%~G#z<;>xkC3@MJ)F!#U18^LbEpX2sF| zNAW^@xM6gk65@$!85&U`CLEj7F8hx9qYpCO(t-jLlFM8;Hm-z){pf)$5q~8NQ%h6f zr&y2*+u$Lyuye6ylqyCQrgEMaWv}zpC?9h{u^?Ej$dh~(CcAgx$hHu{}3VV zuaPw=tf|6~15Ex1JQEruB9+&MdhQ9Ev!5?~hviKF5TXRsZ3O=@n$i1OB6HJWSI1A! z_zrVNiGO)F_60q?Y^{i{5*rF0^5LObWSK8QNp~F_WJX1?Ms|RYi;UwF?$H3N)?hZS z-0LgxB;PQ+X>zJE8pvTdR!PqK`d=m5R<;LF)wqa1OR#yIgzN*NENNUv6);(_Sn5Cr z?VD+66tFke@`#>;!`WSlAN%eb%;y@&l&vLB3L_v1#<>dAc066I%1WVR5_`bH#_pi} z5#Li|BZ&1Cw&G5R{<@9aMe{^(*nxv)?#2a$eU0IgIM;c*fz_bMNrq~XvA%3l&W~l~ zdvD3=J?j0#(PQLi_|{a@c$PvgrCbk#xnT|_L!C%I7y+P$Uh0l3x#muIE$gmW6yj#` zNi@sCRAc5OkXB|bN9fuYrP6=jc__^^mh~?T890PD5Y883pQZZFXqW0b-c3P+*rN__ zB0C_S8SBpoCUe5Kr9tI{RaUIaD_DOoJnvVU$ng*4yDdl>vEZcbxap53Y_WKt!CipV zUWls~2zT|}Y693S()?mi*@o*o3j0Itdvn=N_hEa#eM^6cH3nA?vF=5~} z0eXVn`o$qPFA&ZO=nm81Af!aq!oi|_f00M_yn^&hQtyIVUp@8R+zX&Kb+73xI(Rl13>*h2Uwbdy<>&VMh>S_od&JOtaZ_QFP%t5FFSI?BjQKLnC#T49d;cUQy-i5|<@szw z)ytw?h4Q-BOm=Gs!|VSd(DHHrQITPL zx-)g6UJxJ{^iz4O^-A`h7W`2_kJj=%%Ek7192ik=KbTG!zkg3e$nov()PJMc2lqD> zUZzRBI-C8cGk98}lh*X~)OY0a%>A2LF#|>0bvY~H-yb75vacuGhI9{cK#`@Vg#9?N zYD4zJQHNs7rAI1_vw~*g#Nx`47wyTdzj@lHenkG0zx~ZuSbj|uf%gFy2eZkBl_seo zD!pqt{sjTK*T~d3r2>jWlQG=Lij~?xN)L_GTE5@i?N0;tg(0D#L=xdmGkGKV9wde& zq@>l$Mu9M8U}>&M_nLQxGR?;CzD`^898^-Ry`LoA7NS=o7`-M>17>J?Oc@p@227aZLZ~1HovfNXPnemwy%u(wu0Kif%61kku*_MEsd;s zg6ApmW)BO+S!y-8id#5_$A(@Y;2d`}edFfwqY3`Tn)UuuQGwAtt4o006zV@ljN@{a zbF@g{b0CW{`uR_Z?~K!1Q zUyLc@peb(iSL0xuvc4_KsK%_qs(lSD|7(BFQ3@1_PwWDT-nGQuD^-F}jjJc*Zc5#D1 z5%riMEJ@+#^wkM}bZg+X=7{XD<_J^ee^(GQwtvbDx~AudQaowERnLQ!Y!sP5b=r@^ zde07VCpK=*xs)L25M`Z}avJ*ZtkJsSwrNa#IE*IL(-F`bZ-)CX!M{_6KEYEJFtV5t z17;;m*<(9faXCa-AHmCIZUaQzW#3c|ZDC|j;wB~-Bk6RJ(&K9bSH!B3&mvMeW=Ak_ z!kc6=~sO|=J!pBW*fxaDE&dukBODUjC`g{Hs$U)h_SyzvO$ zi$>{nbZ2@4QbFD1*C^z=%IM6W9*575PEHbvO^jT- z7hMde6aIsaG*BQ@+KSWFDQ)y4ybq(`eb?JBS6kb)bsC&9P#&=kUK=#MDS@(Ktmp2` zwlpOs!M|%pnXf6-2W_*g(IubBunC8QC1TdAotiM#Edc{#+6I~=7oBS^&*eecU6%cs z#Eg+Bs;cKu@!vp>oy%4Uja^1r&hDLL1HWfN6{baJqBkB?H&v4P7cQ zpWn?0H}>0;t?${ID-(-W$2@uO?SuZhIk>L8?V#d)19s$6LiA(Lb$>W3%@72UC&Ae& z3^tQAEYf(&nNk&!ZWtERf&urZd<2!MeGIL5{Cg*!2YfKWDUG1 z+QEl4!~3$F=}kd7bTN4bTkTFfrj+s2pJYUV&)XfZcl;WekGw=ImYIM*8qHR>@}Ef$ z(f=xJk&%%OxM3q6EViN^HuP0UQc4}X?CU|KSXkz3H_w4o3SR2DWh27>XtMIpG@Dv9 z6ZzHG)#i=_&FEaWN?LxJdo%&B-C9Zk2rCs3+^e0diY+n(Dd#$buB!43)|~>|aeWFBG-2b6jz< z^t|(PbHXQ1pWul6MB|!heoK+<_5?G4W5pz#V4Fkz&Knw1ki@j5&YxoCWCtyL7&M?lIYG$gUu4Q*K0!(eU3=v`9zN^enMyh=TnOxQ5yuoEo z6P60xSa6QrwblIbC^q|=R-b8ObgyEW`T3@u@7Nc#ToibXtaj`x87lu!>Vo5`FHPZm zLqCYU?{3{9EkZt6&g1hWw$A`5TZ!5Q!&N7H=%NlH5CyFc4$540c6M?*HDGks*!KzU zQ++h~j8adDX1X;UaLeu>cwZQ8L znNsAgmNKgi8uUL=28jIS_NN|w9U>x;f9W<&?5qBKLNiPdHHIoHZs_zF)#L%h){1YN zm7bi~J1|h%!{y(C5+#a$)j9`Kb=}%~DwZ&&D0^2}$su!#u=mF;MhO)w%Vr1B8lE)b zYR7-DnbPiohQ>nKO?i<~x50Z;hUe?+e*IJG>#~p+KOggkk=^*#HB?12zxgR>8;PS) zmrl&X*k6$M#TBr{@`Ge}_C40u0Vk~Uh0v|U67rl|6{XELtF<(}r{@~=v~RBkfCTLQ zgR4GVU>o(UVK9zQ4{KVZcT9WMqj`K6`>%UG*Xa6|2EgJtQ?Jh>2-Y}BN2@d&{av21 zQl@Ak&H}uIvW=m(K(ECkPNvCB#KM5xaVAQ}QDx^e8cemUk<>>O_3%93$C>s<@G_&c zahze?^xXP@-l26qBR219CvdkC1HSN{jFp~r+@-=WPwfTcD9jfu-5!5ek)7|z?+lj~ zf*mk%JQmF@GB65{nZB2ss3sp=TE2T+DN$XJ<1oJDJMVE(b})h4#RD6Q&oW$+v7lMD zIC7pdW(Ga>h>jSS_`oT>qUpSy zZsP3^Jqg4;wsQr4O4jF+qXFzPkC@p)wEK8TIWia$`4%tBYzDi|0W z8)(09u>y}bz+^|d!Mi@?PPgWaiq)r424krBv{9V4;wvlK&lXZQN*#k|m5_s1~_J}0!Y3|>hig`BntZ}(575p~6 zo&;MX)S70uSERV9l+?oC6dtKouay4Tk1~{0{?ie|qnvt+aUh}Hx#;8Uc}etxU*gEc zdh;5}x|5%(r>@;=Armp*6jz7&L}p#~+7BEKhKo~Kj{)1T0vZQo^|?T&l)4ZH2WU;K zBlj?OJx37)@sDONP*|#|q2}n(RGly5G zrS9=?XpuV+&(WO_)W9&g1RK$-OhB{4$^?AhS2VyEIafXpcW>U~H|FWyOg)IWbX-H3XBkKzrAP!ACVLP z4mlW-r9#O&(QHOaO@nE%G;1?GNA8v#7=7j4T>ua+RV2k9U!FwSG^!*}9Y2IUsK%US z*mF@~{x#HLwO?;nApHI=a_@VRpJYI}*&`a_38pbkNQuv1R72N8V|rQSxqr14#h}0o zd^sdL{mQfg&2j*2JOMmkx9(t(pL>_9jfPW^TJsylfre`lY2z=J0|^>bsmK+soEzEC z@lc1$dUq0xwYIiuQ4CoU>;Fg8S%yW`w&9xY6r^M51_@~xQhF#Q6a)mMyE`SMyGuYq zM7kR!rMtU(=z)Q~-f!>Y*#G$jthJu!zOU;%@6P6`z+LO1Zv)Gro#?%v(^GVJw?{ru<&%h#S$=P(hi65W&9Hy-28gpK<)VjhyCSia(I*! z%3g`G6gf{0TXL^ABE~_gJf|W|OwwuO=SM%E;2E*oFq^z*Bq34!v)|J@2$;aXW~M66A0gz(`+X1=iTlv3F;#8kzYo#~qZ#a073 zK!H4iRyB-XjaqNP{E&wmP0d2hX}|=J=?KRzs9=_oCP~t8aNIPmmp+!Bqn)GnD$J8( z+EonoBmGWUQAowRle4#~BbA$hp#VBL?+qvq;yWDka&Eh$KE4 zTe^I-#l0-ZG}|`QozP7BzUmO^0IW;R+FSjBPL4tPN4tjpntV|T{+}-4$>SuN(BK+E z@11f{x}@J~OBBpJ4@rHI2}NohOo0$cRf1B9qKh*w>9;p0-8W}I)BY~NY*egav=ac<>9ZUo#j~D}6p4+D9j#uc0o?_03vT4z?AY!a9ZIvB zS4b>k3$bikv}!e5H*qhkN+=JA4QH#%zB8ISu(|4+l)uTCiFMfLUsJk?qv>ja#&~e| z%q7vmcy{M+?>`OWSQ$;`6(#`$f6>%;d(ePSBo&J$JYl~NgCuZ0Tb~xc($?>MzLyi} zPzLUO(|h+!JP!QsUi8{x*?1?6_UG(<=d4|n&)U|27N0z$lEcy-a}ha26pNe*A~_@7 zWq^wQ4;ZFs(j-g&*kg#RTjJcCU2U&XN?t0&oJC;6o}9c&ob@e7T)8f>vUg`a!&+*^Otvw@3`iCU>&VS=qANS)C z+Abe+ZCTUC@ZhOVkCt6Mo&7O8s!yb!5-HzOVcdQ{M?MC<;@3ex>^xH2 zW`V7aYbBblhQHLoZ)0 z{&OibDeEfUzd%R|zV%|M{Zde8cjY_|UM z)>^G_|LEl3>XXE>8UXi_AS8S+*Chh}{D}|NsL4tYL8mf)U%rTMv@^&_b*LjJnL;=_ zIhE%>qvPidKH#fn9BFjw;WHPgpVGdUAaL37nDKe~#1Xh}a9`|Zc*DESkueiT9ur19Q|1 zf!QWTBH{d#pBGSH@n(OXr-Ivk8_TK5%DxpU|GTD#clgWU)Nj??gx9a~$Vx9a{?C|b z9AA+%v%|!JhL-lbJymc0zheYu*_5Juq4`O+WM+xD!?h*AMH8k%n)iJ8DJ?JHshB0_ z@*&k~QnkuzdB#c8BY;Op5$f=U_|>iG-tW!g5r;vh)r5 z`sc0uSu^w=mJsEon6{BS`%j=>E}tm}@4(B`o}N>X&PF1Sx3l5?2WGdbKuMCWYdu~3?xD;``Zg5p9&>TAb~>AEz)Z_bntCTJE_>&r!T-;0sox$P zc$r^Hh(3@My)e6~+f<2-&Mm~+u9>fW=zW%E5>&FW?4P+zW0zi* zpJ&pnqpYoZrMN#fzhH57>{WlzI(M2eDrT0TV(e&8Mwhm|cfAExjiA`|{AwEr3uRMh z%_+q1c?>TRf;cYJ4U6H9g<6NCssvZxbmdb=k9_qAYJ|X5OIkK@+1|Kp+4Ut)Wtqee6KjH(AWBO3lIXaJPNv(3QbADy*_BG%Z`CNFOCo|O< zXkOPbPe9n^V6m3jMv?ekqfQ?t^Za{WTSoGra~98f#t%{?M>RQcC?|Cjl62n$OK+WQ zqQ}b>?wq#x7fykWMKbypcDK=aPeJ=PajW>K zDN7?M^!p>SIqJFAHax%=c{2TIji`s%pRZ~=o#`Wp!LLV&YH%YC`LVmoEjWz&YDSvz z*U()J6+rTZ>f@HvW3wC|RuB7*J?{+rEPbwP@PSfI2KA#%;js~&(r%twihac0D>xnP z)&wHJCk`Tz5F1XLkfK6L_`O4={B_WA`thmjC=LBqI+_GLYz?U!JV zqD0RiW0I2D4R>wZiH3VtUC{lyNRm0a0Eg>!TSd~Zzq*&7UrUW7dY$ZYA3K@#9C#8x zVH7i9PE~`*OvkI=KYCa_f)qPxYqpviIWeTkq!vPsS5-3O9*1sJQsJw>cG;k*$m@){ zi4+SUydVfEPE|YUO57Z#w8uVN^BuGv7TyZ(9D6K4h8@;nuan)$|?f&1P({TE- zl>?B6>F){DIXY6b1b^|VB{WkXvyw)LqNx6RfiJ%wl+5HN#TjjS5ZAyjDR_zRw z*D_-*mRcks8*5!ag*U)p(R)kC-mn8I;moc$sD}NYkG>h;&P>PGWemta#|qGzcsSE~ zgq^Rjm9Xl#v`X0Ddu0mJ(p^KfqYd;CZcVgyC)xZ(`G=(5MCIJ8MYhn)R8Ap4gXlt9 z){cB00F1yhg2-fJ$|oLqozD^(ALBiV>E4sem^u?!Zw?a{S?SYvS)nW7w){);g6YuFq&Ip?ANA7nfNK^fUxBUF{fb@KHW zW8;n1h*E6y=?v#CBQyZ3skT;!>Y@IPTV0y3Cuhl8n81L#W!#?Z74`uEHo)rC^u>Jt zu1)1*co)XWd`l48x*ryOy}nR4uun~TytsHXLW1UWkZ~+VHe*F)AB zIF;tcd0+XCaDj5-j3#h)`TIb3yNQ&i_F?H=^8T~T5R*(>7P0f)R@rk0x!U@f^X}-YJ?jo5xL1k01sp84D4w;@kt0DQOZ{Js zq`Y47Q$p&lsjA$I{e@Gr%>E@8r^dp2yN{Gx;_!O>>Hg$3wtAl53XkJB<`cq_E-Tex z?I+UGoN2G7C0~uj%kxr(sZ=Y;VJl~Kj+$EPU;gZ)@fl_j^8U#sEA_J3;sl!X=_(u% z+@3?w6lsj2O0|aRw?7IWUmH3WCD~3@0^%qyf_aJOv`Sz@7-HIeT}X>+BoZ{iqaY@H zPdRRY@_`{LT`B_;KZ}Cp<9UZQWH2MNXqb)-YTVrPkU!H*n&&wDx|W3+2-ULqQ~+t@ zzq6<6PJ|fhMLYBaw=piOeyy2#o)6<4MzCOQ8sS!B{O>edF_=Ae#XA-roK& zGP=*Q1(gUS9xrTjQDI^Et4~~AQqgC@0t%2D?@}WdfKKBdZ8!GZev~qU9nRL;!x5LF zm6*wYgh(zo1pkRnxD-cITwa(_cool`Z(AW|&O4a6*IZ-Ool^2V`mF)`7OwZ7v?jvL zR0!?-46#6bHL77KkJ8!e7q8iMA*5TZeD~30tLhFnMLp|Gmdn5|(@an|LFT~8OK?CI zaLkLmZeaHlIk!J5*o}8cVa=PpgNl3x=lz?}z>9ykUVGZlZRl2|3gU8_)J~kf#=Q0S zzluFwG0t2^ug6i^veB#J)Y9!igH%h9dyXaKQWHc2>4XjKgT3C1X;_XGA{p{6O6`O7 zVfKA@5OxMJAX2Yjxp6@v?kQ8O;ZpkIofrQQCgHwW&5dpBF7ujyr83Nr)5x|qTiNBsU zit(o-MCeo-#v4ovLqoBl!Q2ylV{pIaGyhq^Xxva}QEGb3Gm|b!Np)lBy-vtWAwhVh zW-LKT`xz)s&UsU!Bn-J1TzdOaFspsB)eikE7*}E&MezkM@2wnmwd_%2RKRt?N4p-Q zd6Zf3b34k;YNYY(*Do&!lV(`oI_6rltm7Skp%Qqz&T``5=dkp)a?p=AN@8R!fBVx* z-&}Z?=ll67P|K|`5Z0}}LWd^=^U1|0jYP+wq8+`lF9)$`AOLwZud#KjI~`j0gcz}G z)prKCc7G@1<60w{9T&V=yzAPV?^{;3$*(nw66oS2kA)oV?&v5stn%XFc?7qCXW&hk z%vI9s`!flj+b7~7?@%f?Qm5bR**J10dH$UrA?*!_IoURgTetdCI(K>${HMfQPuAhXZ+QF2OnENl?iLE=m28}0hC{xaq%Ez{ zIAs3|m@Im4Mn#R?|09I}@Z<*U24WkbhGWcWvo`)KX?)6v%BM!JKlg0LMQ%$t$Gm^n z?Y%BJcXVk}+8En1#iIkOv5L$~!!Z=>NQ3{1 zPG*xEHm^Q79XB5cb}5gxnxY;qx0rFF>%}%w`OM5%jJg?%SIZx9l%)|S?)VsV^Hp)W zjX&Qo9gC=MFus4kbf$UHQ$h<;YS^CNi|s$YafB}KRlqNy zDJ&J1BA1_<8r~_4b3{$%?y-T~!Vb~R+k{2x)Z(eEth|4H0AE&fIWPUXE+fEusO%*k zd+yovoG%J2-`|<$GvzbCv&sHwmKbzNb%OPi9{ZQ0K35C(wixh6`s#MN^3{!E4)_EM zU$)b`1T6aVvT@|L9vPEMwU~>KwYrAYj!lkJM%O{WnUt%jXvfKb=Z`P3MaiIE>NMSa z7&rmlYp&Hvt4!lS6a9SV3hbfOM8jd(ZaYVoW6WBWh)`0bkd>c8j#w{1Hv9XBwCb@!D*i}s*?TlnBoqjUeB}}`B z`Wq-Lkq_>XJaoUx#)-1T^869oq4jMVQA@usab&kCBhIwp*mv7`4#{6OAw zAPz16qgZ1ge8JLffQ|+&RW}i_gGP0R!y7^rbZ963$tep?%zRDyw=9da%K|^KRSQ%5 zq`t!s0&kb5$=%K%e*-eRP3afT<| z=%d;1vo1)-zP&4PmR^7O3?bXMLYPbP1?~e132CQAb;sfl1F(nu-5hY(*`_%q#wq1zk5H3gKTeAQd&<_hqeOd?iSpz)_6!hdm13(#jK zgfQ#4q_hT5BkHbljLA!7ZG~te`97*OAdMRdJ>6XiS!2X_lDZYqz)v}B4;ej`nJwYC zx%`13T70jVM+hzMVgLeHT8P7nUkZz*P6(F{va%>fMpv`2pOn;`b7_rOer+X+CQFhA z8THl5F<&$6veVU%jPEi}H2te$7B=1Ef3~759}|K|Uuw61c`TUT?v}zDnEGW7>I&qi zU3>)*tVKfIKLbH-JLID-y6aW056)@wpkpy1Ya_Pz?GY9ioyxPnueBL!@WcH+A>P(lEl z+E<1$BHMCQYa!rb*T7XZE0$Y2k^W~r$gHfr`%#b}VEavXm*%|n0$9$y@7}llhRhA< z=dIKQopP2qXqlso{Js26FH=zMZ#(X$GX4&J)Pb?HtiJM+1JnoO6r4GId#ZtO>5kB0 zmmZ(b**G8$o>4NA^WWA{i{n7KSSdP5c+*ZYfgZ+|B%KW_fr0rXvwYgkmnL=mKG^F_ ztiiDcc%gD_G`Ozl?5mRQmOU-@ZrI!<#b#xB~7a8w8fxBSJo=fYS_n zHlgO~Cj}+#3>#l;iEY~+U$@Qwrn1k~0EEIln>+ z_k#aB#4m9ZR;_mb1)3-=IKHvY7Y$HVkMSEWZfx_Z2j@#ri#{Gv3itB90s%?Zd2 zPJ2I`14f5-DWj$fCm-NL+}mmw{-yeU2$*c&0l5}XT^_}ckOTf33dbo2Qfcd$7i&Tr+Myx`1n>hCFm(Bsqcm5E*XoN97sb=!O zbiEpF(8f;$CC#+zYc9@UyU3CWy>sR-ko<|~>+bvE^?Q%HN1vTo)QN%ZBCI8>2Z;Bs zf2N76!g5RihY*)>zpfcl7G^@v1Pf6yp|h<+IxcC2l;|Y5gGLEro1#XW?~)zJpAJ-g z-6qkI>I~2_B?HZ9f&_jx(`vc}`f5(L&QAV>s30kV-`kv%2`iIkChnB>ssDHNLc*x- zuwh`y8@#9RluSSH>Imq;++25wGzF(nfn%&gi_%Z)^+WXzWEKxMlxpdeH#H2>6}M92rhN-Ync61 z!)wxLrryrFamg%B?+yGwp_>TCQ*C-)NgW^e;zLSu0i7k_y&V&o^5{H z`jW%z0JnwC);90faHXN#ZQue3d(???r3aor5FDE`&Yt%f(&!#|?Aq?f+WiLFR*jig z0$7@tQrC>Xad0E58vZ?5&9#Dz$wks2>L>{vN5gY>pO2kiDTX&9Fgl8Tg!fh`DST{H zo3lSLZk=0asdRR$q`K{K%VH#)i)F_xO<5L3f2vcG|2`!a^OE^`z z56y8lGh=LYFXXhx#Nock<@Zt+OH4_B;qdMEx$nI3(dc)cA|aMRR>b_pKW1LQMWK&7 zen-Nk*L^L$+X|9-8#6wEcO+`O+(7ACrz!!f?)Qb;D2grzX{xidzw!D-Vk+*VD?**D zFqVNs`cn>U?ky%t6U!}iR#LgXxV;41{kJ0${Bk@BB zlcp{!{;>I{|96aqgnfgw(Y9zG4()grbFwC)s!vpB{qy74Am>L1fMRs1tEx^>AwSpR z-pZj}%oUvk`uex3h_cHU!a!E}vw(J5eT&cEK&OVj^p85FRLcZ+PK8W?u|(t)zIc=- zmG1<8!`o!qFG3qa%ZdiV2cbYU@qCM1J7H%_;rF982L4jedfdll`Al0Yvp#>1g!l>3 zUhgSX4}X9dr6gIFW?h#V=YY*I)*+RmV6-CBzpwgNl=3?8ALrcQ4yt^>ve>;qjC0X=-A2Bvlo(4*fwuy^ zggGLNd@QFWB+X<|e}Dss7T?zab<(XK?C;V=c{$gAui5N~ujm=Qe7qubFX1DgX62#3 z#eG@c?yJI3Np zyo}W4R6A+Ig42#gt*?=j4x}$4)?4-~QiMeW5Nv+nr^VB5_!75#cD}--yt*WY_Qc(6@*FxB zTB*g%I8nMk7)@=g_e9@mSTehLBJ8I8jl$4M%LP`)lplY#-#9&!5L5qA_SwW>9D0EB>uY+7`Q&luum+kDx+> zZx}|B4CmQy(qEHwsx3`d{E`>(8Yh{+e9R8JN}RuJX9F7Qcpt~C?L4a{ra%?He)*6g zHPUFXkwm?>eXD2A`oQ#Bsz9UaBiw?y+_l?LFRo>-SP8*vB8(RDkT_oneJ8GuaheRVKoPd<@|O=KdaK{UZP$s02Wn@EIr(fZY{Wgvd^ z)sR`!bcj z@7QgJC#+G^#L);+0&l{m*@8-ic8Cd3W^Y){jW7p6Lc`;c3?gYm>sSoo8Z8w3>~;)0 zng<>-U!OqxFA?#g)N&IIQAv15NGKHirp;c)7vnqU6Qt`w#uCl=@WQAIj8ocT&3Sk} zKEY!+c)QOUSkqqQ@Ri0$b-c4uU|N0iG*+17l!YAwFnrnxYSy9|!FY)}N0AKyWKFDQ zl+R~KeRHPr`BqcFWJ__Gc*+MAFW63YmvdWSfgCyy0Nx)!(U1AQXm!6Lr)sER5u}$BOYg2<_pviIM zV0jY_stOr=#|lI)9{HCIhk?Dn4KM7hr&GgwHz{M(rH)zz@F~>(W_hW%JGnPAGKsfC z40w5?_Mak7$OAwtUG3OoNq6p8Hx9B8%82X^>}#)w0i&mmg&AXuPX@#f3o1F{&FYUq zR?~06a_0n={69P-;Ozq!;raOv))}{R3h^_NksJnY%%e~06g#cVvR7u0-#q)4NQ?ab zI$6cKUFuBjAWxZ5)kHK!I7?p5PCxG2lD8ud~bB>vOSUMZ)21c$g;}l(4-v5 znBM)7j-%wt-LqO_JSe#S@G0x7xNR$IR8;lSXUi92&+clDISVTrbgbX`dD{+)fb3=T z__OP06jeDRcMqc!SCJ}%g6sAB(1~#+A`Oq~ykT$Kh%9)8t=FHMy>Vc!<)9!3Hx?#4 zzi+KuCQlU{hl@C%p|ZqzTt9taMw8W9vR7~UtV6sJMFeg_Zm}A8QxsWJ^uaY&JCkg= z>*AN~tt8s0Y*HN(sg9jIJgCTCE1o%nRk3#FTSTo56=d;~fD6e$tNBT5@Tu*D-6MCV z`WHFk@9O=`J;Y(>x*51!8Gb9&Yo{Ck8;|A7E(Nj81t1<+82&vcbpw8_wR3LmUD}Jk z9Rb1I9pH`OTadX%bd8TPab>vVLx{^BZk(@Dlp#59L@UnCGKlzu?2e9W((U=Na6py2nKlWl+GK1d~Evr!M<9 zg1N1(uiyltvlvcPPOlNBQ(fyN{vyPD0o-?`Zho_XUsc>`I9yz_awA=;$NqNDup}>z zvGfg}OEb~jqNX`Mx=ZO`WFUdzsGB2@-h`>-IB{R+z(WL%TBq2PIyFBCI;6$Y>u#eLK9fs#FOOim#JXkyoU1C<$A)Un; z7=<_@V$h#8W$pcqwSJkJM^yKOg}b#9w(U!;MJckY7s^FShI%*fz?a^s@G3xEecOZOj%|-}rD}GW6Dl_c` zvH2^by`=riwYZtgj;F5oUR%)Q_$J8gkL$Yc1AF`m1~85pYj#2#BGL3o9IpS0DmLav zszg6x76tV)20d+!eXCpEf_gN3^ix0N!Bd4EmPG}N95-HO;53Y1_`bht%kxU>?pAuR z%gP1%fUJ!TgwrU{?*j8~cXxv8`&A?BtimHaj63a>H_Xrv-|eSZ><;*;n8{Yws)M5F z&8d)hcS%S)dxKs1C+uq)#Z+sotVA=hrxtT{TRjpbHZpmBx2e-x22&aQ7g|}>jcm;g zNQJwRn@TAb(S9W-JiJ@(29hNB&)Z70Pw40K&@bl4k?XeB*%N;~ww(ju+Z4l58qYm8 zI!wd+81nB*4D%nhxRX@r-^IJsyRMyLQ{%2ZufK9N2_HbWE9vj zi_`q+9#7`dAlt%qPhWxnvIzLg5>c`?>djO`E@u>tNbf=oQM8(zCQXKEljqmk8|LYm z6d-&_3L0|wEh@Qf@RLkBvsew|^Nx;(_yZFVNBl2Vjr^Fk!{oU+HP47^4^>Upp?y`b zAC3^t=|myMB;6G}63E`;2!stL8D$-DEExuV{d=#54tOxvrg^RgFNZf^$? z`^z01cMZ#0t1{98Ee-ZR_CL;Y`)fSG-;f^kog`#6$@Gt7gKRo_$t#5JH^VpI&iKq> zo4*DEm{EXZ)tAwHGrvrT{lm)#VphtLK3LFAXzB;qA|mhucj)9ZFG2b|V^TEh$y_nO zh_xsJxa;9-TpX%$Vu#0`@0gaoG=u7tbGoILchz*Ou%?P4r$dF0a67am-*~5urDH5! z_GaT%yj%Oq#c;rq@-_F_L?RvK8BUipbWaLGVH5Kj>)h@6V{wV|ONlApYYI3+2*q>a zex(Fw39hod{CyV5-JG(Q`;DBXo|llc

SW{gS#`D zj|Hu8(kB0bKxN32=t-v#QzF~_uu~Pmu1#(*UVInA5Dzh7`=txk0oa3tHY^`=%Soe@ zbElP4D6Bxu2aGx(a&Eh8;7grFr*3F={FBJRQQY1S3(11Qt4-wIv=T4&O^WN3G}>_G z2jhPCw8(B?oxJj(2wu>kvqM z_`|>bD_!r#)4(=<5jN~4g0R5!&Od3MF^F4q5MUQ#U{)lskWclLI7m<;&S8Q$P|T zW&n7>$)i+$^RuQ~zrq{o5tTi)=RA6RL0oPnyweMp^Z!mj8xD${ef6)9ZpiTmew$#0 z`|!B9?Rm9fM{jXJ;O(Tsgw8#}ZtO@Mj%B2M@Yf@5(g1_lx~oH4lwIRQxhXfbO*V-5 zY06+%SPecs(}2!YrfX8Wgmq2 z8^n3W*SyR3r?Nbe2UC#nXZ!;}4WsIAKs?f)W%AZTismqv(8>#X0&&WcAl_C`W@Y;@ zW)($}(fz)t2x$s3%cx#%g>n}KE`MQku$dO^mcRn3)eKBT0oI%Q%!{Qtm^xmySNyP% ze;yYc%lPBt$ZO&XhDG}&0NZ~#j@#_L{OuFrLa8=w`nlWCz>n{7A0^c#KYk2Kda-v1 zfI2fy&*6b`rnvVN*4|R9hb>Vdr5GB{@2I}fgjRteA4di;}-Az$xW^ zdHHW~uGN<4qSxqv1IL*+H#ULsgP_(r2zVaMk9|6;e?&(~HR+X?naHptt{|{ijLr}s zRl3EDkFdzQSDfcq7WBI9%97KW%5ueKy{`KF^IW>wRT2ZPo>=h2Ga99Ll?{iCKZ2Gz z3MSAqHUcaRl`>;fh{6liEi#H%C&V9Ef5{h0w3<)y-Y}HLMg&NS*V)=f-86=N5)_BM z-KMp)SrSx=@=t_N|4Nf&m^uj<6ml#w93fP;xcja+)zc%yfU+J@Lrf*G+1(JOb^8~% za`E)KC{x~T=um{pWzFy=th9{9aI{F!ntWq&F+(hi&Ldn~t?7R8J@%M%eQUMV1ab{D zte!^9Rb0oZ6V5>!a3G1ZI~1i<(nm?8hqEN>TwY={1H$;{*VV6^HW5gbe;mLh<`(hd z*E^`;{mXhJ`yRSj?+F!F6&kcIfopn^$Ge{3Z)x2OX^}L*QT|EFgE77AyAd$*p89U{ z3#&`DKkfBUgp&4F$~g#hI@NULn=S$LXAxLa8;P_5T*G*7K*} z$q4QgPY&V43#Bd$Q`}+?xPzJzVhne8V^0QzT3?VSqmFcU4W|<||HQ+=S&4RT1a;8N zd#%hv-!ZkmasT7bFxi1D?(&1sSC>ZYlH$C0ZKOytZK%!jr4?AAu`|lgUuG*W+L8?h z%k_!eXRA+tZC_3RaZ6xdt;sFF3r3txz^wjOZ93pTw&D`ZjU-(|6XgHFmo?Yu?G2hA zGLo3HspcBTp^(Pjcz*l6^b@za;4bbqxG!!?f*9|QVcuU~r8`sBpAwOSM00ByMxr;B z*k2X6sG?ii37FpFE7BedLE7?q8`uMIkcSZ?e*fccLH2yNyYLwkGew>;5Pz#j-L6o( zL+Q3&%l!Zw{AxmdZ*sh8_K{6E{?4uLisoJ7spN4t<4zi4y8*XF`!+XX>kAD%`9D+r z_*k_UE@@FEp2IO&4;{J&d!f1c`Dr8NEJ`}|N5b|uPdnkkY%386mvZO(8)QIEMFfC1 z@k5=RY~oh~IUGqDLdx+uvy}PaB;-;cUn9c1nLgKSx!= zEwBq_=f2;=TYWcb;dGH2Ey9u=jdXdnrIZRgK0(b@sD5ugN4mH4@-yZ!s2>&*gpsJ` zZ3;e|f4fz0xEriYTLP<=+oDR+$_HeAntm4JHpYx;@B(-O) zAt>#8WlDuH~hgvTt<%cL$BTv!Ub-V1fKZbL|Cj$Jpo&(mF8g1|r zmq-MlN`ldngZKY{0-e9wz`0Iy_Sn*t!1g3C-CS`5{kzBMZdqmHfo8nldcbpUtq#Wq zsU==Yk7T6whjEx)qHmc%NTipBE$IT4fWvpM;*=h_^M7%)Z3pS@p~3(7(@O62W!E1N zDe`NBnX7t@H9j7Or;Gh?LD#fVP;X}=#+nDsb@$6se38?DxhZ&@nH{wiiX8y^RN!4C zju}eiZG|lAnfaca28$g#Ra#OwUn^^Fef@=F;a2|%c_jE&f!4*fYh#E)VhBNPm3SaYReJ=3pAoGvFmQpz-DM~60=j1^?MxM$*UxlztbbUUb z^nV*{*O?jc2i|sCdS2mCz%p9p!<|dE%A?FT=<|EPc_OP5`2{>gYbym-3WzL%G<^Tz zPq+#qh_dejPyT7)gfzcuxnyCC@}#oDQKo%~rF3&7RAk9@DezdvDJtPV z>F`zFh~uM+y5ZVtCr9Eag@%0+7Os~@s9=&_Qz~IwY=!Nf{t``zy+(i=FPrStz1|kN zr9fFe0c}w?$qw@8!+sxtq~pOqesOQ`{5EaXFwz0+|Kj0;j$T5~M(an&dR{b?dfmmj zgMwz|0|d6~oDJu(=SrZ|2;6Pzuh{8%E{yu=n^fq7-Esyo1slP-qHbZZR5>soMNyoc z>0~Q#4ypViavy@*)VlK{qc-4Mk-r6Qq09Biir3$79+@nMzUtV63d7E3zJhm4Kger; zdl)e-efETqY||YYAtWl2lf%tBGvV{y>N%cE<j=u+bJu*-QRUGrGCxKK#Oi_FbJwiGPb8#tPB}lPUiTJ4JAjD_MOhB{kP)= z@Cga{C9ohN(hTde&Mb-u3(pV+%bv>Q#s^muat+`}GrYIsaMHFc-jjxC?VDTN- ztMO7wALr>F88#|&O>2@|@d_Q97h^n?dAzu~?<&es{lje&*E4wm@r$k)fgys#t+*`S zFfS8m`62IZ_g9Z!Qcp@>SQxdWHc-$}ja)Jgcm)KmHRS|!c=ZfE0f_~V8tJ|uk{s3q z3S4pG()@-%&U7U&S1)XsT3XWKFN*GQ;w5>xyva5=e-(Atjqzab;EG zV$!Auk!@&BM`R=BTmi}L<)=T8K;y+@vKu|!#~}3mZy|kv&2;vnKo3hcc0>fLf((9zV;Rr6;jYS0}7ex5FA8{QF zG(TQMy-6O~UC?PnyWXImo}-iq%p!i~;F?jGO55LYn`py0EG1M1@zUXmB&g4HD&ETn zV0-x^+~zBAs)%)mGD2M~$7EszcpRYpr9s57#|-Xm-s-@g*S9H%IveqQT5 z;K9l>;x2_x3u!9srB6pym^_)0KEw$RBibFI9YaJiP+%tQSD*n;sdap5N)L?>Fx%@8 zLAKUETl$?r_P!%PT5w_O%K+=dUw;qUJ-a^-85n17mK10X$UT3&Yj(wr=(@WXID}LlaUI) zm@3g!0mG}_48bv$$f?*1PH0Awg&8lWTjBU8)_iz4;~{TB-5Z)|BUUt82c&hJbH+a_ zF4Dr!%Yc6IOZ@YuNNmzellI!EIvDDo{1R)^xQPFHzUzyxXj;6z4021R_UBth`u=}I+Rq7l3=+AIZ8 zi*%Rq0Ys!$@G_lJ!@oBJ4ORyKi=^%~(JZBO^6Ql`qEgM3>@)?1kxY>&$@}>}Fo;-} zTX^#D6B0|m$Jq*ZR$StEQ?XrAQIJ4n9_V*OGs9EbysP*P)AIs=woKA(1V5NF!u?~r z(X{M!sT(BM-s&q-Trz!Hcuc*B(%KMX{#^ZBDb~z}cCFAa>nLXR98xzC5YK(&BmpZ< zYqeGFng%~lSD)`F`7H6o{B-bk)c8s-luTEOufpCXdPg5RbR51}IEa6^C2~dbG-xhf zwLJ@fVy|Zk=jqw}G(s8gq?L&50$ukeJaTKT4VQDCx=8>;7+eIF?F>UVd)1FNBbb9q znF2|staNoobktbT`DYcv{vC(Lymm3*3NWsNcj5p(W6&E<*nm|8vPZJy?-cWx#G_F>RZg>kX1=YqdW@X4D;}FEPZiQ}-&+|MCWz{vw}iMO z(Ls^hI8;DJ*)?TogXk z21m=nu^Tkvnx8W*!bUbpjPm=ccjhq}6Q0prp zZ6F-c(Npc2JwyJ^ArS%kEy!E(@fUi|7}5D1`o+Idt8ny@U6_4mN1t@(AS(1?nh~2gQ z?%&79=q9D^=WphWi+PkjA(tby@#D^PvfsROz*~p`rof#p{SVPD)+YYWcpyWM4s(Slqq+OH5TVs8-8f%kbGI^2u7*G-`3=0|XC%eCJ<+b0%4zxacFY5lJsqIjV z<|fLI#Lnms>~FjwMMvgAu%cKWluS7)Jskkg9#_Fmu^!xK;zv3gd~=s~z-JdNKes(r zaAcPelWnw?keCeR1|3e9-8jL}ThibWq3((2l(%`4Xfd*aqHEf=Ig;CS^P-oT;b-v6 zJ?iCH1+D%Sg6VO_{x}47q9f{4x^>+4$0gajm9Vu`f)1k>D;q(&;5}Jv5p-G&G zpZJXE$xKwx`shJ@x9H=4d9OxE11C9)DW=)>PlWLo`-^V10LjL?#f2M<|1i#^ay!(r z8_+;}zSM89=8;`u0vLMTvcu_F8fy9D^^2NUBEl>>d81T@3)t_r(TJv<;R%rAx-ACS z`af^qQ{IC0@DjqG$Z-5u>*q|@QS^@6Fasyr<~QqbVS=C|zO)^Z*^p+o{>FgTXCq%2 zowq%Ze8DZ>HpowOSe|*#0M4DF_Nt&P%ArQSZ3gxrA+yv9|4yRl&tkyp5m~AOALv;% zw&r53+{!$%j7F+I%O^7mDAmw&6{&QP-{IjwTK6`6912f||B!4PR(uV?dlC24_1HQK zQyt?wkZbO0S!euB7%|}LI*Vfx?9`Thuvy3N3lsrZbGwx|EHXX?1$sz)j}Y^8)DSx9 zGSdQ?=YMG-dj(}$gRycP;UKXj5F4sb26o(gpv*(Gm;!arQdW!f&>#y=IelB<4l?Ir zj%7sN`qE7T&=K6?aPhPu^$7D@=UR{7OWz8#7_gm`)_)BcPvsj*l`k_os!q$`Rj-X-VG%68G(DC@93KyLoYq} zhR{s_mUVm5SP02595O8kJwKD8{{HP{hFT7%NEYqF>0F|c=o}yADpy!)QQ8(l>n0Ea zEuGqU{2eJt)`cwbTOCaQzGb}{oa{<)t+@Q{S{nW3^*ki$pC=3*3iJ5m8g!B89+!>{ z?+mV+4px}4TQ|v&(zcSqKU_Fj!=I3TWSup|nC^O9c3iibd;?3oj@ywKczf^IO#4ci zmNbA0o4O$vod+>>O*MHhXzv2bwX-mDl_=-~~ z?my(&D;$+@U|!MjM#A2;?3iNpDxV_<5mowMZ=h~A^Qlf>+|r$`KU+vBh)f$ZI9v?oE=`E@ZMZBjC*j)wIfZk*`m;)-KR!;= z1Ku#$nmd4tsyw8ovh6vy?X+5s*_Jffp+p6Jaf)yQM4j0uz?Miv0pf*so#cSiup8f* z=Fxi9_2KKkk`g`~uB_vRW7=*dzB@O2aayIEWc7^xC6YJiX$_=PUJ%HCl9MqdMNJ1* zB3*L%GuA8vVW~rR|3D_Ga$itM$7`JiZr5@mkx6)F_%mD(;u^jfeTLru(*f=OaC8=K zO}||jM;b{%nMjOKKq={N2}Md&Kw=;uATb&dw$Z3`iGVOVRJv=UL6Gk58r_Vs?cMLa zuKfYq_uBV4=Q-!TKX-s~2#Oumy=Y@xg4`X<#98i=?R4fx>f_nCcY3&<9cR?MIw|G4 z&reT&+#$fWADjg?t0{F6dcRZFp){>zqaEyW)pqi&Bv7y0pLMqf0o{2HOvxOf!wqq?f>V~vyYokC$(2q^^3qmYt#D?ethsdh3t;938d`wf|;j-zWBgMRs!9v z-mfDUL?FQ)2PhDZG9JAbz$C10#VvIvmns|_cA0+YvRE>k ze%HqeAC>1g%B7;@_Xf!%v8IZ?TUb?cx0i@Kbv=3D!{PTP=KCxxXx95A`b21bg22u# z=Qg(m8W1+hNA*~o&uLrb#=ycN?5FKpL1vNG z4H?q6R)S_ZPehq?_!ug@2>uWJ4i<|=2AK=50@-f=6#Q!cQX^q#WJ@lYe#jc}ZL8#= zi6d9#g?B-YXP*-%`iQM&j+^iNIFovL7Z3l>U)6S5Q7=e4$<@h4GGS9AE{v;hzxu8n zWS{=}7g0swd*T!Ip$>1r>LFEa{s zyDY#*3lBb_==tJ~Nw^3BqRZ6vR&j~n>Cy2I5Nqa1rxFh*K3Mc=%3htz=`AKvXFD@D z$7>6>D@OX6g3Dp$yq)c;vX7&gpsMM}K?Ah$v+ykSMGejZj(#iVS*4ytr92*ZsKX)) z#nH#Qu5I!p2*2V>#@5KxqbjHDki#sx)wD_g`?EiZR8Pndk&R{0r}Rso*tX@j+jm2_ zQu-m(8z?ogZ|vUuzI6ZS?8Q=DN~t;GVF?u&CWbs^(pi%O8H(z#%WyihL&(!;K3Je; zVUWVL#*u?$f>>6x3Y?|eCsgxyb-&_>)^+gq$u;ULwh;5~vct0q@RlPkZ~63?{0B%uij7 zwJ^y@l^0BZj-Ah2k_F|Ly0VmX^>Fn8kh$4RoKhiG=`DuZfY$^<`|qLg8*9hMe2e_) zlF28cye}c`(y#bT0R6TCdS@oyAhUFQ$AdBBKVYXdaJ?;1jTgMO@DE4<-e}26!BVEi zd4RS~f#=ZM;e=6`97DYCgFiu+!ME*k@~6>NRAuf=2cd~P-^l*B?DH-B zPsSoF?}`|^G9wHuQ;q-NCo*9Bkd}3=BlD?>qT$viv>rJE)cug5zg_zvYVa?(zLwl~ z<>#V|yZBYNj~x1~U-pCn)i6bWT?W}$th=iKqu9ual3sOEqDo$i_MFNv!nA9@gTk*a zTjTnGH*0a>YQ%{HcM0TTzby^^w1+3qGHc1o0qpSclWx<44)($32sYg(K37!kM;-Y_ z(o9<&x(nQqG$}*=m-H3@;wp$Y`nqZTA%i*U%Gvx}?As$+x@)3QgQ4eNceOAZ<$8qRnR?uDYa}2uApbH^(KXa{0XJ{-M;Sd}g3|BA z;Y{+m4)bY)nKPk@yX5xV zlan$(PIbmxxitJ;{w<}?eB1He^~C52pFSEzIf6stmpH=+q+t zK(k(oH&i*oV0iKJ=ry-US{b~noM0hx?A65y@d?K;SF}uCen;>Rl4db9l>`XP`;iRS z8x|l=$kr-kaH5d@GvqY_*#NEXli#=}NS=t^{w;tqvn>}+{ClJHkJKl#^cG|#v~JXj z{XI-eO0Xq@7(U=ztW=dK@jY>*zv4>W+SbI;BJq+%kuv1Pn8lkw`YAd(#S!d-yoDRvYen_rO`eieIiRa$=d`(aD2;dfv4?$3;0UgCnB^DJVfms}r9 zdp)g@$a?X48PM=$(6N$!dnp(??fqp2ZjFqR19abVuoNa{X|IAYcpOial#6pfgh^&8 zXW#zu%FtXq&3Lq!JLD;ds-Q3jz!(T7A~?y?jPS5%bopRP^{7ZCVj#QJg6v(%P6!gg zvO>V=>}3fbNsb1pv(I`t+I^go!&JOZ=sGyJvn*$*2IQX{sOuM zgy@Y@!c|JIKET4vjD_&E;{VM_hFcZ?)#Bq%f0BMV2``cP^wic+>kQcDS zHCR`m*eN#ukf?vF23Kx}8W~*U`VZRuQQYx`)I+uV3&JAyEZDWRQK@G?x1MbaOyZxM zbOA{tglXsnXqAB$i}aN?J7Xsq`Eu?X!Iu`hjr|V+K>-Juphf)j_tK5}ywbl{HSsVF z?3bsfv@JaB=Y(eHJhXpmsm2GU^pvtU<(?3qW$Z)0E{5k#aH58*B}*3rsSQUqInV$n z$S#0R;e*+TV~Puz_W|Q{Hzb3#AwI)dJJi1aN@O$uu%AmvtY)d!;}k+KbhRg?!=Y!a zOVHIIRt&d{svh&pGBCzClOeQ!^h!P$xd?7czU;AgT!QEnMc1l*8D^9<}qR2cK zhyQjD8a?oUWZ!-c3kQ1$0q0MUo5f29BiT)aw@=Z+#gT2EF?V7t7H~}>p0Bv^{2#AS zUFAnCUr)s?Xi7${W>&KjKW8^`VM(^Tj9m$l+}O+qLy5FtC~9Okj*VI|hhz7LZ`Upp z>V9xQPnVsmZck#0ACgr4)KE(?LYvuRnWfpTAC?LZd+IeP54a)1S{6cc>L^L~R}mHp zRL|!2M}0I6fQGI|>}MP6kC%p8$>w@W-uW8OT?0T@{7m) zVVx79s=8V`Gxa<)k+Wa?sGyHXSwU~6^aXo_P?lWDyog=pnh#+B2ftzIqZw6K1}W_P zm_fdiu$ff!n7Tz!TsAvXQZQ{TH%5yV+-1$YV-ZORwnNK4|z=;(?(7~s}ggr2gd&BG)3 zIk_6a(8t1kko1`LMd-&-lasNB8Cj_UtMleTVy`~1d96R@L`J;K;7wHb6(*n|T?=fw z>>jZ<4acf%nf2O=!B+JOZn$n|Df~P;%bS_W$VhglY;2Mj5Z$jX|AX_;GW5ZF-cON( z@|LUF{g5MwD)ajvT9U2%vScb!f93?L4Y;r-IWLX463d=GAYH$QP27eMl9_6><>3J& zO^S(p-9N(g)r zdz|?G4&m+Zdn&a~k@>hV$nBW(%tSA~U2)-7j;$GQRH*>g(Rf?j0Th6M#%&V9k9N!4 z3F`ud$0*|>c|^!kiE<+seLvc<6RNyIGixW=Q4`Q@9CmJeLZc8#BB?;OuB!Ls0$NF7 zN3Yz%aSfpo&Q}c0k{ig9yMOP@k0#%T;e9Qv{>x5(>1XG!{I=F1PY++=j1w%w`hGbd zzGV;H6H!T%+rBKh#_?wl*=8Ya%u)|?4z@<1*PPc>yUzio8G#6D0O#GKD1P#UyKGF$ zr}sa-T76XSlV;LhS>YN|;;y2bz40uM`^`;x*n043cdTdKR3>iGLm2nv?sBRmth?7ugZt&WK_FGbHyCiP z>aie*e@*sFF>7VNuzu){NOMIx%3)hCi?DZq$28~vl`J3%U=DGzCT<%R^p+s*@j9JS z2sn&w0fnsO*`%RMG>|PaQv=Del!faW9Ht;8@Yy|hL`6ftbrnEz$h z>2_}4f)w9+L!jWg=;1kYQOVNf-L#@0-_+L|){8C!`kSX4B@VYZfqE*rh`+Aw`1+Mx7u;nU9QnDP{% zGY)@<;X#?T_q}kqumyg5w6VOkLUWt+y%IRna3=Ba214}cl&f>>Svvh(-|xwql7(4;!mRR^7ckDop(UpxK~564 z#jtZseN(-iF}G%)4b}Sb7?`jo3_dH5$;Ll+^RWLB)@mMDdmGXb^Ir;s&I~23stag$ zoxg4CdFyGul4csT6yfWZwX<0WY&p2tl1j2SYIES@#bmxoUgLV^un-Yo(Z^4irC{US zG3l9~udda1x@G~h%IKLU*l~JEOBs-2E{kKgz#eyD*I+oCN3>(^3j&Mtr4W>u9fTD# zA=_bz`G_qA@#~6mJsG+RV1`4VtCubkwksGsTpd%tAGRgjR%IJvG5=kvDYnk=^vy<@ z#~S7ty>>^&Q0@TqP~@;~N8khu{(L6EdYclZ-`C!x-ue|xRn5-0-iNF_VsCsk`}AzI%ieso@i|4?aqDj&?h@X< zWrW{I7YznoV!0TX6|V)m&(6i@Z>x5b%?R7!pZ=>~_B++T`C!x$s`tVIT#HFg*Nl<=n;if6(=|r z4M+-e3A<#kga6vXRW$~^vvwh9xvfS{aw<*Hb)RquL)xOg!QP>C@gIq9&olJ3b;zL| zPh~LyYCl8JnP3PeYYp>=w3~VY#1_^jo@8mi%%X>eNEr=$`4v@dnp#bUaM|iqZP=yk zb>=<8K)0Fp!yEH~qp#^%C{Z=2Vj&5*0@F}I$uKlem7I0$q0#}W<^GiFFtr(MG|yr= zuaHY+M*f(t`DAWl#foo(+>akP%iyWCzfXu{UE48?7bNC#F#4H9hyNGmSII!Cs_&Gb z`pkTcyn67+H+-BP69UYI8L>{Z+B+74oM}XthQfebt%K08)-gunL$5~VEOWpLOI6%! zq!n%M+$I45lTL2NfFNiyj{0S3UdY87&@{O*{Gh}l9=PNH300tP3m#e~H?aJo2{})5 zeQYwTe)GwhnP$;WyyAg(H}oJ*<%zEJ(*B<#)ms+JJ0a$AZi=J2niMs< zdcdN6zyue-!IJ#AWL%Z>+U@#{qF*c@Os1}_fisf2Dhg8R0(eA^W>ZQWI#_>CL3nf* z()F$J7ZLr~V{^Zatqh?XKbFu-%ClR}=?Ry6Bo=`0m&+LTWhBR%fEJoNbfslf@GMy0 zQ)Nr?rj4Wtpe)ydZn{%b%v|8+hH2EG;sP5Gpxju+zU#t+&RCk?Hg z_0M0pnB3eCK6`Y%!Lyn>1;NzO2T<&&Kri!Hs?29Cp3L>G-$wj+O`CfscaBEKFn0JLjKBK5O`*@>xjko!|FbRo*cyV9IAvyiOH*TCj z<=3JtI@nm>lsvXUA*Kuhdr0PFtu zV>js9Q{!l(pXMq`8jty&L^079N%NYjvsmq^b#D`_w2-OF zwM}{WQE8UQCYAQ*2b5=DBO_`CiU_>KswH5P+P=DTXDDPgmU7JpKs|=Qc4C!nv7pki zHLv)``ho5FOMp-z8`|%2jolTQV$ZoZX+L0aRy~SCEKqC)wCR$bGB^HHefA&8${rGHn&7to3J**P$x=a0b)@$r3~a>c-_7y2?*4 zJicp>TEU^zrcN9!$(169#v9$t{VxRyK^~VPP^R%McFL_9;qkAB&V1V+9Fr&2SK{p{ zF1#~G9{Fkh)1X!}bWy+$b12SnGoi{9)Wr~%K=)W_z>r<8J#+DcGOF9cez+QOIvZOK9-O*D|h+V9FIQoqtxeJj64k7N_^^ zYtLH7Pky}9@DZP0K&Rr?SAFQRtoR*Xt{?JmDND9Kfk$05h#Jz8#B+Wn&8Q?ke{|GC zPf+GYTw77U3{`?eH!7}X`0&U@XOXbyRDCM+d37;mI^qNyj`@O8^fr4Cs6FL*azt&YO4j%WXB%2-v%FZwb#5muf zyfu@|9IVm1WE=TplbSS1|7TQ7?H<0oC@UDhXJ?q*nC^za4LgALXcJDD9uY ztJ3lU-Q$4HJLmX`G{`gCDqrdsEj66YN4lbxL?_D4t^gfp0Ql>%;OG25`FOYzX8_z z{)7Kt+9O^rS~gv?956)aEbs@p2*%uVoYOg%xJy$KzyTL(j5dIsj>SUPN_F?~^rJzl ztd=*mn;=!`>IH`IOKWez+w$=icm_bq5G~R`7jShmwzdnKUJzjlnD!Zi{;a+8h9eK| zn0y)(jEi0;8Jgxb26h>R+8Lt>S?yTDIW@iS;kVDyKR9H&N6yVJP3rl^J&s;aiFWv< zUMde!Y5^pn{XdupYZ$`Mdligqr?RWhzbK34>4TwFv#S=Pq8NpLVAEpf|z2d~*$n-1kMNPmrQtM0QH%MG~^+7_DV`GR(q zJzpy*VWZa)=^A5ymLJk7us1RBgzTEEp10+t#%Y#^lSTz1+0HlftC1+D!(q7(3 zk-rw_(K7^LF=^QyeY;+i+{>WbIs0&27p7R`gH(oGQXrwbOX8bVw&szCN2vCQGP4@Q zzFuvF`w~n4X6<})wcp~$hcn8L=cJ2?hA+8mfAh65m77;~YWTnAm&RHpOLI&U6z6S` zG4yqhql{s=P5BQBafOaMGG*i0^%t-@1NKV_<%`QE@|s(YOx$0Pg##A+OTz6!frL8S z{vGc=o4U1@T765AlmG)Zu`9bZ{c$VzBGioh@iO#S)s0v1tC z(|i4%Zd7Weq5n9BRVK)HC{xjQ_T^_9;y(ZRdUt!C#pf1J>s&ZL$24=+J+Np~qBLve zcL}~02Hsvigc%qc`bx8t{H8ovP2La@FuOBpk&b55 z<2CL58xk-@h?`8U2i)i1#cN){wcqsz%Y95pCf^wJHEs#Yqf82$W;ja#nn|bp_cOMJ zLNnq0e+l442r}ZJ|2>1apN|4ARI2-Fj8CxBqk|r%LY!1iZ-)q6wkJ z_HH8-iK0OxRG>7kY;w5pmUM-2>6+tj03nuk@o#f$X7x*kq-U z6p`#dplAQCN135$4WYyk)s2lPv2BBypjwPeQc432X|Fx6SFqd_!_A&-{iIZVZB0IV zuvf9m`%$^&*SD|aS)$Y$C7v@(q3r#>jk0_iDqAsYaF84ie3IE;E3FTIGPO!irkx!0 z1nov*R?mEar$#>_H7TqBc3ww$v-BChqm0e+5xMEcU`ZCDlDFQU;;e8nemdT4?@yqz zS5UyBufRdrtn6#QSfA4m=9;1--;~t)NM`f)hX20NHru5HY?ssTjztb5mQ`BiugqDb z-v?ScYc8b%*Z#CsswtV<@}+Ywr+GXC{i+L&rCJsWVFINBhMT{}>vlIHp6rvSdb$0K z#@Q7Sv{pVF7<~6%gl5NH0Pc)n3j_RE7|It3H0ep}yyPwdFM!^?zfrU)5vhq4EiHRG z-MIzBE#gmGqxFx*>6YbdVCmg>zE#pxqa4Hfi)^!Z@n`D<8>ckH%?9M`TGyq&sswAMjkA zzl22KeGBq+hwK9_QH#^K_T@7|8L;^Km8?#=ZY5G!o)Xh`0%{}-vNRIpbw=qrE}mad z$0M7@*UpS0NVX$3n&^B%SQJIO$o;o&jC6BuM?LyR;F%AhO1tmlUNyxx#$@@d#7aih z0?-TVV;8v!Ornremj5=Vjno@0`t5qb8EgPDnVtP_=Ppi|+hi7`t6mrr!z*lv2T9?q+xT^=f&u(Y|?>&q{_2+A@9n*Dhe<3i=EU(x7ncYXqEi$Mr zTAKzm9KKi@_NY1NFz)3!FVhWo^G&N=1vhSe$wVyH{bi>pSMIoYxZ)SyFImk#sbaj*uTHZi5Opt`@wp z?_EThJ=#@^G330N6(nVez5HFOWASBo!-7&^dB~&s*w#uE?uMD z0UPBc!$R@q(_DSE4yG$F_~u|l-kJ0=MF&RDarpff>ZCUlsfW}WEm_}Eh&5gt+}7t* z|MgnMOob7sb2Pt>saz)vK;pqIj_Vj4UzHv(apSaDQZf&htd(I6h|6 zXZ+#9ow5B~vsQPed)l4%{rJm*KdZXq@!%n_sFM+yzv9KB(;Em<^us{I=C78?pG!BJ z(m=sYsXVphEaKr{bCKC8H@C7XI(E4;LKfv9 zA3cY{mDIdQV^b3_qKT=}B*?R$KoMKl;kxpb6Ta98{Y~R0K_h+dKzUgjf9K$XaM5b; zi+W$T{fIl4en0gCM(Az8W4Hb5s-I32Iu|(pMzIKr%gsB>` zJ=Yu}6_UR49_j7!)Zlv~OQ;LE^c@`W3SWIa`*FY3u9<#j~j&PjYtn%kzrA3aL>9wd9XFrR*oEc0gCj?i%gt+ zc>33=BKG0FCL@T%z-_>ahw?0r_2+jky=%pz{1%neza;NFcS4ipGDxkOxR7mlk6bH~ zUDC5uRx3QMH_w?Suem))S9buWwy^l^>A9o?%ScU-0k)rpzfgBO5eJO(OA-ml=o+}C zXFLr~jh30!fdxO_E-UJ&P;wZ-CsI8`aY9gBn2q{_SckehDPJr!l+e%pW&(+f$B!ir zc$hxB{j5i|5x7O4j1HU?rZX#qLNFA)J92E77&&g7#zx=gGA5s-mT3|>-#>VL&>qpQ z@~jv(ctV|j?4eZap*jpL=jzi6S=Vj)VgnQ^y^-CtTl{534UQ00({JVxI(frrtoIA> zRbOngE#Q=cO{dqGEVq`LfI9$4Bg$W%t7;TD8}uF9u{623vQqIz63p1wl%w|PXH)g~buhgF1y zlkRXoG)956uUPvEN`HMGYW!Y1Sx;S$c02M^{(Lh5R*!91CYze{d$q5u4Z0O0_Zv&; zVzd3}B_BQ2vGR!<+fEXW+`OD&y%+?pH(i_tdQ=gmin@;$eD5}m<@wRedhJe&`lkJjd0lG(u6xuJP15y|*^I4pH!7s|EfMtn03>tE03 zYD>4bcQU_Kq6(dkAnDvLTo#M6SP*++l;2v&<@aOSwJ9ZRk=ZZ@c#R@^-@LB5CK_zn zV=Z!!kHtOg*D4Kj8e7&5jhD-d%KH5Wph)X&v-EtFF3}wODJ(q#)O3z6gq^LIE!ErY zX3}rTVn1Nxq}@9T4|Ld7WbXeSTQOOi5O$K+1zpFM5BMHAy{sP4$8JXN>NFg8L_ns> z@T(14cWmF8_T$W9iM%A5T47Zc8iAR4bpT1v4Wh)nE zH*EXidCI=FZF&%9B+@*h?|F4V)i-VE4tM|lrznMw#3>;zzq)7Q?Lo2;?51J2a2Bq!=bw)EN> zE~LEs_U`8fui+-}x=+G}#a)Xh42$wq=zCQ(XQ^TTM&_U5bLLV8uRiSoC>S&7Z)Wvj zS2nJ4U0+po}8^CNMWPMsYK=gJ*u7gOaQGhnXG)(BveGL93|Ex&j2zl zKl^Y7wCqqkpK+eA^?J&^oA1Or8nZ3fr$g7ie2&*MfpP1`d|*>Li;K~>x8G8JSZ%@` zm`?IUKmZt=;Jp@?fz0N&@-um636i;R=C{r*$m96!qFr6QN8i~{)?#Fqm=_K$Cey2t%V3?xi81r z2S=(-1KH{k@9M8)2=UqV+0JL%BgUN5VVEjI-RmH&S5ITvIz0`PA|n?(M#6&RL|M@C zU$T7I64X#aKey#jd#OcD`T+H*c$~DMWqQ2|6Hb#2tbS!NAGwwHk3rMmE!oQu)`&9+ zbxZq^@2&JLft?YTA*Fv>6&r53-#zgImNtp`UUb%L?E0?AWylDA*mB`C(cG(!IoRHw zCYrfxzGbMj#We)xT5vY{aUd}nE11jv+)>;>N^JVz-~hqbM?9FP+E7?9z!xljeBD`Jd%&s< z1JLOx(v36@eC-##S-o?@P|#xb#S8!4E*7y)zxrLdvKWVE z0D`)Qe$2pltJM&YzRZV+r~B+jeU=c(mxa60zR4|t{zhkMrN?pSJ8`lbgV447FOiBt89hoDJl1BQt{U%o^%<@14b|%v zO3$bxAm@~^r#&^r)UfD`yv%qVQA#8HMLG{yR+9r7D)vPpqH{o(A5(Z|T_JEYcluh< zeEE|;$6e6nSuSgP^Gs2``=sxbd3llu>ZoUaBUFwL@(}STlb+mTgl`iiy9M#|J`Je# zbcW}isq4_Xur2NJ`vlWJzS?y26&AfxsdR7uo1Rt};0#%No+vFj9*bE>5^I5ZOkuAlc_kYI+c_Qqnd zzvbUB3|T^$wqBZ)&f=p(E;@R$5Y2ZA5DL?^9~Z|7ly%iOsXyb8;-{qN+ml;xoG9?= zR*wF^H0tj+e@{~3pD5ePu^KxfZH0=zW}5xCn&&cMo5dt8$3VHn@WzU{v0iS0=$w+uerVnx4l6g zM_hhiyjT?{8`=racI@l1bp83%&**eKwuLmk#<%BS zyhXOTiRMIHk27N6?R)CLNOqtz2tqe%9wWN1qHSqFngf=n+fzQBge$RM>-K~VOKc>a z570U=@#!s6&Yiffbv_Rm(XrCMQl=|-K7SE|9UFn_E>-#)k9F@OLFLw4K;}m+aTb7S zw+dytnZv2;pVdlawGS>f<)+h-Nz=p*X#sto^Nw%pgA>Z7yO$lP$KhK3uu0pyV<7AJO z626iTcdhXC&56J{dKBml2-rQJ&<%U~dF| zG+C!`GIq^#<6DnZv&Sw|x+T`*VDGErN15|{@BVi+7UO=UMMW{$h~_B|f$mp?f5)DV zde0BK7>XY;9+$i1>L=RifwgOE*4L%$ZM75kV`JAoDZEfj01&s9smkI3NvFo1Bgc+u zV&@xVV;_{J6gI{|QhEx0JUQ$8%k{u9J{Ovp4F)Axf-)hdxs=%b-u)%>YTUv=iZXt# z4lnc3tPgNc!ENRZyt77DZvp{*<)cO5@I*$tVJ`|359)T=h@7`{#7DqzQB{N#w+(FG z>_ja#I8=Wo+6oTYCA_Ovz%-X7zGR@31dtYifQ2P!(4$s`$YKWgZ9h=M*99# zE;oli4=|0qN~3MJxoSCVhN$M`r}^e*s*gjI1?S7fam~vr0XxBu>IA_*v6LIp{-=H| z1pf20NIy3-u8BP(Zr#K{JACGvX0=g+^qQ|@ zgh-3!H#o{^-sF{0E?CL_CDy*0CvC6$#D`E3$aP!UvmZv_*{oA31C9LIHTd#f!vAR< zE+O$t?&fY8M^b@#D{aHalqf}Kk8N7cZ5^ektWDA4=OP;9=Xz64ge+{1K*g39gGoe5 z>`Q5C(sYjeeI4)XzJ`NQ8@9*%)zOiLba(+mKf)PZd5aXek<~cOB!qUr*GO%W$35hG zNv(1HLR-GaAX_Rh%M~8sbsGJK$G~!6-XO{?=Q?N?NzpT)-InhEiN2o7di23PVk_5Ls9N0<;=)EzGC#CmQZwhiKi6(P|8AYT$v+L3 zjyto)reEH(s@Rff%XWJi4qtbPlJ9E_Zo78@)iWTLl{^4?1X}= zLFNx-P8$n>{u+ABy;a6b5@d_2g}LrME%CcL;ZlJP98MizBGb>*d&JV|T00`I;5Bqp zt_xMW^JUm-8R|C;flco1&%t`dz`AJq-d#eWep$@Q>g-JH?0f${RYrP@MAX*%3L)U} z%9WOVmxZ9_)kfyU%m6Gb3#lspOK&@%V|%OaCXKqX! z3eaq#AY5&DUT%2zV})~)69w`M=XnOU?jux``yaNi{)Y;Rt^b3655kmC!;S^PL|lI; z{^`4l&a}m(*5THSzKZa&ejywshZAom_PnK@y*OIV&UDB4H}bM=@3R~4h3y%kdJs*n zre^-?3ntJoi*xN=kJ0NqPbJAAa9E3<>=}7nzoLI4c?%1wyxC-Grq+Y0w*iFj(&u=r z(Q==%rk@5Zl3>9R^_>NXZ=29P#bczQ=-8#1labQi$k?}%Lzu~5g#6$G4fx3J{ifHo z{xYfy$+G`w=axXs)+E}jvr}-{bfdltZ_Z#T0CfN9G*J%rb1Jpmry}1`WBd|dWF?jD z(+9&+M%rB;BO1_a>BAM>UlXrxzPh+>tg+(GFM8CQliKEp)Yzlt;oRqBrE=ZmaT4c* ziZB`ZY=-E>U{3OM*d9ZB@>gLL3b>8AN)nlpbzCXfsa5zF@JIy|z}jw>VR6cd2Dx0t27gx%Vh87OdW&ryd%n z^HXC)nPn420{MOLq1d?PFy6b5Ub&Y`PuYu1ej2eK2+o%l0skCG@huiAFZelYEA=G?=vTkj$K{W<{* zNwR~0THGcIe|-}96Y%6VTj8ik*N@jLoAiV3N53iJ;EGg*WC&|{cJizBq)N9F25Cu zRkpx*&tJsemj4rIP?Y4%Mj!n^5(Rf>6LkAZU3$r=0KL#p0&hIiuII-bt#kmYg4fdig#0eE#_agl&{ngZ(_EWgEyU&Xa zM$_(|pnXM`$9xe@*}I_S&4u~m)sP$aHtD(O9Gc~Y&Db7>aVhn4$-Co2V%gF$zt=W; ziN@F-FT_cY^`D@^7yL3znkXc4AaYyVj3K z>g>Npjg-x{;odw(DINTk69I+xr^pXAzyxmh3P|6nQ!l(30LpECeW&$$sNw1@Zw=w; z9fSa{Z{3$Bvv%$l`A`lV`dT8hR#8+WXv;ygGff6*VTNvBQ?NuG?(MCUYa)ujB&H?l z%Wjf{KT+3+`L*Wmtw?=h#SsDppwur>o9IjP1&K1-(^mmj?Gfu;pADerKhaCgn|r4R zs>49OXO3$DEnj}KR%?Cyoy#astHJAforB9n+OSkDpj>~-pwInN>S{c*8-D6CxGsOX zF42YK3Bn*e+C@a+bR7mgz$iDNC}4OxVCISH=mvfb>>~@?vWNTKNdAN!Be5uZSliOW z3UmlK#~HqGLlpeHc0`Y_BQ_|W9Pon8Ph1W~1N6D5mKgl(H9+XH+_%2bx!eWZ2lQ(S zZLCI9e}~3je^ak8q%?|9H|i9Vxl`{wbC4nNM{9H|ba!RSdr}zevc@fyDabosQR1(Z zG+@2zgFwcC(yRk20Dyn@VJGRXrxaVY*a2WQnfrd9m4V|j_s(FPVSSW}MV5{K*o2Jx z=&n;rU2>i!P3Y{#D===ExK)z+JH-7X_Ns1D*af?^b|%EJbTn(6IemuOcbK~(e}z~N zwDGlIbG}P4U8%iENE2a%{CQ~g;0MR?BVJN1&7_S68@3<%eU{z7Y@Yn~S-=!uysb6$ zs3mm(mfzoh>ayM9;NMVx51m*Gz^(A`JZax~uty#j=jr|hgAru-O_#+nvL;E8cyG3t zc2oAh`#Ldjdbk;&ap2|QtXvM8?LWjGFhVz$hhWEv5$KK5-OSH>-Z4fZkegd9xxdWN zSfoFn^jqv=WWcRfBc$Kj^}}vO>3^WJLRTZv(>?`&dfg}8zSo(LB*tU??b)55Rbcq4 zDOBO?SnumudtdykyI!gR{ZXc|IY`c-dn;b^CaA{rnv2k9d-fG9-(GJ~^Ev;T|9nRV zrE*!xQpI7`^v9dbJG78+yR`TwTHG$zT$}GrHmAjQJv2O~!uZ($buGv6Qk@x9j*{z# zHx9QD?ua7QgkdU-LHNnIFREo0{c3Q?E#wG7$klCZK4ywtHo$!ObEq-FJ|6PoE0>cyYsdM#B@Zb`aDySCzce- zuj~s73nQBR8|G3Hoi^T`ah((SS%o}`WyyD6T4sJu}zw}>S@*jLN8 zPU}kH$^AikPAOK1@HV_ONU29>+eWS#bt+w83@FUF=n>Dm@kwsZCVWM6clJMiK&g*6 z0u6<&87z7rw#UqUXlnk$Kj4Igi0j6?Hb7{m>7oZ+A+bU(pS857?1g9s&0bpW}Jz%`#dz2&LPB3@AT_x1NxOvXlo8!oc zo8X^Hd(l--XCiEfW8Bex>mQ^1!CM92=Zo8|udRHi+U@6_uo^K4*XZ##mpSuCEKx%s z?4R%Ka6Psaj{q5}$BBe2r(ZfGiH*4yviC}lvhhXUDmA}Oz8{cz82nHNO@+E|eu*j| z{5OroJqjzSl@k$-=;o&E6r9Yhh>>sMpHW|2#G9R1Na6wmY>gh@WD*(yklAJNiO_2y z@_C7J!o6r=I!~niMf>KLW%rh0GKLLlE=p#dwsXR909{Au7z;n1B#wBcDvwDf>vvTv zr#{KW+9{iFifeLEE%B~C5Pr>^^BpF7&ikZMMc-LleWBj>0D1E(Qm3Lfi|vS7V)uJp zja~`R7$vga)c`e3hWt=VH4?GaOG^)%tf{U&juiCpq3pns0L(2F?Kv<>0c-Zg5buGD zntrpn|6cmJqdsHc+|jQXoef#ZfZ0xo*{*+yOfI&gCc-QGRtEsnD)@VUgUe}UlmUls*3Kbs!}Vk}I+ZkL;N+!e#yq1Uf-^2f;e3 zwzAO**GA+y<`FKA<-%W1?@9l&@Y0&>kyQF$V{iS{*7JQ2x5bN<(&8?~6FfkH;O@|( zAyB-ym13bdE$*eb1_}j=yK8VO?(S~Ep1l5m@B8^=&05J1lY8&XoqO(?efB;FG-&A7 z+hG$qLdV*G&vMS~)ctLG>QNB{N-tl|aHotNiTEBbxac?gAv~2*ZIiUzwvu zjZIl*_qQ)xE-mzqkAfA8LPXs4tK)K!y(6g9P-5=ccLzoAW^8JGt%&5zgx9+sKu6nD zVdt}Ap@dt&hNis&yW4b;*4RZhx{Q763zvtWI7$C~K3q?mol95JMdJwk1eGko`r;gydw{PDX z5}Ef3Pha3W=diNE80W}yGqu%p94`*bY2_9rx@5VVo12TBs9{_1v{Z?(Nem$h=PwJ( zMJO2by$b&@f@0V#_Ik*Nl zNf(FJ)zzTW!$TpEIY~7Y;fAXisd4V%%KYAY=fSIZVru7oIwIeKv`y=s4j(xLqXpV_ z)Q>rjI7QBzGr7-69v|=I`g-5hcovqG@dCp>wYZJaD2AxXSL2226QHdf(PGST7u0Up zd=!1)B1@n?ERK*~v3A_Rh3!gww4nXhzNZ7*EGfmz_OQhPh$ zu`k)AygttWZI>A!bPv9Zm6Ax4eZzDVjKf4Q4m5ETvyh z{F`u%pI?fgeO{!?a<^8QO&fEZ0GXA}xYmXQN5roO?6cQ)<*rB^(qij4c3tx6dM8#R zUwWH=f5~j#$8`;;1~{V|fk>Ue+%hCiu79s|NjUFbxKj>KAJ)|&pU?K23N`UF#U}ZG zGoV_Ph9=n%E=!E4(&-zmNHijXHSgD66IcXNq+PX7$z~e~N{&Z}ZbV7NN%#UW2Z{HS zW{3bn&8ADiwGc758?=vIaI$lM8_-~e@){i1K7C0*oA>iG{_`ebPh3uF^woRFu1z8B zwIJyy!hEk-LeiKezgtANsXZ*Spv8fd5)psB>Ka6IVe8Q*S3<-7Ks;=^&{$IKu5_gA%d1WZcZn zsnJ^qr1`Bd*Fz&M>lV+}P*xKp(ov?Hbf&DYI6mpLr+f{$+GSp_juPDjORJ;BMddcU zQOnHPg^qklRtT}c;4#jBh$`B@vCkwe~kJ*8{Z5P(;}T-|E(-2*ec~Q>lqAmAe;p?Rvz(1 zv-URDh0SiJQ-2_DTM_!}l@>D$pDk4gjMvWBdov+~?oPTX30c-OG;E~U%*^+{5uY5| zUvZHAiA4Btg9k~^NsFI3rC02bUA|jueh{^)HSH&<(Cgc!vd^!gwC12eS&#S0xEs!* z=1XMjfj-WXdoMT6UsjPj(ksp3?UYrKJ1I`mj@i&5RrDvK6<+VMk*H9flQk@9309Fu zecFnhwyfU^`U;whaFoDG(jvVf)c=Ote4FIwK+=|dhz)rqTsjk- zRpEcJ!$8JDJLy>}Qosl?wprb*C}%rE3{Yib;~gbH{Efmo7a81MPUA39T#ub4A-W2u zFIDtLzPS+#mKRD9GcR;6NBqP3IZ#921~ zT6C{5;6qS&%Rt2~(?P?nJ2e8NoXyO|;kGy2uXeY$qD_w8m~Qc~LBjaOIf)J^ zdCN*h+Sk^rx&GKMykUHbeO~B&4f!$^SEE04y{cjS4L$Sr2PCTQqn4M+213dXdqc_B zthL$O{ruKLY^KJ`_#f$x^XcxKh5g}SWgv!E1rE+(+*adES99p){6OH0snG8q7jIWR zAg))mim-L(*H$F6*guk4fgD>p6*;a-=rE|+Bc%_|D2K^XJq(_;!o@Ssq_As)8aFR zw{QAh{6ZiQdeG`w^xg-#!bqSAMVsT~-XI{mm2r`$1 zBK>{BSg=<6jEqCEOXS_`_-;I{(`WWvQn7B`sU0GyUylmXL2K!t+YC_PieBp*X7^Ws zR)QXXkx1pOcN)B(P?g$+q3o%z$I6BzJl#4;S}`FjQYSk5LgNGY%7+WwVf>CL7H^5# zcu&CQ%C4301^>8MEULOrR@F&{^HJ)P;h3_}Ze^~c6X{9Kq8%}u7|Yu^{V(1uZ^UUBDa*q$zzaOofMuO}>U#&?+L# z*Q)TwM?ZF}NVft*%{6QFFf1V0RNL6^XUkfjDvC+ei)imk?=p3G&DQc%{XF{-`G=LM zPcLnzt6%jGc5lrcFfPLkfr2609{JhUiGBxqk0AKpG(3ROhj&&SFi zzSx&Y<4>R{NVEyTHS`U?Dr{3$nBk}Ho+auKk*8jz3T&$TR>O2inROqjCS=}fxaE@W zAF(V)g2M#V%J$s+pvH7KoMet)pr1kBIR>~gp+b_hen#fwCN0Euz6!T_0IhVgY2QBJyMgWNoL+-|HCpeNO=H@zwb z^cltrE`2IuCrHXo1-{F$7tt<`a|RFT1(}9(}>Ge>4+rj zH|`n7Xy_!aF2;Gw!;F9s@rsBV53Sx;NrL~u)*f(odZ_{O%9gt}3tg_Q017I^{fjhC zk#A;CJgjem7?U*vM1FZPvwh2TdCB#6$JHZH!gJ!39DUw+cBaa*Jr1Y=zkTQPsBO^i zJGJ#YJQ*|$Lq~GpY7z}0Q{3(j21Ki(DjfW^_5_ca34Pg;YxGsGou8XfkY4XhlQ{hJ z8=Jn7Q$Z;Vat{&a#u z&8^lnplT9UOyBl(<*Egcjr`TxD%v*}5`JRT!%TpOl4#zK!TT8eM=#%oTZ z1^sI_Ta?9{Z%{D_=k@%-P6pZotk1MDUpHUZhsCiQG6CmAO)_B2i5WVpByB8lAQqsA zy|TrTtXqTcr7#rHmdsI>;kpDmZgt!IQtPJ;=nM`XIqc9Dzn6Z$f@ihln_zAJ44 zA-I`0BnP^&$!c3T@M=EYL9c;*EvWUFXQoCUVYF5W|2xG5eq_8~{P>4_05bmzd|02yz_xuq)lJc?373Ff@-hzAkyvuLaS1(ljv;`?Q#%WMR!FXRw zyUSx;Pim%c%ILK)bO*yvD$jb{W;y#JY3+_jEuEf=fna@k7)FnVyy5>RwFH)>e)ZWIL%yPJCo?AETXvdO%)iOPfU&J2JB zm}++)l)@ilT!53OJJI{dusL`YNg28<32X898L~+AznQFdRLJ!?eI>a1GVjCw5P=4hCFm1m@mzu5m0^9`z;u7@?4A- z^Q>6VNbg5s))S^~#M!pM;tfOEA#wg-_4B+pHR=fB>xAxF?18i#hXYB^QiaYQ8mruo zC$_jg@uK5OcQOx@g}_3Wco*ys&z^y^V7ARTcjrQ3V&_JB7aoe!x1ZYh_o!-SUcp^K zVv{vq4iuzKuA1*bFmB)Jk;apMC~gKV-5f10fk-XnC7tvFD6hXT24nE{vX_;X6IW#J zQwrS7FZOb+{oe;I;P}04%zY$nA)}9}^rNy7<`n3j@GkYh&<&&}dV5*TWq)`o=r4Lc z8HGk2S(tqG>4iudyKJLK-l?6Ob)zID4!jlr;f|r*jaxn2$1lt)W(NHm;-&M&`H16T#J!|WhW&6SIcnSA1WGGO)LRnTH-$lLhXO2$=CM0aZ^&EE z;?(Ws;7*f8^t8G83jeB;M-{AeQTZ$tQZ+}PxU)g7kTO5Q(;~WQ(v)j1hsl4cg@wwK zXuIKU_=2@>u@W^|V@O(k1`LMjW2+b*tg$+W+{@p~0X-5}o$YJpjA(m!c*!c)%2R;@ z`Y6J){o4yp{F{wMNCP=qJFiWaRp+9q(5M3p6P5$ENfv)lVP;Px9SZ8vZ{0#sRdz(_#|H(W`_HChHy;09>_m1l(_Jq+ zUliqWXLnJP;IT)}y?eZ>Ypt!t-NLj9{SVim_aGwxl(ad582k3~nl%+(d|;*E-xB6T zO3+=tJ&~cKEy*0jz1&_#$v@uOm@H3E*Y5lm4qY>_J3-8@APY~kCcfkCmb9O8sUp8z zrx;!I1w(`~DPrURbZhfv_1~_Qf>18xmQAZvt<3Gs<`g2D}V$R(g*Co*lPk!-)^iQ%yF7duLN})h)q%$a+ra-uioIIXrnq(1zT7G`dkof$f zB&|k&;9Fpa#&z%0xj%j2ra~M;XA6)n^EU?h8K zvBPUy5Er4FvvRZYXPE&fNIBavc8}K&4Q{VGw~sLax4{ooiR+9$o3FF}#b%Y#BwI%; zG89R-Ik%ISiZNn7-z`otQDBeRkwfg%yAv3`Ua!%x1%umzAVqg&THqOVmtKtar`}^d zST-z~uBmJ%8kZD(yoq$_6xR7Qsq$)iHrW*VAh*YCHX=zqRFHrkar<;>ijx6m* zhE%QjsS-vr@&;_BP8XH<7Ek0`5k=Vh~>uWXYGpbtHb-J2giVtHFFB%HD>Ps&w;&N10dEsO+DXz5WkYE3XtQ z=xwwll8EM~7syB@liVp@%DC}r8Ox!Ma7MwW^`-V`&$>i7T>sh~qpV>~?*g{g-j&|` zviy9Ru+`M8@Vu6$X8YgYj)s_V;B}LLfU7@FMvXsBX17X-zc4Dvo|FVI#YTN1wlU6P zxLy#rIDQ%AvR+-p!CTYbTt~D)$=fH z?1;#MP=Iavo4tc&rzKu>Oucau)*3

2BotSZ7o#=flK>2^~gTslN^L@A-u7K$=`m#%n0Ft)$+g{7r1Dr0Wv z{yI|$I*{A3j`; z?u_M@q2eXmmNEN?>hfeh?;`8t6oS!Rv$9;#rYvApa{ccm z0w%^tV#@I4Aa<HKNhT_*mG zehU{uzLb!31|l?9Q9A>7^6JO90LWdTD8dTyQ!or?qer*ta`!6QXNu4FOS4ubQrH@^_6rKAXfs5`W2- zIC}fT06fm%-H&@YKu!7sTtKN*Ze%?$3ISKBM$8OV-wQfPxg3yj-i zEWLZ7yeG8Y=;ZoA-S0@nLCw|i?0uNFRtWY)2zR8){wX4M+k~hBe62!KAHG(* zsys2kB?DDWC70D8^5{RRvMKazgamr!CkRSIsCL=e=0~;t6}P3v$xZ%sY#br$eZw3> zxdpj_y1+Xsb92p*(=gy6(%v~LK5 z5v_5+wwD@ycFmw2(bKU@W_V#FzDoY$JTAh~PN(*SPIBz`5bA@!!9n zOsS-mGW5cj*J_E#)v+&%a&5WZ9m)9qckk`O%F*0c>TRctFdH>S71Dw5sKcgf@XA)1B30(=lZcK>@9~ELdorw&Og9C2(F#`$Ed=s~_rV zox!KuOSk>XrS>*ymyj+}jGE{wS`TBl{_1kWZ6j1f!a0|CULN3;+ybJqr?yC|@(IV7 z=ofibA5!VfI3bY9aVsIrEtV~zZF5Ni1t!jMS(q)VZe zg@%bxe#^gKn4v-Q;UZmIpDI^cjU)mdArrmg769b%>bkH^Q6}>|J6H2Vq9j+pP-0{f z8gZYWkNo!|{9U~kwC2_JlZBe8?Ck7)SqvWD&Rv0B$5JzmhjhXLIo( zB*jPQ2yV5aA^^a ztX?aL%#jcWs8n;$Q(CaA=1S^cXaGT3=%d58bxfJWP(awf?mxeYS)?Wa7SD8pm-!BV z(NZUz8sO0>y_7wSp39OtPJW|;Yca$h&L!)mlX+$umqLh;ACEM%4_8jHWTeUo9JcY! zzQpYYxxCr?Co@@OdE>eQbg`G4L{1SJWri0n2i;NcQS2`$hvClQ-Q!*w&k;O=*Iy-x z^sVz|xl7%Vw1_c_s#gJAvqbeEYJ8szW8Jo*Zzp|hAAKfi#%*Y-BM>R-s>ONKp0T21 z^v#3wtUKqy!+*j2Sld1A!LmqkJJ3o-Fu<|x)S;{RA(x<#pjh-}Z1~(9yDovs`{=hr zP$$(Ttm~0XJWKtA*9Dq)+P@skGl`SpbQD2RDaM%98Oe>ULvB2pzZ@D~zN2y;9>}LH zS0T5o=N+1()oBJ3xd4Us=r{946zf%kBJJg%TUu7GxqHmS$b$V5yBvqL8%7yhvA6Yn zK!D0iMemQJNJ;2^+@v@1=qtm9579=`GZ%*+jJ6nO8$w~c@B_bo7}JrWu=dgK`L$Q- zp4#<4*gU2L<|Ov`59{}+_L)S->=Q{RI?8v@*SZVZ2oK0}Jps}Ru+=b=;=~SB~i7#KmGZ8FKb!0+5dRnP%CVSdofPUh;aw2jZJi9zfiraW;@ZI{#u0Q z7r`N^g0Fx4Blg+@e_ykc(%r9H-!^{f+24?Y5m7!Up!ioc-5@x@9YzzLuC7Qto^{!@ z{4>B|;0U5h`K77VdL643dLh~SxNCR~BeP9g&;Wgzk2sBAvdi#7C=cPo?_GL=;z12H zKancrVN2~ckKtTsvU>vK?Y!sPm0?J6703GR%Cj-AuBLKh#V2evgv)k&)C9=R@2MlX z*1rBC5RG*}Yzz@cxm5QJ?!2bx2uKq=r$uRw&w4M3 z7R#s+K(uD7qR}da2FS17JaOS|#zSyc*&+B+ZZqLS7b*qeAaFosKW=9+3hhpPxyf7i z{6EE0-G6!?6YVXkuo>~!fCAI^!smJYl&KWrH7)h4RZu}8>O5)Qi`$4jX^Kq_cVZiz z)Xu-*h*+CTcX{`8uUva{>h;PJkTiAm^YPN0gDwCco}Z4q`nAkt@RsWQ(>Jfbu4=Cj zImVFAf^<jH#x@V@Ho9@X3EWRMC5h4RzKJaJ7BcJy;Dle9a$US10sCW}qkEec6>~ z^yVQ`d^s&SyZ^)mFBLES z3Redt21IwzP;$KVD(7BWz(U=_LvSj1ZmP<8`wo|gmIAi9K8O=9I3#K_-h-?5e!U*$ z09=jKh0c)n{j7MqD4AB%xgm3A`Ac9?#kD14t8>5Kcg6g+7Joc-(&<$i5$V2!!;DJHZ+rbCw}c<1|4Pg`aVUlq=t4$&6KXDU zAw!g3?oH{DDd9B--=}+-7Meci+MsQ_X3}S%-spw$2gjdQ2#Glv z5x8{(;T|aPNH5c|@DX05iWitvp8z^*FW*6{2$k*!n6`Y)ve>B2j8J|#CRR7syL6NM zo_6_SPM4|XnYs3PM?Q!FVzg4a>CBMl6RSJr9gEQ!RxEQ@x1)P(-^{Oer;zR_b2lS& zx}k~-2Z;}O0%iz3KYp+J-BNs*r>vEcFXe`*Hb;OUfMqE=-CB#MIJQ0}ygybc-l02x zru#utlipK8Z{*JU*F#MP;E%N@cl7V$cA+;{u(hq|FM>~d56i|Cx1A7G#j5n*Pkibk zo)c+o@Dgz2>ZD((^c{}p?S9rym0#f{9hZjjC(<;vFkcCS%QQkYMbmACNMeunqcT9yD4N<{}4-F{V;Cl$4)^mESx24!*9TS+O#=}-k(w%SWM0)U9 zOtsWL*A?a^5Ut~&G2p6)Fwh27pN-*@$B3mc$!WMNT>OBqRK@YKuILSG3Z8uMLnStT z9S7a$AE`z7N2Z8>i!lTENG+fTspk^1IL1qcUh{utAVq$26u0~tPIH~Jx=k6goPYc% zkDrF%vMvt`Sq(#O%9u-L66~6#AvnOkFq+AHdO{Ydb2zfiD|^>6P4FXtT^AOn49QI+ z_A8AsGS;(d&fYn~2Yk5SS%jBw(6(w) zM`>^U)<)Ss^=K^gC+QJ&EJlPT0fRD9f!KmW?wfcN`Jj3{9uo2kh2ygl=EQSak?`DS zMVh3&2f9a@{^P2fAhK+xAnc6{x^Jdp9IV)uLWl`15oSWya^`zw|3=9FRC71Hpnl>& zqV>{sI`2VvPAnKI`o1fUO2m8w62ipPl#MaXvb;uwz|g^3D!%9or-SLc$&Io6#Fxy- z0q%R0ZaJFOfhi5G7vshbly@A+?c2V40=oy!|7dnvIH0H^cMFRLQMM=7bau0>%gyF`_XU#$K#Gjpw{)KW^LFgj1hJyvYpp+D^51ED!4m{6Z~C8 zlX&~S-;fzHRfGZ`ByViud}BzNG=1ROf$b&M57Q{hBd}X)K-YY8Bk$_t9p+o=#YJMl zz?<2NB*n;F#i%%1f}Z=_SSJVdJOOG?53ALv_0C)zrRus^okH2o!}#IZZ}HU z@vsaQ(-z|^!@P4=H-ixmhwA!nYppc13no=(w9em=1SH*|+X zy~JLnPq?Q|xFw4WbxR>|sit9V*<1@AA_u*0Et|B0=nc~Xk1N^IW@8Rfp+OrWRrHtE zjJ-O#$Hiqu{$fRlU6TjBr`ZTZbju7eVmh9yEK%rbSI&Ofu7XBP=k~Bv~JXy^nQ~o!xRIrdF&%!}o;} z_BF`9q;kaj{6faPyLOr)xr#uM#)+_KzmD*HKnUlIHVW%ecYI@f8@?D8o$!5I5AYdE zb`hInBHVWE&c>xt7SKfJn`7oQ`L+i`q|;|K&`kXPaZW?td~M^+3>D&~b2|^nvl3Xh z{;cPBDY&(Hz0Kycm5cA~?}z`ezCZHd@>6R{6>=rYgAI0(09)IK{Ioz9)>j3J?(z1y z8~$zAWz4-t#s(RWVx3DKbUJFk&0hC{Z!az{6(Ob?zI9CF8Y(h1ZpPxV_b~lQw0_)8 z{1Kp|7Q*MVmk)K)nLn=EFY%r{V(yQ@4%ZQ2r?7{?W*K}=n=dn?u{G)U7CSqHRlRNC9u9v-R-4WSQbOU_u{jXhYdOr9c8=!d{7=eU~amW+vOcex4j zcg;33=JLYy(FT4yNdNN)vliy#8z@35dKdA%AZ2&D$@-pWMbuqxKQJ@8O8PE}DLg&7 zNd)exYq+w7=^=3F@z8-b>e;nDH*BQ81~zRZvV4kJWi#t(=}pF;)YUys5iKy@vet;c z_v~0nN$%-)ALn=Yyr-P!&eVEXGI&o=H6 z77JQeoWA_z>o1`XCV7p%tti!>2bD}zy@?5sUyP!x_u`>ERux%|8{`ZSFEn(*8vxh2 zQm6-<_1btA3ucL*Kb@Az?~?_#jC>u*#~;ef)D7-^7(Y1M*k+FOq`>Z{9gp6)9w^9^ z7t6lYPPSx%I_32CK}Q|957uyU$yrRlOD>VgW{!s9UcBA*Syb)#vhOP}bjuB>6Q9@^ zhj-O`PzAVc$1#P%3<9~k3wbyHZYRgnJq&|s6!RN10ed_?W7^xqkA$1=Y@~mFi3bO= zJZ}YdauYJ)K`-OGR2@H#@qVbkif_a{7kyHIMv6Z@OC%rBEO_I%!Z+M~_vPC#7(f~a z#&l6kQXz?>0WWT<_!%)BUqBugO-ngl+hL~u-9Mg&M*NaCH5>w5P8PnMU7Kfqtl!?Y zpeuB|m;$ic3@Gyu&3^sDQr?WmXl!2^u}NMqf=wDNElfhrP~x(Y6|gNM|23**d#&wm zbN+|V#@3OK*7cD5_$pPux{4F{M|-~nL3-v}R%IKjXvSloRE;B1OpCSgsJ!n|G{i}M zh)w0YH!cTfozm!wIMdLT5rKm=+IM@&xNN``(w`xLRn$WtW8f}uZ*KlWElY*kE8IXc zWPRbvPfydJEjHHoYaOrUjebUT#Re86l774sCi^As&NSS-F11S6&%kubPXkaO9oC$& zQFP@oZJAO`|CHj@+wS=c$O0ynPf=hR73NP&5uyqou6Om>QFHnl;WH7j#j|{^>G=u_ z)$hx6=9JWz)_vd+dB}os&vg@teF?pR=l13h3;m1thZLxXy1w3~(lmr{GO5>RF@@|- zd()EM10SoQeq~w9CoNhto&Hetm#;)wJiXG7e6 z7~UlAbqHBI>>*Yni2!HaPmeZFZN;x!uudp3^BCc8KACO01q6$E4XfL>WD4-Bf4*K^ z<+12DtG4YTWWlf)29{5Xx2!^svF|WqfIx#UXaNwXVAyje+I~^@)Bug;?&3(z=IeDI zVV=QyLu2_%qh9#KoYHp4>xAH&zJjYxezndxD;B~;|E_#&ws|A&lkb_NvYj@A^7Pgj zGG)6)vzHSdBD%&dgJ%;$_>I_O_G`nN`;8&H#JA1})1{wkiIF|A{^MoN7nL#KoZDCz z4^JKbr{_1BqnEmgJwi!l=yrHf+8XhwLVYy&^1f$WB%NLdlvQ-b?tRnDL1?)JrrTJN ziSLujyUYm_!+9q!gKjNorbZ|Xj4!$+vAW?j4cxJ2St7qZH?sn_tIP_v5%jq4xc^vi z>&=a)Su%=CJ~&4uGWngbZ|iv&>FvFW@!FhXlUklm=EQdk;p3dn){z@mdlf>yO51qC zb1N&VUZ|#=u-&H-r)hNN-UBA_=81_2MHN{l7?T!(hr0|1lfSuDY}@yn@N~IMAEQmI zM$|(ews|x?i$@BLs=^YSVRuQ#70>v(tA2wwoGpYo&wgq7Kqy~&fe!XGR9IQunS?AC z6R@9+Bs1FsH~?bRwRYDRPd}JFz|<7O>V`$D4~i7yS}(^lTT zrdv{3h1M@eh1WtW(a%$-+=@p6_CU+4QRHv#U1an`AHkgfV~dzN&V^>IhSGQ3r{3?L zO%&{&b@q(8tV_MPWTWlDstMv&$=p14<2rGL%a+_rnwYg|#8#JqU^8T18*juzp;$Na zBe&wzmzhs+*rQN7-kYRl-VSb^(1q+hxqt3mZud7D;?GB)A0F>wI_w2eofB%#uMOYX zm(uo1{M{_I0bcpOq=$~2MMs{sxoC(97l>V=poZ<+$#aR?P4y$D&b<(fz8WxqO5sZd z+027F*eQyIZ1R+zkm+C;pF`jBxo(ZY$4yqsrDRG8Ir-UV6+OPs(LKW&P^+~-KH+Ce zd~}yEb&_kw!p7*iz`;CmBwq#LfFEn36}b6JZ_crc1T*+EDy3#v-?vugdk~FR)JACN ziv<5nQnBIa5%yaPDh_ClQHUuSa~J3nOi#q&0um^T$){mi7s1byrB>#gl-_#dV0Lnhwaf`Gbp6nk~oIacUZSdAwY7jZ>d)oyCD@ zzVwLbN%!r$vk!ONF=?ttsMCy{+W(m|ksZ-r9_XX^J~ZpMINomsOq%NLhjf?~Q-+uZb=NZf0KAyoM(eVRh-E&z zL>4k^yWPp`GpS6wZVbd)Q~4%NNSu92djf~V5^l$^uYRWWU56fj{WMVwO`Lf#iceSA zi{<U4vk&}&wPUEZuK<^ct_i<-EEAum~6Q#%pi0dp7xmVWu%)?bs!7TWSGG& zGJ|tF?Nm`0oUh$KU8bAn;XpyYz65SRO^;`|BRWp^2PG~e>_ciOz0~}2pB%|-=tgCC z{_GVrNMu3`>AemfN4S+_v%-fjC)9z%VbKU4Xv*@S}bCWphMH zHgZlKzW9|vXgHj2JUEFcn|6i~c+g8O$D<)erLaAzYem)>?DH^f^&Yl(5{=)<-H~(E z?QuJTHP}O@zfP9^aof$i+|$urF3R6Lw?cGGggvLl6fbB83uF;V6+8d)E#~C9)=u(* z^t?0@FnHtdw#;3j(jq=_i_AE_f8vXHHvjAs>YDynlg6FB;5pk#E|6Mn*&;sdMfH{& zE61n%-~S5Xxghi0=!+$iU#VXYU>hTq!0Y6Wh?&8B)ya+e_5W^Uzq)4oOjG~st`%bc zjOYK|^S^^cWMhCFsmR%=$Wy}E(-`G9K-qr(P4XMk|I@&lod3H-#%P_O$2csH$hs`l zOvEzSp@sd=83{4P6Ei1P$VBg4ZF$BZxs6(=WNdAM-~QJuqPp~qZuIpL`8K35;x(Ky zA{q5Mr@?b-#bNP3nj;}ye z^ZdW>|J6ch&fZ4-ZfAto1n<5ha4;Q!Nj2^M$ie=h%YA;IMTe;TkmfT$V^nndo5p}rWXp)gwf9~P4VdaC#*PLdkne>W%Z d2NC0=AALJdudeM=$urbPQC3Z+RLUga{{y6_s`~%{ literal 0 HcmV?d00001 diff --git a/img/vrct_logo_mark_white_square.png b/img/vrct_logo_mark_white_square.png new file mode 100644 index 0000000000000000000000000000000000000000..bfb6f60dd609876d6f38c82b6a0d73a03ba0543f GIT binary patch literal 13408 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 DURATION: + break + + time.sleep(0.01) + + DURATION = 0.2 + if num == 3: + start_time = time.time() + while True: + elapsed_time = time.time() - start_time + progress = min(elapsed_time / DURATION, 1.0) + eased_progress = 1 - math.pow(1 - progress, 6) + + # angleが45度未満の場合は0から45度に進むようにし、45度以上の場合は45度に固定 + angle = min(45, 45 * eased_progress) + angle = -angle + + rotated_img = self.rotateImage(self.chato_img, angle) + self.chato_img_label.configure(image=CTkImage(rotated_img, size=(self.CHATO_IMG_WIDTH, self.CHATO_IMG_HEIGHT))) + + rely = 1.0 - eased_progress * 0.6 + self.chato_img_label.place_configure(rely=rely) + self.update() + + if elapsed_time >= DURATION: + break + + time.sleep(0.01) + + def rotateImage(self, image, angle): + # 画像を回転させる + rotated_image = image.rotate(angle, expand=True) + return rotated_image + + # This making gradient color process was made by ChatGPT. + def generateGradientColor(self, value): + # 0の時の色と1の時の色を指定 + color_start = [242, 242, 242] # RGB values for #f2f2f2 + color_end = [72, 164, 149] # RGB values for #48a495 + + # 補完色を計算 + interpolated_color = [ + int(start + (end - start) * value) for start, end in zip(color_start, color_end) + ] + + # RGB値を0から255の範囲にクリップ + interpolated_color = [max(0, min(255, val)) for val in interpolated_color] + + # RGBを16進数に変換 + hex_color = "#{:02x}{:02x}{:02x}".format(*interpolated_color) + + return hex_color + def updateDownloadProgress(self, progress:float): - if self.is_showed_progressbar is False: - self.progressbar_widget.place(relwidth=0.9, relx=0.5, rely=0.9, anchor="s") - self.text_label.place(relx=0.98, rely=0.98, anchor="se") - self.is_showed_progressbar = True + if self.is_showed_weight_download_progressbar is False: + self.vrct_second_text_img_label.place_forget() + self.progressbar_wrapper.place_forget() + self.starting_up_text_label.place_forget() - self.progressbar_widget.set(progress) + self.vrct_logo_img_label.place(relx=0.5, rely=0.4, anchor="center") + self.weight_download_progressbar_widget.place(relwidth=0.9, relx=0.5, rely=0.84, anchor="s") + self.weight_download_text_label.place(relx=0.98, rely=0.96, anchor="se") + self.is_showed_weight_download_progressbar = True + self.update() + + progress_color = self.generateGradientColor(progress) + self.weight_download_progressbar_widget.configure(progress_color=progress_color) + self.weight_download_progressbar_widget.set(progress) self.update_idletasks() From 7c4cdbbdf63debe62d29a2c0f5f7c7063919f47c Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 26 Jan 2024 22:54:30 +0900 Subject: [PATCH 67/67] =?UTF-8?q?=F0=9F=91=8D[Update]=20Version=202.0.2=20?= =?UTF-8?q?->=202.1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index c1af4611..dc882684 100644 --- a/config.py +++ b/config.py @@ -731,7 +731,7 @@ class Config: def init_config(self): # Read Only - self._VERSION = "2.0.2" + self._VERSION = "2.1.0" self._ENABLE_SPEAKER2CHATBOX = False # Speaker2Chatbox self._PATH_LOCAL = os_path.dirname(sys.argv[0]) self._PATH_CONFIG = os_path.join(self._PATH_LOCAL, "config.json")