Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add automatic translation feature #41

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 55 additions & 16 deletions addon/globalPlugins/instantTranslate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand All @@ -318,24 +321,46 @@ 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.
description=_("It translates the last spoken text"),
**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.
description=_("Announces all available layered commands"),
**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.
Expand All @@ -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 = {
Expand Down
6 changes: 4 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand All @@ -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 ##
Expand Down