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
@@ -679,6 +683,10 @@ class IndentationAudio(Plugin):
""" """
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)