mindroot 9.3.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.
Files changed (183) hide show
  1. mindroot/coreplugins/admin/__init__.py +3 -1
  2. mindroot/coreplugins/admin/agent_router.py +250 -7
  3. mindroot/coreplugins/admin/asset_manager.py +164 -0
  4. mindroot/coreplugins/admin/command_router.py +236 -1
  5. mindroot/coreplugins/admin/mcp_catalog_routes.py +156 -0
  6. mindroot/coreplugins/admin/mcp_publish_routes.py +450 -0
  7. mindroot/coreplugins/admin/mcp_registry_routes.py +495 -0
  8. mindroot/coreplugins/admin/mcp_routes.py +216 -0
  9. mindroot/coreplugins/admin/mod.py +62 -0
  10. mindroot/coreplugins/admin/oauth_callback_router.py +84 -0
  11. mindroot/coreplugins/admin/persona_handler.py +15 -6
  12. mindroot/coreplugins/admin/persona_router.py +158 -2
  13. mindroot/coreplugins/admin/plugin_manager.py +63 -0
  14. mindroot/coreplugins/admin/plugin_router_fixed.py +23 -0
  15. mindroot/coreplugins/admin/plugin_router_new_not_working.py +145 -0
  16. mindroot/coreplugins/admin/plugin_routes.py +114 -0
  17. mindroot/coreplugins/admin/registry_settings_routes.py +140 -0
  18. mindroot/coreplugins/admin/router.py +116 -15
  19. mindroot/coreplugins/admin/service_models.py +1 -1
  20. mindroot/coreplugins/admin/settings_router.py +1 -0
  21. mindroot/coreplugins/admin/static/css/admin-custom.css +357 -2
  22. mindroot/coreplugins/admin/static/css/dark.css +1 -0
  23. mindroot/coreplugins/admin/static/css/default.css +4 -0
  24. mindroot/coreplugins/admin/static/js/about-info.js +367 -0
  25. mindroot/coreplugins/admin/static/js/agent-form.js +83 -3
  26. mindroot/coreplugins/admin/static/js/api-key-script.js +307 -0
  27. mindroot/coreplugins/admin/static/js/mcp-manager.js +348 -0
  28. mindroot/coreplugins/admin/static/js/mcp-publisher.js +780 -0
  29. mindroot/coreplugins/admin/static/js/persona-editor.js +34 -5
  30. mindroot/coreplugins/admin/static/js/plugin-toggle.js +1 -1
  31. mindroot/coreplugins/admin/static/js/recommended-plugin-install.js +63 -0
  32. mindroot/coreplugins/admin/static/js/registry-auth-section.js +132 -0
  33. mindroot/coreplugins/admin/static/js/registry-manager-base.js +613 -0
  34. mindroot/coreplugins/admin/static/js/registry-manager-old.js +385 -0
  35. mindroot/coreplugins/admin/static/js/registry-manager-publish-old-delete.js +166 -0
  36. mindroot/coreplugins/admin/static/js/registry-manager.js +351 -0
  37. mindroot/coreplugins/admin/static/js/registry-publish-section.js +377 -0
  38. mindroot/coreplugins/admin/static/js/registry-search-section.js +400 -0
  39. mindroot/coreplugins/admin/static/js/registry-search-section.js.bak +3 -0
  40. mindroot/coreplugins/admin/static/js/registry-settings.js +69 -0
  41. mindroot/coreplugins/admin/static/js/registry-shared-services.js +857 -0
  42. mindroot/coreplugins/admin/static/js/registry-simple-sections.js +85 -0
  43. mindroot/coreplugins/admin/static/js/secure-widget-manager.js +438 -0
  44. mindroot/coreplugins/admin/static/logo.png +0 -0
  45. mindroot/coreplugins/admin/templates/admin.jinja2 +275 -110
  46. mindroot/coreplugins/agent/Assistant/agent.json +27 -11
  47. mindroot/coreplugins/agent/agent.py +2 -2
  48. mindroot/coreplugins/agent/command_parser.py +25 -10
  49. mindroot/coreplugins/agent/templates/system.jinja2 +0 -12
  50. mindroot/coreplugins/chat/__init__.py +4 -1
  51. mindroot/coreplugins/chat/router.py +132 -20
  52. mindroot/coreplugins/chat/router_dedup_patch.py +20 -0
  53. mindroot/coreplugins/chat/services.py +31 -1
  54. mindroot/coreplugins/chat/static/css/action-fix.css +32 -0
  55. mindroot/coreplugins/chat/static/css/admin-custom.css +5 -3
  56. mindroot/coreplugins/chat/static/css/dark.css +24 -3
  57. mindroot/coreplugins/chat/static/css/default.css +24 -3
  58. mindroot/coreplugins/chat/static/css/main.css +1 -0
  59. mindroot/coreplugins/chat/static/js/action.js +137 -60
  60. mindroot/coreplugins/chat/static/js/chat-history.js +3 -0
  61. mindroot/coreplugins/chat/static/js/chat.js +59 -16
  62. mindroot/coreplugins/chat/static/js/chat.js.diff +221 -0
  63. mindroot/coreplugins/chat/static/js/chatform.js +2 -2
  64. mindroot/coreplugins/chat/static/site.webmanifest +1 -1
  65. mindroot/coreplugins/chat/templates/chat.jinja2 +3 -3
  66. mindroot/coreplugins/chat/widget_manager.py +139 -0
  67. mindroot/coreplugins/chat/widget_routes.py +287 -0
  68. mindroot/coreplugins/check_list/inject/admin.jinja2 +1 -1
  69. mindroot/coreplugins/email/__init__.py +2 -0
  70. mindroot/coreplugins/email/email_provider.py +2 -2
  71. mindroot/coreplugins/email/mod.py +100 -0
  72. mindroot/coreplugins/email/services.py +5 -3
  73. mindroot/coreplugins/email/smtp_handler.py +9 -3
  74. mindroot/coreplugins/email/test_email_service.py +75 -0
  75. mindroot/coreplugins/env_manager/mod.py +61 -25
  76. mindroot/coreplugins/home/router.py +37 -2
  77. mindroot/coreplugins/home/static/imgs/logo.png +0 -0
  78. mindroot/coreplugins/home/static/imgs/logo.png.bak +0 -0
  79. mindroot/coreplugins/home/static/imgs/logo_teal.png +0 -0
  80. mindroot/coreplugins/home/static/imgs/logo_teal2.png +0 -0
  81. mindroot/coreplugins/home/static/imgs/logo_teal_detailed.png +0 -0
  82. mindroot/coreplugins/home/static/imgs/logo_teal_python.png +0 -0
  83. mindroot/coreplugins/home/templates/home.jinja2 +15 -6
  84. mindroot/coreplugins/index/indices/default/index.json +6 -6
  85. mindroot/coreplugins/jwt_auth/middleware.py +47 -2
  86. mindroot/coreplugins/jwt_auth/mod.py +40 -17
  87. mindroot/coreplugins/l8n/__init__.py +6 -0
  88. mindroot/coreplugins/l8n/debug_loader.py +85 -0
  89. mindroot/coreplugins/l8n/debug_middleware.py +74 -0
  90. mindroot/coreplugins/l8n/l8n_constants.py +19 -0
  91. mindroot/coreplugins/l8n/language_detection.py +183 -0
  92. mindroot/coreplugins/l8n/middleware.py +151 -0
  93. mindroot/coreplugins/l8n/mod.py +277 -0
  94. mindroot/coreplugins/l8n/monkey_patch_to_delete.py +186 -0
  95. mindroot/coreplugins/l8n/test_enhanced.py +298 -0
  96. mindroot/coreplugins/l8n/test_l8n.py +95 -0
  97. mindroot/coreplugins/l8n/test_l8n_standalone.py +251 -0
  98. mindroot/coreplugins/l8n/test_middleware.py +272 -0
  99. mindroot/coreplugins/l8n/utils.py +232 -0
  100. mindroot/coreplugins/mcp_/__init__.py +14 -0
  101. mindroot/coreplugins/mcp_/catalog_commands.py +328 -0
  102. mindroot/coreplugins/mcp_/catalog_manager.py +263 -0
  103. mindroot/coreplugins/mcp_/dynamic_commands.py +154 -0
  104. mindroot/coreplugins/mcp_/mcp_manager.py +1031 -0
  105. mindroot/coreplugins/mcp_/mod.py +367 -0
  106. mindroot/coreplugins/mcp_/oauth_storage.py +144 -0
  107. mindroot/coreplugins/mcp_/server_installer.py +79 -0
  108. mindroot/coreplugins/mcp_/setup.py +26 -0
  109. mindroot/coreplugins/mcp_/test_dynamic_commands.py +134 -0
  110. mindroot/coreplugins/mcp_/testmcpclient.py +92 -0
  111. mindroot/coreplugins/persona/mod.py +12 -7
  112. mindroot/coreplugins/signup/templates/signup.jinja2 +1 -1
  113. mindroot/coreplugins/subscriptions/__init__.py +1 -0
  114. mindroot/coreplugins/subscriptions/mod.py +14 -3
  115. mindroot/coreplugins/subscriptions/router.py +3 -0
  116. mindroot/coreplugins/user_service/__init__.py +1 -2
  117. mindroot/coreplugins/user_service/admin_init.py +1 -0
  118. mindroot/coreplugins/user_service/email_service.py +72 -17
  119. mindroot/coreplugins/user_service/mod.py +10 -2
  120. mindroot/coreplugins/user_service/router.py +2 -0
  121. mindroot/lib/auth/api_key.py +28 -0
  122. mindroot/lib/cli/plugins.py +94 -0
  123. mindroot/lib/plugins/default_plugin_manifest.json +20 -0
  124. mindroot/lib/plugins/installation.py +5 -5
  125. mindroot/lib/plugins/l8n_static_handler.py +225 -0
  126. mindroot/lib/plugins/loader.py +33 -3
  127. mindroot/lib/plugins/loader_with_l8n.py +281 -0
  128. mindroot/lib/plugins/manifest.py +236 -24
  129. mindroot/lib/providers/commands.py +3 -1
  130. mindroot/lib/route_decorators.py +5 -5
  131. mindroot/lib/templates.py +183 -11
  132. mindroot/lib/utils/merge_arrays.py +1 -1
  133. mindroot/migrate.py +39 -20
  134. mindroot/registry/data_access.py +1 -1
  135. mindroot/server.py +42 -13
  136. mindroot/server_missing_normal_args.py +197 -0
  137. mindroot/server_prev.py +173 -0
  138. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/METADATA +7 -2
  139. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/RECORD +144 -112
  140. mindroot/coreplugins/admin/static/favicon/about.txt +0 -6
  141. mindroot/coreplugins/admin/static/favicon/android-chrome-512x512.png +0 -0
  142. mindroot/coreplugins/admin/static/favicon/apple-touch-icon.png +0 -0
  143. mindroot/coreplugins/admin/static/favicon/favicon-16x16.png +0 -0
  144. mindroot/coreplugins/admin/static/favicon/favicon-32x32.png +0 -0
  145. mindroot/coreplugins/admin/static/favicon/favicon.ico +0 -0
  146. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/about.txt +0 -6
  147. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
  148. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
  149. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
  150. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
  151. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
  152. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon.ico +0 -0
  153. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/site.webmanifest +0 -1
  154. mindroot/coreplugins/admin/static/favicon/logo.png +0 -0
  155. mindroot/coreplugins/admin/static/favicon/site.webmanifest +0 -1
  156. mindroot/coreplugins/admin/static/js/backup/agent-editor.js +0 -186
  157. mindroot/coreplugins/admin/static/js/backup/agent-form.js +0 -1133
  158. mindroot/coreplugins/admin/static/js/backup/agent-list.js +0 -94
  159. mindroot/coreplugins/chat/static/favicon/about.txt +0 -6
  160. mindroot/coreplugins/chat/static/favicon/android-chrome-192x192.png +0 -0
  161. mindroot/coreplugins/chat/static/favicon/android-chrome-512x512.png +0 -0
  162. mindroot/coreplugins/chat/static/favicon/apple-touch-icon.png +0 -0
  163. mindroot/coreplugins/chat/static/favicon/favicon-16x16.png +0 -0
  164. mindroot/coreplugins/chat/static/favicon/favicon-32x32.png +0 -0
  165. mindroot/coreplugins/chat/static/favicon/favicon.ico +0 -0
  166. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/about.txt +0 -6
  167. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
  168. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
  169. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
  170. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
  171. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
  172. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon.ico +0 -0
  173. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/site.webmanifest +0 -1
  174. mindroot/coreplugins/chat/static/favicon/logo.png +0 -0
  175. mindroot/coreplugins/chat/static/favicon/site.webmanifest +0 -1
  176. mindroot/coreplugins/index/default.json +0 -76
  177. mindroot/coreplugins/user_service/file_trigger_service.py +0 -12
  178. mindroot/coreplugins/user_service/hooks.py +0 -23
  179. /mindroot/coreplugins/{admin/static/favicon/android-chrome-192x192.png → home/static/imgs/backuplogo.png} +0 -0
  180. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/WHEEL +0 -0
  181. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/entry_points.txt +0 -0
  182. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/licenses/LICENSE +0 -0
  183. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Standalone test script for the l8n localization plugin.
4
+
5
+ This script tests the core functionality without MindRoot dependencies.
6
+ """
7
+
8
+ import asyncio
9
+ import os
10
+ import re
11
+ from pathlib import Path
12
+
13
+ # Simulate the command decorator for testing
14
+ def command():
15
+ def decorator(func):
16
+ return func
17
+ return decorator
18
+
19
+ # Copy the core functions from mod.py without MindRoot dependencies
20
+ TRANSLATIONS = {}
21
+ LOCALIZED_FILES_DIR = Path(__file__).parent / "localized_files"
22
+
23
+ def extract_plugin_root(absolute_path: str) -> str:
24
+ """Extract the plugin root from an absolute path."""
25
+ path = Path(absolute_path)
26
+ parts = path.parts
27
+
28
+ # Find coreplugins in the path
29
+ if 'coreplugins' in parts:
30
+ coreplugins_idx = parts.index('coreplugins')
31
+ if coreplugins_idx + 1 < len(parts):
32
+ return '/'.join(parts[coreplugins_idx + 1:])
33
+
34
+ # Find src/[plugin_name] pattern for external plugins
35
+ for i, part in enumerate(parts):
36
+ if part == 'src' and i + 1 < len(parts):
37
+ potential_plugin = parts[i + 1]
38
+ if i + 2 < len(parts): # Has more path after src/plugin_name
39
+ return '/'.join(parts[i + 1:])
40
+
41
+ # Fallback: return the filename
42
+ return path.name
43
+
44
+ def get_localized_file_path(original_path: str) -> Path:
45
+ """Convert an original file path to its localized version path."""
46
+ plugin_root = extract_plugin_root(original_path)
47
+ path = Path(plugin_root)
48
+
49
+ # Determine if this is a core plugin or external plugin
50
+ if any(core_plugin in plugin_root for core_plugin in ['chat', 'admin', 'l8n']):
51
+ # Core plugin
52
+ base_dir = LOCALIZED_FILES_DIR / "coreplugins"
53
+ else:
54
+ # External plugin
55
+ base_dir = LOCALIZED_FILES_DIR / "external_plugins"
56
+
57
+ # Add .i18n before the file extension
58
+ stem = path.stem
59
+ suffix = path.suffix
60
+ new_filename = f"{stem}.i18n{suffix}"
61
+
62
+ localized_path = base_dir / path.parent / new_filename
63
+ return localized_path
64
+
65
+ async def write_localized_file(original_path: str, content: str, context=None):
66
+ """Write a localized version of a file with static placeholders."""
67
+ try:
68
+ localized_path = get_localized_file_path(original_path)
69
+
70
+ # Create directory if it doesn't exist
71
+ localized_path.parent.mkdir(parents=True, exist_ok=True)
72
+
73
+ # Write the content
74
+ with open(localized_path, 'w', encoding='utf-8') as f:
75
+ f.write(content)
76
+
77
+ return f"Localized file written to: {localized_path}"
78
+
79
+ except Exception as e:
80
+ return f"Error writing localized file: {str(e)}"
81
+
82
+ async def append_localized_file(original_path: str, content: str, context=None):
83
+ """Append content to an existing localized file."""
84
+ try:
85
+ localized_path = get_localized_file_path(original_path)
86
+
87
+ # Create directory if it doesn't exist
88
+ localized_path.parent.mkdir(parents=True, exist_ok=True)
89
+
90
+ # Append the content
91
+ with open(localized_path, 'a', encoding='utf-8') as f:
92
+ f.write(content)
93
+
94
+ return f"Content appended to: {localized_path}"
95
+
96
+ except Exception as e:
97
+ return f"Error appending to localized file: {str(e)}"
98
+
99
+ async def set_translations(language: str, translations: dict, context=None):
100
+ """Set translations for a specific language."""
101
+ try:
102
+ if not isinstance(translations, dict):
103
+ return "Error: translations must be a dictionary"
104
+
105
+ # Validate translation keys
106
+ invalid_keys = []
107
+ for key in translations.keys():
108
+ if not re.match(r'^[a-z0-9_]+$', key):
109
+ invalid_keys.append(key)
110
+
111
+ if invalid_keys:
112
+ return f"Error: Invalid translation keys: {invalid_keys}"
113
+
114
+ # Store translations
115
+ if language not in TRANSLATIONS:
116
+ TRANSLATIONS[language] = {}
117
+
118
+ TRANSLATIONS[language].update(translations)
119
+
120
+ return f"Set {len(translations)} translations for language '{language}'. Total languages: {len(TRANSLATIONS)}"
121
+
122
+ except Exception as e:
123
+ return f"Error setting translations: {str(e)}"
124
+
125
+ async def get_translations(language: str = None, context=None):
126
+ """Get translations for a specific language or all languages."""
127
+ try:
128
+ if language:
129
+ return TRANSLATIONS.get(language, {})
130
+ else:
131
+ return TRANSLATIONS
132
+
133
+ except Exception as e:
134
+ return f"Error getting translations: {str(e)}"
135
+
136
+ async def list_localized_files(context=None):
137
+ """List all localized files that have been created."""
138
+ try:
139
+ localized_files = []
140
+
141
+ if LOCALIZED_FILES_DIR.exists():
142
+ for file_path in LOCALIZED_FILES_DIR.rglob("*.i18n.*"):
143
+ localized_files.append(str(file_path.relative_to(LOCALIZED_FILES_DIR)))
144
+
145
+ return {
146
+ "count": len(localized_files),
147
+ "files": sorted(localized_files)
148
+ }
149
+
150
+ except Exception as e:
151
+ return f"Error listing localized files: {str(e)}"
152
+
153
+ def replace_placeholders(content: str, language: str) -> str:
154
+ """Replace __TRANSLATE_key__ placeholders with actual translations."""
155
+ if language not in TRANSLATIONS:
156
+ return content
157
+
158
+ translations = TRANSLATIONS[language]
159
+
160
+ def replace_match(match):
161
+ key = match.group(1)
162
+ return translations.get(key, match.group(0)) # Return original if no translation
163
+
164
+ # Replace all __TRANSLATE_key__ patterns
165
+ return re.sub(r'__TRANSLATE_([a-z0-9_]+)__', replace_match, content)
166
+
167
+ async def test_basic_functionality():
168
+ """Test the basic l8n functionality."""
169
+ print("Testing MindRoot l8n Plugin (Standalone)...\n")
170
+
171
+ # Test 1: Create a localized file
172
+ print("1. Testing write_localized_file...")
173
+ test_path = "/files/mindroot/src/mindroot/coreplugins/chat/templates/chat.jinja2"
174
+ test_content = "<h1>__TRANSLATE_chat_title__</h1><button>__TRANSLATE_buttons_send__</button>"
175
+
176
+ result = await write_localized_file(test_path, test_content)
177
+ print(f" Result: {result}")
178
+
179
+ # Test 2: Append to the file
180
+ print("\n2. Testing append_localized_file...")
181
+ append_content = "<p>__TRANSLATE_welcome_message__</p>"
182
+
183
+ result = await append_localized_file(test_path, append_content)
184
+ print(f" Result: {result}")
185
+
186
+ # Test 3: Set translations
187
+ print("\n3. Testing set_translations...")
188
+ spanish_translations = {
189
+ 'chat_title': 'Interfaz de Chat',
190
+ 'buttons_send': 'Enviar Mensaje',
191
+ 'welcome_message': 'Bienvenido al chat'
192
+ }
193
+
194
+ result = await set_translations('es', spanish_translations)
195
+ print(f" Result: {result}")
196
+
197
+ # Test 4: Set more translations
198
+ french_translations = {
199
+ 'chat_title': 'Interface de Chat',
200
+ 'buttons_send': 'Envoyer le Message',
201
+ 'welcome_message': 'Bienvenue dans le chat'
202
+ }
203
+
204
+ result = await set_translations('fr', french_translations)
205
+ print(f" Result: {result}")
206
+
207
+ # Test 5: Get translations
208
+ print("\n4. Testing get_translations...")
209
+ es_translations = await get_translations('es')
210
+ print(f" Spanish translations: {es_translations}")
211
+
212
+ all_translations = await get_translations()
213
+ print(f" All languages: {list(all_translations.keys())}")
214
+
215
+ # Test 6: List localized files
216
+ print("\n5. Testing list_localized_files...")
217
+ files_result = await list_localized_files()
218
+ print(f" Result: {files_result}")
219
+
220
+ # Test 7: Test placeholder replacement
221
+ print("\n6. Testing placeholder replacement...")
222
+ test_template = "<h1>__TRANSLATE_chat_title__</h1><button>__TRANSLATE_buttons_send__</button><p>__TRANSLATE_welcome_message__</p>"
223
+
224
+ spanish_result = replace_placeholders(test_template, 'es')
225
+ print(f" Spanish: {spanish_result}")
226
+
227
+ french_result = replace_placeholders(test_template, 'fr')
228
+ print(f" French: {french_result}")
229
+
230
+ # Test 8: Test with unknown language (should return original)
231
+ unknown_result = replace_placeholders(test_template, 'de')
232
+ print(f" German (unknown): {unknown_result}")
233
+
234
+ # Test 9: Check created file content
235
+ print("\n7. Checking created file content...")
236
+ localized_path = get_localized_file_path(test_path)
237
+ if localized_path.exists():
238
+ with open(localized_path, 'r', encoding='utf-8') as f:
239
+ file_content = f.read()
240
+ print(f" File content: {repr(file_content)}")
241
+
242
+ # Test replacement on actual file content
243
+ spanish_file_result = replace_placeholders(file_content, 'es')
244
+ print(f" Spanish version: {spanish_file_result}")
245
+ else:
246
+ print(f" File not found: {localized_path}")
247
+
248
+ print("\n✅ All tests completed!")
249
+
250
+ if __name__ == "__main__":
251
+ asyncio.run(test_basic_functionality())
@@ -0,0 +1,272 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script for the l8n middleware functionality.
4
+
5
+ This script simulates FastAPI request objects to test the middleware.
6
+ """
7
+
8
+ import asyncio
9
+ import os
10
+ from unittest.mock import Mock
11
+
12
+ # Mock FastAPI Request for testing
13
+ class MockRequest:
14
+ def __init__(self, path="/", query_params=None, cookies=None, headers=None):
15
+ self.url = Mock()
16
+ self.url.path = path
17
+ self.query_params = query_params or {}
18
+ self.cookies = cookies or {}
19
+ self.headers = headers or {}
20
+ self.state = Mock()
21
+
22
+ # Mock Response for testing
23
+ class MockResponse:
24
+ def __init__(self):
25
+ self.cookies_set = {}
26
+
27
+ def set_cookie(self, key, value, max_age=None, httponly=None, samesite=None):
28
+ self.cookies_set[key] = {
29
+ 'value': value,
30
+ 'max_age': max_age,
31
+ 'httponly': httponly,
32
+ 'samesite': samesite
33
+ }
34
+
35
+ # Import the middleware functions with sys.path manipulation
36
+ import sys
37
+ from pathlib import Path
38
+ sys.path.insert(0, str(Path(__file__).parent))
39
+
40
+ # Now import with absolute imports
41
+ import middleware
42
+ from language_detection import _parse_accept_language_header, get_fallback_language
43
+
44
+ # Create aliases for easier use
45
+ middleware_func = middleware.middleware
46
+ detect_language_from_request = middleware.detect_language_from_request
47
+ get_request_language = middleware.get_request_language
48
+
49
+ async def mock_call_next(request):
50
+ """Mock the next handler in the middleware chain."""
51
+ return MockResponse()
52
+
53
+ async def test_language_detection():
54
+ """Test language detection from various request sources."""
55
+ print("Testing Language Detection from Requests...\n")
56
+
57
+ # Test 1: URL parameter
58
+ print("1. Testing URL parameter detection...")
59
+ request = MockRequest(
60
+ path="/chat",
61
+ query_params={'lang': 'es'}
62
+ )
63
+ detected = detect_language_from_request(request)
64
+ print(f" URL param 'lang=es' -> {detected}")
65
+
66
+ # Test 2: Cookie value
67
+ print("\n2. Testing cookie detection...")
68
+ request = MockRequest(
69
+ path="/admin",
70
+ cookies={'mindroot_language': 'fr'}
71
+ )
72
+ detected = detect_language_from_request(request)
73
+ print(f" Cookie 'mindroot_language=fr' -> {detected}")
74
+
75
+ # Test 3: Accept-Language header
76
+ print("\n3. Testing Accept-Language header...")
77
+ test_headers = [
78
+ 'en-US,en;q=0.9,es;q=0.8',
79
+ 'es-ES,es;q=0.9,en;q=0.8',
80
+ 'fr-FR,fr;q=0.9',
81
+ 'de-DE,de;q=0.8,en;q=0.5',
82
+ 'zh-CN,zh;q=0.9,en;q=0.1'
83
+ ]
84
+
85
+ for header in test_headers:
86
+ request = MockRequest(
87
+ path="/test",
88
+ headers={'accept-language': header}
89
+ )
90
+ detected = detect_language_from_request(request)
91
+ print(f" '{header}' -> {detected}")
92
+
93
+ # Test 4: Environment variable
94
+ print("\n4. Testing environment variable...")
95
+ os.environ['MINDROOT_LANGUAGE'] = 'de'
96
+ request = MockRequest(path="/test")
97
+ detected = detect_language_from_request(request)
98
+ print(f" MINDROOT_LANGUAGE=de -> {detected}")
99
+
100
+ # Test 5: Priority order (URL param should override everything)
101
+ print("\n5. Testing priority order...")
102
+ request = MockRequest(
103
+ path="/test",
104
+ query_params={'lang': 'es'},
105
+ cookies={'mindroot_language': 'fr'},
106
+ headers={'accept-language': 'de-DE,de;q=0.9'}
107
+ )
108
+ detected = detect_language_from_request(request)
109
+ print(f" URL=es, Cookie=fr, Header=de -> {detected} (should be es)")
110
+
111
+ # Clean up
112
+ os.environ.pop('MINDROOT_LANGUAGE', None)
113
+
114
+ print("\n✅ Language detection tests completed!")
115
+
116
+ async def test_middleware_functionality():
117
+ """Test the complete middleware functionality."""
118
+ print("\nTesting Middleware Functionality...\n")
119
+
120
+ # Test 1: Basic middleware flow
121
+ print("1. Testing basic middleware flow...")
122
+ request = MockRequest(
123
+ path="/chat",
124
+ query_params={'lang': 'es'},
125
+ headers={'accept-language': 'en-US,en;q=0.9'}
126
+ )
127
+
128
+ response = await middleware_func(request, mock_call_next)
129
+
130
+ print(f" Request language set to: {request.state.language}")
131
+ print(f" Global language: {get_request_language()}")
132
+ print(f" Cookie set: {'mindroot_language' in response.cookies_set}")
133
+
134
+ if 'mindroot_language' in response.cookies_set:
135
+ cookie_info = response.cookies_set['mindroot_language']
136
+ print(f" Cookie value: {cookie_info['value']}")
137
+ print(f" Cookie max_age: {cookie_info['max_age']} seconds")
138
+
139
+ # Test 2: Different language sources
140
+ print("\n2. Testing different language sources...")
141
+
142
+ test_cases = [
143
+ {
144
+ 'name': 'Accept-Language only',
145
+ 'request': MockRequest(
146
+ path="/admin",
147
+ headers={'accept-language': 'fr-FR,fr;q=0.9,en;q=0.8'}
148
+ ),
149
+ 'expected': 'fr'
150
+ },
151
+ {
152
+ 'name': 'Cookie preference',
153
+ 'request': MockRequest(
154
+ path="/settings",
155
+ cookies={'mindroot_language': 'de'}
156
+ ),
157
+ 'expected': 'de'
158
+ },
159
+ {
160
+ 'name': 'URL override',
161
+ 'request': MockRequest(
162
+ path="/dashboard",
163
+ query_params={'lang': 'es'},
164
+ cookies={'mindroot_language': 'fr'}
165
+ ),
166
+ 'expected': 'es'
167
+ }
168
+ ]
169
+
170
+ for case in test_cases:
171
+ print(f"\n Testing: {case['name']}")
172
+ response = await middleware_func(case['request'], mock_call_next)
173
+ actual = case['request'].state.language
174
+ expected = case['expected']
175
+ status = "✅" if actual == expected else "❌"
176
+ print(f" {status} Expected: {expected}, Got: {actual}")
177
+
178
+ # Test 3: Error handling
179
+ print("\n3. Testing error handling...")
180
+
181
+ # Create a request that might cause issues
182
+ request = MockRequest(
183
+ path="/test",
184
+ headers={'accept-language': 'invalid-header-format'}
185
+ )
186
+
187
+ try:
188
+ response = await middleware_func(request, mock_call_next)
189
+ print(f" ✅ Error handled gracefully, language: {request.state.language}")
190
+ except Exception as e:
191
+ print(f" ❌ Unexpected error: {e}")
192
+
193
+ print("\n✅ Middleware functionality tests completed!")
194
+
195
+ async def test_integration_with_templates():
196
+ """Test integration with the template system."""
197
+ print("\nTesting Integration with Template System...\n")
198
+
199
+ # Import template-related functions
200
+ from test_l8n_standalone import set_translations, replace_placeholders
201
+
202
+ # Set up some translations
203
+ print("1. Setting up translations...")
204
+ await set_translations('en', {
205
+ 'page_title': 'Welcome to MindRoot',
206
+ 'nav_home': 'Home',
207
+ 'nav_settings': 'Settings'
208
+ })
209
+
210
+ await set_translations('es', {
211
+ 'page_title': 'Bienvenido a MindRoot',
212
+ 'nav_home': 'Inicio',
213
+ 'nav_settings': 'Configuración'
214
+ })
215
+
216
+ await set_translations('fr', {
217
+ 'page_title': 'Bienvenue à MindRoot',
218
+ 'nav_home': 'Accueil',
219
+ 'nav_settings': 'Paramètres'
220
+ })
221
+
222
+ # Template content
223
+ template = "<title>__TRANSLATE_page_title__</title><nav><a>__TRANSLATE_nav_home__</a><a>__TRANSLATE_nav_settings__</a></nav>"
224
+
225
+ # Test different language requests
226
+ print("\n2. Testing template translation with different requests...")
227
+
228
+ test_requests = [
229
+ ('English (default)', MockRequest(path="/")),
230
+ ('Spanish (URL param)', MockRequest(path="/", query_params={'lang': 'es'})),
231
+ ('French (cookie)', MockRequest(path="/", cookies={'mindroot_language': 'fr'})),
232
+ ('German (header)', MockRequest(path="/", headers={'accept-language': 'de-DE,de;q=0.9,en;q=0.8'}))
233
+ ]
234
+
235
+ for name, request in test_requests:
236
+ print(f"\n Testing: {name}")
237
+
238
+ # Process request through middleware
239
+ await middleware_func(request, mock_call_next)
240
+
241
+ # Get the detected language
242
+ detected_lang = get_request_language()
243
+ print(f" Detected language: {detected_lang}")
244
+
245
+ # Translate the template
246
+ translated = replace_placeholders(template, detected_lang)
247
+
248
+ # Extract title for display
249
+ title_start = translated.find('<title>') + 7
250
+ title_end = translated.find('</title>')
251
+ title = translated[title_start:title_end]
252
+
253
+ print(f" Translated title: {title}")
254
+
255
+ print("\n✅ Template integration tests completed!")
256
+
257
+ async def main():
258
+ """Run all middleware tests."""
259
+ print("=" * 60)
260
+ print("MindRoot l8n Plugin - Middleware Testing Suite")
261
+ print("=" * 60)
262
+
263
+ await test_language_detection()
264
+ await test_middleware_functionality()
265
+ await test_integration_with_templates()
266
+
267
+ print("\n" + "=" * 60)
268
+ print("✅ All middleware tests completed successfully!")
269
+ print("=" * 60)
270
+
271
+ if __name__ == "__main__":
272
+ asyncio.run(main())