diff --git a/docs/app_i18n.md b/docs/app_i18n.md index 0703fed..c0f8564 100644 --- a/docs/app_i18n.md +++ b/docs/app_i18n.md @@ -15,7 +15,7 @@ That module owns: - refreshing translated widgets when the language changes The public lookup API is `translate(self.id, key, default)`. -The app UI should use that API everywhere a label, tooltip, placeholder, or tab title needs a translated value. +The app UI should use that API everywhere a label, tooltip, placeholder, action text, or tab title needs a translated value. ## **Language Files** @@ -52,17 +52,7 @@ 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. call `translate(self.id, ...)` when reading text dynamically - -For standard widgets, use the helper already used in the codebase: - -```python -_declare_i18n( - self.compile_btn, - i18n_text_key="build_all", - i18n_tooltip_key="tt_build_all", -) -``` +4. let the generic walker read the widget properties and call `translate(self.id, ...)` If you need to attach properties manually, use: @@ -84,6 +74,13 @@ widget.setProperty("i18n_tooltip_key", "tt_build_all") - `i18n_format_attr` - `i18n_none_key` +The walker is generic: + +- `QGroupBox` uses `setTitle(...)` +- `QAction`, buttons, labels, and checkboxes use `setText(...)` +- line edits and similar widgets use `setPlaceholderText(...)` +- tooltips are applied when `i18n_tooltip_key` is present + Typical use cases: - `i18n_text_key`: button text, label text, action text @@ -102,7 +99,7 @@ When the user changes the language: 2. `get_translations()` loads the selected YAML file. 3. `i18n_synchro()` stores the active catalog. 4. `_apply_main_app_translations()` walks the UI tree and reapplies texts. -5. The IDE-like actions, engine registry, and plugin SDK are refreshed through their generic host hooks. +5. The engine registry and plugin SDK are refreshed through generic host hooks. This is why the application should not hardcode translated strings inside the refresh path. The refresh path must stay generic and data-driven. diff --git a/docs/contributing.md b/docs/contributing.md index 291bf8c..ed700b8 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -19,6 +19,8 @@ If you are extending PyCompiler ARK, start with: - **Creating a Compilation Engine**: [how_to_create_an_engine.md](https://github.com/raidos23/PyCompiler_ARK/blob/main/docs/how_to_create_an_engine.md) - **Creating a Pre-Compile Plugin**: [how_to_create_a_bc_plugin.md](https://github.com/raidos23/PyCompiler_ARK/blob/main/docs/how_to_create_a_bc_plugin.md) +For translation work, read the application guide first, then the engine or plugin guide that matches the area you are changing. + ## **Core References** - **BuildContext Spec**: [BuildContext.md](https://github.com/raidos23/PyCompiler_ARK/blob/main/docs/BuildContext.md) diff --git a/docs/how_to_create_a_bc_plugin.md b/docs/how_to_create_a_bc_plugin.md index 895c9c3..bc1f692 100644 --- a/docs/how_to_create_a_bc_plugin.md +++ b/docs/how_to_create_a_bc_plugin.md @@ -221,6 +221,7 @@ Notes. - `on_save(config_dict)` can return an updated dict. - Each plugin entry stores its config in the `config` field inside the `plugins` collection in `bcasl.yml`. - `create_tab(...)` may return a widget, a `(title, widget)` tuple, a `(title, widget, on_save)` tuple, or a dict with `title`, `widget`, and `on_save`. +- Keep the widget tree stable and assign meaningful `objectName()` values so the host can refresh translations in place. **Plugin i18n (GeneralContext)** @@ -251,8 +252,10 @@ Notes. - Keep plugin-specific keys inside the plugin package only. - Use `translate(...)` directly in the plugin UI code. - Do not add a custom i18n refresh hook inside the plugin package; the host handles language synchronization. +- When building a plugin tab, attach i18n properties such as `i18n_text_key`, `i18n_tooltip_key`, `i18n_placeholder_key`, or `i18n_title_key` to the widgets that need refresh. - The SDK also accepts the plugin folder name as ID (case-insensitive). - If a key is missing, `translate()` falls back to the default you pass in. +- Avoid importing application UI modules from a plugin. Keep the dependency boundary inside `Plugins_SDK` and the standard library. **Sandbox and Resource Limits** diff --git a/docs/how_to_create_an_engine.md b/docs/how_to_create_an_engine.md index 00fde04..7d7512e 100644 --- a/docs/how_to_create_an_engine.md +++ b/docs/how_to_create_an_engine.md @@ -85,7 +85,9 @@ UI and i18n. - `create_tab(self, gui) -> (QWidget, label) | None`: adds a tab. - `translate(self.id, key, default=None)`: engine-local translation lookup. -- The host refreshes the engine UI when the language changes, so the engine only needs to declare translation keys on its widgets and use `translate(...)` when building the UI. +- The host refreshes the engine UI when the language changes. +- Set stable widget `objectName()` values and, when needed, i18n properties such as `i18n_text_key`, `i18n_tooltip_key`, `i18n_placeholder_key`, and `i18n_tab_key`. +- Use `translate(...)` when building the UI and let the host reapply the active catalog at runtime. Tools and dependencies. @@ -107,6 +109,7 @@ Tools and dependencies. - **IMPORTANT**: Do not include UI components for **Icon** selection or **Output directory** in your engine tab. These are globally managed in `ark.yml` and carried by `BuildContext`. Focus only on engine-specific flags and options. - Prefer grouping options with `QGroupBox` sections and compact hints, following the built-in engines layout style. - Keep widget attribute names stable once they are used by config persistence or compilation logic. +- Do not import application UI modules from an engine. Use the engine SDK only. ### **Engine Config (get_config / set_config)** @@ -118,6 +121,7 @@ Flow: - `get_config(gui)` returns a JSON‑serializable dict of current UI state. - `set_config(gui, cfg)` applies a config dict back to the widgets. - The Core saves configs on compile and reloads them when a workspace is applied. +- The host can refresh translated text live without recreating the engine, so keep the widget tree stable. #### Minimal example diff --git a/pycompiler_ark/Core/engine/registry.py b/pycompiler_ark/Core/engine/registry.py index 20f5dbc..d6368f0 100644 --- a/pycompiler_ark/Core/engine/registry.py +++ b/pycompiler_ark/Core/engine/registry.py @@ -53,16 +53,6 @@ _ENGINE_TR: dict[str, dict[str, Any]] = {} -def _declare_i18n(widget, **props) -> None: - if widget is None: - return - for key, value in props.items(): - try: - widget.setProperty(key, value) - except Exception: - pass - - def _iter_i18n_roots(engine: CompilerEngine): seen: set[int] = set() try: @@ -84,6 +74,48 @@ def _iter_i18n_roots(engine: CompilerEngine): continue +def _object_name(obj: Any) -> str: + try: + name = getattr(obj, "objectName", lambda: "")() + if isinstance(name, str): + return name.strip() + except Exception: + pass + return "" + + +def _binding_for_name(name: str) -> str: + if not name: + return "" + return { + "build_group": "build_group", + "onefile_checkbox": "onefile_checkbox", + "windowed_checkbox": "windowed_checkbox", + "standalone_checkbox": "standalone_checkbox", + "disable_console_checkbox": "disable_console_checkbox", + "debug_checkbox": "debug_checkbox", + "verbose_checkbox": "verbose_checkbox", + "mode_label": "mode_label", + "type_label": "type_label", + "console_label": "console_label", + "diagnostics_group": "diagnostics_group", + "hint_text": "hint_text", + }.get(name, "") + + +def _tooltip_for_name(name: str) -> str: + return { + "windowed_checkbox": "tt_windowed", + "disable_console_checkbox": "tt_disable_console", + "debug_checkbox": "tt_debug", + "verbose_checkbox": "tt_verbose", + }.get(name, "") + + +def _placeholder_for_name(name: str) -> str: + return "" + + def _apply_engine_i18n(root: Any, engine_id: str) -> None: def _prop(obj: Any, name: str) -> Any: if hasattr(obj, "property"): @@ -145,7 +177,8 @@ def _apply_tab_text(obj: Any, key: str, default: str | None = None) -> None: parent = getattr(parent, "parent", lambda: None)() for obj in _iter_objects(root): - text_key = _prop(obj, "i18n_text_key") + name = _object_name(obj) + text_key = _prop(obj, "i18n_text_key") or _binding_for_name(name) if text_key: system_key = _prop(obj, "i18n_text_system_key") system_attr = _prop(obj, "i18n_system_attr") @@ -154,6 +187,22 @@ 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 == "hint_text": + chosen_key = "hint_text" + elif name == "build_group": + chosen_key = "build_group" + elif name == "diagnostics_group": + chosen_key = "diagnostics_group" + elif name == "mode_label": + chosen_key = "mode_label" + elif name == "type_label": + chosen_key = "type_label" + elif name == "console_label": + chosen_key = "console_label" + elif name in {"windowed_checkbox", "disable_console_checkbox", "debug_checkbox", "verbose_checkbox", "onefile_checkbox", "standalone_checkbox"}: + chosen_key = name + if system_key and system_attr and _is_system_value(getattr(root, str(system_attr), None)): chosen_key = str(system_key) @@ -169,12 +218,12 @@ def _apply_tab_text(obj: Any, key: str, default: str | None = None) -> None: _apply_text(obj, chosen_key, current if isinstance(current, str) else None) - tooltip_key = _prop(obj, "i18n_tooltip_key") + tooltip_key = _prop(obj, "i18n_tooltip_key") or _tooltip_for_name(name) if tooltip_key: current = obj.toolTip() if hasattr(obj, "toolTip") else None _apply_tooltip(obj, str(tooltip_key), current if isinstance(current, str) else None) - placeholder_key = _prop(obj, "i18n_placeholder_key") + placeholder_key = _prop(obj, "i18n_placeholder_key") or _placeholder_for_name(name) if placeholder_key: current = obj.placeholderText() if hasattr(obj, "placeholderText") else None _apply_placeholder( diff --git a/pycompiler_ark/Plugins/Cleaner/__init__.py b/pycompiler_ark/Plugins/Cleaner/__init__.py index a94a11d..2680385 100644 --- a/pycompiler_ark/Plugins/Cleaner/__init__.py +++ b/pycompiler_ark/Plugins/Cleaner/__init__.py @@ -99,32 +99,38 @@ def create_tab(self, parent, ctx: PreCompileContext, config: dict): return None w = QWidget(parent) + w.setObjectName("plugin_cleaner") lay = QVBoxLayout(w) lay.setSpacing(8) lay.setContentsMargins(8, 8, 8, 8) # Safety safety_group = QGroupBox(translate("cleaner", "ui_safety", "Safety"), w) + safety_group.setObjectName("ui_safety") safety_layout = QVBoxLayout() safety_layout.setSpacing(4) chk_confirm = QCheckBox( translate("cleaner", "ui_confirm", "Ask confirmation before cleaning"), safety_group, ) + chk_confirm.setObjectName("ui_confirm") safety_layout.addWidget(chk_confirm) safety_group.setLayout(safety_layout) # Targets targets_group = QGroupBox(translate("cleaner", "ui_targets", "Targets"), w) + targets_group.setObjectName("ui_targets") targets_layout = QFormLayout() targets_layout.setSpacing(6) chk_pyc = QCheckBox( translate("cleaner", "ui_pyc", "Remove .pyc files"), targets_group ) + chk_pyc.setObjectName("ui_pyc") chk_pycache = QCheckBox( translate("cleaner", "ui_pycache", "Remove __pycache__ folders"), targets_group, ) + chk_pycache.setObjectName("ui_pycache") targets_layout.addRow(chk_pyc) targets_layout.addRow(chk_pycache) targets_group.setLayout(targets_layout) @@ -142,6 +148,7 @@ def create_tab(self, parent, ctx: PreCompileContext, config: dict): ), w, ) + hint.setObjectName("ui_tip") hint.setStyleSheet("color: #888; font-size: 11px;") hint.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) lay.addWidget(hint) @@ -153,7 +160,7 @@ def on_save(cfg: dict): cfg["clean_pycache"] = bool(chk_pycache.isChecked()) return cfg - return ("Cleaner", w, on_save) + return (translate("cleaner", "tab_title", "Cleaner"), w, on_save) def on_pre_compile(self, ctx: PreCompileContext) -> None: """Nettoie le workspace avant la compilation.""" diff --git a/pycompiler_ark/Plugins/Cleaner/languages/en.yml b/pycompiler_ark/Plugins/Cleaner/languages/en.yml index 227d8a8..6e1724f 100644 --- a/pycompiler_ark/Plugins/Cleaner/languages/en.yml +++ b/pycompiler_ark/Plugins/Cleaner/languages/en.yml @@ -4,6 +4,7 @@ ui_confirm: Ask confirmation before cleaning ui_pyc: Remove .pyc files ui_pycache: Remove __pycache__ folders ui_tip: 'Tip: disable items you don''t want to delete.' +tab_title: Cleaner dlg_confirm: Do you want to clean the workspace (.pyc and __pycache__)? log_cancel: Cleaner cancelled by user log_noop: 'Cleaner: nothing to do (both options disabled)' diff --git a/pycompiler_ark/Plugins/Cleaner/languages/fr.yml b/pycompiler_ark/Plugins/Cleaner/languages/fr.yml index d81a2f2..3e95601 100644 --- a/pycompiler_ark/Plugins/Cleaner/languages/fr.yml +++ b/pycompiler_ark/Plugins/Cleaner/languages/fr.yml @@ -4,6 +4,7 @@ ui_confirm: Demander confirmation avant le nettoyage ui_pyc: Supprimer les fichiers .pyc ui_pycache: Supprimer les dossiers __pycache__ ui_tip: 'Astuce : désactivez ce que vous ne voulez pas supprimer.' +tab_title: Nettoyeur dlg_confirm: Voulez-vous nettoyer le workspace (.pyc et __pycache__) ? log_cancel: Nettoyage annulé par l'utilisateur log_noop: 'Cleaner : rien à faire (options désactivées)' diff --git a/pycompiler_ark/Plugins/OutputCleaner/__init__.py b/pycompiler_ark/Plugins/OutputCleaner/__init__.py index 46fb940..b083d0e 100644 --- a/pycompiler_ark/Plugins/OutputCleaner/__init__.py +++ b/pycompiler_ark/Plugins/OutputCleaner/__init__.py @@ -77,6 +77,67 @@ def _get_config(self, ctx: PreCompileContext) -> dict: except Exception: return {} + def create_tab(self, parent, ctx: PreCompileContext, config: dict): + try: + from PySide6.QtWidgets import ( + QCheckBox, + QGroupBox, + QLabel, + QSizePolicy, + QVBoxLayout, + QWidget, + ) + except Exception: + return None + + widget = QWidget(parent) + widget.setObjectName("plugin_outputcleaner") + layout = QVBoxLayout(widget) + layout.setSpacing(8) + layout.setContentsMargins(8, 8, 8, 8) + + safety_group = QGroupBox( + _tr("ui_safety", "Safety"), widget + ) + safety_group.setObjectName("ui_safety") + safety_layout = QVBoxLayout(safety_group) + safety_layout.setSpacing(4) + + chk_confirm = QCheckBox( + _tr( + "ui_confirm", + "Ask confirmation before cleaning the output directory", + ), + safety_group, + ) + chk_confirm.setObjectName("ui_confirm") + safety_layout.addWidget(chk_confirm) + + hint = QLabel( + _tr( + "ui_hint", + "This plugin removes the build output before compilation.", + ), + widget, + ) + hint.setObjectName("ui_hint") + hint.setWordWrap(True) + hint.setStyleSheet("color: #888; font-size: 11px;") + hint.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + + safety_group.setLayout(safety_layout) + layout.addWidget(safety_group) + layout.addWidget(hint) + layout.addStretch(1) + + chk_confirm.setChecked(bool(config.get("confirm", True))) + + def on_save(cfg: dict): + cfg["confirm"] = bool(chk_confirm.isChecked()) + return cfg + + return (_tr("tab_title", "Output Cleaner"), widget, on_save) + def on_pre_compile(self, ctx: PreCompileContext) -> None: """Nettoie le dossier output avant la compilation.""" try: diff --git a/pycompiler_ark/Plugins/OutputCleaner/languages/en.yml b/pycompiler_ark/Plugins/OutputCleaner/languages/en.yml index 361b597..2ace306 100644 --- a/pycompiler_ark/Plugins/OutputCleaner/languages/en.yml +++ b/pycompiler_ark/Plugins/OutputCleaner/languages/en.yml @@ -1,6 +1,10 @@ _meta: code: en name: English +tab_title: "Output Cleaner" +ui_safety: "Safety" +ui_confirm: "Ask confirmation before cleaning the output directory" +ui_hint: "This plugin removes the build output before compilation." dialog_title: "Output Cleaner" warn_no_build_context: "OutputCleaner: No BuildContext available. Cannot identify output directory." warn_no_output_dir: "OutputCleaner: No output_dir defined in BuildContext." diff --git a/pycompiler_ark/Plugins/OutputCleaner/languages/fr.yml b/pycompiler_ark/Plugins/OutputCleaner/languages/fr.yml index dc08e4c..9e4f420 100644 --- a/pycompiler_ark/Plugins/OutputCleaner/languages/fr.yml +++ b/pycompiler_ark/Plugins/OutputCleaner/languages/fr.yml @@ -1,6 +1,10 @@ _meta: code: fr name: Français +tab_title: "Nettoyeur de sortie" +ui_safety: "Sécurité" +ui_confirm: "Demander confirmation avant de nettoyer le dossier de sortie" +ui_hint: "Ce plugin supprime la sortie de compilation avant la compilation." dialog_title: "Nettoyeur de sortie" warn_no_build_context: "OutputCleaner: Aucun BuildContext disponible. Impossible d'identifier le dossier de sortie." warn_no_output_dir: "OutputCleaner: Aucun output_dir défini dans BuildContext." diff --git a/pycompiler_ark/Plugins_SDK/GeneralContext/__init__.py b/pycompiler_ark/Plugins_SDK/GeneralContext/__init__.py index f7f8914..55dc15d 100644 --- a/pycompiler_ark/Plugins_SDK/GeneralContext/__init__.py +++ b/pycompiler_ark/Plugins_SDK/GeneralContext/__init__.py @@ -23,6 +23,7 @@ register_i18n_handler, register_plugin_translations, resolve_language_code, + refresh_widget_translations, set_translations, translate, unregister_i18n_handler, @@ -39,6 +40,7 @@ "normalize_language_code", "register_i18n_handler", "register_plugin_translations", + "refresh_widget_translations", "resolve_language_code", "set_translations", "translate", diff --git a/pycompiler_ark/Plugins_SDK/GeneralContext/i18n.py b/pycompiler_ark/Plugins_SDK/GeneralContext/i18n.py index 8e742ee..6affd5f 100644 --- a/pycompiler_ark/Plugins_SDK/GeneralContext/i18n.py +++ b/pycompiler_ark/Plugins_SDK/GeneralContext/i18n.py @@ -128,6 +128,114 @@ def translate(plugin_id: str, key: str, default: Optional[str] = None) -> str: return default if default is not None else str(key) +def _object_name(obj: Any) -> str: + try: + name = getattr(obj, "objectName", lambda: "")() + if isinstance(name, str): + return name.strip() + except Exception: + pass + return "" + + +def refresh_widget_translations(root: Any, plugin_id: str) -> None: + """Apply the active plugin catalog to an existing widget tree.""" + def _iter_objects(root_obj: Any): + stack = [root_obj] + seen: set[int] = set() + while stack: + obj = stack.pop() + if obj is None: + continue + oid = id(obj) + if oid in seen: + continue + seen.add(oid) + yield obj + try: + children = list(obj.children()) + except Exception: + children = [] + for child in reversed(children): + stack.append(child) + + def _prop(obj: Any, name: str) -> str: + try: + value = getattr(obj, name, None) + if isinstance(value, str) and value.strip(): + return value.strip() + except Exception: + pass + try: + value = obj.property(name) if hasattr(obj, "property") else None + if isinstance(value, str) and value.strip(): + return value.strip() + except Exception: + pass + return "" + + def _apply_text(obj: Any, key: str, default: str | None = None) -> None: + if not hasattr(obj, "setText"): + return + value = translate(plugin_id, key, default) + if isinstance(value, str) and value: + obj.setText(value) + + def _apply_title(obj: Any, key: str, default: str | None = None) -> None: + if not hasattr(obj, "setTitle"): + return + value = translate(plugin_id, key, default) + if isinstance(value, str) and value: + obj.setTitle(value) + + def _apply_placeholder(obj: Any, key: str, default: str | None = None) -> None: + if not hasattr(obj, "setPlaceholderText"): + return + value = translate(plugin_id, key, default) + if isinstance(value, str) and value: + obj.setPlaceholderText(value) + + def _apply_tooltip(obj: Any, key: str, default: str | None = None) -> None: + if not hasattr(obj, "setToolTip"): + return + value = translate(plugin_id, key, default) + if isinstance(value, str) and value: + obj.setToolTip(value) + + for obj in _iter_objects(root): + name = _object_name(obj) + if not name: + continue + + title_key = _prop(obj, "i18n_title_key") or name + text_key = _prop(obj, "i18n_text_key") or name + tooltip_key = _prop(obj, "i18n_tooltip_key") + placeholder_key = _prop(obj, "i18n_placeholder_key") + + if hasattr(obj, "setTitle"): + current = obj.title() if hasattr(obj, "title") else None + _apply_title(obj, title_key, current if isinstance(current, str) else None) + continue + + if hasattr(obj, "setText"): + current = obj.text() if hasattr(obj, "text") else None + _apply_text(obj, text_key, current if isinstance(current, str) else None) + + if tooltip_key: + current = obj.toolTip() if hasattr(obj, "toolTip") else None + _apply_tooltip( + obj, tooltip_key, current if isinstance(current, str) else None + ) + + if placeholder_key: + current = ( + obj.placeholderText() if hasattr(obj, "placeholderText") else None + ) + _apply_placeholder( + obj, placeholder_key, current if isinstance(current, str) else None + ) + + def register_i18n_handler(fn: Callable[[Any, dict], None]) -> None: if callable(fn): _HANDLERS.add(fn) diff --git a/pycompiler_ark/Ui/Gui/Dialogs/BcaslDialog.py b/pycompiler_ark/Ui/Gui/Dialogs/BcaslDialog.py index aae18f2..1c87a85 100644 --- a/pycompiler_ark/Ui/Gui/Dialogs/BcaslDialog.py +++ b/pycompiler_ark/Ui/Gui/Dialogs/BcaslDialog.py @@ -53,6 +53,10 @@ ) from pycompiler_ark.Core.Configs import load_ark_config, save_ark_config +from pycompiler_ark.Plugins_SDK.GeneralContext import ( + refresh_widget_translations, + translate, +) # --------------------------------------------------------------------------- # Helpers Thème @@ -453,14 +457,17 @@ def __init__( self._sections: list[_SectionWidget] = [] self._plugin_ui_state: dict[str, dict[str, Any]] = {} + self._language_refresh_cb = self._refresh_i18n self.setWindowTitle(gui.tr("BCASL Pipeline", "BCASL Pipeline")) self.resize(860, 680) self.setModal(False) self._build_ui() + self._register_language_refresh() self._install_shortcuts() self._push_undo() # état initial + self._refresh_i18n() # ------------------------------------------------------------------ # Construction UI @@ -559,6 +566,10 @@ def _on_bcasl_enabled_toggled(self, checked: bool) -> None: else: self._tabs.setToolTip("") + def closeEvent(self, event) -> None: + self._unregister_language_refresh() + super().closeEvent(event) + def _populate_sections(self) -> None: """Grouper les plugins par section et les insérer.""" plugins_raw = ( @@ -677,13 +688,73 @@ def _build_plugin_config_tabs(self) -> None: widget = tab_res if widget is None: continue + try: + if hasattr(widget, "setObjectName"): + widget.setObjectName(f"plugin_{pid}") + if hasattr(widget, "setProperty"): + widget.setProperty("i18n_domain", pid) + except Exception: + pass if not title: title = getattr(getattr(plugin, "meta", None), "name", None) or pid self._tabs.addTab(widget, str(title)) - self._plugin_ui_state[pid] = {"config": base_cfg, "on_save": on_save} + self._plugin_ui_state[pid] = { + "config": base_cfg, + "on_save": on_save, + "widget": widget, + "title_default": str(title), + } except Exception: continue + def _register_language_refresh(self) -> None: + try: + if hasattr(self._gui, "register_language_refresh"): + self._gui.register_language_refresh(self._language_refresh_cb) + except Exception: + pass + + def _unregister_language_refresh(self) -> None: + try: + if hasattr(self._gui, "unregister_language_refresh"): + self._gui.unregister_language_refresh(self._language_refresh_cb) + except Exception: + pass + + def _refresh_i18n(self) -> None: + try: + self.setWindowTitle(self._gui.tr("BCASL Pipeline", "BCASL Pipeline")) + except Exception: + pass + try: + if hasattr(self, "_chk_bcasl_enabled"): + self._chk_bcasl_enabled.setText( + self._gui.tr("Activer BCASL", "Enable BCASL") + ) + except Exception: + pass + try: + if hasattr(self, "_tabs"): + self._tabs.setTabText(0, self._gui.tr("Pipeline", "Pipeline")) + except Exception: + pass + for pid, state in self._plugin_ui_state.items(): + widget = state.get("widget") + if widget is not None: + try: + refresh_widget_translations(widget, pid) + except Exception: + pass + idx = self._tabs.indexOf(widget) if widget is not None else -1 + if idx >= 0: + title_default = str(state.get("title_default", pid)) + try: + self._tabs.setTabText( + idx, translate(pid, "tab_title", title_default) + ) + except Exception: + pass + # ------------------------------------------------------------------ # Raccourcis clavier # ------------------------------------------------------------------ diff --git a/pycompiler_ark/Ui/Gui/IdeLikeGui/connections.py b/pycompiler_ark/Ui/Gui/IdeLikeGui/connections.py index 389268b..4d78e37 100644 --- a/pycompiler_ark/Ui/Gui/IdeLikeGui/connections.py +++ b/pycompiler_ark/Ui/Gui/IdeLikeGui/connections.py @@ -48,7 +48,6 @@ _apply_initial_theme, _auto_resize_for_screen, _connect_dialogs_to_app, - _declare_i18n, ) from pycompiler_ark.Ui.Gui.UiConnection import ( _connect_signals as _connect_classic_signals, @@ -59,6 +58,7 @@ show_theme_dialog, themed_svg_icon, ) +from pycompiler_ark.Ui.i18n import translate def _prime_expected_attrs(self) -> None: @@ -220,34 +220,6 @@ def _find(cls, name: str): ) except Exception: self.status_hint = None - _declare_i18n(self.btn_select_folder, i18n_text_key="select_folder", i18n_tooltip_key="tt_select_folder") - _declare_i18n(self.venv_button, i18n_text_key="venv_button", i18n_tooltip_key="tt_venv_button") - _declare_i18n(self.venv_label, i18n_text_key="venv_label", i18n_text_system_key="venv_label_system", i18n_system_attr="use_system_python") - _declare_i18n(self.label_folder, i18n_text_key="label_folder") - _declare_i18n(self.label_workspace_status, i18n_text_key="label_workspace_status", i18n_none_key="label_workspace_status_none", i18n_format_attr="workspace_dir") - _declare_i18n(self.label_workspace_section, i18n_text_key="label_workspace_section") - _declare_i18n(self.label_files_section, i18n_text_key="label_files_section") - _declare_i18n(self.label_tools, i18n_text_key="label_tools") - _declare_i18n(self.label_options_section, i18n_text_key="label_options_section") - _declare_i18n(self.label_logs_section, i18n_text_key="label_logs_section") - _declare_i18n(self.label_progress, i18n_text_key="label_progress") - _declare_i18n(self.btn_select_files, i18n_text_key="select_files", i18n_tooltip_key="tt_select_files") - _declare_i18n(self.btn_remove_file, i18n_text_key="btn_remove_file", i18n_tooltip_key="tt_remove_file") - _declare_i18n(self.btn_clear_workspace, i18n_text_key="btn_clear_workspace", i18n_tooltip_key="tt_clear_workspace") - _declare_i18n(self.btn_bc_loader, i18n_text_key="bc_loader", i18n_tooltip_key="tt_bc_loader") - _declare_i18n(self.compile_btn, i18n_text_key="build_all", i18n_tooltip_key="tt_build_all") - _declare_i18n(self.cancel_btn, i18n_text_key="cancel_all", i18n_tooltip_key="tt_cancel_all") - _declare_i18n(self.btn_help, i18n_text_key="help", i18n_tooltip_key="tt_help") - _declare_i18n(self.btn_suggest_deps, i18n_text_key="suggest_deps", i18n_tooltip_key="tt_suggest_deps") - _declare_i18n(self.activity_btn_deps, i18n_tooltip_key="tt_suggest_deps") - _declare_i18n(self.btn_bc_loader, i18n_tooltip_key="tt_bc_loader") - _declare_i18n(self.btn_acasl_loader, i18n_tooltip_key="tt_bc_loader") - _declare_i18n(self.btn_show_stats, i18n_text_key="show_stats", i18n_tooltip_key="tt_show_stats") - _declare_i18n(self.advanced_cfg_btn, i18n_text_key="advanced_config") - _declare_i18n(self.select_lang, i18n_text_key="choose_language_button", i18n_text_system_key="choose_language_system_button", i18n_system_attr="language_pref", i18n_tooltip_key="tt_select_lang") - _declare_i18n(self.select_theme, i18n_text_key="choose_theme_button", i18n_text_system_key="choose_theme_system_button", i18n_system_attr="theme", i18n_tooltip_key="tt_select_theme") - _declare_i18n(self.status_hint, i18n_text_key="status_ready") - _declare_i18n(self.file_filter_input, i18n_placeholder_key="file_filter_placeholder") _setup_status_bar(self) @@ -344,104 +316,98 @@ def _setup_more_tools_menu(self) -> None: return try: - _declare_i18n(more_btn, i18n_tooltip_key="tt_more_actions") + more_btn.setToolTip(translate(self, "tt_more_actions", more_btn.toolTip())) menu = QMenu(more_btn) self._ide_more_tools_menu = menu - act_workspace = QAction(menu) + act_workspace = QAction(translate(self, "action_select_workspace", "Select Workspace"), menu) + act_workspace.setObjectName("action_select_workspace") act_workspace.triggered.connect( lambda: getattr(self, "select_workspace", lambda: None)() ) - _declare_i18n(act_workspace, i18n_text_key="action_select_workspace") menu.addAction(act_workspace) - act_init = QAction(menu) + act_init = QAction(translate(self, "action_init_project", "Initialise Project"), menu) + act_init.setObjectName("action_init_project") act_init.triggered.connect( lambda: getattr(self, "open_init_workspace_dialog", lambda: None)() ) - _declare_i18n(act_init, i18n_text_key="action_init_project") menu.addAction(act_init) - act_venv = QAction(menu) + act_venv = QAction(translate(self, "action_select_venv", "Select Venv"), menu) + act_venv.setObjectName("action_select_venv") act_venv.triggered.connect( lambda: getattr(self, "select_venv_manually", lambda: None)() ) - _declare_i18n(act_venv, i18n_text_key="action_select_venv") menu.addAction(act_venv) - act_add_files = QAction(menu) + act_add_files = QAction(translate(self, "action_add_files", "Add Files"), menu) + act_add_files.setObjectName("action_add_files") act_add_files.triggered.connect( lambda: getattr(self, "select_files_manually", lambda: None)() ) - _declare_i18n(act_add_files, i18n_text_key="action_add_files") menu.addAction(act_add_files) - act_clear_workspace = QAction(menu) + act_clear_workspace = QAction(translate(self, "btn_clear_workspace", "Clear Workspace"), menu) + act_clear_workspace.setObjectName("btn_clear_workspace") act_clear_workspace.triggered.connect( lambda: getattr(self, "clear_workspace", lambda: None)() ) - _declare_i18n(act_clear_workspace, i18n_text_key="btn_clear_workspace") menu.addAction(act_clear_workspace) - act_stats = QAction(menu) + act_stats = QAction(translate(self, "show_stats", "Show Stats"), menu) + act_stats.setObjectName("show_stats") act_stats.triggered.connect( lambda: getattr(self, "show_statistics", lambda: None)() ) - _declare_i18n(act_stats, i18n_text_key="show_stats") menu.addAction(act_stats) menu.addSeparator() - act_language = QAction(menu) + act_language = QAction( + translate(self, "choose_language_button", "Language"), menu + ) + act_language.setObjectName("act_language") act_language.triggered.connect( lambda: getattr(self, "show_language_dialog", lambda: None)() ) - _declare_i18n( - act_language, - i18n_text_key="choose_language_button", - i18n_text_system_key="choose_language_system_button", - i18n_system_attr="language_pref", - ) menu.addAction(act_language) - act_theme = QAction(menu) + act_theme = QAction(translate(self, "choose_theme_button", "Theme"), menu) + act_theme.setObjectName("act_theme") act_theme.triggered.connect(lambda: _open_theme_dialog(self)) - _declare_i18n( - act_theme, - i18n_text_key="choose_theme_button", - i18n_text_system_key="choose_theme_system_button", - i18n_system_attr="theme", - ) menu.addAction(act_theme) menu.addSeparator() - act_advanced = QAction(menu) + act_advanced = QAction(translate(self, "advanced_config", "Advanced Config"), menu) + act_advanced.setObjectName("advanced_config") act_advanced.triggered.connect( lambda: getattr(self, "open_advanced_config_editor", lambda: None)() ) - _declare_i18n(act_advanced, i18n_text_key="advanced_config") menu.addAction(act_advanced) - act_lock = QAction(menu) + act_lock = QAction(translate(self, "lock_manager", "Lock Manager"), menu) + act_lock.setObjectName("lock_manager") act_lock.triggered.connect( lambda: getattr(self, "open_lock_dialog", lambda: None)() ) - _declare_i18n(act_lock, i18n_text_key="lock_manager") menu.addAction(act_lock) - act_save_engines = QAction(menu) + act_save_engines = QAction( + translate(self, "save_engine_configs", "Save Engine Configs"), menu + ) + act_save_engines.setObjectName("save_engine_configs") act_save_engines.triggered.connect( lambda: getattr(self, "save_all_engine_configs", lambda: None)() ) - _declare_i18n(act_save_engines, i18n_text_key="save_engine_configs") menu.addAction(act_save_engines) - act_help = QAction(menu) + act_help = QAction(translate(self, "help", "Help"), menu) + act_help.setObjectName("help") act_help.triggered.connect( lambda: getattr(self, "show_help_dialog", lambda: None)() ) - _declare_i18n(act_help, i18n_text_key="help") menu.addAction(act_help) self._ide_more_menu_actions = { @@ -640,7 +606,7 @@ def _setup_status_bar(self) -> None: try: self.status_hint = QLabel("Ready") self.status_hint.setObjectName("status_hint") - _declare_i18n(self.status_hint, i18n_text_key="status_ready") + self.status_hint.setText(translate(self, "status_ready", "Ready")) self.statusbar.addPermanentWidget(self.status_hint, 1) except Exception: pass diff --git a/pycompiler_ark/Ui/Gui/UiConnection.py b/pycompiler_ark/Ui/Gui/UiConnection.py index 6144ea7..cc28451 100644 --- a/pycompiler_ark/Ui/Gui/UiConnection.py +++ b/pycompiler_ark/Ui/Gui/UiConnection.py @@ -217,16 +217,6 @@ def _setup_sidebar_logo(self) -> None: logo_label.setScaledContents(True) -def _declare_i18n(widget, **props) -> None: - if widget is None: - return - for key, value in props.items(): - try: - widget.setProperty(key, value) - except Exception: - pass - - def _auto_resize_for_screen(self) -> None: """Resize and center the window to fit the current screen safely.""" try: @@ -481,35 +471,6 @@ def _find(cls, name: str): self.btn_acasl_loader.hide() self.btn_acasl_loader.setEnabled(False) - for widget, props in ( - (self.btn_select_folder, {"i18n_text_key": "select_folder", "i18n_tooltip_key": "tt_select_folder"}), - (self.btn_select_files, {"i18n_text_key": "select_files", "i18n_tooltip_key": "tt_select_files"}), - (self.compile_btn, {"i18n_text_key": "build_all", "i18n_tooltip_key": "tt_build_all"}), - (self.cancel_btn, {"i18n_text_key": "cancel_all", "i18n_tooltip_key": "tt_cancel_all"}), - (self.btn_suggest_deps, {"i18n_text_key": "suggest_deps", "i18n_tooltip_key": "tt_suggest_deps"}), - (self.btn_help, {"i18n_text_key": "help", "i18n_tooltip_key": "tt_help"}), - (self.btn_show_stats, {"i18n_text_key": "show_stats", "i18n_tooltip_key": "tt_show_stats"}), - (self.advanced_cfg_btn, {"i18n_text_key": "advanced_config"}), - (self.btn_remove_file, {"i18n_text_key": "btn_remove_file", "i18n_tooltip_key": "tt_remove_file"}), - (self.btn_clear_workspace, {"i18n_text_key": "btn_clear_workspace", "i18n_tooltip_key": "tt_clear_workspace"}), - (self.btn_bc_loader, {"i18n_text_key": "bc_loader", "i18n_tooltip_key": "tt_bc_loader"}), - (self.venv_button, {"i18n_text_key": "venv_button", "i18n_tooltip_key": "tt_venv_button"}), - (self.label_workspace_section, {"i18n_text_key": "label_workspace_section"}), - (self.venv_label, {"i18n_text_key": "venv_label", "i18n_text_system_key": "venv_label_system", "i18n_system_attr": "use_system_python"}), - (self.label_folder, {"i18n_text_key": "label_folder"}), - (self.label_files_section, {"i18n_text_key": "label_files_section"}), - (self.label_tools, {"i18n_text_key": "label_tools"}), - (self.label_options_section, {"i18n_text_key": "label_options_section"}), - (self.label_logs_section, {"i18n_text_key": "label_logs_section"}), - (self.label_progress, {"i18n_text_key": "label_progress"}), - (self.select_lang, {"i18n_text_key": "choose_language_button", "i18n_text_system_key": "choose_language_system_button", "i18n_system_attr": "language_pref", "i18n_tooltip_key": "tt_select_lang"}), - (self.select_theme, {"i18n_text_key": "choose_theme_button", "i18n_text_system_key": "choose_theme_system_button", "i18n_system_attr": "theme", "i18n_tooltip_key": "tt_select_theme"}), - (self.label_workspace_status, {"i18n_text_key": "label_workspace_status", "i18n_none_key": "label_workspace_status_none", "i18n_format_attr": "workspace_dir"}), - (self.file_filter_input, {"i18n_placeholder_key": "file_filter_placeholder"}), - ): - _declare_i18n(widget, **props) - - def _setup_compiler_tabs(self) -> None: """Initialize compiler tabs and bind available engines.""" from PySide6.QtWidgets import QTabWidget, QWidget diff --git a/pycompiler_ark/Ui/Gui/UiFeatures.py b/pycompiler_ark/Ui/Gui/UiFeatures.py index 825d99c..b3ec944 100644 --- a/pycompiler_ark/Ui/Gui/UiFeatures.py +++ b/pycompiler_ark/Ui/Gui/UiFeatures.py @@ -94,19 +94,10 @@ def select_nuitka_icon(self): def show_help_dialog(self): """Show the localized help dialog.""" try: - from pycompiler_ark.Ui.i18n import FALLBACK_EN, is_french_language - - if is_french_language(self): - tr = getattr(self, "_tr", None) - if isinstance(tr, dict): - help_title = tr.get("help_title", "Aide") - help_text = tr.get("help_text", FALLBACK_EN.get("help_text", "")) - else: - help_title = "Aide" - help_text = FALLBACK_EN.get("help_text", "") - else: - help_title = FALLBACK_EN.get("help_title", "Help") - help_text = FALLBACK_EN.get("help_text", "") + from pycompiler_ark.Ui.i18n import translate + + help_title = translate(self, "help_title", "Help") + help_text = translate(self, "help_text", "") except Exception: help_title = "Help" help_text = "" @@ -631,6 +622,17 @@ def register_language_refresh(self, callback: Callable) -> None: except Exception: pass + def unregister_language_refresh(self, callback: Callable) -> None: + """Unregister a previously registered language refresh callback.""" + try: + callbacks = getattr(self, "_language_refresh_callbacks", None) + if not callbacks: + return + if callback in callbacks: + callbacks.remove(callback) + except Exception: + pass + def log_i18n(self, fr: str, en: str) -> None: """Append a localized message to the log.""" try: diff --git a/pycompiler_ark/Ui/i18n.py b/pycompiler_ark/Ui/i18n.py index a53526b..734423b 100644 --- a/pycompiler_ark/Ui/i18n.py +++ b/pycompiler_ark/Ui/i18n.py @@ -223,6 +223,16 @@ def translate(domain: object | None, key: str, default: str | None = None) -> st return default if default is not None else str(key) +def _object_name(obj: Any) -> str: + try: + name = getattr(obj, "objectName", lambda: "")() + if isinstance(name, str): + return name.strip() + except Exception: + pass + return "" + + # Public async Plugins with real-time caching and error handling @@ -767,9 +777,6 @@ def i18n_synchro(self, lang_pref: str, tr: dict[str, Any]) -> str: _apply_main_app_translations(self, tr) - for cb in getattr(self, "_language_refresh_callbacks", []) or []: - cb() - from pycompiler_ark.Ui.Gui.IdeLikeGui.connections import ( _retranslate_ide_like_actions, ) @@ -786,6 +793,9 @@ def i18n_synchro(self, lang_pref: str, tr: dict[str, Any]) -> str: sdk_apply_tr(self, tr) + for cb in getattr(self, "_language_refresh_callbacks", []) or []: + cb() + if hasattr(self, "save_preferences"): self.save_preferences() @@ -834,6 +844,86 @@ def _prop(obj: Any, name: str) -> Any: def _is_system_value(value: Any) -> bool: return str(value).strip().lower() == "system" + 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) + + 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 "" + + 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" + return "" + def _iter_objects(root: Any): stack = [root] seen: set[int] = set() @@ -881,7 +971,8 @@ def _apply_tab_text(obj: Any, key: str, default: str | None = None) -> None: parent = getattr(parent, "parent", lambda: None)() for obj in _iter_objects(self): - text_key = _prop(obj, "i18n_text_key") + name = _object_name(obj) + text_key = _prop(obj, "i18n_text_key") or _binding_for_name(name) if text_key: system_key = _prop(obj, "i18n_text_system_key") system_attr = _prop(obj, "i18n_system_attr") @@ -890,6 +981,20 @@ 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) @@ -907,19 +1012,19 @@ def _apply_tab_text(obj: Any, key: str, default: str | None = None) -> None: _apply_text(obj, chosen_key, current if isinstance(current, str) else None) - tooltip_key = _prop(obj, "i18n_tooltip_key") + tooltip_key = _prop(obj, "i18n_tooltip_key") or _tooltip_for_name(name) if tooltip_key: current = obj.toolTip() if hasattr(obj, "toolTip") else None _apply_tooltip(obj, str(tooltip_key), current if isinstance(current, str) else None) - placeholder_key = _prop(obj, "i18n_placeholder_key") + placeholder_key = _prop(obj, "i18n_placeholder_key") or _placeholder_for_name(name) if placeholder_key: current = obj.placeholderText() if hasattr(obj, "placeholderText") else None _apply_placeholder( obj, str(placeholder_key), current if isinstance(current, str) else None ) - tab_key = _prop(obj, "i18n_tab_key") + tab_key = _prop(obj, "i18n_tab_key") or (name if name.startswith("tab_") else "") if tab_key: current = obj.text() if hasattr(obj, "text") else None _apply_tab_text(obj, str(tab_key), current if isinstance(current, str) else None) diff --git a/pycompiler_ark/engines/cx_freeze/__init__.py b/pycompiler_ark/engines/cx_freeze/__init__.py index 3423576..dff17f2 100644 --- a/pycompiler_ark/engines/cx_freeze/__init__.py +++ b/pycompiler_ark/engines/cx_freeze/__init__.py @@ -36,16 +36,6 @@ from pycompiler_ark.engine_sdk.utils import log_with_level -def _declare_i18n(widget, **props) -> None: - if widget is None: - return - for key, value in props.items(): - try: - widget.setProperty(key, value) - except Exception: - pass - - @engine_register class CXFreezeEngine(CompilerEngine): """ @@ -188,8 +178,8 @@ def create_tab(self, gui): # Create the tab widget tab = QWidget() - tab.setObjectName("tab_cx_freeze_dynamic") - _declare_i18n(tab, i18n_tab_key="tab_label") + tab.setObjectName(getattr(self, "name", self.id)) + tab.setProperty("i18n_tab_key", "tab_label") # Create main layout layout = QVBoxLayout(tab) @@ -197,7 +187,7 @@ def create_tab(self, gui): layout.setContentsMargins(8, 8, 8, 8) build_group = QGroupBox(translate(self.id, "build_group", "Build"), tab) - _declare_i18n(build_group, i18n_text_key="build_group") + build_group.setObjectName("build_group") build_layout = QFormLayout() build_layout.setSpacing(6) @@ -205,8 +195,7 @@ def create_tab(self, gui): self._cx_windowed = QCheckBox( translate(self.id, "windowed_checkbox", "No console") ) - self._cx_windowed.setObjectName("cx_windowed_dynamic") - _declare_i18n(self._cx_windowed, i18n_text_key="windowed_checkbox", i18n_tooltip_key="tt_windowed") + self._cx_windowed.setObjectName("windowed_checkbox") self._cx_windowed.setToolTip( translate(self.id, "tt_windowed", "Disable the console window.") ) @@ -217,15 +206,14 @@ def create_tab(self, gui): diagnostics_group = QGroupBox( translate(self.id, "diagnostics_group", "Diagnostics"), tab ) - _declare_i18n(diagnostics_group, i18n_text_key="diagnostics_group") + diagnostics_group.setObjectName("diagnostics_group") diagnostics_layout = QVBoxLayout() diagnostics_layout.setSpacing(4) self._cx_debug = QCheckBox( translate(self.id, "debug_checkbox", "Debug") ) - self._cx_debug.setObjectName("cx_debug_dynamic") - _declare_i18n(self._cx_debug, i18n_text_key="debug_checkbox", i18n_tooltip_key="tt_debug") + self._cx_debug.setObjectName("debug_checkbox") self._cx_debug.setToolTip( translate(self.id, "tt_debug", "Enable debug output.") ) @@ -234,8 +222,7 @@ def create_tab(self, gui): self._cx_verbose = QCheckBox( translate(self.id, "verbose_checkbox", "Verbose") ) - self._cx_verbose.setObjectName("cx_verbose_dynamic") - _declare_i18n(self._cx_verbose, i18n_text_key="verbose_checkbox", i18n_tooltip_key="tt_verbose") + self._cx_verbose.setObjectName("verbose_checkbox") self._cx_verbose.setToolTip( translate(self.id, "tt_verbose", "Enable verbose output.") ) @@ -250,7 +237,7 @@ def create_tab(self, gui): ), tab, ) - _declare_i18n(hint, i18n_text_key="hint_text") + hint.setObjectName("hint_text") hint.setStyleSheet("color: #888; font-size: 11px;") hint.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) @@ -263,7 +250,7 @@ def create_tab(self, gui): self._gui = gui self._tab_widget = tab - return tab, translate(self.id, "tab_label", "CX_Freeze") + return tab, translate(self.id, "tab_label", getattr(self, "name", self.id)) except Exception as e: try: diff --git a/pycompiler_ark/engines/nuitka/__init__.py b/pycompiler_ark/engines/nuitka/__init__.py index 6dec765..e125901 100644 --- a/pycompiler_ark/engines/nuitka/__init__.py +++ b/pycompiler_ark/engines/nuitka/__init__.py @@ -35,16 +35,6 @@ from pycompiler_ark.engine_sdk.utils import log_with_level -def _declare_i18n(widget, **props) -> None: - if widget is None: - return - for key, value in props.items(): - try: - widget.setProperty(key, value) - except Exception: - pass - - @engine_register class NuitkaEngine(CompilerEngine): """ @@ -208,8 +198,8 @@ def create_tab(self, gui): # Create the tab widget tab = QWidget() - tab.setObjectName("tab_nuitka_dynamic") - _declare_i18n(tab, i18n_tab_key="tab_label") + tab.setObjectName(getattr(self, "name", self.id)) + tab.setProperty("i18n_tab_key", "tab_label") # Create main layout layout = QVBoxLayout(tab) @@ -217,7 +207,7 @@ def create_tab(self, gui): layout.setContentsMargins(8, 8, 8, 8) build_group = QGroupBox(translate(self.id, "build_group", "Build"), tab) - _declare_i18n(build_group, i18n_text_key="build_group") + build_group.setObjectName("build_group") build_layout = QFormLayout() build_layout.setSpacing(6) @@ -225,28 +215,25 @@ def create_tab(self, gui): self._nuitka_onefile = QCheckBox( translate(self.id, "onefile_checkbox", "Onefile (--onefile)") ) - self._nuitka_onefile.setObjectName("nuitka_onefile_dynamic") - _declare_i18n(self._nuitka_onefile, i18n_text_key="onefile_checkbox") + self._nuitka_onefile.setObjectName("onefile_checkbox") mode_label = QLabel(translate(self.id, "mode_label", "Mode:"), tab) - _declare_i18n(mode_label, i18n_text_key="mode_label") + mode_label.setObjectName("mode_label") build_layout.addRow(mode_label, self._nuitka_onefile) # Standalone option self._nuitka_standalone = QCheckBox( translate(self.id, "standalone_checkbox", "Standalone (--standalone)") ) - self._nuitka_standalone.setObjectName("nuitka_standalone_dynamic") - _declare_i18n(self._nuitka_standalone, i18n_text_key="standalone_checkbox") + self._nuitka_standalone.setObjectName("standalone_checkbox") type_label = QLabel(translate(self.id, "type_label", "Type:"), tab) - _declare_i18n(type_label, i18n_text_key="type_label") + type_label.setObjectName("type_label") build_layout.addRow(type_label, self._nuitka_standalone) # Disable console option self._nuitka_disable_console = QCheckBox( translate(self.id, "disable_console_checkbox", "Disable console") ) - self._nuitka_disable_console.setObjectName("nuitka_disable_console_dynamic") - _declare_i18n(self._nuitka_disable_console, i18n_text_key="disable_console_checkbox", i18n_tooltip_key="tt_disable_console") + self._nuitka_disable_console.setObjectName("disable_console_checkbox") self._nuitka_disable_console.setToolTip( translate( self.id, @@ -255,7 +242,7 @@ def create_tab(self, gui): ) ) console_label = QLabel(translate(self.id, "console_label", "Console:"), tab) - _declare_i18n(console_label, i18n_text_key="console_label") + console_label.setObjectName("console_label") build_layout.addRow(console_label, self._nuitka_disable_console) build_group.setLayout(build_layout) @@ -267,7 +254,7 @@ def create_tab(self, gui): ), tab, ) - _declare_i18n(hint, i18n_text_key="hint_text") + hint.setObjectName("hint_text") hint.setStyleSheet("color: #888; font-size: 11px;") hint.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) @@ -279,7 +266,7 @@ def create_tab(self, gui): self._gui = gui self._tab_widget = tab - return tab, translate(self.id, "tab_label", "Nuitka") + return tab, translate(self.id, "tab_label", getattr(self, "name", self.id)) except Exception as e: try: diff --git a/pycompiler_ark/engines/pyinstaller/__init__.py b/pycompiler_ark/engines/pyinstaller/__init__.py index 0e19f8b..4812ef2 100644 --- a/pycompiler_ark/engines/pyinstaller/__init__.py +++ b/pycompiler_ark/engines/pyinstaller/__init__.py @@ -36,16 +36,6 @@ from pycompiler_ark.engine_sdk.utils import log_with_level -def _declare_i18n(widget, **props) -> None: - if widget is None: - return - for key, value in props.items(): - try: - widget.setProperty(key, value) - except Exception: - pass - - @engine_register class PyInstallerEngine(CompilerEngine): """ @@ -189,8 +179,8 @@ def create_tab(self, gui): # Create the tab widget tab = QWidget() - tab.setObjectName("tab_pyinstaller_dynamic") - _declare_i18n(tab, i18n_tab_key="tab_label") + tab.setObjectName(getattr(self, "name", self.id)) + tab.setProperty("i18n_tab_key", "tab_label") # Create main layout layout = QVBoxLayout(tab) @@ -198,7 +188,7 @@ def create_tab(self, gui): layout.setContentsMargins(8, 8, 8, 8) build_group = QGroupBox(translate(self.id, "build_group", "Build"), tab) - _declare_i18n(build_group, i18n_text_key="build_group") + build_group.setObjectName("build_group") build_layout = QFormLayout() build_layout.setSpacing(6) @@ -206,18 +196,16 @@ def create_tab(self, gui): self._opt_onefile = QCheckBox( translate(self.id, "onefile_checkbox", "Onefile") ) - self._opt_onefile.setObjectName("opt_onefile_dynamic") - _declare_i18n(self._opt_onefile, i18n_text_key="onefile_checkbox") + self._opt_onefile.setObjectName("onefile_checkbox") mode_label = QLabel(translate(self.id, "mode_label", "Mode:"), tab) - _declare_i18n(mode_label, i18n_text_key="mode_label") + mode_label.setObjectName("mode_label") build_layout.addRow(mode_label, self._opt_onefile) # Windowed option self._opt_windowed = QCheckBox( translate(self.id, "windowed_checkbox", "Windowed") ) - self._opt_windowed.setObjectName("opt_windowed_dynamic") - _declare_i18n(self._opt_windowed, i18n_text_key="windowed_checkbox") + self._opt_windowed.setObjectName("windowed_checkbox") build_layout.addRow(self._opt_windowed) build_group.setLayout(build_layout) @@ -230,7 +218,7 @@ def create_tab(self, gui): ), tab, ) - _declare_i18n(hint, i18n_text_key="hint_text") + hint.setObjectName("hint_text") hint.setStyleSheet("color: #888; font-size: 11px;") hint.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) @@ -242,7 +230,7 @@ def create_tab(self, gui): self._gui = gui self._tab_widget = tab - return tab, translate(self.id, "tab_label", "PyInstaller") + return tab, translate(self.id, "tab_label", getattr(self, "name", self.id)) except Exception as e: try: diff --git a/requirements.txt b/requirements.txt index 1902603..0ba2733 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,33 +1,22 @@ # PyCompiler ARK (ARK++) — Runtime requirements # Core GUI framework (Qt for Python) -# Matrix: -# - Python 3.11 => keep PySide6/shiboken6 < 6.5 as requested -# - Python 3.12 => stay on the Qt 6.7 line for better stability -# - Python >= 3.13 => PySide6/shiboken6 >= 6.8 (adds 3.13 support) -# Note: -# - I found official support for Python 3.11 on the 6.4 line. -# - I did not find a reliable official source proving that a given PyPI wheel avoids SSE/SSE2/SSE4.x requirements. -# - So the < 6.5 pin is a compatibility choice, not a hard guarantee for very old CPUs. -# - On such machines, prefer distro packages or a source build of PySide6/shiboken6. -PySide6>=6.8,<6.11; python_version >= "3.13" -shiboken6>=6.8,<6.11; python_version >= "3.13" -PySide6>=6.6,<6.8; python_version >= "3.12" and python_version < "3.13" -shiboken6>=6.6,<6.8; python_version >= "3.12" and python_version < "3.13" -PySide6>=6.4,<6.5; python_version >= "3.11" and python_version < "3.12" -shiboken6>=6.4,<6.5; python_version >= "3.11" and python_version < "3.12" +Pyside6 +shiboken6 +PySide6 +shiboken6 +PySide6 +shiboken6 # System utilities psutil -# Configuration and data formats -PyYAML>=5.4.1,<7.0.0 -# tomli only needed on Python < 3.11 (tomllib builtin on 3.11+) -tomli>=2.0.1,<3.0.0; python_version < "3.11" +# Data +PyYAML +tomli jsonschema # CLI - click rich colorama \ No newline at end of file diff --git a/todo.md b/todo.md index d96f033..5b4fb10 100644 --- a/todo.md +++ b/todo.md @@ -1,2 +1,5 @@ # refactor -- [] refactor chaque module pour plus de lisibilité et reduction du code pour une meilleur maintenance. \ No newline at end of file +- [] 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