Auto translate added with cthulhu+Control+Shift+T
This commit is contained in:
@@ -19,3 +19,4 @@ will not start the server.
|
||||
- The server listens on 127.0.0.1 and uses the port from NVDA2SPEECHD_HOST
|
||||
if set; otherwise it defaults to 3457.
|
||||
- Toggle interrupt/no-interrupt mode with cthulhu+shift+n.
|
||||
- Toggle translation with cthulhu+control+shift+t (requires translate-shell).
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
from collections import OrderedDict
|
||||
import subprocess
|
||||
import threading
|
||||
import urllib.parse
|
||||
|
||||
@@ -49,6 +52,9 @@ from cthulhu import settings_manager
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_PORT = 3457
|
||||
TRANSLATE_COMMAND = ("trans", "-no-autocorrect", "-no-warn", "-brief")
|
||||
TRANSLATE_TIMEOUT = 5.0
|
||||
TRANSLATE_CACHE_MAX = 512
|
||||
|
||||
|
||||
def _coerce_text(value):
|
||||
@@ -92,6 +98,8 @@ class Nvda2Cthulhu(Plugin):
|
||||
self.httpServer = None
|
||||
self.ioLoop = None
|
||||
self.asyncioLoop = None
|
||||
self.translationCache = OrderedDict()
|
||||
self.translationCacheLock = threading.Lock()
|
||||
|
||||
@cthulhu_hookimpl
|
||||
def activate(self, plugin=None):
|
||||
@@ -105,6 +113,12 @@ class Nvda2Cthulhu(Plugin):
|
||||
"kb:cthulhu+shift+n",
|
||||
learnModeEnabled=True
|
||||
)
|
||||
self.registerGestureByString(
|
||||
self.toggle_translation,
|
||||
"NVDA to Cthulhu translation",
|
||||
"kb:cthulhu+control+shift+t",
|
||||
learnModeEnabled=True
|
||||
)
|
||||
if not self._dependencies_available():
|
||||
self._present_message("NVDA to Cthulhu missing dependencies: python-msgpack and python-tornado")
|
||||
logger.warning("NVDA to Cthulhu dependencies missing: msgpack or tornado")
|
||||
@@ -160,6 +174,19 @@ class Nvda2Cthulhu(Plugin):
|
||||
self._present_message(f"NVDA to Cthulhu {mode}")
|
||||
return True
|
||||
|
||||
def toggle_translation(self, script=None, inputEvent=None):
|
||||
if not self.settingsManager:
|
||||
return False
|
||||
if not self._translation_command_available():
|
||||
self._present_message("NVDA to Cthulhu translation unavailable (missing translate-shell)")
|
||||
return True
|
||||
currentValue = bool(self.settingsManager.getSetting('nvda2cthulhuTranslateEnabled'))
|
||||
newValue = not currentValue
|
||||
self.settingsManager.setSetting('nvda2cthulhuTranslateEnabled', newValue)
|
||||
mode = "translation enabled" if newValue else "translation disabled"
|
||||
self._present_message(f"NVDA to Cthulhu {mode}")
|
||||
return True
|
||||
|
||||
def handle_message(self, message):
|
||||
request = self._parse_request(message)
|
||||
if not request:
|
||||
@@ -272,6 +299,8 @@ class Nvda2Cthulhu(Plugin):
|
||||
def _handle_speak(self, text):
|
||||
if not text or not text.strip():
|
||||
return
|
||||
if self._translation_enabled():
|
||||
text = self._translate_text(text)
|
||||
speech.speak(text, interrupt=self.interruptEnabled)
|
||||
|
||||
def _handle_braille(self, text):
|
||||
@@ -307,3 +336,62 @@ class Nvda2Cthulhu(Plugin):
|
||||
|
||||
def _dependencies_available(self):
|
||||
return msgpack is not None and tornado is not None
|
||||
|
||||
def _translation_enabled(self):
|
||||
if not self.settingsManager:
|
||||
return False
|
||||
return bool(self.settingsManager.getSetting('nvda2cthulhuTranslateEnabled'))
|
||||
|
||||
def _translation_command_available(self):
|
||||
return shutil.which(TRANSLATE_COMMAND[0]) is not None
|
||||
|
||||
def _translate_text(self, text):
|
||||
cached = self._get_cached_translation(text)
|
||||
if cached is not None:
|
||||
return cached
|
||||
if not self._translation_command_available():
|
||||
logger.warning("NVDA to Cthulhu translation failed: translate-shell not available")
|
||||
return text
|
||||
try:
|
||||
result = subprocess.run(
|
||||
TRANSLATE_COMMAND,
|
||||
input=text,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
timeout=TRANSLATE_TIMEOUT
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.warning("NVDA to Cthulhu translation failed: timed out")
|
||||
return text
|
||||
except Exception as exc:
|
||||
logger.warning(f"NVDA to Cthulhu translation failed: {exc}")
|
||||
return text
|
||||
|
||||
if result.returncode != 0:
|
||||
stderr = result.stderr.strip()
|
||||
if stderr:
|
||||
logger.warning(f"NVDA to Cthulhu translation failed: {stderr}")
|
||||
return text
|
||||
|
||||
output = result.stdout.strip()
|
||||
if not output:
|
||||
return text
|
||||
self._set_cached_translation(text, output)
|
||||
return output
|
||||
|
||||
def _get_cached_translation(self, text):
|
||||
with self.translationCacheLock:
|
||||
cached = self.translationCache.get(text)
|
||||
if cached is None:
|
||||
return None
|
||||
self.translationCache.move_to_end(text)
|
||||
return cached
|
||||
|
||||
def _set_cached_translation(self, text, translated):
|
||||
with self.translationCacheLock:
|
||||
if text in self.translationCache:
|
||||
self.translationCache.move_to_end(text)
|
||||
self.translationCache[text] = translated
|
||||
while len(self.translationCache) > TRANSLATE_CACHE_MAX:
|
||||
self.translationCache.popitem(last=False)
|
||||
|
||||
@@ -65,6 +65,7 @@ userCustomizableSettings = [
|
||||
"enableEchoBySentence",
|
||||
"enableKeyEcho",
|
||||
"gameMode",
|
||||
"nvda2cthulhuTranslateEnabled",
|
||||
"enableAlphabeticKeys",
|
||||
"enableNumericKeys",
|
||||
"enablePunctuationKeys",
|
||||
@@ -319,6 +320,7 @@ speechVerbosityLevel = VERBOSITY_LEVEL_VERBOSE
|
||||
messagesAreDetailed = True
|
||||
enablePauseBreaks = True
|
||||
gameMode = False
|
||||
nvda2cthulhuTranslateEnabled = False
|
||||
speakDescription = True
|
||||
speakContextBlockquote = True
|
||||
speakContextPanel = True
|
||||
|
||||
Reference in New Issue
Block a user