Skip to content

Commit 8a9c5bb

Browse files
committed
Move i18n logging helpers into Core
1 parent ef0ea70 commit 8a9c5bb

4 files changed

Lines changed: 482 additions & 222 deletions

File tree

Core/Auto_Command_Builder/auto_build.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import re
2121
from typing import Optional
2222

23-
from engine_sdk.utils import log_with_level, log_i18n_level
23+
from Core.i18n import log_with_level, log_i18n_level
2424

2525
# Optional access to registered engines for discovery
2626
try:

Core/Compiler/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191

9292
# Importations de EngineLoader
9393
from EngineLoader.registry import get_engine, create
94-
from engine_sdk.utils import log_with_level, log_i18n_level
94+
from Core.i18n import log_with_level, log_i18n_level
9595

9696
__all__ = [
9797
# compiler.py

Core/i18n.py

Lines changed: 253 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,9 @@
1919
import json
2020
import locale
2121
import os
22+
import re
2223
from typing import Any
2324

24-
from engine_sdk.utils import log_i18n_level
25-
2625
# Built-in fallback for English if language files are missing
2726
FALLBACK_EN: dict[str, Any] = {
2827
"name": "English",
@@ -534,6 +533,258 @@ def tr_fr_en(gui: object | None, fr: str, en: str) -> str:
534533
return en
535534

536535

536+
# -------------------------------
537+
# i18n-aware logging helpers
538+
# (moved from engine_sdk.utils)
539+
# -------------------------------
540+
541+
_REDACT_PATTERNS = [
542+
re.compile(r"(password\s*[:=]\s*)([^\s]+)", re.IGNORECASE),
543+
re.compile(r"(authorization\s*[:]\s*bearer\s+)([A-Za-z0-9\-_.]+)", re.IGNORECASE),
544+
re.compile(r"(token\s*[:=]\s*)([A-Za-z0-9\-_.]{12,})", re.IGNORECASE),
545+
]
546+
547+
548+
def redact_secrets(text: str) -> str:
549+
"""Return text with obvious secrets masked to avoid log leakage."""
550+
if not text:
551+
return text
552+
redacted = str(text)
553+
try:
554+
for pat in _REDACT_PATTERNS:
555+
redacted = pat.sub(lambda m: m.group(1) + "<redacted>", redacted)
556+
except Exception:
557+
pass
558+
return redacted
559+
560+
561+
def clamp_text(text: str, *, max_len: int = 10000) -> str:
562+
"""Clamp long text to max_len characters (suffix with …)."""
563+
if text is None:
564+
return ""
565+
s = str(text)
566+
return s if len(s) <= max_len else (s[: max_len - 1] + "…")
567+
568+
569+
def tr(gui: Any, fr: str, en: str) -> str:
570+
"""Robust translator wrapper using the host GUI translator when available."""
571+
try:
572+
fn = getattr(gui, "tr", None)
573+
if callable(fn):
574+
return fn(fr, en)
575+
except Exception:
576+
pass
577+
return tr_fr_en(gui, fr, en)
578+
579+
580+
essential_log_max_len = 10000
581+
582+
583+
def safe_log(gui: Any, text: str, *, redact: bool = True, clamp: bool = True) -> None:
584+
"""Append text to GUI log safely (or print), with optional redaction and clamping."""
585+
try:
586+
msg = str(text)
587+
if redact:
588+
msg = redact_secrets(msg)
589+
if clamp:
590+
msg = clamp_text(msg, max_len=essential_log_max_len)
591+
if hasattr(gui, "log") and getattr(gui, "log") is not None:
592+
try:
593+
gui.log.append(msg)
594+
return
595+
except Exception:
596+
pass
597+
print(msg)
598+
except Exception:
599+
try:
600+
print(text)
601+
except Exception:
602+
pass
603+
604+
605+
_LOG_LEVEL_LABELS = {
606+
"info": "INFO",
607+
"warning": "WARN",
608+
"error": "ERROR",
609+
"success": "SUCCESS",
610+
"state": "STATE",
611+
}
612+
613+
_LOG_LEVEL_RICH = {
614+
"info": "cyan",
615+
"warning": "yellow",
616+
"error": "red",
617+
"success": "green",
618+
"state": "blue",
619+
}
620+
621+
_LOG_LEVEL_COLORAMA = {
622+
"info": "CYAN",
623+
"warning": "YELLOW",
624+
"error": "RED",
625+
"success": "GREEN",
626+
"state": "BLUE",
627+
}
628+
629+
_LOG_LEVEL_QT_COLORS = {
630+
"info": "#1E88E5",
631+
"warning": "#EF6C00",
632+
"error": "#D32F2F",
633+
"success": "#2E7D32",
634+
"state": "#00897B",
635+
"debug": "#546E7A",
636+
}
637+
638+
_RICH_CONSOLE = None
639+
_COLORAMA_READY = False
640+
641+
642+
def _get_rich_console():
643+
global _RICH_CONSOLE
644+
if _RICH_CONSOLE is None:
645+
from rich.console import Console # type: ignore
646+
647+
_RICH_CONSOLE = Console()
648+
return _RICH_CONSOLE
649+
650+
651+
def _console_log(level: str, label: str, message: str) -> None:
652+
# Prefer rich when available for consistent styling.
653+
try:
654+
console = _get_rich_console()
655+
style = _LOG_LEVEL_RICH.get(level, "white")
656+
console.print(f"[{style}]{label}[/] {message}")
657+
return
658+
except Exception:
659+
pass
660+
661+
# Fallback to colorama for basic ANSI colors.
662+
try:
663+
from colorama import Fore, Style, init # type: ignore
664+
665+
global _COLORAMA_READY
666+
if not _COLORAMA_READY:
667+
init(autoreset=True)
668+
_COLORAMA_READY = True
669+
670+
color_name = _LOG_LEVEL_COLORAMA.get(level)
671+
color = getattr(Fore, color_name, "")
672+
if color:
673+
print(f"{color}{label}{Style.RESET_ALL} {message}")
674+
else:
675+
print(f"{label} {message}")
676+
return
677+
except Exception:
678+
pass
679+
680+
print(f"{label} {message}")
681+
682+
683+
def _append_gui_log(gui: Any, level: str, label: str, msg: str) -> bool:
684+
"""Append a log line to the GUI log, with color when possible."""
685+
try:
686+
log = getattr(gui, "log", None)
687+
except Exception:
688+
return False
689+
if log is None:
690+
return False
691+
692+
line = f"[{label}] {msg}"
693+
694+
# List-backed logs (tests)
695+
try:
696+
if isinstance(log, list):
697+
log.append(line)
698+
return True
699+
except Exception:
700+
pass
701+
702+
# QTextEdit with rich formatting
703+
try:
704+
if hasattr(log, "textCursor") and callable(log.textCursor):
705+
try:
706+
from PySide6.QtGui import QColor, QTextCharFormat, QTextCursor
707+
except Exception:
708+
return False
709+
cursor = log.textCursor()
710+
cursor.movePosition(QTextCursor.End)
711+
fmt = QTextCharFormat()
712+
color = _LOG_LEVEL_QT_COLORS.get(level)
713+
if color:
714+
fmt.setForeground(QColor(color))
715+
cursor.insertText(line, fmt)
716+
cursor.insertText("\n")
717+
try:
718+
log.setTextCursor(cursor)
719+
log.ensureCursorVisible()
720+
except Exception:
721+
pass
722+
return True
723+
except Exception:
724+
pass
725+
726+
# QPlainTextEdit / generic append
727+
try:
728+
if hasattr(log, "appendPlainText") and callable(log.appendPlainText):
729+
log.appendPlainText(line)
730+
return True
731+
except Exception:
732+
pass
733+
734+
try:
735+
if hasattr(log, "append") and callable(log.append):
736+
log.append(line)
737+
return True
738+
except Exception:
739+
pass
740+
741+
return False
742+
743+
744+
def log_with_level(
745+
gui: Any,
746+
level: str,
747+
message: str,
748+
*,
749+
redact: bool = True,
750+
clamp: bool = True,
751+
) -> None:
752+
"""Append a level-tagged message to GUI log or print with colors in console."""
753+
try:
754+
lvl = str(level).lower() if level is not None else "info"
755+
except Exception:
756+
lvl = "info"
757+
label = _LOG_LEVEL_LABELS.get(lvl, str(level).upper())
758+
759+
msg = str(message) if message is not None else ""
760+
if redact:
761+
msg = redact_secrets(msg)
762+
if clamp:
763+
msg = clamp_text(msg, max_len=essential_log_max_len)
764+
765+
try:
766+
if _append_gui_log(gui, lvl, label, msg):
767+
return
768+
except Exception:
769+
pass
770+
771+
_console_log(lvl, label, msg)
772+
773+
774+
def log_i18n_level(
775+
gui: Any,
776+
level: str,
777+
fr: str,
778+
en: str,
779+
*,
780+
redact: bool = True,
781+
clamp: bool = True,
782+
) -> None:
783+
"""Translate then log a level-tagged message."""
784+
msg = tr(gui, fr, en)
785+
log_with_level(gui, level, msg, redact=redact, clamp=clamp)
786+
787+
537788
def apply_language(self, lang_display: str) -> None:
538789
"""Applique la langue sélectionnée (centralisé)."""
539790
from .Globals import _run_coro_async

0 commit comments

Comments
 (0)