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

View File

@@ -340,6 +340,10 @@ class IndentationAudio(Plugin):
def _on_caret_moved(self, event):
"""Handle caret movement events."""
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():
return
@@ -674,11 +678,15 @@ class IndentationAudio(Plugin):
def check_indentation_change(self, obj, line_text):
"""Check if indentation has changed and play audio cue if needed.
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)
# 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():
return

View File

@@ -20,3 +20,5 @@ will not start the server.
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).
- Translation defaults to your system locale; override with `NVDA2CTHULHU_TRANSLATE_TARGET`
(e.g., `NVDA2CTHULHU_TRANSLATE_TARGET=en`).

View File

@@ -1 +1,5 @@
"""NVDA to Cthulhu bridge plugin."""
from .plugin import Nvda2Cthulhu
__all__ = ['Nvda2Cthulhu']

View File

@@ -21,6 +21,7 @@
"""NVDA to Cthulhu bridge plugin."""
import asyncio
import locale
import logging
import os
import shutil
@@ -46,6 +47,7 @@ except Exception: # pragma: no cover - optional dependency
from cthulhu.plugin import Plugin, cthulhu_hookimpl
from cthulhu import braille
from cthulhu import debug
from cthulhu import speech
from cthulhu import settings_manager
@@ -90,20 +92,29 @@ class Nvda2Cthulhu(Plugin):
"""Bridge server that accepts NVDA to Cthulhu messages."""
def __init__(self):
super().__init__()
self.settingsManager = settings_manager.getManager()
self.interruptEnabled = True
self.serverThread = None
self.serverLock = threading.Lock()
self.httpServer = None
self.ioLoop = None
self.asyncioLoop = None
self.translationCache = OrderedDict()
self.translationCacheLock = threading.Lock()
logger.info("NVDA to Cthulhu __init__() called")
try:
super().__init__()
logger.info("NVDA to Cthulhu super().__init__() completed")
self.settingsManager = settings_manager.getManager()
self.interruptEnabled = True
self.serverThread = None
self.serverLock = threading.Lock()
self.httpServer = None
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
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:
logger.info(f"NVDA to Cthulhu activate() early return: plugin is not self")
return
logger.info("Activating NVDA to Cthulhu plugin")
@@ -300,7 +311,10 @@ class Nvda2Cthulhu(Plugin):
if not text or not text.strip():
return
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)
def _handle_braille(self, text):
@@ -345,36 +359,81 @@ class Nvda2Cthulhu(Plugin):
def _translation_command_available(self):
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):
cached = self._get_cached_translation(text)
if cached is not None:
logger.info(f"NVDA to Cthulhu: using cached translation")
return cached
if not self._translation_command_available():
logger.warning("NVDA to Cthulhu translation failed: translate-shell not available")
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:
result = subprocess.run(
TRANSLATE_COMMAND,
command,
input=text,
text=True,
encoding="utf-8",
errors="replace",
capture_output=True,
check=False,
timeout=TRANSLATE_TIMEOUT
)
except subprocess.TimeoutExpired:
logger.warning("NVDA to Cthulhu translation failed: timed out")
debug.printMessage(debug.LEVEL_INFO, "NVDA to Cthulhu translation failed: timed out", True)
return text
except Exception as 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
if result.returncode != 0:
stderr = result.stderr.strip()
if 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
output = result.stdout.strip()
logger.info(f"NVDA to Cthulhu: trans output: {output[:50]}")
if not output:
return text
self._set_cached_translation(text, output)

View File

@@ -494,7 +494,20 @@ presentChatRoomLast = False
presentLiveRegionFromInactiveTab = False
# Plugins
activePlugins = ['DisplayVersion', 'OCR', 'PluginManager', 'HelloCthulhu', 'ByeCthulhu', 'WindowTitleReader']
activePlugins = [
'PluginManager',
'ByeCthulhu',
'Clipboard',
'HelloCthulhu',
'DisplayVersion',
'GameMode',
'IndentationAudio',
'nvda2cthulhu',
'OCR',
'SpeechHistory',
'SSIPProxy',
'WindowTitleReader'
]
pluginSources = []
# AI Assistant settings (disabled by default for opt-in behavior)