diff --git a/NOTICE b/NOTICE index c97d4d70..65348137 100644 --- a/NOTICE +++ b/NOTICE @@ -5,7 +5,9 @@ This product includes software developed by third parties. See licenses below for details. ================================================================================ -THIRD-PARTY SOFTWARE LICENSES + +# THIRD-PARTY SOFTWARE LICENSES + ================================================================================ 1. PySide6 & shiboken6 diff --git a/docs/app_i18n.md b/docs/app_i18n.md index c0f8564c..45b42327 100644 --- a/docs/app_i18n.md +++ b/docs/app_i18n.md @@ -49,10 +49,14 @@ tt_build_all: Démarrer la compilation The recommended pattern is: -1. create the widget -2. attach i18n properties to it -3. let `pycompiler_ark/Ui/i18n.py` resolve the active language -4. let the generic walker read the widget properties and call `translate(self.id, ...)` +1. **Naming Convention (Automatic)**: + Name your widget using one of the following prefixes to have it translated automatically: + - `btn_` (e.g. `btn_select_folder` translates using `select_folder` and resolves its tooltip as `tt_select_folder`). + - `action_` (e.g. `action_select_workspace`). + - `tab_` (e.g. `tab_hello`). + +2. **Explicit Properties**: + Attach explicit `i18n_*` properties to the widget (via PySide's `.setProperty()` or inside the `.ui` file) if you want to override the default convention lookup. If you need to attach properties manually, use: @@ -91,6 +95,20 @@ Typical use cases: - `i18n_format_attr`: value inserted into a translated template, such as a workspace path - `i18n_none_key`: fallback text when the dynamic attribute is empty +## **When to Use What** + +- **Naming Conventions (`btn_*`, `action_*`, `tab_*`)**: + - **When**: Building standard persistent UI elements like buttons, actions, and tab widgets. + - **Why**: Zero configuration. Just name the widget correctly and the system translates the text and tooltip automatically. + +- **Explicit Properties (`i18n_text_key`, `i18n_format_attr`, etc.)**: + - **When**: Surcharging standard convention lookups, setting up line edit placeholder keys, formatting strings with dynamic values (like `{path}` via `i18n_format_attr`), or handling system preference toggles. + - **Why**: Allows advanced dynamic text formatting and fallback keys (`i18n_none_key`). + +- **Direct API `translate(self.id, key, default)`**: + - **When**: Translating non-persistent text dynamically in Python code (e.g. dialog messages, warning/error popups, dynamic log outputs). + - **Why**: Best for ad-hoc strings that do not belong to static UI widgets. + ## **Language Change Flow** When the user changes the language: diff --git a/pycompiler_ark/Core/Compiler/engine_runner.py b/pycompiler_ark/Core/Compiler/engine_runner.py index bc4f1d8c..54ded038 100644 --- a/pycompiler_ark/Core/Compiler/engine_runner.py +++ b/pycompiler_ark/Core/Compiler/engine_runner.py @@ -284,10 +284,12 @@ def append(self, message: str): # message is already formatted by log_i18n_level self.log_cb("", message) - def _safe_log(self, text, text_en=None, level=None): - # Fallback for VenvManager - msg = text_en if text_en else text - self.log_cb("", msg) + def log_i18n(self, fr: str, en: str, level: str | None = None): + from pycompiler_ark.Ui.i18n import log_i18n + log_i18n(self, fr, en, level) + + def log_i18n_level(self, level: str, fr: str, en: str): + self.log_i18n(fr, en, level) def tr(self, fr, en): return en # Simple fallback diff --git a/pycompiler_ark/Core/Venv_Manager/Manager.py b/pycompiler_ark/Core/Venv_Manager/Manager.py index 68afb9c6..9ebfa34e 100644 --- a/pycompiler_ark/Core/Venv_Manager/Manager.py +++ b/pycompiler_ark/Core/Venv_Manager/Manager.py @@ -454,7 +454,7 @@ def ensure_tools_installed(self, venv_root: str, tools: list[str]) -> None: from pycompiler_ark.Core.Compiler.utils import check_internet_connection if not check_internet_connection(): - self._safe_log( + self.log_i18n( "🛑 [ERROR] Pas de connexion internet. Installation des outils annulée.", "🛑 [ERROR] No internet connection. Tool installation cancelled.", level="error", @@ -513,7 +513,7 @@ def ensure_tools_installed(self, venv_root: str, tools: list[str]) -> None: loop.exec() self._venv_check_loop = None except Exception as e: - self._safe_log( + self.log_i18n( f"[ERROR] {self._tools_stage_prefix()}Erreur ensure_tools_installed: {e}" ) @@ -523,7 +523,7 @@ def ensure_tools_installed_system(self, tools: list[str]) -> None: from pycompiler_ark.Core.Compiler.utils import check_internet_connection if not check_internet_connection(): - self._safe_log( + self.log_i18n( "🛑 [ERROR] Pas de connexion internet. Installation système annulée.", "🛑 [ERROR] No internet connection. System installation cancelled.", level="error", @@ -586,7 +586,7 @@ def ensure_tools_installed_system(self, tools: list[str]) -> None: loop.exec() self._venv_check_loop = None except Exception as e: - self._safe_log( + self.log_i18n( f"[ERROR] {self._tools_stage_prefix()}Erreur ensure_tools_installed_system: {e}" ) @@ -653,11 +653,19 @@ def _infer_log_level(self, text: str | None) -> str: return "state" return "info" - def _safe_log( + def log_i18n( self, text: str, text_en: str | None = None, level: str | None = None ): - """Execute _safe_log logic for this component via UI callback.""" - lvl = level or self._infer_log_level(text_en if text_en is not None else text) + """Log a localized message via UI callback or fallback to host log.""" + en_val = text_en if text_en is not None else text + try: + from pycompiler_ark.Ui.i18n import log_i18n + log_i18n(self.parent, text, en_val, level) + return + except Exception: + pass + + lvl = level or self._infer_log_level(en_val) # Try primary UI callback for i18n logging if text_en is not None: @@ -671,10 +679,8 @@ def _safe_log( # Fallback to parent methods if available try: - if text_en is not None: - text = self.tr(text, text_en) - if hasattr(self.parent, "_safe_log"): - self.parent._safe_log(text) + if hasattr(self.parent, "log_i18n"): + self.parent.log_i18n(text, en_val, level) return except Exception: pass @@ -699,7 +705,7 @@ def _request_cancel(self, action_label: str | None = None) -> None: return self._cancel_requested = True suffix = f" ({action_label})" if action_label else "" - self._safe_log( + self.log_i18n( f"[CANCEL] Annulation demandee par l'utilisateur{suffix}.", f"[CANCEL] Cancellation requested by user{suffix}.", level="warning", @@ -729,7 +735,7 @@ def _safe_rmtree(self, path: str, max_retries: int = 3) -> bool: except Exception: pass else: - self._safe_log( + self.log_i18n( f"[WARNING] Failed to remove {path} after {max_retries} attempts: {e}" ) return False @@ -741,7 +747,7 @@ def _safe_mkdir(self, path: str) -> bool: os.makedirs(path, exist_ok=True) return True except Exception as e: - self._safe_log(f"[WARNING] Failed to create directory {path}: {e}") + self.log_i18n(f"[WARNING] Failed to create directory {path}: {e}") return False def _prompt_recreate_invalid_venv(self, venv_root: str, reason: str) -> bool: @@ -755,7 +761,7 @@ def _prompt_recreate_invalid_venv(self, venv_root: str, reason: str) -> bool: # Business logic: delete the bad venv try: shutil.rmtree(venv_root) - self._safe_log(f"[DELETE] Deleted invalid venv: {venv_root}") + self.log_i18n(f"[DELETE] Deleted invalid venv: {venv_root}") except Exception as e: self._call_ui( "show_error_dialog", @@ -893,7 +899,7 @@ def check_tools_in_venv(self, venv_path: str): self._reset_cancel_state() ok, reason = self.validate_venv_strict(venv_path) if not ok: - self._safe_log(f"[ERROR] Invalid venv: {reason}") + self.log_i18n(f"[ERROR] Invalid venv: {reason}") # Offer to delete and recreate self._prompt_recreate_invalid_venv(venv_path, reason) return @@ -902,7 +908,7 @@ def check_tools_in_venv(self, venv_path: str): def _after_binding(ok_bind: bool): """Execute _after_binding logic for this component.""" if not ok_bind: - self._safe_log( + self.log_i18n( "[ERROR] Invalid venv binding: python/pip do not point to the selected venv." ) self._prompt_recreate_invalid_venv( @@ -922,18 +928,18 @@ def _after_binding(ok_bind: bool): if not sys_deps.check_system_packages([t]) ] if missing_sys: - self._safe_log( + self.log_i18n( f"[WARNING] Outils systeme manquants : {', '.join(missing_sys)}", f"[WARNING] Missing system tools: {', '.join(missing_sys)}", ) # On pourrait proposer l'installation, mais on laisse les engines gérer # ou on affiche juste l'avertissement. else: - self._safe_log("[OK] Outils systeme verifies.") + self.log_i18n("[OK] Outils systeme verifies.") # 2. Proceed with python tools in venv if not python_tools: - self._safe_log( + self.log_i18n( "[INFO] Aucun outil Python requis detecte depuis les engines.", "[INFO] No required Python tools detected from pycompiler_ark.engines.", ) @@ -973,7 +979,7 @@ def _after_binding(ok_bind: bool): self._verify_venv_binding_async(venv_path, _after_binding) except Exception as e: - self._safe_log(f"[ERROR] Erreur lors de la verification du venv: {e}") + self.log_i18n(f"[ERROR] Erreur lors de la verification du venv: {e}") def _check_next_venv_pkg(self): """Execute _check_next_venv_pkg logic for this component.""" @@ -1031,11 +1037,11 @@ def _on_venv_pkg_checked(self, process, code, status, pkg): return if code == 0: if self._venv_check_use_python: - self._safe_log( + self.log_i18n( f"[OK] {self._tools_stage_prefix()}{pkg} deja installe (Python systeme)." ) else: - self._safe_log( + self.log_i18n( f"[OK] {self._tools_stage_prefix()}{pkg} deja installe dans le venv." ) self._venv_check_index += 1 @@ -1060,7 +1066,7 @@ def _on_venv_pkg_checked(self, process, code, status, pkg): from pycompiler_ark.Core.Compiler.utils import check_internet_connection if not check_internet_connection(): - self._safe_log( + self.log_i18n( f"🛑 [ERROR] Pas de connexion internet. Impossible d'installer {pkg}.", f"🛑 [ERROR] No internet connection. Unable to install {pkg}.", level="error", @@ -1073,7 +1079,7 @@ def _on_venv_pkg_checked(self, process, code, status, pkg): loop.quit() return - self._safe_log( + self.log_i18n( f"[INSTALL] {self._tools_stage_prefix()}Installation automatique de {pkg}..." ) self._call_ui( @@ -1130,7 +1136,7 @@ def _on_venv_check_output(self, process, error=False): for line in lines: lvl = "warning" if error else "info" # We use a slight indentation to make it clear it's sub-output - self._safe_log(f" [pip] {line}", level=lvl) + self.log_i18n(f" [pip] {line}", level=lvl) def verify_venv_binding(self, venv_root: str) -> bool: """Keep synchronous verification path for internal compatibility.""" @@ -1243,7 +1249,7 @@ def _on_timeout(): """Handle the related event callback.""" try: if process.state() != QProcess.NotRunning: - self._safe_log( + self.log_i18n( f"[TIMEOUT] Timeout exceeded for {label} ({timeout_ms} ms). Killing process..." ) from pycompiler_ark.Core.process_killer import ( @@ -1376,15 +1382,15 @@ def select_best_venv(self, workspace_dir: str) -> str | None: venvs = self._find_all_venvs_in(workspace_dir) if not venvs: - self._safe_log("[INFO] Aucun venv valide trouve dans le workspace.") + self.log_i18n("[INFO] Aucun venv valide trouve dans le workspace.") return None if len(venvs) == 1: - self._safe_log(f"[OK] Un seul venv trouve: {venvs[0]}") + self.log_i18n(f"[OK] Un seul venv trouve: {venvs[0]}") return venvs[0] # Multiple venvs found - score and select the best - self._safe_log( + self.log_i18n( f"[INFO] {len(venvs)} venv(s) trouve(s), selection du meilleur..." ) @@ -1392,7 +1398,7 @@ def select_best_venv(self, workspace_dir: str) -> str | None: for venv_path in venvs: score, reason = self._score_venv(venv_path, workspace_dir) scored_venvs.append((score, venv_path, reason)) - self._safe_log( + self.log_i18n( f" - {os.path.basename(venv_path)}: score={score} ({reason})" ) @@ -1402,15 +1408,15 @@ def select_best_venv(self, workspace_dir: str) -> str | None: best_score, best_venv, best_reason = scored_venvs[0] if best_score == 0: - self._safe_log("[ERROR] Aucun venv valide avec une bonne liaison.") + self.log_i18n("[ERROR] Aucun venv valide avec une bonne liaison.") return None - self._safe_log( + self.log_i18n( f"[OK] Meilleur venv selectionne: {os.path.basename(best_venv)} (score={best_score})" ) return best_venv except Exception as e: - self._safe_log( + self.log_i18n( f"[WARNING] Erreur lors de la selection du meilleur venv: {e}" ) return None @@ -1422,9 +1428,9 @@ def _on_venv_pkg_installed(self, process, code, status, pkg): if self._is_cancel_requested(): return if code == 0: - self._safe_log(f"[OK] {pkg} installe dans le venv.") + self.log_i18n(f"[OK] {pkg} installe dans le venv.") else: - self._safe_log(f"[ERROR] Erreur installation {pkg} (code {code})") + self.log_i18n(f"[ERROR] Erreur installation {pkg} (code {code})") self._venv_check_index += 1 self._call_ui( "update_progress_progress", @@ -1443,14 +1449,14 @@ def create_venv_if_needed(self, path: str): # Validate existing venv; if invalid, propose deletion/recreation ok, reason = self.validate_venv_strict(venv_path) if not ok: - self._safe_log(f"[ERROR] Invalid venv detected: {reason}") + self.log_i18n(f"[ERROR] Invalid venv detected: {reason}") recreated = self._prompt_recreate_invalid_venv(venv_path, reason) if not recreated: return else: return - self._safe_log("[CONFIG] Aucun venv trouve, creation automatique...") + self.log_i18n("[CONFIG] Aucun venv trouve, creation automatique...") try: self._reset_cancel_state() # Recherche d'un python embarque a cote de l'executable @@ -1493,15 +1499,15 @@ def create_venv_if_needed(self, path: str): python_candidate.startswith(exe_dir) or "python_embedded" in python_candidate ): - self._safe_log( + self.log_i18n( f"[STATE] Utilisation de l'interpreteur Python embarque : {python_candidate}" ) elif base in ("py", "py.exe") or shutil.which(base): - self._safe_log( + self.log_i18n( f"[STATE] Utilisation de l'interpreteur systeme : {python_candidate}" ) else: - self._safe_log( + self.log_i18n( f"[STATE] Utilisation de sys.executable : {python_candidate}" ) @@ -1543,7 +1549,7 @@ def create_venv_if_needed(self, path: str): # Safety timeout for venv creation (10 min) self._arm_process_timeout(process, 600_000, "venv creation") except Exception as e: - self._safe_log( + self.log_i18n( f"[ERROR] Echec de creation du venv ou installation des outils : {e}" ) @@ -1574,18 +1580,18 @@ def _on_venv_output(self, process, error=False): if is_verbose or error: for line in lines: lvl = "warning" if error else "info" - self._safe_log(f" [venv] {line}", level=lvl) + self.log_i18n(f" [venv] {line}", level=lvl) def _on_venv_created(self, process, code, status, venv_path): """Handle the related event callback.""" if getattr(self.parent, "_closing", False): return if self._is_cancel_requested(): - self._safe_log("[INFO] Creation du venv annulee.") + self.log_i18n("[INFO] Creation du venv annulee.") self._call_ui("close_progress", "venv_creation") return if code == 0: - self._safe_log("[OK] Environnement virtuel cree avec succes.") + self.log_i18n("[OK] Environnement virtuel cree avec succes.") self._call_ui("update_progress_message", "venv_creation", "Venv cree.") self._call_ui("close_progress", "venv_creation") @@ -1595,7 +1601,7 @@ def _on_venv_created(self, process, code, status, venv_path): except Exception: pass else: - self._safe_log(f"[ERROR] Echec de creation du venv (code {code})") + self.log_i18n(f"[ERROR] Echec de creation du venv (code {code})") self._call_ui( "update_progress_message", "venv_creation", @@ -1650,17 +1656,17 @@ def _get_requirements_file(self, workspace_dir: str) -> str | None: return others[0] # 3. Use DepsAnalyser to generate requirements.txt from project analysis - self._safe_log( + self.log_i18n( "[SEARCH] Analyse des dependances du projet via DepsAnalyser..." ) generated = deps_analyser.write_requirements_txt(workspace_dir) if generated and os.path.isfile(generated): - self._safe_log("[OK] requirements.txt genere via DepsAnalyser.") + self.log_i18n("[OK] requirements.txt genere via DepsAnalyser.") return generated return None except Exception as e: - self._safe_log( + self.log_i18n( f"[WARNING] Erreur lors de la detection des requirements: {e}" ) return None @@ -1671,7 +1677,7 @@ def install_requirements_if_needed(self, path: str, force_pip: bool = False): # Get or generate requirements file req_path = self._get_requirements_file(path) if not req_path: - self._safe_log("[INFO] Aucun fichier de dependances trouve ou genere.") + self.log_i18n("[INFO] Aucun fichier de dependances trouve ou genere.") return if self._using_system_python(): @@ -1693,7 +1699,7 @@ def install_requirements_if_needed(self, path: str, force_pip: bool = False): venv_root = existing2 or venv_root ok, reason = self.validate_venv_strict(venv_root) if not ok: - self._safe_log(f"[WARNING] Invalid venv for requirements: {reason}") + self.log_i18n(f"[WARNING] Invalid venv for requirements: {reason}") # Offer to delete and recreate, then retry installation if self._prompt_recreate_invalid_venv(venv_root, reason): # if recreated, try install again @@ -1704,7 +1710,7 @@ def install_requirements_if_needed(self, path: str, force_pip: bool = False): def _after_binding(ok_bind: bool): """Execute _after_binding logic for this component.""" if not ok_bind: - self._safe_log( + self.log_i18n( "[WARNING] Liaison venv invalide (python/pip ne pointent pas vers le venv); installation ignoree." ) return @@ -1723,7 +1729,7 @@ def _start_requirements_install( from pycompiler_ark.Core.Compiler.utils import check_internet_connection if not check_internet_connection(): - self._safe_log( + self.log_i18n( "🛑 [ERROR] Pas de connexion internet. Installation des dépendances annulée.", "🛑 [ERROR] No internet connection. Dependencies installation cancelled.", level="error", @@ -1733,7 +1739,7 @@ def _start_requirements_install( self._reset_cancel_state() py_exe = sys.executable if use_system_python else self.python_path(venv_root) if not os.path.isfile(py_exe): - self._safe_log( + self.log_i18n( "[WARNING] python introuvable dans le venv; installation requirements ignoree." ) return @@ -1743,7 +1749,7 @@ def _start_requirements_install( data = f.read() req_hash = hashlib.sha256(data).hexdigest() except Exception as e: - self._safe_log( + self.log_i18n( f"[WARNING] Impossible de calculer le hash de requirements.txt: {e}" ) req_hash = None @@ -1760,13 +1766,13 @@ def _start_requirements_install( with open(marker_path, encoding="utf-8") as mf: current = mf.read().strip() if current == req_hash: - self._safe_log( + self.log_i18n( "[OK] requirements.txt deja installe (aucun changement detecte)." ) return except Exception: pass - self._safe_log( + self.log_i18n( "[INSTALL] Installation des dependances a partir de requirements.txt..." ) try: @@ -1812,7 +1818,7 @@ def _start_requirements_install( # Safety timeout for ensurepip (3 min) self._arm_process_timeout(process, 180_000, "ensurepip") except Exception as e: - self._safe_log(f"[ERROR] Echec installation requirements.txt : {e}") + self.log_i18n(f"[ERROR] Echec installation requirements.txt : {e}") def _on_pip_output(self, process, error=False): """Handle the related event callback.""" @@ -1840,14 +1846,14 @@ def _on_pip_output(self, process, error=False): if is_verbose or error: for line in lines: lvl = "warning" if error else "info" - self._safe_log(f" [pip] {line}", level=lvl) + self.log_i18n(f" [pip] {line}", level=lvl) def _on_pip_finished(self, process, code, status): """Handle the related event callback.""" if getattr(self.parent, "_closing", False): return if self._is_cancel_requested(): - self._safe_log("[INFO] Installation des dependances annulee.") + self.log_i18n("[INFO] Installation des dependances annulee.") self._call_ui("close_progress", "reqs_install") return phase = self._pip_phase @@ -1925,7 +1931,7 @@ def _on_pip_finished(self, process, code, status): ) return else: - self._safe_log( + self.log_i18n( f"[ERROR] Echec mise a niveau pip/setuptools/wheel (code {code})" ) self._call_ui( @@ -1935,7 +1941,7 @@ def _on_pip_finished(self, process, code, status): ) else: if code == 0: - self._safe_log("[OK] requirements.txt installe.") + self.log_i18n("[OK] requirements.txt installe.") # Write/update marker if we computed it try: if getattr(self, "_req_marker_path", None) and getattr( @@ -1952,7 +1958,7 @@ def _on_pip_finished(self, process, code, status): "update_progress_message", "reqs_install", "Installation terminee." ) else: - self._safe_log( + self.log_i18n( f"[ERROR] Echec installation requirements.txt (code {code})" ) self._call_ui( @@ -1993,7 +1999,7 @@ def terminate_tasks(self): pass if had_active: - self._safe_log( + self.log_i18n( "[CANCEL] Operations venv interrompues.", "[CANCEL] Venv operations interrupted.", level="warning", @@ -2037,7 +2043,7 @@ def setup_workspace(self, workspace_dir: str, check_tools: bool = True) -> bool: if not existing_env: self.create_venv_if_needed(workspace_dir) else: - self._safe_log(f"[OK] Venv existant detecte: {existing_env}") + self.log_i18n(f"[OK] Venv existant detecte: {existing_env}") # Check and install tools if requested if check_tools: @@ -2048,18 +2054,18 @@ def setup_workspace(self, workspace_dir: str, check_tools: bool = True) -> bool: def _after_binding(ok_bind: bool): if ok_bind: - self._safe_log( + self.log_i18n( "[SEARCH] Verification des outils de compilation..." ) self.check_tools_in_venv(existing_check) else: - self._safe_log( + self.log_i18n( "[WARNING] Liaison venv invalide, verification des outils ignoree." ) self._verify_venv_binding_async(existing_check, _after_binding) else: - self._safe_log( + self.log_i18n( f"[WARNING] Venv invalide, verification des outils ignoree: {reason}" ) @@ -2068,13 +2074,13 @@ def _after_binding(ok_bind: bool): from pycompiler_ark.Core.Configs import create_default_ark_config if create_default_ark_config(workspace_dir): - self._safe_log("[STATE] Fichier ark.yml cree dans le workspace.") + self.log_i18n("[STATE] Fichier ark.yml cree dans le workspace.") except Exception as e: - self._safe_log(f"[WARNING] Impossible de creer ark.yml: {e}") + self.log_i18n(f"[WARNING] Impossible de creer ark.yml: {e}") return True except Exception as e: - self._safe_log(f"[ERROR] Erreur lors de la configuration du workspace: {e}") + self.log_i18n(f"[ERROR] Erreur lors de la configuration du workspace: {e}") return False def get_manager_info(self, workspace_dir: str) -> dict: diff --git a/pycompiler_ark/Core/engine/base.py b/pycompiler_ark/Core/engine/base.py index 150ba5f2..3ce5af83 100644 --- a/pycompiler_ark/Core/engine/base.py +++ b/pycompiler_ark/Core/engine/base.py @@ -95,6 +95,13 @@ def resolve_engine_meta(engine_or_cls: object) -> EngineMeta: def log_i18n_level(gui, level: str, fr: str, en: str) -> None: """Minimal i18n log helper to avoid engine loader <-> engine_sdk circular imports.""" + try: + from pycompiler_ark.Ui.i18n import log_i18n + log_i18n(gui, fr, en, level) + return + except Exception: + pass + try: if hasattr(gui, "tr") and callable(getattr(gui, "tr")): msg = gui.tr(fr, en) diff --git a/pycompiler_ark/Ui/Forms/classic_main_window.ui b/pycompiler_ark/Ui/Forms/classic_main_window.ui index dabaeccf..a93c656d 100644 --- a/pycompiler_ark/Ui/Forms/classic_main_window.ui +++ b/pycompiler_ark/Ui/Forms/classic_main_window.ui @@ -76,20 +76,20 @@ QPushButton:hover { background: #454545; border-color: #454545; } QPushButton:pressed { background: #2A2A2A; } QPushButton:disabled { color: #8A8A8A; background: #2B2B2B; border-color: #2B2B2B; } -QPushButton#compile_btn { +QPushButton#btn_build_all { background: #0E639C; color: #FFFFFF; border-color: #0E639C; font-weight: 600; } -QPushButton#compile_btn:hover { background: #1177BB; border-color: #1177BB; } -QPushButton#compile_btn:pressed { background: #0B4F7A; } -QPushButton#cancel_btn { +QPushButton#btn_build_all:hover { background: #1177BB; border-color: #1177BB; } +QPushButton#btn_build_all:pressed { background: #0B4F7A; } +QPushButton#btn_cancel_all { background: #5A1D1D; color: #F2B8B8; border: 1px solid #6A2A2A; } -QPushButton#cancel_btn:hover { background: #6A2727; border-color: #7A3030; } +QPushButton#btn_cancel_all:hover { background: #6A2727; border-color: #7A3030; } /* Checkboxes & Radio */ QCheckBox { color: #D4D4D4; } @@ -356,7 +356,7 @@ QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0; } 0 - + Choisir la langue de l'interface @@ -366,7 +366,7 @@ QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0; } - + Choisir le thème de l'interface @@ -376,7 +376,7 @@ QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0; } - + Démarrer la compilation @@ -386,7 +386,7 @@ QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0; } - + Annuler la compilation en cours @@ -396,7 +396,7 @@ QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0; } - + Configurations avancées @@ -476,7 +476,7 @@ QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0; } - + Choisir un dossier venv diff --git a/pycompiler_ark/Ui/Forms/ide_main_window.ui b/pycompiler_ark/Ui/Forms/ide_main_window.ui index 200c3be7..528ceaeb 100644 --- a/pycompiler_ark/Ui/Forms/ide_main_window.ui +++ b/pycompiler_ark/Ui/Forms/ide_main_window.ui @@ -42,14 +42,14 @@ QPushButton { padding: 4px 10px; } QPushButton:hover { background: #454545; } -QPushButton#compile_btn { +QPushButton#btn_build_all { background: #0e639c; border-color: #0e639c; color: #ffffff; font-weight: 600; } -QPushButton#compile_btn:hover { background: #1177bb; } -QPushButton#cancel_btn { +QPushButton#btn_build_all:hover { background: #1177bb; } +QPushButton#btn_cancel_all { background: #5a1d1d; border-color: #6a2a2a; color: #f2b8b8; @@ -143,30 +143,30 @@ QProgressBar::chunk { background: #0e639c; } - - - - 124 - 34 - - - - Build - - - - - - - - 124 - 34 - - - - Cancel - - + + + + 124 + 34 + + + + Build + + + + + + + + 124 + 34 + + + + Cancel + + @@ -215,34 +215,34 @@ QProgressBar::chunk { background: #0e639c; } 8 - - - - 28 - 28 - - - - More tools - - - ... - - - - - - - - 28 - 28 - - - - Analyser dépendances - - - + + + + 28 + 28 + + + + More tools + + + ... + + + + + + + + 28 + 28 + + + + Analyser dépendances + + + @@ -313,11 +313,11 @@ QProgressBar::chunk { background: #0e639c; } - - - Venv - - + + + Venv + + @@ -462,40 +462,40 @@ QProgressBar::chunk { background: #0e639c; } - - - Advanced Config - - - - - - - Help - - - - - - - Stats - - - - - - - - Language - - - - - - - Theme - - + + + Advanced Config + + + + + + + Help + + + + + + + Stats + + + + + + + + Language + + + + + + + Theme + + diff --git a/pycompiler_ark/Ui/Gui/Compilation/compiler.py b/pycompiler_ark/Ui/Gui/Compilation/compiler.py index 364a9426..55b0aa82 100644 --- a/pycompiler_ark/Ui/Gui/Compilation/compiler.py +++ b/pycompiler_ark/Ui/Gui/Compilation/compiler.py @@ -81,11 +81,11 @@ def tr(self, fr, en): except Exception: return en - def _safe_log(self, text, text_en=None, level=None): - if text_en: - self.log_i18n_triggered.emit(level or "info", text, text_en) - else: - self.log_triggered.emit(level or "info", text) + def log_i18n(self, fr: str, en: str, level: str | None = None) -> None: + self.log_i18n_triggered.emit(level or "info", fr, en) + + def log_i18n_level(self, level: str, fr: str, en: str) -> None: + self.log_i18n_triggered.emit(level, fr, en) @property def log(self): diff --git a/pycompiler_ark/Ui/Gui/Gui.py b/pycompiler_ark/Ui/Gui/Gui.py index bcebecd3..d5b90d7f 100644 --- a/pycompiler_ark/Ui/Gui/Gui.py +++ b/pycompiler_ark/Ui/Gui/Gui.py @@ -376,61 +376,6 @@ def tr(self, fr: str, en: str) -> str: # JOURNALISATION # ========================================================================= - def _infer_log_level(self, text) -> str: - try: - s = str(text or "").strip() - except Exception: - s = "" - if not s: - return "info" - emoji_levels = { - "❌": "error", - "⚠️": "warning", - "✅": "success", - "ℹ️": "info", - "📝": "state", - "📋": "state", - "🔍": "state", - "🔧": "state", - "🔨": "state", - "➡️": "state", - "📦": "state", - "🗑️": "state", - } - for emoji, lvl in emoji_levels.items(): - if s.startswith(emoji): - return lvl - low = s.lower() - if any( - tok in low - for tok in ( - "error", - "erreur", - "échec", - "echec", - "failed", - "invalid", - "refus", - ) - ): - return "error" - if any(tok in low for tok in ("warning", "avert", "warn", "attention")): - return "warning" - if any(tok in low for tok in ("success", "succès", "reussi", "réussi")): - return "success" - if any(tok in low for tok in ("state", "status", "état", "etat")): - return "state" - return "info" - - def _safe_log(self, text): - """Write a log line with safe fallback behavior.""" - try: - level = self._infer_log_level(text) - log_with_level(self, level, text) - return - except Exception: - pass - # ========================================================================= # TÂCHES EN ARRIÈRE-PLAN # ========================================================================= diff --git a/pycompiler_ark/Ui/Gui/IdeLikeGui/connections.py b/pycompiler_ark/Ui/Gui/IdeLikeGui/connections.py index 4d78e37c..274c3004 100644 --- a/pycompiler_ark/Ui/Gui/IdeLikeGui/connections.py +++ b/pycompiler_ark/Ui/Gui/IdeLikeGui/connections.py @@ -179,7 +179,7 @@ def _find(cls, name: str): return self.ui.findChild(cls, name) self.btn_select_folder = _find(QPushButton, "btn_select_folder") - self.venv_button = _find(QPushButton, "venv_button") + self.venv_button = _find(QPushButton, "btn_venv_button") self.venv_label = _find(QLabel, "venv_label") self.label_folder = _find(QLabel, "label_folder") self.label_workspace_status = _find(QLabel, "label_workspace_status") @@ -193,33 +193,55 @@ def _find(cls, name: str): self.compiler_tabs = _find(QTabWidget, "compiler_tabs") self.tab_hello = _find(QWidget, "tab_hello") self.file_list = _find(QListWidget, "file_list") - self.compile_btn = _find(QPushButton, "compile_btn") - self.cancel_btn = _find(QPushButton, "cancel_btn") + self.compile_btn = _find(QPushButton, "btn_build_all") + self.cancel_btn = _find(QPushButton, "btn_cancel_all") self.btn_help = _find(QPushButton, "btn_help") self.btn_suggest_deps = _find(QPushButton, "btn_suggest_deps") - self.activity_btn_deps = _find(QToolButton, "activity_btn_deps") + self.activity_btn_deps = _find(QToolButton, "btn_activity_deps") self.btn_bc_loader = _find(QPushButton, "btn_bc_loader") self.btn_acasl_loader = _find(QPushButton, "btn_acasl_loader") self.btn_show_stats = _find(QPushButton, "btn_show_stats") - self.select_lang = _find(QPushButton, "select_lang") - self.select_theme = _find(QPushButton, "select_theme") - self.advanced_cfg_btn = _find(QPushButton, "advanced_cfg_btn") + self.select_lang = _find(QPushButton, "btn_select_lang") + self.select_theme = _find(QPushButton, "btn_select_theme") + self.advanced_cfg_btn = _find(QPushButton, "btn_advanced_config") self.btn_select_files = _find(QPushButton, "btn_select_files") self.btn_remove_file = _find(QPushButton, "btn_remove_file") self.btn_clear_workspace = _find(QPushButton, "btn_clear_workspace") self.btn_select_icon = None self.btn_nuitka_icon = None - self.toolButton_more = _find(QToolButton, "toolButton_more") + self.toolButton_more = _find(QToolButton, "btn_more_actions") self.log = _find(QTextEdit, "log") self.progress = _find(QProgressBar, "progress") self.statusbar = self.findChild(QStatusBar, "statusbar") self.status_hint = None try: self.status_hint = ( - self.statusbar.findChild(QLabel, "status_hint") if self.statusbar else None + self.statusbar.findChild(QLabel, "status_ready") if self.statusbar else None ) except Exception: self.status_hint = None + + # Set properties for dynamic i18n + if self.select_lang: + self.select_lang.setProperty("i18n_text_system_key", "choose_language_system_button") + self.select_lang.setProperty("i18n_system_attr", "language_pref") + if self.select_theme: + self.select_theme.setProperty("i18n_text_system_key", "choose_theme_system_button") + self.select_theme.setProperty("i18n_system_attr", "theme") + if self.venv_label: + self.venv_label.setProperty("i18n_text_system_key", "venv_label_system") + self.venv_label.setProperty("i18n_system_attr", "use_system_python") + if self.label_workspace_status: + self.label_workspace_status.setProperty("i18n_format_attr", "workspace_dir") + self.label_workspace_status.setProperty("i18n_none_key", "label_workspace_status_none") + if self.file_filter_input: + self.file_filter_input.setProperty("i18n_placeholder_key", "file_filter_placeholder") + if self.btn_acasl_loader: + self.btn_acasl_loader.setProperty("i18n_text_key", "bc_loader") + self.btn_acasl_loader.setProperty("i18n_tooltip_key", "tt_bc_loader") + if self.activity_btn_deps: + self.activity_btn_deps.setProperty("i18n_tooltip_key", "tt_suggest_deps") + _setup_status_bar(self) @@ -367,14 +389,14 @@ def _setup_more_tools_menu(self) -> None: act_language = QAction( translate(self, "choose_language_button", "Language"), menu ) - act_language.setObjectName("act_language") + act_language.setObjectName("btn_select_lang") act_language.triggered.connect( lambda: getattr(self, "show_language_dialog", lambda: None)() ) menu.addAction(act_language) act_theme = QAction(translate(self, "choose_theme_button", "Theme"), menu) - act_theme.setObjectName("act_theme") + act_theme.setObjectName("btn_select_theme") act_theme.triggered.connect(lambda: _open_theme_dialog(self)) menu.addAction(act_theme) @@ -605,7 +627,7 @@ def _setup_status_bar(self) -> None: return try: self.status_hint = QLabel("Ready") - self.status_hint.setObjectName("status_hint") + self.status_hint.setObjectName("status_ready") self.status_hint.setText(translate(self, "status_ready", "Ready")) self.statusbar.addPermanentWidget(self.status_hint, 1) except Exception: @@ -623,7 +645,7 @@ def _apply_status_bar_theme(self, dark: bool, fg: str, border: str) -> None: f"border-top: 1px solid {border};" "}" "QStatusBar::item { border: none; }" - "QLabel#status_hint { padding: 2px 8px; }" + "QLabel#status_ready { padding: 2px 8px; }" ) try: self.statusbar.setStyleSheet(style) diff --git a/pycompiler_ark/Ui/Gui/UiConnection.py b/pycompiler_ark/Ui/Gui/UiConnection.py index cc284515..dd4abaa6 100644 --- a/pycompiler_ark/Ui/Gui/UiConnection.py +++ b/pycompiler_ark/Ui/Gui/UiConnection.py @@ -304,7 +304,7 @@ def _block(selector: str) -> str | None: def _colors(text: str) -> list[str]: return re.findall(r"#[0-9a-fA-F]{3,6}", text) - for selector in ("QPushButton#compile_btn", "#compile_btn"): + for selector in ("QPushButton#btn_build_all", "#btn_build_all"): block = _block(selector) if block: colors = _colors(block) @@ -421,7 +421,7 @@ def _find(cls, name: str): return self.ui.findChild(cls, name) self.btn_select_folder = _find(QPushButton, "btn_select_folder") - self.venv_button = _find(QPushButton, "venv_button") + self.venv_button = _find(QPushButton, "btn_venv_button") self.venv_label = _find(QLabel, "venv_label") self.label_folder = _find(QLabel, "label_folder") self.label_workspace_status = _find(QLabel, "label_workspace_status") @@ -439,8 +439,8 @@ def _find(cls, name: str): self.btn_remove_file = _find(QPushButton, "btn_remove_file") self.btn_clear_workspace = _find(QPushButton, "btn_clear_workspace") - self.compile_btn = _find(QPushButton, "compile_btn") - self.cancel_btn = _find(QPushButton, "cancel_btn") + self.compile_btn = _find(QPushButton, "btn_build_all") + self.cancel_btn = _find(QPushButton, "btn_cancel_all") self.btn_help = _find(QPushButton, "btn_help") self.btn_suggest_deps = _find(QPushButton, "btn_suggest_deps") @@ -451,9 +451,28 @@ def _find(cls, name: str): self.progress = _find(QProgressBar, "progress") self.log = _find(QTextEdit, "log") self.btn_show_stats = _find(QPushButton, "btn_show_stats") - self.advanced_cfg_btn = _find(QPushButton, "advanced_cfg_btn") - self.select_lang = _find(QPushButton, "select_lang") - self.select_theme = _find(QPushButton, "select_theme") + self.advanced_cfg_btn = _find(QPushButton, "btn_advanced_config") + self.select_lang = _find(QPushButton, "btn_select_lang") + self.select_theme = _find(QPushButton, "btn_select_theme") + + # Set properties for dynamic i18n + if self.select_lang: + self.select_lang.setProperty("i18n_text_system_key", "choose_language_system_button") + self.select_lang.setProperty("i18n_system_attr", "language_pref") + if self.select_theme: + self.select_theme.setProperty("i18n_text_system_key", "choose_theme_system_button") + self.select_theme.setProperty("i18n_system_attr", "theme") + if self.venv_label: + self.venv_label.setProperty("i18n_text_system_key", "venv_label_system") + self.venv_label.setProperty("i18n_system_attr", "use_system_python") + if self.label_workspace_status: + self.label_workspace_status.setProperty("i18n_format_attr", "workspace_dir") + self.label_workspace_status.setProperty("i18n_none_key", "label_workspace_status_none") + if self.file_filter_input: + self.file_filter_input.setProperty("i18n_placeholder_key", "file_filter_placeholder") + if self.btn_acasl_loader: + self.btn_acasl_loader.setProperty("i18n_text_key", "bc_loader") + self.btn_acasl_loader.setProperty("i18n_tooltip_key", "tt_bc_loader") for _lbl in (self.label_folder, self.venv_label): if _lbl is None: @@ -711,7 +730,7 @@ def _block(selector: str) -> str | None: def _colors(text: str) -> list[str]: return re.findall(r"#[0-9a-fA-F]{3,6}", text) - for selector in ("QPushButton#compile_btn", "#compile_btn"): + for selector in ("QPushButton#btn_build_all", "#btn_build_all"): block = _block(selector) if block: colors = _colors(block) diff --git a/pycompiler_ark/Ui/Gui/UiFeatures.py b/pycompiler_ark/Ui/Gui/UiFeatures.py index b3ec9446..541a48ec 100644 --- a/pycompiler_ark/Ui/Gui/UiFeatures.py +++ b/pycompiler_ark/Ui/Gui/UiFeatures.py @@ -636,29 +636,8 @@ def unregister_language_refresh(self, callback: Callable) -> None: def log_i18n(self, fr: str, en: str) -> None: """Append a localized message to the log.""" try: - from pycompiler_ark.Ui.i18n import log_i18n_level - - lvl = "info" - for emo, lv in ( - ("❌", "error"), - ("⚠️", "warning"), - ("❗", "warning"), - ("✅", "success"), - ("ℹ️", "info"), - ("⏩", "state"), - ("📝", "state"), - ("📋", "state"), - ("🔍", "state"), - ("🔧", "state"), - ("🔨", "state"), - ("➡️", "state"), - ("📦", "state"), - ("🗑️", "state"), - ): - if str(fr).startswith(emo) or str(en).startswith(emo): - lvl = lv - break - log_i18n_level(self, lvl, fr, en) + from pycompiler_ark.Ui.i18n import log_i18n + log_i18n(self, fr, en) except Exception: try: msg = self.tr(fr, en) @@ -666,7 +645,6 @@ def log_i18n(self, fr: str, en: str) -> None: msg = en try: from pycompiler_ark.Ui.i18n import log_with_level - log_with_level(self, "info", msg) except Exception: pass diff --git a/pycompiler_ark/Ui/i18n.py b/pycompiler_ark/Ui/i18n.py index 734423b6..aee0a760 100644 --- a/pycompiler_ark/Ui/i18n.py +++ b/pycompiler_ark/Ui/i18n.py @@ -747,20 +747,58 @@ def log_with_level( _console_log(lvl, label, msg) -def log_i18n_level( +def log_i18n( gui: Any, - level: str, fr: str, en: str, + level: str | None = None, *, redact: bool = True, clamp: bool = True, ) -> None: - """Translate then log a level-tagged message.""" + """Translate and log a message, automatically inferring the level from emojis if not provided.""" + lvl = level + if lvl is None: + lvl = "info" + fr_str = str(fr) + en_str = str(en) + for emo, lv in ( + ("❌", "error"), + ("⚠️", "warning"), + ("❗", "warning"), + ("✅", "success"), + ("ℹ️", "info"), + ("⏩", "state"), + ("📝", "state"), + ("📋", "state"), + ("🔍", "state"), + ("🔧", "state"), + ("🔨", "state"), + ("➡️", "state"), + ("📦", "state"), + ("🗑️", "state"), + ): + if fr_str.startswith(emo) or en_str.startswith(emo): + lvl = lv + break + fr2 = _strip_emoji_prefix(fr) en2 = _strip_emoji_prefix(en) msg = tr(gui, fr2, en2) - log_with_level(gui, level, msg, redact=redact, clamp=clamp) + log_with_level(gui, lvl, msg, redact=redact, clamp=clamp) + + +def log_i18n_level( + gui: Any, + level: str, + fr: str, + en: str, + *, + redact: bool = True, + clamp: bool = True, +) -> None: + """Translate then log a level-tagged message.""" + log_i18n(gui, fr, en, level, redact=redact, clamp=clamp) def i18n_synchro(self, lang_pref: str, tr: dict[str, Any]) -> str: @@ -847,81 +885,42 @@ def _is_system_value(value: Any) -> bool: def _binding_for_name(name: str) -> str: if not name: return "" - return { - "select_lang": "choose_language_button", - "select_theme": "choose_theme_button", - "act_language": "choose_language_button", - "act_theme": "choose_theme_button", - "btn_show_stats": "show_stats", - "btn_suggest_deps": "suggest_deps", - "btn_help": "help", - "btn_bc_loader": "bc_loader", - "btn_acasl_loader": "bc_loader", - "status_hint": "status_ready", - "act_workspace": "action_select_workspace", - "act_init": "action_init_project", - "act_venv": "action_select_venv", - "act_add_files": "action_add_files", - "act_clear_workspace": "btn_clear_workspace", - "act_stats": "show_stats", - "act_language": "choose_language_button", - "act_theme": "choose_theme_button", - "act_advanced": "advanced_config", - "act_lock": "lock_manager", - "act_save_engines": "save_engine_configs", - "act_help": "help", - }.get(name, name) + if name in tr: + return name + for prefix in ("btn_", "action_", "tab_", "lbl_", "label_"): + if name.startswith(prefix): + key = name[len(prefix):] + if key in tr: + return key + return name def _tooltip_for_name(name: str) -> str: if not name: return "" - if name in { - "btn_select_folder", - "btn_select_files", - "compile_btn", - "cancel_btn", - "btn_remove_file", - "btn_select_icon", - "btn_nuitka_icon", - "btn_help", - "btn_bc_loader", - "btn_suggest_deps", - "btn_show_stats", - "btn_clear_workspace", - "venv_button", - }: - return f"tt_{name[4:]}" if name.startswith("btn_") else f"tt_{name}" - if name == "activity_btn_deps": - return "tt_suggest_deps" - if name == "btn_acasl_loader": - return "tt_bc_loader" - if name == "select_lang": - return "tt_select_lang" - if name == "select_theme": - return "tt_select_theme" - if name == "act_language": - return "tt_select_lang" - if name == "act_theme": - return "tt_select_theme" - if name in {"toolButton_more", "more_btn", "btn_more_actions"}: - return "tt_more_actions" - if name == "output_dir_input": - return "tt_output_dir" - if name == "windowed_checkbox": - return "tt_windowed" - if name == "disable_console_checkbox": - return "tt_disable_console" - if name == "debug_checkbox": - return "tt_debug" - if name == "verbose_checkbox": - return "tt_verbose" - return f"tt_{name}" if name.startswith("opt_") else "" + if name.startswith("btn_"): + candidate = f"tt_{name[4:]}" + if candidate in tr: + return candidate + candidate = f"tt_{name}" + if candidate in tr: + return candidate + if name.startswith("opt_"): + candidate = f"tt_{name}" + if candidate in tr: + return candidate + return "" def _placeholder_for_name(name: str) -> str: - if name == "file_filter_input": - return "file_filter_placeholder" - if name == "nuitka_output_dir": - return "nuitka_output_dir" + if not name: + return "" + candidate = f"{name}_placeholder" + if candidate in tr: + return candidate + for prefix in ("btn_", "action_", "tab_", "lbl_", "label_"): + if name.startswith(prefix): + candidate = f"{name[len(prefix):]}_placeholder" + if candidate in tr: + return candidate return "" def _iter_objects(root: Any): @@ -981,19 +980,6 @@ def _apply_tab_text(obj: Any, key: str, default: str | None = None) -> None: current = obj.text() if hasattr(obj, "text") else None chosen_key = str(text_key) - if not _prop(obj, "i18n_text_key"): - if name in {"select_lang", "act_language"}: - system_key = "choose_language_system_button" - system_attr = "language_pref" - elif name in {"select_theme", "act_theme"}: - system_key = "choose_theme_system_button" - system_attr = "theme" - elif name == "venv_label": - system_key = "venv_label_system" - system_attr = "use_system_python" - elif name == "label_workspace_status": - format_attr = "workspace_dir" - none_key = "label_workspace_status_none" if system_key and system_attr and _is_system_value(getattr(self, str(system_attr), None)): chosen_key = str(system_key) diff --git a/pycompiler_ark/themes/arctic_light.qss b/pycompiler_ark/themes/arctic_light.qss index f511aafb..9888c712 100644 --- a/pycompiler_ark/themes/arctic_light.qss +++ b/pycompiler_ark/themes/arctic_light.qss @@ -63,34 +63,34 @@ QPushButton:disabled { } /* Boutons principaux mis en avant */ -QPushButton#compile_btn { +QPushButton#btn_build_all { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #3685FF, stop:1 #2D7DFF); color: #FDFEFF; border-color: #2D7DFF; font-weight: 600; } -QPushButton#compile_btn:hover { +QPushButton#btn_build_all:hover { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #4090FF, stop:1 #3280FF); border-color: #3685FF; } -QPushButton#compile_btn:pressed { +QPushButton#btn_build_all:pressed { background: #226AF0; } -QPushButton#compile_btn:disabled { +QPushButton#btn_build_all:disabled { background: #E0E0E0; color: #A0A0A0; border-color: #D0D0D0; } -QPushButton#cancel_btn { +QPushButton#btn_cancel_all { background: #FFEFF2; color: #9B2438; border: 1px solid #F9C8D1; } -QPushButton#cancel_btn:hover { +QPushButton#btn_cancel_all:hover { background: #FFE6EA; border-color: #F3AFBA; } -QPushButton#cancel_btn:disabled { +QPushButton#btn_cancel_all:disabled { background: #E0E0E0; color: #A0A0A0; border-color: #D0D0D0; diff --git a/pycompiler_ark/themes/dark.qss b/pycompiler_ark/themes/dark.qss index 19278c92..ffa73c3f 100644 --- a/pycompiler_ark/themes/dark.qss +++ b/pycompiler_ark/themes/dark.qss @@ -63,34 +63,34 @@ QPushButton:disabled { } /* Boutons principaux */ -QPushButton#compile_btn { +QPushButton#btn_build_all { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #3685FF, stop:1 #2D7DFF); color: #ffffff; border-color: #2D7DFF; font-weight: 600; } -QPushButton#compile_btn:hover { +QPushButton#btn_build_all:hover { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #4090FF, stop:1 #3280FF); border-color: #3685FF; } -QPushButton#compile_btn:pressed { +QPushButton#btn_build_all:pressed { background: #226AF0; } -QPushButton#compile_btn:disabled { +QPushButton#btn_build_all:disabled { background: #1A1D21; color: #55595F; border-color: #1F2329; } -QPushButton#cancel_btn { +QPushButton#btn_cancel_all { background: #3A2023; color: #ffb3ba; border: 1px solid #5A2A30; } -QPushButton#cancel_btn:hover { +QPushButton#btn_cancel_all:hover { background: #44262A; border-color: #6A3A40; } -QPushButton#cancel_btn:disabled { +QPushButton#btn_cancel_all:disabled { background: #1A1D21; color: #55595F; border-color: #1F2329; diff --git a/pycompiler_ark/themes/forest.qss b/pycompiler_ark/themes/forest.qss index 4bbb8349..562fbd6e 100644 --- a/pycompiler_ark/themes/forest.qss +++ b/pycompiler_ark/themes/forest.qss @@ -63,34 +63,34 @@ QPushButton:disabled { } /* Boutons principaux */ -QPushButton#compile_btn { +QPushButton#btn_build_all { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #38AF76, stop:1 #2FA36B); color: #ffffff; border-color: #2FA36B; font-weight: 600; } -QPushButton#compile_btn:hover { +QPushButton#btn_build_all:hover { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #4ABA84, stop:1 #3CAA75); border-color: #38AF76; } -QPushButton#compile_btn:pressed { +QPushButton#btn_build_all:pressed { background: #258A5F; } -QPushButton#compile_btn:disabled { +QPushButton#btn_build_all:disabled { background: #1A1D21; color: #55595F; border-color: #1F2329; } -QPushButton#cancel_btn { +QPushButton#btn_cancel_all { background: #3A2023; color: #ffb3ba; border: 1px solid #5A2A30; } -QPushButton#cancel_btn:hover { +QPushButton#btn_cancel_all:hover { background: #44262A; border-color: #6A3A40; } -QPushButton#cancel_btn:disabled { +QPushButton#btn_cancel_all:disabled { background: #1A1D21; color: #55595F; border-color: #1F2329; diff --git a/pycompiler_ark/themes/light.qss b/pycompiler_ark/themes/light.qss index 7ed3cfc1..b297ea66 100644 --- a/pycompiler_ark/themes/light.qss +++ b/pycompiler_ark/themes/light.qss @@ -63,34 +63,34 @@ QPushButton:disabled { } /* Boutons principaux mis en avant */ -QPushButton#compile_btn { +QPushButton#btn_build_all { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #3685FF, stop:1 #2D7DFF); color: #ffffff; border-color: #2D7DFF; font-weight: 600; } -QPushButton#compile_btn:hover { +QPushButton#btn_build_all:hover { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #4090FF, stop:1 #3280FF); border-color: #3685FF; } -QPushButton#compile_btn:pressed { +QPushButton#btn_build_all:pressed { background: #226AF0; } -QPushButton#compile_btn:disabled { +QPushButton#btn_build_all:disabled { background: #E0E0E0; color: #A0A0A0; border-color: #D0D0D0; } -QPushButton#cancel_btn { +QPushButton#btn_cancel_all { background: #ffe9ea; color: #b00020; border: 1px solid #ffccd0; } -QPushButton#cancel_btn:hover { +QPushButton#btn_cancel_all:hover { background: #ffdfe2; border-color: #ffb8bd; } -QPushButton#cancel_btn:disabled { +QPushButton#btn_cancel_all:disabled { background: #E0E0E0; color: #A0A0A0; border-color: #D0D0D0; diff --git a/pycompiler_ark/themes/mint_light.qss b/pycompiler_ark/themes/mint_light.qss index c1d1b8b5..0afb5448 100644 --- a/pycompiler_ark/themes/mint_light.qss +++ b/pycompiler_ark/themes/mint_light.qss @@ -63,34 +63,34 @@ QPushButton:disabled { } /* Boutons principaux mis en avant */ -QPushButton#compile_btn { +QPushButton#btn_build_all { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #38AF76, stop:1 #2FA36B); color: #ffffff; border-color: #2FA36B; font-weight: 600; } -QPushButton#compile_btn:hover { +QPushButton#btn_build_all:hover { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #4ABA84, stop:1 #3CAA75); border-color: #38AF76; } -QPushButton#compile_btn:pressed { +QPushButton#btn_build_all:pressed { background: #258A5F; } -QPushButton#compile_btn:disabled { +QPushButton#btn_build_all:disabled { background: #E0E0E0; color: #A0A0A0; border-color: #D0D0D0; } -QPushButton#cancel_btn { +QPushButton#btn_cancel_all { background: #FFF1F3; color: #8E2E3D; border: 1px solid #F4CFD6; } -QPushButton#cancel_btn:hover { +QPushButton#btn_cancel_all:hover { background: #FCE6EA; border-color: #EFBAC1; } -QPushButton#cancel_btn:disabled { +QPushButton#btn_cancel_all:disabled { background: #E0E0E0; color: #A0A0A0; border-color: #D0D0D0; diff --git a/pycompiler_ark/themes/rose_light.qss b/pycompiler_ark/themes/rose_light.qss index c56ebcbf..90211042 100644 --- a/pycompiler_ark/themes/rose_light.qss +++ b/pycompiler_ark/themes/rose_light.qss @@ -63,34 +63,34 @@ QPushButton:disabled { } /* Boutons principaux mis en avant */ -QPushButton#compile_btn { +QPushButton#btn_build_all { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #D06A8C, stop:1 #C4577B); color: #ffffff; border-color: #C4577B; font-weight: 600; } -QPushButton#compile_btn:hover { +QPushButton#btn_build_all:hover { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #DA7A99, stop:1 #CB6184); border-color: #D06A8C; } -QPushButton#compile_btn:pressed { +QPushButton#btn_build_all:pressed { background: #AF466A; } -QPushButton#compile_btn:disabled { +QPushButton#btn_build_all:disabled { background: #E0E0E0; color: #A0A0A0; border-color: #D0D0D0; } -QPushButton#cancel_btn { +QPushButton#btn_cancel_all { background: #FFEFF3; color: #9B1F44; border: 1px solid #F5CAD7; } -QPushButton#cancel_btn:hover { +QPushButton#btn_cancel_all:hover { background: #FEE4EB; border-color: #EFA8B6; } -QPushButton#cancel_btn:disabled { +QPushButton#btn_cancel_all:disabled { background: #E0E0E0; color: #A0A0A0; border-color: #D0D0D0; diff --git a/pycompiler_ark/themes/sand.qss b/pycompiler_ark/themes/sand.qss index 9fbfcaf3..94f54316 100644 --- a/pycompiler_ark/themes/sand.qss +++ b/pycompiler_ark/themes/sand.qss @@ -63,34 +63,34 @@ QPushButton:disabled { } /* Boutons principaux mis en avant */ -QPushButton#compile_btn { +QPushButton#btn_build_all { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #EA9635, stop:1 #E28A2D); color: #FFF9F1; border-color: #E28A2D; font-weight: 600; } -QPushButton#compile_btn:hover { +QPushButton#btn_build_all:hover { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #F0A040, stop:1 #E69031); border-color: #EA9635; } -QPushButton#compile_btn:pressed { +QPushButton#btn_build_all:pressed { background: #D97C20; } -QPushButton#compile_btn:disabled { +QPushButton#btn_build_all:disabled { background: #E0E0E0; color: #A0A0A0; border-color: #D0D0D0; } -QPushButton#cancel_btn { +QPushButton#btn_cancel_all { background: #ffe9ea; color: #b00020; border: 1px solid #ffccd0; } -QPushButton#cancel_btn:hover { +QPushButton#btn_cancel_all:hover { background: #ffdfe2; border-color: #ffb8bd; } -QPushButton#cancel_btn:disabled { +QPushButton#btn_cancel_all:disabled { background: #E0E0E0; color: #A0A0A0; border-color: #D0D0D0; diff --git a/pycompiler_ark/themes/slate.qss b/pycompiler_ark/themes/slate.qss index c850af8d..cb663fc7 100644 --- a/pycompiler_ark/themes/slate.qss +++ b/pycompiler_ark/themes/slate.qss @@ -63,34 +63,34 @@ QPushButton:disabled { } /* Boutons principaux */ -QPushButton#compile_btn { +QPushButton#btn_build_all { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #5CCCD1, stop:1 #4CC2C8); color: #ffffff; border-color: #4CC2C8; font-weight: 600; } -QPushButton#compile_btn:hover { +QPushButton#btn_build_all:hover { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #66D4D9, stop:1 #54C7CD); border-color: #5CCCD1; } -QPushButton#compile_btn:pressed { +QPushButton#btn_build_all:pressed { background: #3AAAB0; } -QPushButton#compile_btn:disabled { +QPushButton#btn_build_all:disabled { background: #1A1D21; color: #55595F; border-color: #1F2329; } -QPushButton#cancel_btn { +QPushButton#btn_cancel_all { background: #3A2023; color: #ffb3ba; border: 1px solid #5A2A30; } -QPushButton#cancel_btn:hover { +QPushButton#btn_cancel_all:hover { background: #44262A; border-color: #6A3A40; } -QPushButton#cancel_btn:disabled { +QPushButton#btn_cancel_all:disabled { background: #1A1D21; color: #55595F; border-color: #1F2329; diff --git a/tests/test_venv_manager_internet.py b/tests/test_venv_manager_internet.py index a3993e83..4055e377 100644 --- a/tests/test_venv_manager_internet.py +++ b/tests/test_venv_manager_internet.py @@ -10,8 +10,8 @@ def setUp(self): # Create a mock parent self.mock_parent = MagicMock() self.manager = VenvManager(self.mock_parent) - # Mock _safe_log and _call_ui to avoid side effects - self.manager._safe_log = MagicMock() + # Mock log_i18n and _call_ui to avoid side effects + self.manager.log_i18n = MagicMock() self.manager._call_ui = MagicMock() @patch("pycompiler_ark.Core.Compiler.utils.check_internet_connection") @@ -22,7 +22,7 @@ def test_ensure_tools_installed_no_internet(self, mock_check): self.manager.ensure_tools_installed("/fake/venv", ["tool"]) # Should log error and return early - self.manager._safe_log.assert_called_with( + self.manager.log_i18n.assert_called_with( "🛑 [ERROR] Pas de connexion internet. Installation des outils annulée.", "🛑 [ERROR] No internet connection. Tool installation cancelled.", level="error", @@ -37,7 +37,7 @@ def test_ensure_tools_installed_system_no_internet(self, mock_check): self.manager.ensure_tools_installed_system(["tool"]) - self.manager._safe_log.assert_called_with( + self.manager.log_i18n.assert_called_with( "🛑 [ERROR] Pas de connexion internet. Installation système annulée.", "🛑 [ERROR] No internet connection. System installation cancelled.", level="error", diff --git a/todo.md b/todo.md index 5b4fb10d..a4500329 100644 --- a/todo.md +++ b/todo.md @@ -1,5 +1,5 @@ -# refactor -- [] cnetraliser toute les `def _declare_i18n` dans Ui/i18n.py cat il sont tous identique.recherche dabord parotut avant de faire la centralisation. -- [] appliquer le i18n à help_text dans uifeatures. -- [] les engine ne doivent avoir access que a leur sdk de meme que les plugins. -- [] pour tous les i18n analyser pour voir si il nest pas possible de inteegre le declare i18n dna stranslate pour que on est plus beoin de declarer et on fera juste `translate(self.id, key, default)`. \ No newline at end of file +# Refactor Status + +- [x] Centraliser tout les `def log_i18n` dans `pycompiler_ark/Ui/i18n.py` comme seule méthode pour le logging i18n de l'app. +- [x] Retirer l'utilisation des `safe_logs` et `_safe_log` dans toute l'appli pour privilégier l'usage de `log_i18n`. +- [x] Éviter le hardcoding dans i18n en nettoyant les noms des boutons et en exploitant la convention de traduction automatique par `tt_*`, `btn_*`, `tab_*` du système i18n et les propriétés dynamiques Qt. \ No newline at end of file