From d76bae84d6006f683d5c5fdb3244a5b799bcf568 Mon Sep 17 00:00:00 2001 From: cary-rowen Date: Fri, 29 Nov 2024 07:32:59 +0800 Subject: [PATCH 1/2] Add automatic translation feature --- .../instantTranslate/__init__.py | 71 ++++++++++++++----- 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/addon/globalPlugins/instantTranslate/__init__.py b/addon/globalPlugins/instantTranslate/__init__.py index 3e8930e..d1786a3 100644 --- a/addon/globalPlugins/instantTranslate/__init__.py +++ b/addon/globalPlugins/instantTranslate/__init__.py @@ -117,6 +117,7 @@ def __init__(self, *args, **kwargs): self.lastSpokenText = '' self.settings = {"lang_from": "from", "lang_to": "into", "lang_swap": "swap", "copyTranslation": "copytranslatedtext", "autoSwap": "autoswap", "isAutoSwapped": "isautoswapped", "replaceUnderscores": "replaceUnderscores", "useMirror": "useMirror"} [setattr(self.__class__, propertyMethod, property(lambda self, propertyName=propertyName: self.addonConf[propertyName], lambda self, value, propertyName=propertyName: self.addonConf.__setitem__(propertyName, value))) for propertyMethod, propertyName in self.settings.items()] + self.autoTranslate = False def getScript(self, gesture): if not self.toggling: @@ -149,6 +150,7 @@ def script_ITLayer(self, gesture): def terminate(self): gui.settingsDialogs.NVDASettingsDialog.categoryClasses.remove(InstantTranslateSettingsPanel) + speechModule.speak = self._speak @scriptHandler.script( # Translators: message presented in input help mode, when user presses the shortcut keys for this addon. @@ -231,7 +233,8 @@ def translateAndCache(self, text, langFrom, langTo, langSwap=None): i = 0 myTranslator.join() if myTranslator.error: - queueHandler.queueFunction(queueHandler.eventQueue, ui.message, _("Translation failed")) + if not self.autoTranslate: + queueHandler.queueFunction(queueHandler.eventQueue, ui.message, _("Translation failed")) raise RuntimeError('Translation failure') return myTranslator @@ -307,7 +310,7 @@ def script_identifyLanguage(self, gesture): ui.message(_("Language is...")) myTranslator.start() i=0 - while myTranslator.is_alive(): + while myTranslator.is_alive(): sleep(0.1) i+=1 if i == 10: @@ -318,8 +321,29 @@ def script_identifyLanguage(self, gesture): queueHandler.queueFunction(queueHandler.eventQueue, ui.message, g(language)) def _localSpeak(self, sequence, *args, **kwargs): - self._speak(sequence, *args, **kwargs) - self.lastSpokenText = speechViewer.SPEECH_ITEM_SEPARATOR.join([x for x in sequence if isinstance(x, str)]) + text_items = [x for x in sequence if isinstance(x, str)] + self.lastSpokenText = speechViewer.SPEECH_ITEM_SEPARATOR.join(text_items) + if self.autoTranslate and text_items: + text_to_translate = self.lastSpokenText + if self.replaceUnderscores: + text_to_translate = text_to_translate.replace("_", " ") + # Perform translation synchronously + try: + result = self.translateAndCache(text_to_translate, self.lang_from, self.lang_to) + translated_text = result.translation + # Create a new sequence with the translated text + new_sequence = [] + if config.conf['speech']['autoLanguageSwitching']: + new_sequence.append(LangChangeCommand(result.lang_to)) + new_sequence.append(translated_text) + self._speak(new_sequence, *args, **kwargs) + # Optionally, copy the result + self.copyResult(translated_text) + except RuntimeError: + # If translation fails, speak the original sequence + self._speak(sequence, *args, **kwargs) + else: + self._speak(sequence, *args, **kwargs) @scriptHandler.script( # Translators: Presented in input help mode. @@ -327,7 +351,8 @@ def _localSpeak(self, sequence, *args, **kwargs): **speakOnDemand, ) def script_translateLastSpokenText(self, gesture): - self.lastSpokenText and threading.Thread(target=self.translate, args=(self.lastSpokenText, self.lang_from, self.lang_to)).start() + if self.lastSpokenText: + threading.Thread(target=self.translate, args=(self.lastSpokenText, self.lang_from, self.lang_to)).start() @scriptHandler.script( # Translators: Presented in input help mode. @@ -335,7 +360,7 @@ def script_translateLastSpokenText(self, gesture): **speakOnDemand, ) def script_displayHelp(self, gesture): - ui.message(_("t translates selected text, shift+t translates clipboard text, a announces current swap configuration, s swaps source and target languages, c copies last result to clipboard, i identify the language of selected text, l translates last spoken text, o open translation settings dialog, h displays this message.")) + ui.message(_("t translates selected text, shift+t translates clipboard text, a announces current swap configuration, s swaps source and target languages, c copies last result to clipboard, i identify the language of selected text, l translates last spoken text, o open translation settings dialog, v toggles automatic translation, h displays this message.")) @scriptHandler.script( # Translators: Presented in input help mode. @@ -344,16 +369,30 @@ def script_displayHelp(self, gesture): def script_showSettings(self, gesture): wx.CallAfter(gui.mainFrame._popupSettingsDialog, gui.settingsDialogs.NVDASettingsDialog, InstantTranslateSettingsPanel) - __ITGestures={ - "kb:t":"translateSelection", - "kb:shift+t":"translateClipboardText", - "kb:s":"swapLanguages", - "kb:a":"announceLanguages", - "kb:c":"copyLastResult", - "kb:i":"identifyLanguage", - "kb:l":"translateLastSpokenText", - "kb:o":"showSettings", - "kb:h":"displayHelp", + @scriptHandler.script( + # Translators: Presented in input help mode. + description=_("Toggle automatic translation of speech output."), + ) + def script_toggleAutoTranslate(self, gesture): + self.autoTranslate = not self.autoTranslate + if self.autoTranslate: + # Translators: message presented to announce that automatic translation is enabled. + ui.message(_("Automatic translation enabled")) + else: + # Translators: message presented to announce that automatic translation is disabled. + ui.message(_("Automatic translation disabled")) + + __ITGestures = { + "kb:t": "translateSelection", + "kb:shift+t": "translateClipboardText", + "kb:s": "swapLanguages", + "kb:a": "announceLanguages", + "kb:c": "copyLastResult", + "kb:i": "identifyLanguage", + "kb:l": "translateLastSpokenText", + "kb:o": "showSettings", + "kb:h": "displayHelp", + "kb:v": "toggleAutoTranslate", } __gestures = { From 857446bf86509b527d68b5e6835b57ba88f0109d Mon Sep 17 00:00:00 2001 From: cary-rowen Date: Fri, 29 Nov 2024 10:33:04 +0800 Subject: [PATCH 2/2] Update documentation --- readme.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 05fb415..701f288 100644 --- a/readme.md +++ b/readme.md @@ -29,11 +29,12 @@ Anyway, this is a temporary configuration; if this option has no effect (it's ex At least, in the speech settings parameters dialog (NVDA Menu >> Preferences >> Speech), you may want to check the "Automatic language switching (when supported)" option. This way, if you are using a multi-lingual synthesizer, the translation will be announced using the target language voice of the synthesizer. ## Using ## -You can use this add-on in three ways: +You can use this add-on in four ways: 1. Select some text using selection commands (shift with arrow keys, for example) and press associated key to translate. translation result will be read with synthesizer which you are using. 2. You can also translate text from the Clipboard. 3. Press the dedicated shortcut key to translate the last spoken text. +4. Enable automatic translation to translate every speech output of NVDA ## Shortcuts ## All following commands must be pressed after modifier key "NVDA+Shift+t": @@ -45,7 +46,8 @@ All following commands must be pressed after modifier key "NVDA+Shift+t": * C: copy last result to clipboard, * I: identify the language of selected text, * L: translate the last spoken text, -* O: open translation settings dialog +* V: toggle automatic translation of speech output, +* O: open translation settings dialog, * H: announces all available layered commands. ## Changes for 4.7 ##