mindroot 9.3.0__py3-none-any.whl → 9.6.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 +105 -9
  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-publish-old-delete.js +166 -0
  35. mindroot/coreplugins/admin/static/js/registry-manager.js +351 -0
  36. mindroot/coreplugins/admin/static/js/registry-publish-section.js +377 -0
  37. mindroot/coreplugins/admin/static/js/registry-search-section.js +400 -0
  38. mindroot/coreplugins/admin/static/js/registry-search-section.js.bak +3 -0
  39. mindroot/coreplugins/admin/static/js/registry-settings.js +69 -0
  40. mindroot/coreplugins/admin/static/js/registry-shared-services.js +903 -0
  41. mindroot/coreplugins/admin/static/js/registry-simple-sections.js +85 -0
  42. mindroot/coreplugins/admin/static/js/secure-widget-manager.js +438 -0
  43. mindroot/coreplugins/admin/static/logo.png +0 -0
  44. mindroot/coreplugins/admin/templates/admin.jinja2 +275 -110
  45. mindroot/coreplugins/agent/Assistant/agent.json +27 -11
  46. mindroot/coreplugins/agent/agent.py +2 -2
  47. mindroot/coreplugins/agent/command_parser.py +25 -10
  48. mindroot/coreplugins/agent/templates/system.jinja2 +0 -12
  49. mindroot/coreplugins/chat/__init__.py +4 -1
  50. mindroot/coreplugins/chat/router.py +132 -20
  51. mindroot/coreplugins/chat/router_dedup_patch.py +20 -0
  52. mindroot/coreplugins/chat/services.py +31 -1
  53. mindroot/coreplugins/chat/static/css/action-fix.css +32 -0
  54. mindroot/coreplugins/chat/static/css/admin-custom.css +5 -3
  55. mindroot/coreplugins/chat/static/css/dark.css +24 -3
  56. mindroot/coreplugins/chat/static/css/default.css +24 -3
  57. mindroot/coreplugins/chat/static/css/main.css +1 -0
  58. mindroot/coreplugins/chat/static/js/action.js +137 -60
  59. mindroot/coreplugins/chat/static/js/chat-history.js +3 -0
  60. mindroot/coreplugins/chat/static/js/chat.js +59 -16
  61. mindroot/coreplugins/chat/static/js/chat.js.diff +221 -0
  62. mindroot/coreplugins/chat/static/js/chatform.js +2 -2
  63. mindroot/coreplugins/chat/static/site.webmanifest +1 -1
  64. mindroot/coreplugins/chat/templates/chat.jinja2 +3 -3
  65. mindroot/coreplugins/chat/widget_manager.py +139 -0
  66. mindroot/coreplugins/chat/widget_routes.py +287 -0
  67. mindroot/coreplugins/check_list/inject/admin.jinja2 +1 -1
  68. mindroot/coreplugins/email/__init__.py +2 -0
  69. mindroot/coreplugins/email/email_provider.py +2 -2
  70. mindroot/coreplugins/email/mod.py +100 -0
  71. mindroot/coreplugins/email/services.py +5 -3
  72. mindroot/coreplugins/email/smtp_handler.py +9 -3
  73. mindroot/coreplugins/email/test_email_service.py +75 -0
  74. mindroot/coreplugins/env_manager/mod.py +61 -25
  75. mindroot/coreplugins/home/router.py +37 -2
  76. mindroot/coreplugins/home/static/imgs/logo.png +0 -0
  77. mindroot/coreplugins/home/static/imgs/logo.png.bak +0 -0
  78. mindroot/coreplugins/home/static/imgs/logo_teal.png +0 -0
  79. mindroot/coreplugins/home/static/imgs/logo_teal2.png +0 -0
  80. mindroot/coreplugins/home/static/imgs/logo_teal_detailed.png +0 -0
  81. mindroot/coreplugins/home/static/imgs/logo_teal_python.png +0 -0
  82. mindroot/coreplugins/home/templates/home.jinja2 +15 -6
  83. mindroot/coreplugins/index/indices/default/index.json +39 -6
  84. mindroot/coreplugins/jwt_auth/middleware.py +47 -2
  85. mindroot/coreplugins/jwt_auth/mod.py +40 -17
  86. mindroot/coreplugins/l8n/__init__.py +6 -0
  87. mindroot/coreplugins/l8n/debug_loader.py +85 -0
  88. mindroot/coreplugins/l8n/debug_middleware.py +74 -0
  89. mindroot/coreplugins/l8n/l8n_constants.py +19 -0
  90. mindroot/coreplugins/l8n/language_detection.py +183 -0
  91. mindroot/coreplugins/l8n/middleware.py +151 -0
  92. mindroot/coreplugins/l8n/mod.py +277 -0
  93. mindroot/coreplugins/l8n/monkey_patch_to_delete.py +186 -0
  94. mindroot/coreplugins/l8n/test_enhanced.py +298 -0
  95. mindroot/coreplugins/l8n/test_l8n.py +95 -0
  96. mindroot/coreplugins/l8n/test_l8n_standalone.py +251 -0
  97. mindroot/coreplugins/l8n/test_middleware.py +272 -0
  98. mindroot/coreplugins/l8n/utils.py +232 -0
  99. mindroot/coreplugins/mcp_/__init__.py +14 -0
  100. mindroot/coreplugins/mcp_/catalog_commands.py +328 -0
  101. mindroot/coreplugins/mcp_/catalog_manager.py +263 -0
  102. mindroot/coreplugins/mcp_/dynamic_commands.py +154 -0
  103. mindroot/coreplugins/mcp_/mcp_manager.py +1031 -0
  104. mindroot/coreplugins/mcp_/mod.py +367 -0
  105. mindroot/coreplugins/mcp_/oauth_storage.py +144 -0
  106. mindroot/coreplugins/mcp_/server_installer.py +79 -0
  107. mindroot/coreplugins/mcp_/setup.py +26 -0
  108. mindroot/coreplugins/mcp_/test_dynamic_commands.py +134 -0
  109. mindroot/coreplugins/mcp_/testmcpclient.py +92 -0
  110. mindroot/coreplugins/persona/mod.py +12 -7
  111. mindroot/coreplugins/signup/templates/signup.jinja2 +1 -1
  112. mindroot/coreplugins/subscriptions/__init__.py +1 -0
  113. mindroot/coreplugins/subscriptions/mod.py +14 -3
  114. mindroot/coreplugins/subscriptions/router.py +3 -0
  115. mindroot/coreplugins/user_service/__init__.py +1 -2
  116. mindroot/coreplugins/user_service/admin_init.py +1 -0
  117. mindroot/coreplugins/user_service/email_service.py +72 -17
  118. mindroot/coreplugins/user_service/mod.py +10 -2
  119. mindroot/coreplugins/user_service/router.py +2 -0
  120. mindroot/lib/auth/api_key.py +28 -0
  121. mindroot/lib/cli/plugins.py +94 -0
  122. mindroot/lib/plugins/default_plugin_manifest.json +20 -0
  123. mindroot/lib/plugins/installation.py +5 -5
  124. mindroot/lib/plugins/l8n_static_handler.py +225 -0
  125. mindroot/lib/plugins/loader.py +33 -3
  126. mindroot/lib/plugins/loader_with_l8n.py +281 -0
  127. mindroot/lib/plugins/manifest.py +236 -24
  128. mindroot/lib/providers/commands.py +3 -1
  129. mindroot/lib/route_decorators.py +5 -5
  130. mindroot/lib/templates.py +183 -11
  131. mindroot/lib/utils/merge_arrays.py +1 -1
  132. mindroot/migrate.py +39 -20
  133. mindroot/registry/data_access.py +1 -1
  134. mindroot/server.py +42 -13
  135. mindroot/server_missing_normal_args.py +197 -0
  136. mindroot/server_prev.py +173 -0
  137. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/METADATA +7 -2
  138. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/RECORD +143 -113
  139. mindroot/coreplugins/admin/plugin_manager_backup.py +0 -615
  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.6.0.dist-info}/WHEEL +0 -0
  181. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/entry_points.txt +0 -0
  182. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/licenses/LICENSE +0 -0
  183. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/top_level.txt +0 -0
@@ -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")
@@ -0,0 +1,186 @@
1
+ to_delete = """
2
+ import os
3
+ import re
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ # Import the translation replacement function
8
+ from .mod import replace_placeholders, LOCALIZED_FILES_DIR
9
+
10
+ # Import the enhanced language detection
11
+ from .language_detection import get_fallback_language
12
+ from .middleware import get_request_language
13
+
14
+ # Store original Jinja2 loader methods
15
+ _original_get_source = None
16
+ _original_loader_get_source = None
17
+
18
+ def get_current_language() -> str:
19
+ """
20
+ Get the current language for the request using enhanced detection.
21
+ """
22
+ return get_fallback_language(get_request_language())
23
+
24
+ def find_localized_template(template_name: str) -> Optional[Path]:
25
+ """
26
+ Find a localized version of a template.
27
+
28
+ Uses relative path matching to find templates regardless of installation directory.
29
+
30
+ Args:
31
+ template_name: Original template name/path
32
+
33
+ Returns:
34
+ Path to localized template if found, None otherwise
35
+ """
36
+ if not LOCALIZED_FILES_DIR.exists():
37
+ return None
38
+
39
+ # Convert template name to potential localized paths
40
+ template_path = Path(template_name)
41
+ stem = template_path.stem
42
+ suffix = template_path.suffix
43
+
44
+ # Create the .i18n version filename
45
+ localized_filename = f"{stem}.i18n{suffix}"
46
+
47
+ # Search strategies in order of preference:
48
+ search_strategies = [
49
+ # 1. Exact filename match anywhere in localized_files
50
+ lambda: list(LOCALIZED_FILES_DIR.rglob(localized_filename)),
51
+
52
+ # 2. Match last 2 path components
53
+ lambda: _find_by_path_components(template_path, localized_filename, 2),
54
+
55
+ # 3. Match last 3 path components
56
+ lambda: _find_by_path_components(template_path, localized_filename, 3),
57
+ ]
58
+
59
+ for strategy in search_strategies:
60
+ matches = strategy()
61
+ if matches:
62
+ # Return the first match
63
+ return matches[0]
64
+
65
+ return None
66
+
67
+ def _find_by_path_components(original_path: Path, localized_filename: str, num_components: int) -> list:
68
+ """
69
+ Find localized templates by matching the last N path components.
70
+
71
+ Args:
72
+ original_path: Original template path
73
+ localized_filename: Localized filename to search for
74
+ num_components: Number of path components to match
75
+
76
+ Returns:
77
+ List of matching paths
78
+ """
79
+ if len(original_path.parts) < num_components:
80
+ return []
81
+
82
+ # Get the last N-1 components (excluding filename) + localized filename
83
+ target_components = original_path.parts[-(num_components-1):]
84
+ target_path_pattern = '/'.join(target_components[:-1]) + '/' + localized_filename
85
+
86
+ matches = []
87
+ for candidate in LOCALIZED_FILES_DIR.rglob(localized_filename):
88
+ candidate_relative = candidate.relative_to(LOCALIZED_FILES_DIR)
89
+ if str(candidate_relative).endswith(target_path_pattern):
90
+ matches.append(candidate)
91
+
92
+ return matches
93
+
94
+ def patched_get_source(self, environment, template):
95
+ """
96
+ Patched version of Jinja2 loader's get_source method.
97
+
98
+ This intercepts template loading and:
99
+ 1. Checks for localized versions
100
+ 2. Replaces placeholders with translations
101
+ 3. Falls back to original templates if no localized version exists
102
+ """
103
+ # First, try to find a localized version
104
+ localized_path = find_localized_template(template)
105
+
106
+ if localized_path and localized_path.exists():
107
+ try:
108
+ # Read the localized template
109
+ with open(localized_path, 'r', encoding='utf-8') as f:
110
+ source = f.read()
111
+
112
+ # Replace placeholders with translations
113
+ current_language = get_current_language()
114
+ translated_source = replace_placeholders(source, current_language, str(localized_path))
115
+
116
+ # Return the translated source
117
+ # Note: We use the original template name for caching purposes
118
+ return translated_source, str(localized_path), lambda: True
119
+
120
+ except Exception as e:
121
+ # If there's an error reading the localized template, fall back to original
122
+ print(f"Error loading localized template {localized_path}: {e}")
123
+
124
+ # Fall back to original template loading
125
+ return _original_get_source(self, environment, template)
126
+
127
+ def install_monkey_patch():
128
+ """
129
+ Install the monkey patch for Jinja2 template loading.
130
+
131
+ This patches the FileSystemLoader.get_source method to intercept
132
+ template loading and provide localized versions when available.
133
+ """
134
+ global _original_get_source
135
+ return
136
+ try:
137
+ from jinja2 import FileSystemLoader
138
+
139
+ # Store the original method
140
+ if _original_get_source is None:
141
+ _original_get_source = FileSystemLoader.get_source
142
+
143
+ # Install the patch
144
+ FileSystemLoader.get_source = patched_get_source
145
+
146
+ print("L8n monkey patch installed successfully")
147
+ return True
148
+
149
+ except ImportError:
150
+ print("Warning: Could not import Jinja2 for monkey patching")
151
+ return False
152
+ except Exception as e:
153
+ print(f"Error installing l8n monkey patch: {e}")
154
+ return False
155
+
156
+ def uninstall_monkey_patch():
157
+ """
158
+ Remove the monkey patch and restore original Jinja2 behavior.
159
+ """
160
+ global _original_get_source
161
+
162
+ try:
163
+ from jinja2 import FileSystemLoader
164
+
165
+ if _original_get_source is not None:
166
+ FileSystemLoader.get_source = _original_get_source
167
+ print("L8n monkey patch removed successfully")
168
+ return True
169
+ else:
170
+ print("No monkey patch to remove")
171
+ return False
172
+
173
+ except ImportError:
174
+ print("Warning: Could not import Jinja2 for monkey patch removal")
175
+ return False
176
+ except Exception as e:
177
+ print(f"Error removing l8n monkey patch: {e}")
178
+ return False
179
+
180
+ # Auto-install the monkey patch when this module is imported
181
+ # Prevent double installation by checking if already installed
182
+ if __name__ != '__main__' and not hasattr(install_monkey_patch, '_auto_installed'):
183
+ install_monkey_patch()
184
+ install_monkey_patch._auto_installed = True
185
+
186
+ """