Auto translate added with cthulhu+Control+Shift+T

This commit is contained in:
Storm Dragon
2026-01-10 18:18:48 -05:00
parent aa14e665a6
commit 52a687c770
3 changed files with 91 additions and 0 deletions

View File

@@ -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).

View File

@@ -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)

View File

@@ -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