""" title: UI Language Debugger author: Fu-Jie author_url: https://github.com/Fu-Jie/openwebui-extensions funding_url: https://github.com/open-webui version: 0.1.0 icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxwYXRoIGQ9Im01IDggNiA2Ii8+CiAgPHBhdGggZD0ibTQgMTQgNi02IDItMiIvPgogIDxwYXRoIGQ9Ik0yIDVoMTIiLz4KICA8cGF0aCBkPSJNNyAyaDEiLz4KICA8cGF0aCBkPSJtMjIgMjItNS0xMC01IDEwIi8+CiAgPHBhdGggZD0iTTE0IDE4aDYiLz4KPC9zdmc+Cg== description: Debug UI language detection in the browser console and on-page panel. """ import json import logging from typing import Optional, Dict, Any, Callable, Awaitable from pydantic import BaseModel, Field logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", ) logger = logging.getLogger(__name__) HTML_WRAPPER_TEMPLATE = """
""" CONTENT_TEMPLATE = """
🧭 UI Language Debugger
python.ui_language{python_language}
document.documentElement.lang-
document.documentElement.getAttribute('lang')-
document.documentElement.dir-
document.body.lang-
navigator.language-
navigator.languages-
localStorage.language-
localStorage.locale-
localStorage.i18n-
localStorage.settings-
document.documentElement.dataset-
""" STYLE_TEMPLATE = """ .lang-debug-card { border: 1px solid #e2e8f0; border-radius: 12px; background: #ffffff; overflow: hidden; box-shadow: 0 2px 10px rgba(15, 23, 42, 0.06); } .lang-debug-header { padding: 12px 16px; background: linear-gradient(135deg, #6366f1, #8b5cf6); color: #fff; font-weight: 600; } .lang-debug-body { padding: 12px 16px; display: flex; flex-direction: column; gap: 8px; } .lang-debug-row { display: flex; justify-content: space-between; gap: 12px; font-size: 0.9em; color: #1f2937; } .lang-debug-row code { background: #f8fafc; border: 1px solid #e2e8f0; padding: 2px 6px; border-radius: 6px; color: #0f172a; } """ SCRIPT_TEMPLATE = """ """ class Action: class Valves(BaseModel): SHOW_STATUS: bool = Field( default=True, description="Whether to show operation status updates.", ) SHOW_DEBUG_LOG: bool = Field( default=True, description="Whether to print debug logs in the browser console.", ) def __init__(self): self.valves = self.Valves() def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]: if isinstance(__user__, (list, tuple)): user_data = __user__[0] if __user__ else {} elif isinstance(__user__, dict): user_data = __user__ else: user_data = {} return { "user_id": user_data.get("id", "unknown_user"), "user_name": user_data.get("name", "User"), "user_language": user_data.get("language", ""), } def _get_chat_context( self, body: dict, __metadata__: Optional[dict] = None ) -> Dict[str, str]: chat_id = "" message_id = "" if isinstance(body, dict): chat_id = body.get("chat_id", "") message_id = body.get("id", "") if not chat_id or not message_id: body_metadata = body.get("metadata", {}) if isinstance(body_metadata, dict): if not chat_id: chat_id = body_metadata.get("chat_id", "") if not message_id: message_id = body_metadata.get("message_id", "") if __metadata__ and isinstance(__metadata__, dict): if not chat_id: chat_id = __metadata__.get("chat_id", "") if not message_id: message_id = __metadata__.get("message_id", "") return { "chat_id": str(chat_id).strip(), "message_id": str(message_id).strip(), } async def _emit_status( self, emitter: Optional[Callable[[Any], Awaitable[None]]], description: str, done: bool = False, ): if self.valves.SHOW_STATUS and emitter: await emitter( {"type": "status", "data": {"description": description, "done": done}} ) async def _emit_debug_log( self, emitter: Optional[Callable[[Any], Awaitable[None]]], title: str, data: dict, ): if not self.valves.SHOW_DEBUG_LOG or not emitter: return try: js_code = f""" (async function() {{ console.group("🛠️ {title}"); console.log({json.dumps(data, ensure_ascii=False)}); console.groupEnd(); }})(); """ await emitter({"type": "execute", "data": {"code": js_code}}) except Exception as e: logger.error("Error emitting debug log: %s", e, exc_info=True) def _merge_html( self, existing_html: str, new_content: str, new_styles: str = "", new_scripts: str = "", user_language: str = "en-US", ) -> str: if not existing_html: base_html = HTML_WRAPPER_TEMPLATE.replace("{user_language}", user_language) else: base_html = existing_html if "" in base_html: base_html = base_html.replace( "", f"{new_content}\n ", ) if new_styles and "/* STYLES_INSERTION_POINT */" in base_html: base_html = base_html.replace( "/* STYLES_INSERTION_POINT */", f"{new_styles}\n /* STYLES_INSERTION_POINT */", ) if new_scripts and "" in base_html: base_html = base_html.replace( "", f"{new_scripts}\n ", ) return base_html async def action( self, body: dict, __user__: Optional[Dict[str, Any]] = None, __event_emitter__: Optional[Any] = None, __event_call__: Optional[Callable[[Any], Awaitable[None]]] = None, __metadata__: Optional[dict] = None, __request__: Optional[Any] = None, ) -> Optional[dict]: await self._emit_status(__event_emitter__, "Detecting UI language...", False) user_ctx = self._get_user_context(__user__) await self._emit_debug_log( __event_emitter__, "Language Debugger: user context", user_ctx, ) ui_language = "" if __event_call__: try: response = await __event_call__( { "type": "execute", "data": { "code": "return (localStorage.getItem('locale') || localStorage.getItem('language') || (navigator.languages && navigator.languages[0]) || navigator.language || document.documentElement.lang || '')", }, } ) await self._emit_debug_log( __event_emitter__, "Language Debugger: execute response", {"response": response}, ) if isinstance(response, dict) and "value" in response: ui_language = response.get("value", "") or "" elif isinstance(response, str): ui_language = response except Exception as e: logger.error( "Failed to read UI language from frontend: %s", e, exc_info=True ) unique_id = f"lang_{int(__import__('time').time() * 1000)}" content_html = CONTENT_TEMPLATE.replace("{unique_id}", unique_id).replace( "{python_language}", ui_language or "-" ) script_html = SCRIPT_TEMPLATE.replace("{unique_id}", unique_id) script_html = script_html.replace("{{", "{").replace("}}", "}") final_html = self._merge_html( "", content_html, STYLE_TEMPLATE, script_html, "en", ) html_embed_tag = f"```html\n{final_html}\n```" body["messages"][-1]["content"] = ( body["messages"][-1].get("content", "") + "\n\n" + html_embed_tag ) await self._emit_status(__event_emitter__, "UI language captured.", True) return body