Fixed a bug in the translation part of the plugin.

This commit is contained in:
Storm Dragon
2026-01-13 09:31:55 -05:00
parent 06cd376cd4
commit 9bdb7510c9
5 changed files with 100 additions and 14 deletions
@@ -340,6 +340,10 @@ class IndentationAudio(Plugin):
def _on_caret_moved(self, event): def _on_caret_moved(self, event):
"""Handle caret movement events.""" """Handle caret movement events."""
try: try:
# Check if plugin is activated first (prevents race conditions during deactivation)
if not self._activated or not self._enabled:
return
if not self._beeps_enabled(): if not self._beeps_enabled():
return return
@@ -674,11 +678,15 @@ class IndentationAudio(Plugin):
def check_indentation_change(self, obj, line_text): def check_indentation_change(self, obj, line_text):
"""Check if indentation has changed and play audio cue if needed. """Check if indentation has changed and play audio cue if needed.
This method is intended to be called by scripts during line navigation. This method is intended to be called by scripts during line navigation.
""" """
debug.printMessage(debug.LEVEL_INFO, f"IndentationAudio: check_indentation_change called: line='{line_text}'", True) debug.printMessage(debug.LEVEL_INFO, f"IndentationAudio: check_indentation_change called: line='{line_text}'", True)
# Check if plugin is activated and enabled
if not self._activated or not self._enabled:
return
if not line_text or not self._beeps_enabled(): if not line_text or not self._beeps_enabled():
return return
@@ -20,3 +20,5 @@ will not start the server.
if set; otherwise it defaults to 3457. if set; otherwise it defaults to 3457.
- Toggle interrupt/no-interrupt mode with cthulhu+shift+n. - Toggle interrupt/no-interrupt mode with cthulhu+shift+n.
- Toggle translation with cthulhu+control+shift+t (requires translate-shell). - Toggle translation with cthulhu+control+shift+t (requires translate-shell).
- Translation defaults to your system locale; override with `NVDA2CTHULHU_TRANSLATE_TARGET`
(e.g., `NVDA2CTHULHU_TRANSLATE_TARGET=en`).
@@ -1 +1,5 @@
"""NVDA to Cthulhu bridge plugin.""" """NVDA to Cthulhu bridge plugin."""
from .plugin import Nvda2Cthulhu
__all__ = ['Nvda2Cthulhu']
+71 -12
View File
@@ -21,6 +21,7 @@
"""NVDA to Cthulhu bridge plugin.""" """NVDA to Cthulhu bridge plugin."""
import asyncio import asyncio
import locale
import logging import logging
import os import os
import shutil import shutil
@@ -46,6 +47,7 @@ except Exception: # pragma: no cover - optional dependency
from cthulhu.plugin import Plugin, cthulhu_hookimpl from cthulhu.plugin import Plugin, cthulhu_hookimpl
from cthulhu import braille from cthulhu import braille
from cthulhu import debug
from cthulhu import speech from cthulhu import speech
from cthulhu import settings_manager from cthulhu import settings_manager
@@ -90,20 +92,29 @@ class Nvda2Cthulhu(Plugin):
"""Bridge server that accepts NVDA to Cthulhu messages.""" """Bridge server that accepts NVDA to Cthulhu messages."""
def __init__(self): def __init__(self):
super().__init__() logger.info("NVDA to Cthulhu __init__() called")
self.settingsManager = settings_manager.getManager() try:
self.interruptEnabled = True super().__init__()
self.serverThread = None logger.info("NVDA to Cthulhu super().__init__() completed")
self.serverLock = threading.Lock() self.settingsManager = settings_manager.getManager()
self.httpServer = None self.interruptEnabled = True
self.ioLoop = None self.serverThread = None
self.asyncioLoop = None self.serverLock = threading.Lock()
self.translationCache = OrderedDict() self.httpServer = None
self.translationCacheLock = threading.Lock() self.ioLoop = None
self.asyncioLoop = None
self.translationCache = OrderedDict()
self.translationCacheLock = threading.Lock()
logger.info("NVDA to Cthulhu __init__() completed successfully")
except Exception as e:
logger.error(f"NVDA to Cthulhu __init__() failed: {e}")
raise
@cthulhu_hookimpl @cthulhu_hookimpl
def activate(self, plugin=None): def activate(self, plugin=None):
logger.info(f"NVDA to Cthulhu activate() called with plugin={plugin}, self={self}")
if plugin is not None and plugin is not self: if plugin is not None and plugin is not self:
logger.info(f"NVDA to Cthulhu activate() early return: plugin is not self")
return return
logger.info("Activating NVDA to Cthulhu plugin") logger.info("Activating NVDA to Cthulhu plugin")
@@ -300,7 +311,10 @@ class Nvda2Cthulhu(Plugin):
if not text or not text.strip(): if not text or not text.strip():
return return
if self._translation_enabled(): if self._translation_enabled():
text = self._translate_text(text) logger.info(f"NVDA to Cthulhu: translating text: {text[:50]}")
translated = self._translate_text(text)
logger.info(f"NVDA to Cthulhu: translated to: {translated[:50]}")
text = translated
speech.speak(text, interrupt=self.interruptEnabled) speech.speak(text, interrupt=self.interruptEnabled)
def _handle_braille(self, text): def _handle_braille(self, text):
@@ -345,36 +359,81 @@ class Nvda2Cthulhu(Plugin):
def _translation_command_available(self): def _translation_command_available(self):
return shutil.which(TRANSLATE_COMMAND[0]) is not None return shutil.which(TRANSLATE_COMMAND[0]) is not None
def _get_target_language(self):
"""Get the target language code from system locale."""
try:
# Get system locale (e.g., 'en_US.UTF-8')
system_locale = locale.getdefaultlocale()[0]
if system_locale:
# Extract language code (e.g., 'en' from 'en_US')
lang_code = system_locale.split('_')[0]
return lang_code
except Exception as exc:
logger.warning(f"NVDA to Cthulhu: failed to get system locale: {exc}")
# Fallback to environment variable
try:
lang_env = os.environ.get('LANG', '')
if lang_env:
# Extract language code from LANG (e.g., 'en' from 'en_US.UTF-8')
lang_code = lang_env.split('_')[0]
if lang_code:
return lang_code
except Exception:
pass
return None
def _get_translate_target(self):
env_target = os.environ.get("NVDA2CTHULHU_TRANSLATE_TARGET")
if env_target:
return env_target
return self._get_target_language()
def _translate_text(self, text): def _translate_text(self, text):
cached = self._get_cached_translation(text) cached = self._get_cached_translation(text)
if cached is not None: if cached is not None:
logger.info(f"NVDA to Cthulhu: using cached translation")
return cached return cached
if not self._translation_command_available(): if not self._translation_command_available():
logger.warning("NVDA to Cthulhu translation failed: translate-shell not available") logger.warning("NVDA to Cthulhu translation failed: translate-shell not available")
return text return text
translate_target = self._get_translate_target()
command = list(TRANSLATE_COMMAND)
if translate_target:
command.append(f":{translate_target}")
logger.info(f"NVDA to Cthulhu: running trans command on: {text[:50]}")
try: try:
result = subprocess.run( result = subprocess.run(
TRANSLATE_COMMAND, command,
input=text, input=text,
text=True, text=True,
encoding="utf-8",
errors="replace",
capture_output=True, capture_output=True,
check=False, check=False,
timeout=TRANSLATE_TIMEOUT timeout=TRANSLATE_TIMEOUT
) )
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
logger.warning("NVDA to Cthulhu translation failed: timed out") logger.warning("NVDA to Cthulhu translation failed: timed out")
debug.printMessage(debug.LEVEL_INFO, "NVDA to Cthulhu translation failed: timed out", True)
return text return text
except Exception as exc: except Exception as exc:
logger.warning(f"NVDA to Cthulhu translation failed: {exc}") logger.warning(f"NVDA to Cthulhu translation failed: {exc}")
debug.printMessage(debug.LEVEL_INFO, f"NVDA to Cthulhu translation failed: {exc}", True)
return text return text
if result.returncode != 0: if result.returncode != 0:
stderr = result.stderr.strip() stderr = result.stderr.strip()
if stderr: if stderr:
logger.warning(f"NVDA to Cthulhu translation failed: {stderr}") logger.warning(f"NVDA to Cthulhu translation failed: {stderr}")
debug.printMessage(debug.LEVEL_INFO, f"NVDA to Cthulhu translation failed: {stderr}", True)
return text return text
output = result.stdout.strip() output = result.stdout.strip()
logger.info(f"NVDA to Cthulhu: trans output: {output[:50]}")
if not output: if not output:
return text return text
self._set_cached_translation(text, output) self._set_cached_translation(text, output)
+14 -1
View File
@@ -494,7 +494,20 @@ presentChatRoomLast = False
presentLiveRegionFromInactiveTab = False presentLiveRegionFromInactiveTab = False
# Plugins # Plugins
activePlugins = ['DisplayVersion', 'OCR', 'PluginManager', 'HelloCthulhu', 'ByeCthulhu', 'WindowTitleReader'] activePlugins = [
'PluginManager',
'ByeCthulhu',
'Clipboard',
'HelloCthulhu',
'DisplayVersion',
'GameMode',
'IndentationAudio',
'nvda2cthulhu',
'OCR',
'SpeechHistory',
'SSIPProxy',
'WindowTitleReader'
]
pluginSources = [] pluginSources = []
# AI Assistant settings (disabled by default for opt-in behavior) # AI Assistant settings (disabled by default for opt-in behavior)