Source code for mindroot.lib.plugins.l8n_static_handler

import os
import re
from pathlib import Path
from fastapi import Request, Response
from fastapi.staticfiles import StaticFiles
from starlette.responses import FileResponse
from starlette.types import Scope, Receive, Send
#from lib.utils.debug import debug_box
import sys
import traceback

# Import l8n translation functions
try:
    from mindroot.coreplugins.l8n.utils import replace_placeholders, extract_plugin_root, get_localized_file_path, load_plugin_translations
    from mindroot.coreplugins.l8n.middleware import get_request_language
    from mindroot.coreplugins.l8n.language_detection import get_fallback_language
    L8N_AVAILABLE = True
except ImportError as e:
    trace = traceback.format_exc()
    print(f"L8n not available for static files: {e}\n{trace}")
    L8N_AVAILABLE = False
    sys.exit(1)

[docs] class TranslatedStaticFiles(StaticFiles): """Custom StaticFiles handler that applies l8n translations to JavaScript files.""" def __init__(self, *, directory: str, plugin_name: str = None, **kwargs): super().__init__(directory=directory, **kwargs) self.plugin_name = plugin_name self.directory_path = Path(directory)
[docs] def get_current_language(self, request: Request) -> str: """Get the current language for the request.""" print("Getting current language for static file request...") if not L8N_AVAILABLE: print("WARNING: L8n not available, defaulting to 'en'.") return 'en' try: # Try to get from request state first (set by middleware) if hasattr(request.state, 'language'): print(f"Using language from request state: {request.state.language}") return request.state.language # Fallback to l8n middleware function lang = get_request_language() return get_fallback_language(lang) if lang else 'en' except Exception as e: print(f"Error getting language for static file: {e}") return 'en'
[docs] def should_translate_file(self, file_path: Path) -> bool: """Check if a file should be translated.""" if not L8N_AVAILABLE: print("L8n not available, skipping translation check.") return False # show suffix print(f"Checking if file should be translated: {file_path} suffix is {file_path.suffix}") # Only translate JavaScript files for now return file_path.suffix.lower() in ['.js', '.mjs']
[docs] def apply_translations_to_js(self, content: str, language: str, file_path: str) -> str: """Apply translations to JavaScript content. This looks for __TRANSLATE_key__ placeholders in JS files and replaces them with translated strings. If translations are missing, returns None to signal that the original file should be served instead. """ if not L8N_AVAILABLE or not content: return content try: # Use the l8n replace_placeholders function translated_content = replace_placeholders(content, language, file_path) # If None is returned, translations are incomplete if translated_content is None: print(f"L8n: Missing translations for JS file, will serve original: {file_path}") return None return translated_content except Exception as e: print(f"Error applying translations to JS file {file_path}: {e}") return None # Fallback to original file on error
[docs] async def get_response(self, path: str, scope: Scope) -> Response: """Override to add translation support for JavaScript files.""" try: # Get the full file path full_path = self.directory_path / path # Check if file exists and should be translated print(f"l8n Checking static file: {full_path}") if full_path.exists() and self.should_translate_file(full_path): print(f"l8n Translating static file: {full_path}") # Create a request object to get language request = Request(scope) current_language = self.get_current_language(request) print(f"[UPDATE] l8n Current language for static file: {current_language}") localized_path = get_localized_file_path(str(full_path)) # Check if localized file exists if localized_path.exists(): # Read the localized file content with open(localized_path, "r", encoding="utf-8") as f: content = f.read() # Apply translations translated_content = self.apply_translations_to_js( content, current_language, str(localized_path) ) # If translations are complete, serve translated content if translated_content is not None: print(f"l8n: Serving translated JS file: {full_path}") return Response( content=translated_content, media_type="application/javascript", headers={ "Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0" } ) else: print(f"l8n: Translations incomplete, serving original JS file: {full_path}") else: print(f"l8n: No localized version found, serving original JS file: {full_path}") # For non-JS files or when translation is not available, use default behavior print(f"l8n Serving static file without translation: {full_path}") return await super().get_response(path, scope) except Exception as e: trace = traceback.format_exc() print(f"Error in translated static file handler: {e}\n{trace}") # Fallback to default behavior on error return await super().get_response(path, scope)
[docs] def mount_translated_static_files(app, plugin_name: str, category: str): """Mount plugin static files with translation support. Args: app (FastAPI): The FastAPI application instance plugin_name (str): Name of the plugin category (str): Plugin category ('core' or 'installed') """ from .paths import get_plugin_path # debug_box(f"Mounting translated static files for plugin: {plugin_name} in category: {category}") plugin_dir = get_plugin_path(plugin_name) if not plugin_dir: return dir_name = os.path.basename(plugin_dir) if category != 'core': static_path = os.path.join(plugin_dir, 'src', dir_name, 'static') if not os.path.exists(static_path): static_path = os.path.join(plugin_dir, 'static') else: static_path = os.path.join(plugin_dir, 'static') if os.path.exists(static_path): # Use our custom TranslatedStaticFiles instead of regular StaticFiles app.mount( f"/{dir_name}/static", TranslatedStaticFiles(directory=static_path, plugin_name=plugin_name), name=f"/{dir_name}/static" ) print(f"Mounted translated static files for plugin: {plugin_name} at {static_path}") #debug_box(f"Mounted translated static files for plugin: {plugin_name} at {static_path}") else: print(f"No static files found for plugin: {plugin_name}. Searched in {static_path}")
#debug_box(f"No static files found for plugin: {plugin_name}. Searched in {static_path}") # JavaScript-specific translation helpers
[docs] def create_js_translation_object(translations: dict) -> str: """Create a JavaScript object containing translations. This can be injected into JS files to provide client-side translation support. Args: translations: Dictionary of translation key-value pairs Returns: JavaScript code defining a translation object """ import json # Safely serialize translations to JavaScript js_translations = json.dumps(translations, ensure_ascii=False) return f""" // Auto-generated translation object window.MINDROOT_TRANSLATIONS = {js_translations}; // Helper function to get translations window.translate = function(key, fallback) {{ return window.MINDROOT_TRANSLATIONS[key] || fallback || key; }}; // Alias for shorter usage window.t = window.translate; """
[docs] def inject_translations_into_js(content: str, translations: dict) -> str: """Inject translation object into JavaScript content. This prepends translation definitions to JS files. Args: content: Original JavaScript content translations: Translation dictionary Returns: JavaScript content with translations injected """ if not translations: return content translation_js = create_js_translation_object(translations) return translation_js + "\n\n" + content