Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 10 additions & 13 deletions docs/app_i18n.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**

Expand Down Expand Up @@ -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:

Expand All @@ -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
Expand All @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions docs/how_to_create_a_bc_plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)**

Expand Down Expand Up @@ -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**

Expand Down
6 changes: 5 additions & 1 deletion docs/how_to_create_an_engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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)**

Expand All @@ -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

Expand Down
75 changes: 62 additions & 13 deletions pycompiler_ark/Core/engine/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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"):
Expand Down Expand Up @@ -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")
Expand All @@ -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)

Expand All @@ -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(
Expand Down
9 changes: 8 additions & 1 deletion pycompiler_ark/Plugins/Cleaner/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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."""
Expand Down
1 change: 1 addition & 0 deletions pycompiler_ark/Plugins/Cleaner/languages/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)'
1 change: 1 addition & 0 deletions pycompiler_ark/Plugins/Cleaner/languages/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)'
61 changes: 61 additions & 0 deletions pycompiler_ark/Plugins/OutputCleaner/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions pycompiler_ark/Plugins/OutputCleaner/languages/en.yml
Original file line number Diff line number Diff line change
@@ -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."
Expand Down
4 changes: 4 additions & 0 deletions pycompiler_ark/Plugins/OutputCleaner/languages/fr.yml
Original file line number Diff line number Diff line change
@@ -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."
Expand Down
2 changes: 2 additions & 0 deletions pycompiler_ark/Plugins_SDK/GeneralContext/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
register_i18n_handler,
register_plugin_translations,
resolve_language_code,
refresh_widget_translations,
set_translations,
translate,
unregister_i18n_handler,
Expand All @@ -39,6 +40,7 @@
"normalize_language_code",
"register_i18n_handler",
"register_plugin_translations",
"refresh_widget_translations",
"resolve_language_code",
"set_translations",
"translate",
Expand Down
Loading