mindroot 9.2.0__py3-none-any.whl → 9.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mindroot/coreplugins/admin/__init__.py +3 -1
- mindroot/coreplugins/admin/agent_router.py +250 -7
- mindroot/coreplugins/admin/asset_manager.py +164 -0
- mindroot/coreplugins/admin/command_router.py +236 -1
- mindroot/coreplugins/admin/mcp_catalog_routes.py +156 -0
- mindroot/coreplugins/admin/mcp_publish_routes.py +450 -0
- mindroot/coreplugins/admin/mcp_registry_routes.py +495 -0
- mindroot/coreplugins/admin/mcp_routes.py +216 -0
- mindroot/coreplugins/admin/mod.py +62 -0
- mindroot/coreplugins/admin/oauth_callback_router.py +84 -0
- mindroot/coreplugins/admin/persona_handler.py +15 -6
- mindroot/coreplugins/admin/persona_router.py +158 -2
- mindroot/coreplugins/admin/plugin_manager.py +63 -0
- mindroot/coreplugins/admin/plugin_router.py +1 -1
- mindroot/coreplugins/admin/plugin_router_fixed.py +23 -0
- mindroot/coreplugins/admin/plugin_router_new_not_working.py +145 -0
- mindroot/coreplugins/admin/plugin_routes.py +114 -0
- mindroot/coreplugins/admin/registry_settings_routes.py +140 -0
- mindroot/coreplugins/admin/router.py +116 -15
- mindroot/coreplugins/admin/service_models.py +1 -1
- mindroot/coreplugins/admin/settings_router.py +1 -0
- mindroot/coreplugins/admin/static/css/admin-custom.css +357 -2
- mindroot/coreplugins/admin/static/css/dark.css +1 -0
- mindroot/coreplugins/admin/static/css/default.css +4 -0
- mindroot/coreplugins/admin/static/js/about-info.js +367 -0
- mindroot/coreplugins/admin/static/js/agent-form.js +83 -3
- mindroot/coreplugins/admin/static/js/api-key-script.js +307 -0
- mindroot/coreplugins/admin/static/js/mcp-manager.js +348 -0
- mindroot/coreplugins/admin/static/js/mcp-publisher.js +780 -0
- mindroot/coreplugins/admin/static/js/persona-editor.js +34 -5
- mindroot/coreplugins/admin/static/js/plugin-toggle.js +1 -1
- mindroot/coreplugins/admin/static/js/recommended-plugin-install.js +63 -0
- mindroot/coreplugins/admin/static/js/registry-auth-section.js +132 -0
- mindroot/coreplugins/admin/static/js/registry-manager-base.js +613 -0
- mindroot/coreplugins/admin/static/js/registry-manager-old.js +385 -0
- mindroot/coreplugins/admin/static/js/registry-manager-publish-old-delete.js +166 -0
- mindroot/coreplugins/admin/static/js/registry-manager.js +351 -0
- mindroot/coreplugins/admin/static/js/registry-publish-section.js +377 -0
- mindroot/coreplugins/admin/static/js/registry-search-section.js +400 -0
- mindroot/coreplugins/admin/static/js/registry-search-section.js.bak +3 -0
- mindroot/coreplugins/admin/static/js/registry-settings.js +69 -0
- mindroot/coreplugins/admin/static/js/registry-shared-services.js +857 -0
- mindroot/coreplugins/admin/static/js/registry-simple-sections.js +85 -0
- mindroot/coreplugins/admin/static/js/secure-widget-manager.js +438 -0
- mindroot/coreplugins/admin/static/logo.png +0 -0
- mindroot/coreplugins/admin/templates/admin.jinja2 +275 -110
- mindroot/coreplugins/agent/Assistant/agent.json +27 -11
- mindroot/coreplugins/agent/agent.py +2 -2
- mindroot/coreplugins/agent/command_parser.py +25 -10
- mindroot/coreplugins/agent/templates/system.jinja2 +0 -12
- mindroot/coreplugins/chat/__init__.py +4 -1
- mindroot/coreplugins/chat/router.py +132 -20
- mindroot/coreplugins/chat/router_dedup_patch.py +20 -0
- mindroot/coreplugins/chat/services.py +31 -1
- mindroot/coreplugins/chat/static/css/action-fix.css +32 -0
- mindroot/coreplugins/chat/static/css/admin-custom.css +5 -3
- mindroot/coreplugins/chat/static/css/dark.css +24 -3
- mindroot/coreplugins/chat/static/css/default.css +24 -3
- mindroot/coreplugins/chat/static/css/main.css +1 -0
- mindroot/coreplugins/chat/static/js/action.js +137 -60
- mindroot/coreplugins/chat/static/js/chat-history.js +3 -0
- mindroot/coreplugins/chat/static/js/chat.js +59 -16
- mindroot/coreplugins/chat/static/js/chat.js.diff +221 -0
- mindroot/coreplugins/chat/static/js/chatform.js +2 -2
- mindroot/coreplugins/chat/static/site.webmanifest +1 -1
- mindroot/coreplugins/chat/templates/chat.jinja2 +3 -3
- mindroot/coreplugins/chat/widget_manager.py +139 -0
- mindroot/coreplugins/chat/widget_routes.py +287 -0
- mindroot/coreplugins/check_list/inject/admin.jinja2 +1 -1
- mindroot/coreplugins/email/__init__.py +2 -0
- mindroot/coreplugins/email/email_provider.py +2 -2
- mindroot/coreplugins/email/mod.py +100 -0
- mindroot/coreplugins/email/services.py +5 -3
- mindroot/coreplugins/email/smtp_handler.py +9 -3
- mindroot/coreplugins/email/test_email_service.py +75 -0
- mindroot/coreplugins/env_manager/mod.py +61 -25
- mindroot/coreplugins/home/router.py +37 -2
- mindroot/coreplugins/home/static/imgs/logo.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo.png.bak +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal2.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal_detailed.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal_python.png +0 -0
- mindroot/coreplugins/home/templates/home.jinja2 +15 -6
- mindroot/coreplugins/index/handlers/plugin_ops.py +1 -1
- mindroot/coreplugins/index/indices/default/index.json +6 -6
- mindroot/coreplugins/jwt_auth/middleware.py +47 -1
- mindroot/coreplugins/jwt_auth/mod.py +40 -17
- mindroot/coreplugins/l8n/__init__.py +6 -0
- mindroot/coreplugins/l8n/debug_loader.py +85 -0
- mindroot/coreplugins/l8n/debug_middleware.py +74 -0
- mindroot/coreplugins/l8n/l8n_constants.py +19 -0
- mindroot/coreplugins/l8n/language_detection.py +183 -0
- mindroot/coreplugins/l8n/middleware.py +151 -0
- mindroot/coreplugins/l8n/mod.py +277 -0
- mindroot/coreplugins/l8n/monkey_patch_to_delete.py +186 -0
- mindroot/coreplugins/l8n/test_enhanced.py +298 -0
- mindroot/coreplugins/l8n/test_l8n.py +95 -0
- mindroot/coreplugins/l8n/test_l8n_standalone.py +251 -0
- mindroot/coreplugins/l8n/test_middleware.py +272 -0
- mindroot/coreplugins/l8n/utils.py +232 -0
- mindroot/coreplugins/mcp_/__init__.py +14 -0
- mindroot/coreplugins/mcp_/catalog_commands.py +328 -0
- mindroot/coreplugins/mcp_/catalog_manager.py +263 -0
- mindroot/coreplugins/mcp_/dynamic_commands.py +154 -0
- mindroot/coreplugins/mcp_/mcp_manager.py +1031 -0
- mindroot/coreplugins/mcp_/mod.py +367 -0
- mindroot/coreplugins/mcp_/oauth_storage.py +144 -0
- mindroot/coreplugins/mcp_/server_installer.py +79 -0
- mindroot/coreplugins/mcp_/setup.py +26 -0
- mindroot/coreplugins/mcp_/test_dynamic_commands.py +134 -0
- mindroot/coreplugins/mcp_/testmcpclient.py +92 -0
- mindroot/coreplugins/persona/mod.py +12 -7
- mindroot/coreplugins/signup/templates/signup.jinja2 +1 -1
- mindroot/coreplugins/subscriptions/__init__.py +1 -0
- mindroot/coreplugins/subscriptions/mod.py +14 -3
- mindroot/coreplugins/subscriptions/router.py +3 -0
- mindroot/coreplugins/user_service/__init__.py +1 -2
- mindroot/coreplugins/user_service/admin_init.py +1 -0
- mindroot/coreplugins/user_service/email_service.py +72 -17
- mindroot/coreplugins/user_service/mod.py +10 -2
- mindroot/coreplugins/user_service/password_reset_service.py +180 -27
- mindroot/coreplugins/user_service/router.py +84 -22
- mindroot/lib/auth/api_key.py +28 -0
- mindroot/lib/cli/plugins.py +94 -0
- mindroot/lib/plugins/default_plugin_manifest.json +20 -0
- mindroot/lib/plugins/installation.py +5 -5
- mindroot/lib/plugins/l8n_static_handler.py +225 -0
- mindroot/lib/plugins/loader.py +33 -3
- mindroot/lib/plugins/loader_with_l8n.py +281 -0
- mindroot/lib/plugins/manifest.py +238 -17
- mindroot/lib/providers/commands.py +3 -1
- mindroot/lib/route_decorators.py +5 -5
- mindroot/lib/templates.py +183 -11
- mindroot/lib/utils/merge_arrays.py +1 -1
- mindroot/migrate.py +49 -0
- mindroot/registry/data_access.py +1 -1
- mindroot/server.py +47 -13
- mindroot/server_missing_normal_args.py +197 -0
- mindroot/server_prev.py +173 -0
- {mindroot-9.2.0.dist-info → mindroot-9.5.0.dist-info}/METADATA +7 -2
- {mindroot-9.2.0.dist-info → mindroot-9.5.0.dist-info}/RECORD +147 -114
- mindroot/coreplugins/admin/static/favicon/about.txt +0 -6
- mindroot/coreplugins/admin/static/favicon/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/admin/static/favicon/apple-touch-icon.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon-16x16.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon-32x32.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon.ico +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/about.txt +0 -6
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon.ico +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/site.webmanifest +0 -1
- mindroot/coreplugins/admin/static/favicon/logo.png +0 -0
- mindroot/coreplugins/admin/static/favicon/site.webmanifest +0 -1
- mindroot/coreplugins/admin/static/js/backup/agent-editor.js +0 -186
- mindroot/coreplugins/admin/static/js/backup/agent-form.js +0 -1133
- mindroot/coreplugins/admin/static/js/backup/agent-list.js +0 -94
- mindroot/coreplugins/chat/static/favicon/about.txt +0 -6
- mindroot/coreplugins/chat/static/favicon/android-chrome-192x192.png +0 -0
- mindroot/coreplugins/chat/static/favicon/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/chat/static/favicon/apple-touch-icon.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon-16x16.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon-32x32.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon.ico +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/about.txt +0 -6
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon.ico +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/site.webmanifest +0 -1
- mindroot/coreplugins/chat/static/favicon/logo.png +0 -0
- mindroot/coreplugins/chat/static/favicon/site.webmanifest +0 -1
- mindroot/coreplugins/index/default.json +0 -76
- mindroot/coreplugins/user_service/file_trigger_service.py +0 -72
- mindroot/coreplugins/user_service/hooks.py +0 -23
- /mindroot/coreplugins/{admin/static/favicon/android-chrome-192x192.png → home/static/imgs/backuplogo.png} +0 -0
- {mindroot-9.2.0.dist-info → mindroot-9.5.0.dist-info}/WHEEL +0 -0
- {mindroot-9.2.0.dist-info → mindroot-9.5.0.dist-info}/entry_points.txt +0 -0
- {mindroot-9.2.0.dist-info → mindroot-9.5.0.dist-info}/licenses/LICENSE +0 -0
- {mindroot-9.2.0.dist-info → mindroot-9.5.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Debug script to test middleware import and functionality.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
# Add the mindroot path
|
|
10
|
+
sys.path.insert(0, '/files/mindroot/src/mindroot')
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
print("Testing middleware import...")
|
|
14
|
+
import coreplugins.l8n.middleware as mw
|
|
15
|
+
print(f"✅ Middleware imported successfully")
|
|
16
|
+
print(f"✅ Has middleware function: {hasattr(mw, 'middleware')}")
|
|
17
|
+
print(f"✅ Middleware type: {type(mw.middleware)}")
|
|
18
|
+
|
|
19
|
+
# Test if the function is callable
|
|
20
|
+
import inspect
|
|
21
|
+
print(f"✅ Function signature: {inspect.signature(mw.middleware)}")
|
|
22
|
+
print(f"✅ Is coroutine function: {inspect.iscoroutinefunction(mw.middleware)}")
|
|
23
|
+
|
|
24
|
+
# Test basic functionality
|
|
25
|
+
print("\nTesting basic middleware functionality...")
|
|
26
|
+
|
|
27
|
+
# Mock request and call_next
|
|
28
|
+
class MockRequest:
|
|
29
|
+
def __init__(self):
|
|
30
|
+
self.url = type('obj', (object,), {'path': '/test'})
|
|
31
|
+
self.query_params = {}
|
|
32
|
+
self.cookies = {}
|
|
33
|
+
self.headers = {}
|
|
34
|
+
self.state = type('obj', (object,), {})
|
|
35
|
+
|
|
36
|
+
class MockResponse:
|
|
37
|
+
def __init__(self):
|
|
38
|
+
self.cookies_set = {}
|
|
39
|
+
def set_cookie(self, **kwargs):
|
|
40
|
+
self.cookies_set.update(kwargs)
|
|
41
|
+
|
|
42
|
+
async def mock_call_next(request):
|
|
43
|
+
return MockResponse()
|
|
44
|
+
|
|
45
|
+
# Test the middleware
|
|
46
|
+
import asyncio
|
|
47
|
+
|
|
48
|
+
async def test_middleware():
|
|
49
|
+
request = MockRequest()
|
|
50
|
+
try:
|
|
51
|
+
response = await mw.middleware(request, mock_call_next)
|
|
52
|
+
print(f"✅ Middleware executed successfully")
|
|
53
|
+
print(f"✅ Request language set: {hasattr(request.state, 'language')}")
|
|
54
|
+
if hasattr(request.state, 'language'):
|
|
55
|
+
print(f"✅ Language: {request.state.language}")
|
|
56
|
+
return True
|
|
57
|
+
except Exception as e:
|
|
58
|
+
print(f"❌ Middleware execution failed: {e}")
|
|
59
|
+
import traceback
|
|
60
|
+
traceback.print_exc()
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
# Run the test
|
|
64
|
+
result = asyncio.run(test_middleware())
|
|
65
|
+
|
|
66
|
+
if result:
|
|
67
|
+
print("\n🎉 All middleware tests passed!")
|
|
68
|
+
else:
|
|
69
|
+
print("\n💥 Middleware tests failed!")
|
|
70
|
+
|
|
71
|
+
except Exception as e:
|
|
72
|
+
print(f"❌ Error during testing: {e}")
|
|
73
|
+
import traceback
|
|
74
|
+
traceback.print_exc()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
L8N_AVAILABLE = True
|
|
5
|
+
|
|
6
|
+
# Base directory for l8n plugin
|
|
7
|
+
L8N_DIR = Path(os.environ.get('MR_SOURCE_DIR', '/files/mindroot/src/mindroot/coreplugins/l8n'))
|
|
8
|
+
|
|
9
|
+
# Base directory for localized files
|
|
10
|
+
LOCALIZED_FILES_DIR = L8N_DIR / "localized_files"
|
|
11
|
+
|
|
12
|
+
# Base directory for translations
|
|
13
|
+
TRANSLATIONS_DIR = L8N_DIR / "translations"
|
|
14
|
+
|
|
15
|
+
# Global cache for translations
|
|
16
|
+
# Structure: {plugin_path: {language: {key: translation}}}
|
|
17
|
+
TRANSLATIONS = {}
|
|
18
|
+
|
|
19
|
+
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
def get_current_language_from_request() -> str:
|
|
5
|
+
"""
|
|
6
|
+
Get the current language from various request sources.
|
|
7
|
+
|
|
8
|
+
This function checks multiple sources in order of priority:
|
|
9
|
+
1. Environment variable MINDROOT_LANGUAGE
|
|
10
|
+
2. FastAPI request context (if available)
|
|
11
|
+
3. HTTP Accept-Language header
|
|
12
|
+
4. URL parameters (?lang=es)
|
|
13
|
+
5. Cookie values
|
|
14
|
+
6. User preferences from database/session
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Language code (e.g., 'en', 'es', 'fr')
|
|
18
|
+
"""
|
|
19
|
+
# 1. Check environment variable first (for testing/override)
|
|
20
|
+
env_lang = os.environ.get('MINDROOT_LANGUAGE')
|
|
21
|
+
if env_lang:
|
|
22
|
+
return env_lang
|
|
23
|
+
|
|
24
|
+
# 2. Try to get language from FastAPI request context
|
|
25
|
+
try:
|
|
26
|
+
from contextvars import ContextVar
|
|
27
|
+
import contextvars
|
|
28
|
+
|
|
29
|
+
# Try to find request context
|
|
30
|
+
request_lang = _get_language_from_fastapi_context()
|
|
31
|
+
if request_lang:
|
|
32
|
+
return request_lang
|
|
33
|
+
except (ImportError, Exception):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
# 3. Fallback to default language
|
|
37
|
+
return 'en'
|
|
38
|
+
|
|
39
|
+
def _get_language_from_fastapi_context() -> Optional[str]:
|
|
40
|
+
"""
|
|
41
|
+
Extract language from FastAPI request context.
|
|
42
|
+
|
|
43
|
+
This function attempts to access the current FastAPI request
|
|
44
|
+
and extract language information from various sources.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Language code if found, None otherwise
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
# Try to get the current request from FastAPI context
|
|
51
|
+
from starlette.requests import Request
|
|
52
|
+
import contextvars
|
|
53
|
+
|
|
54
|
+
# This is a placeholder - in a real implementation, we would need
|
|
55
|
+
# to access the actual request context that MindRoot uses
|
|
56
|
+
# For now, we'll implement the detection logic structure
|
|
57
|
+
|
|
58
|
+
# Check if there's a way to get the current request
|
|
59
|
+
# This would need to be integrated with MindRoot's request handling
|
|
60
|
+
|
|
61
|
+
return None # Placeholder
|
|
62
|
+
|
|
63
|
+
except (ImportError, Exception):
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
def _parse_accept_language_header(accept_language: str) -> str:
|
|
67
|
+
"""
|
|
68
|
+
Parse the Accept-Language header and return the preferred language.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
accept_language: Accept-Language header value
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Preferred language code
|
|
75
|
+
|
|
76
|
+
Example:
|
|
77
|
+
'en-US,en;q=0.9,es;q=0.8,fr;q=0.7' -> 'en'
|
|
78
|
+
"""
|
|
79
|
+
if not accept_language:
|
|
80
|
+
return 'en'
|
|
81
|
+
|
|
82
|
+
# Parse the Accept-Language header
|
|
83
|
+
languages = []
|
|
84
|
+
for lang_range in accept_language.split(','):
|
|
85
|
+
lang_range = lang_range.strip()
|
|
86
|
+
if ';q=' in lang_range:
|
|
87
|
+
lang, quality = lang_range.split(';q=', 1)
|
|
88
|
+
try:
|
|
89
|
+
quality = float(quality)
|
|
90
|
+
except ValueError:
|
|
91
|
+
quality = 1.0
|
|
92
|
+
else:
|
|
93
|
+
lang = lang_range
|
|
94
|
+
quality = 1.0
|
|
95
|
+
|
|
96
|
+
# Extract just the language code (e.g., 'en' from 'en-US')
|
|
97
|
+
lang_code = lang.split('-')[0].lower()
|
|
98
|
+
languages.append((lang_code, quality))
|
|
99
|
+
|
|
100
|
+
# Sort by quality (highest first)
|
|
101
|
+
languages.sort(key=lambda x: x[1], reverse=True)
|
|
102
|
+
|
|
103
|
+
# Return the highest quality language
|
|
104
|
+
if languages:
|
|
105
|
+
return languages[0][0]
|
|
106
|
+
|
|
107
|
+
return 'en'
|
|
108
|
+
|
|
109
|
+
def set_language_for_request(language: str):
|
|
110
|
+
"""
|
|
111
|
+
Set the language for the current request context.
|
|
112
|
+
|
|
113
|
+
This function would be called by middleware or route handlers
|
|
114
|
+
to set the language for the current request.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
language: Language code to set
|
|
118
|
+
"""
|
|
119
|
+
# Set environment variable as a simple implementation
|
|
120
|
+
os.environ['MINDROOT_LANGUAGE'] = language
|
|
121
|
+
|
|
122
|
+
# In a real implementation, this would set the language
|
|
123
|
+
# in the request context or thread-local storage
|
|
124
|
+
|
|
125
|
+
def get_supported_languages() -> list:
|
|
126
|
+
"""
|
|
127
|
+
Get the list of supported languages.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
List of supported language codes
|
|
131
|
+
"""
|
|
132
|
+
# This could be configured via environment variables or config files
|
|
133
|
+
supported = os.environ.get('MINDROOT_SUPPORTED_LANGUAGES', 'en,es,fr,de,it,pt,ru,zh,ja,ko')
|
|
134
|
+
return [lang.strip() for lang in supported.split(',')]
|
|
135
|
+
|
|
136
|
+
def is_language_supported(language: str) -> bool:
|
|
137
|
+
"""
|
|
138
|
+
Check if a language is supported.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
language: Language code to check
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
True if language is supported, False otherwise
|
|
145
|
+
"""
|
|
146
|
+
return language in get_supported_languages()
|
|
147
|
+
|
|
148
|
+
def get_fallback_language(language: str) -> str:
|
|
149
|
+
"""
|
|
150
|
+
Get a fallback language if the requested language is not supported.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
language: Requested language code
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Fallback language code
|
|
157
|
+
"""
|
|
158
|
+
if is_language_supported(language):
|
|
159
|
+
return language
|
|
160
|
+
|
|
161
|
+
# Language family fallbacks
|
|
162
|
+
fallbacks = {
|
|
163
|
+
'en-us': 'en',
|
|
164
|
+
'en-gb': 'en',
|
|
165
|
+
'es-es': 'es',
|
|
166
|
+
'es-mx': 'es',
|
|
167
|
+
'fr-fr': 'fr',
|
|
168
|
+
'fr-ca': 'fr',
|
|
169
|
+
'de-de': 'de',
|
|
170
|
+
'de-at': 'de',
|
|
171
|
+
'pt-br': 'pt',
|
|
172
|
+
'pt-pt': 'pt',
|
|
173
|
+
'zh-cn': 'zh',
|
|
174
|
+
'zh-tw': 'zh',
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
# Try fallback
|
|
178
|
+
fallback = fallbacks.get(language.lower())
|
|
179
|
+
if fallback and is_language_supported(fallback):
|
|
180
|
+
return fallback
|
|
181
|
+
|
|
182
|
+
# Default to English
|
|
183
|
+
return 'en'
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from fastapi import Request
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from .language_detection import (
|
|
5
|
+
_parse_accept_language_header,
|
|
6
|
+
get_fallback_language,
|
|
7
|
+
set_language_for_request
|
|
8
|
+
)
|
|
9
|
+
except ImportError:
|
|
10
|
+
# For standalone testing
|
|
11
|
+
from language_detection import (
|
|
12
|
+
_parse_accept_language_header,
|
|
13
|
+
get_fallback_language,
|
|
14
|
+
set_language_for_request
|
|
15
|
+
)
|
|
16
|
+
import os
|
|
17
|
+
|
|
18
|
+
# Global variable to store the current request language
|
|
19
|
+
# This will be set by the middleware for each request
|
|
20
|
+
_current_request_language = None
|
|
21
|
+
|
|
22
|
+
def get_request_language() -> str:
|
|
23
|
+
"""
|
|
24
|
+
Get the language that was detected for the current request.
|
|
25
|
+
|
|
26
|
+
This function is called by the monkey patch system to get the
|
|
27
|
+
language for template translation.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Language code for the current request
|
|
31
|
+
"""
|
|
32
|
+
global _current_request_language
|
|
33
|
+
print(f"L8n: Current request language is '{_current_request_language}'")
|
|
34
|
+
# If we have a request-specific language, use it
|
|
35
|
+
if _current_request_language:
|
|
36
|
+
return _current_request_language
|
|
37
|
+
|
|
38
|
+
# Fallback to environment variable or default
|
|
39
|
+
print("L8n: No request language set, falling back to environment variable or default")
|
|
40
|
+
return os.environ.get('MINDROOT_LANGUAGE', 'en')
|
|
41
|
+
|
|
42
|
+
def detect_language_from_request(request: Request) -> str:
|
|
43
|
+
"""
|
|
44
|
+
Detect the preferred language from a FastAPI request.
|
|
45
|
+
|
|
46
|
+
Checks multiple sources in order of priority:
|
|
47
|
+
1. URL parameter (?lang=es)
|
|
48
|
+
2. Cookie value (mindroot_language)
|
|
49
|
+
3. Accept-Language header
|
|
50
|
+
4. Environment variable
|
|
51
|
+
5. Default to 'en'
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
request: FastAPI Request object
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Detected language code
|
|
58
|
+
"""
|
|
59
|
+
# 1. Check URL parameter first (highest priority)
|
|
60
|
+
print(f"L8n: Checking URL parameters for language: {request.query_params}")
|
|
61
|
+
url_lang = request.query_params.get('lang')
|
|
62
|
+
if url_lang:
|
|
63
|
+
|
|
64
|
+
return get_fallback_language(url_lang.lower())
|
|
65
|
+
|
|
66
|
+
# 2. Check cookie value
|
|
67
|
+
print(f"L8n: Checking cookies for language: {request.cookies}")
|
|
68
|
+
cookie_lang = request.cookies.get('mindroot_language')
|
|
69
|
+
if cookie_lang:
|
|
70
|
+
print(f"L8n: Found language in cookie: {cookie_lang}")
|
|
71
|
+
return get_fallback_language(cookie_lang.lower())
|
|
72
|
+
|
|
73
|
+
# 3. Check Accept-Language header
|
|
74
|
+
print(f"L8n: Accept-Language header: {request.headers.get('accept-language')}")
|
|
75
|
+
accept_language = request.headers.get('accept-language')
|
|
76
|
+
if accept_language:
|
|
77
|
+
print(f"L8n: Parsing Accept-Language header: {accept_language}")
|
|
78
|
+
parsed_lang = _parse_accept_language_header(accept_language)
|
|
79
|
+
if parsed_lang:
|
|
80
|
+
print(f"L8n: Parsed language from header: {parsed_lang}")
|
|
81
|
+
return get_fallback_language(parsed_lang)
|
|
82
|
+
|
|
83
|
+
# 4. Check environment variable
|
|
84
|
+
env_lang = os.environ.get('MINDROOT_LANGUAGE')
|
|
85
|
+
if env_lang:
|
|
86
|
+
return get_fallback_language(env_lang.lower())
|
|
87
|
+
|
|
88
|
+
# 5. Default to English
|
|
89
|
+
return 'en'
|
|
90
|
+
|
|
91
|
+
async def middleware(request: Request, call_next):
|
|
92
|
+
"""
|
|
93
|
+
L8n middleware for language detection and setting.
|
|
94
|
+
|
|
95
|
+
This middleware runs early in the request pipeline to:
|
|
96
|
+
1. Detect the preferred language for the request
|
|
97
|
+
2. Store it globally for use by the template system
|
|
98
|
+
3. Set it in the request state for other components
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
request: FastAPI Request object
|
|
102
|
+
call_next: Next middleware/handler in the chain
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Response from the next handler
|
|
106
|
+
"""
|
|
107
|
+
global _current_request_language
|
|
108
|
+
try:
|
|
109
|
+
print("L8n middleware: Starting language detection")
|
|
110
|
+
# Detect the language for this request
|
|
111
|
+
detected_language = detect_language_from_request(request)
|
|
112
|
+
|
|
113
|
+
print(f"L8n: Detected language '{detected_language}' for {request.url.path}")
|
|
114
|
+
# Store it globally for the template system
|
|
115
|
+
_current_request_language = detected_language
|
|
116
|
+
|
|
117
|
+
# Also store it in request state for other components
|
|
118
|
+
request.state.language = detected_language
|
|
119
|
+
|
|
120
|
+
# Set it using the language detection system
|
|
121
|
+
set_language_for_request(detected_language)
|
|
122
|
+
|
|
123
|
+
# Debug logging (can be removed in production)
|
|
124
|
+
print(f"L8n: Detected language '{detected_language}' for {request.url.path}")
|
|
125
|
+
|
|
126
|
+
# Process the request
|
|
127
|
+
response = await call_next(request)
|
|
128
|
+
|
|
129
|
+
# Optionally set a cookie to remember the language preference
|
|
130
|
+
# Only set if it was explicitly requested via URL parameter
|
|
131
|
+
if request.query_params.get('lang'):
|
|
132
|
+
response.set_cookie(
|
|
133
|
+
key="mindroot_language",
|
|
134
|
+
value=detected_language,
|
|
135
|
+
max_age=30 * 24 * 60 * 60, # 30 days
|
|
136
|
+
httponly=True,
|
|
137
|
+
samesite="lax"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return response
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
print(f"L8n middleware error: {e}")
|
|
144
|
+
# If there's an error, continue with default language
|
|
145
|
+
_current_request_language = 'en'
|
|
146
|
+
request.state.language = 'en'
|
|
147
|
+
return await call_next(request)
|
|
148
|
+
|
|
149
|
+
finally:
|
|
150
|
+
# Clean up the global variable after the request
|
|
151
|
+
_current_request_language = None
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
# Try to import from lib first (for same instance), fallback to mindroot.lib
|
|
7
|
+
try:
|
|
8
|
+
from lib.providers.commands import command, command_manager
|
|
9
|
+
print("l8n: Using lib.providers.commands (same instance)")
|
|
10
|
+
except ImportError:
|
|
11
|
+
from mindroot.lib.providers.commands import command, command_manager
|
|
12
|
+
print("l8n: Using mindroot.lib.providers.commands (fallback)")
|
|
13
|
+
|
|
14
|
+
from .utils import extract_plugin_root, get_localized_file_path, load_plugin_translations, get_plugin_translations_path
|
|
15
|
+
from mindroot.lib.utils.debug import debug_box
|
|
16
|
+
|
|
17
|
+
debug_box("l8n: Top of mod.py")
|
|
18
|
+
|
|
19
|
+
from .l8n_constants import *
|
|
20
|
+
|
|
21
|
+
def save_plugin_translations(plugin_path: str, translations: dict):
|
|
22
|
+
"""Save translations for a specific plugin to disk."""
|
|
23
|
+
translations_file = get_plugin_translations_path(plugin_path)
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
# Ensure directory exists
|
|
27
|
+
translations_file.parent.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
with open(translations_file, 'w', encoding='utf-8') as f:
|
|
29
|
+
json.dump(translations, f, indent=2, ensure_ascii=False)
|
|
30
|
+
return True
|
|
31
|
+
except Exception as e:
|
|
32
|
+
print(f"Warning: Could not save translations to {translations_file}: {e}")
|
|
33
|
+
return False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
debug_box("l8n: defining command")
|
|
37
|
+
|
|
38
|
+
# Debug: Check if command_manager has functions
|
|
39
|
+
debug_box(f"l8n: command_manager has {len(command_manager.functions)} functions before registration")
|
|
40
|
+
debug_box(f"l8n: command_manager instance ID: {id(command_manager)}")
|
|
41
|
+
|
|
42
|
+
@command()
|
|
43
|
+
async def write_localized_file(original_path: str, content: str, context=None):
|
|
44
|
+
"""
|
|
45
|
+
Write a localized version of a file with static placeholders.
|
|
46
|
+
|
|
47
|
+
This command creates a localized version of a template or source file
|
|
48
|
+
with __TRANSLATE_key__ placeholders that will be replaced with actual
|
|
49
|
+
translations when the file is loaded.
|
|
50
|
+
|
|
51
|
+
PLACEHOLDER FORMAT RULES:
|
|
52
|
+
- Always use the exact format: __TRANSLATE_key_name__
|
|
53
|
+
- Must start with __TRANSLATE_ and end with __
|
|
54
|
+
- Use lowercase letters, numbers, and underscores only for the key name
|
|
55
|
+
- Use descriptive, hierarchical key names like section_element or buttons_save
|
|
56
|
+
- NO spaces, hyphens, or special characters in key names
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
original_path: Absolute path to the original file
|
|
60
|
+
content: File content with __TRANSLATE_key__ placeholders
|
|
61
|
+
context: Command context (optional)
|
|
62
|
+
|
|
63
|
+
Examples:
|
|
64
|
+
await write_localized_file(
|
|
65
|
+
"/files/mindroot/src/mindroot/coreplugins/chat/templates/chat.jinja2",
|
|
66
|
+
"<h1>__TRANSLATE_chat_title__</h1><button>__TRANSLATE_buttons_send__</button>"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
await write_localized_file(
|
|
70
|
+
"/some/path/src/my_plugin/templates/dashboard.jinja2",
|
|
71
|
+
"<div>__TRANSLATE_dashboard_welcome__</div>"
|
|
72
|
+
)
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
localized_path = get_localized_file_path(original_path)
|
|
76
|
+
|
|
77
|
+
# Create directory if it doesn't exist
|
|
78
|
+
localized_path.parent.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
|
|
80
|
+
# Write the content
|
|
81
|
+
with open(localized_path, 'w', encoding='utf-8') as f:
|
|
82
|
+
f.write(content)
|
|
83
|
+
|
|
84
|
+
return f"Localized file written to: {localized_path}"
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
return f"Error writing localized file: {str(e)}"
|
|
88
|
+
|
|
89
|
+
@command()
|
|
90
|
+
async def append_localized_file(original_path: str, content: str, context=None):
|
|
91
|
+
"""
|
|
92
|
+
Append content to an existing localized file.
|
|
93
|
+
|
|
94
|
+
Use this for large files that need to be built incrementally.
|
|
95
|
+
Follow the same placeholder format rules as write_localized_file.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
original_path: Absolute path to the original file
|
|
99
|
+
content: Content to append with __TRANSLATE_key__ placeholders
|
|
100
|
+
context: Command context (optional)
|
|
101
|
+
|
|
102
|
+
Example:
|
|
103
|
+
# First write the beginning
|
|
104
|
+
await write_localized_file(
|
|
105
|
+
"/path/to/large_template.jinja2",
|
|
106
|
+
"<html><head><title>__TRANSLATE_page_title__</title></head>"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Then append more sections
|
|
110
|
+
await append_localized_file(
|
|
111
|
+
"/path/to/large_template.jinja2",
|
|
112
|
+
"<body><h1>__TRANSLATE_main_heading__</h1></body></html>"
|
|
113
|
+
)
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
localized_path = get_localized_file_path(original_path)
|
|
117
|
+
|
|
118
|
+
# Create directory if it doesn't exist
|
|
119
|
+
localized_path.parent.mkdir(parents=True, exist_ok=True)
|
|
120
|
+
|
|
121
|
+
# Append the content
|
|
122
|
+
with open(localized_path, 'a', encoding='utf-8') as f:
|
|
123
|
+
f.write(content)
|
|
124
|
+
|
|
125
|
+
return f"Content appended to: {localized_path}"
|
|
126
|
+
|
|
127
|
+
except Exception as e:
|
|
128
|
+
return f"Error appending to localized file: {str(e)}"
|
|
129
|
+
|
|
130
|
+
@command()
|
|
131
|
+
async def set_translations(original_path: str, language: str, translations: dict, context=None):
|
|
132
|
+
"""
|
|
133
|
+
Set translations for a specific language and plugin.
|
|
134
|
+
|
|
135
|
+
This command stores the translation mappings that will be used to replace
|
|
136
|
+
__TRANSLATE_key__ placeholders in localized files. Translations are stored
|
|
137
|
+
per plugin based on the provided file path.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
original_path: Absolute path to a file in the plugin (used to identify the plugin)
|
|
141
|
+
language: Language code (e.g., 'en', 'es', 'fr', 'de')
|
|
142
|
+
translations: Dictionary mapping translation keys to translated text
|
|
143
|
+
context: Command context (optional)
|
|
144
|
+
|
|
145
|
+
Examples:
|
|
146
|
+
await set_translations(
|
|
147
|
+
"/files/mindroot/src/mindroot/coreplugins/chat/templates/chat.jinja2",
|
|
148
|
+
'es',
|
|
149
|
+
{
|
|
150
|
+
'chat_title': 'Interfaz de Chat',
|
|
151
|
+
'buttons_send': 'Enviar Mensaje',
|
|
152
|
+
'nav_home': 'Inicio',
|
|
153
|
+
'error_connection_failed': 'Error de conexión'
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
await set_translations(
|
|
158
|
+
"/some/path/src/my_plugin/templates/dashboard.jinja2",
|
|
159
|
+
'fr',
|
|
160
|
+
{
|
|
161
|
+
'dashboard_welcome': 'Bienvenue au Tableau de Bord',
|
|
162
|
+
'buttons_save': 'Enregistrer',
|
|
163
|
+
'nav_home': 'Accueil'
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
"""
|
|
167
|
+
try:
|
|
168
|
+
if not isinstance(translations, dict):
|
|
169
|
+
return "Error: translations must be a dictionary"
|
|
170
|
+
|
|
171
|
+
# Get the plugin-specific translations path
|
|
172
|
+
plugin_key = str(get_plugin_translations_path(original_path))
|
|
173
|
+
|
|
174
|
+
# Load existing translations for this plugin
|
|
175
|
+
plugin_translations = load_plugin_translations(original_path)
|
|
176
|
+
|
|
177
|
+
# Validate translation keys (should match placeholder format)
|
|
178
|
+
invalid_keys = []
|
|
179
|
+
for key in translations.keys():
|
|
180
|
+
if not re.match(r'^[a-z0-9_]+$', key):
|
|
181
|
+
invalid_keys.append(key)
|
|
182
|
+
|
|
183
|
+
if invalid_keys:
|
|
184
|
+
return f"Error: Invalid translation keys (use lowercase, numbers, underscores only): {invalid_keys}"
|
|
185
|
+
|
|
186
|
+
# Update translations for this language
|
|
187
|
+
if language not in plugin_translations:
|
|
188
|
+
plugin_translations[language] = {}
|
|
189
|
+
|
|
190
|
+
plugin_translations[language].update(translations)
|
|
191
|
+
|
|
192
|
+
# Save translations to disk
|
|
193
|
+
if save_plugin_translations(original_path, plugin_translations):
|
|
194
|
+
# Update cache
|
|
195
|
+
TRANSLATIONS[plugin_key] = plugin_translations
|
|
196
|
+
return f"Set {len(translations)} translations for language '{language}' in {Path(plugin_key).parent.name} plugin"
|
|
197
|
+
else:
|
|
198
|
+
return f"Error: Could not save translations"
|
|
199
|
+
|
|
200
|
+
except Exception as e:
|
|
201
|
+
return f"Error setting translations: {str(e)}"
|
|
202
|
+
|
|
203
|
+
@command()
|
|
204
|
+
async def get_translations(original_path: str = None, language: str = None, context=None):
|
|
205
|
+
"""
|
|
206
|
+
Get translations for a specific plugin and language.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
original_path: Absolute path to a file in the plugin (optional)
|
|
210
|
+
If not provided, returns all cached translations
|
|
211
|
+
language: Language code to get translations for (optional)
|
|
212
|
+
context: Command context (optional)
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Dictionary of translations
|
|
216
|
+
|
|
217
|
+
Examples:
|
|
218
|
+
# Get all translations for a plugin
|
|
219
|
+
translations = await get_translations(
|
|
220
|
+
"/files/mindroot/src/mindroot/coreplugins/chat/templates/chat.jinja2"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Get Spanish translations for a plugin
|
|
224
|
+
spanish = await get_translations(
|
|
225
|
+
"/files/mindroot/src/mindroot/coreplugins/chat/templates/chat.jinja2",
|
|
226
|
+
'es'
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Get all cached translations
|
|
230
|
+
all_cached = await get_translations()
|
|
231
|
+
"""
|
|
232
|
+
try:
|
|
233
|
+
if original_path:
|
|
234
|
+
# Load translations for specific plugin
|
|
235
|
+
plugin_translations = load_plugin_translations(original_path)
|
|
236
|
+
|
|
237
|
+
# Update cache
|
|
238
|
+
plugin_key = str(get_plugin_translations_path(original_path))
|
|
239
|
+
TRANSLATIONS[plugin_key] = plugin_translations
|
|
240
|
+
|
|
241
|
+
if language:
|
|
242
|
+
return plugin_translations.get(language, {})
|
|
243
|
+
else:
|
|
244
|
+
return plugin_translations
|
|
245
|
+
else:
|
|
246
|
+
# Return all cached translations
|
|
247
|
+
return TRANSLATIONS
|
|
248
|
+
|
|
249
|
+
except Exception as e:
|
|
250
|
+
return f"Error getting translations: {str(e)}"
|
|
251
|
+
|
|
252
|
+
@command()
|
|
253
|
+
async def list_localized_files(context=None):
|
|
254
|
+
"""
|
|
255
|
+
List all localized files that have been created.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
List of paths to localized files
|
|
259
|
+
"""
|
|
260
|
+
try:
|
|
261
|
+
localized_files = []
|
|
262
|
+
|
|
263
|
+
if LOCALIZED_FILES_DIR.exists():
|
|
264
|
+
for file_path in LOCALIZED_FILES_DIR.rglob("*.i18n.*"):
|
|
265
|
+
localized_files.append(str(file_path.relative_to(LOCALIZED_FILES_DIR)))
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
"count": len(localized_files),
|
|
269
|
+
"files": sorted(localized_files)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
except Exception as e:
|
|
273
|
+
return f"Error listing localized files: {str(e)}"
|
|
274
|
+
|
|
275
|
+
debug_box(f"l8n: command_manager has {len(command_manager.functions)} functions after registration")
|
|
276
|
+
|
|
277
|
+
debug_box("l8n: End of mod.py")
|