Fixed a bug in the translation part of the plugin.
This commit is contained in:
@@ -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']
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user