Files
arma-modlist-tools/gui/views/logs.py
Tran G. (Revernomad) Khoa 903cd366e2 feat: add Vietnamese localization to GUI
Introduces a two-language (EN/VI) i18n system with hot-swap support.
All ~160 user-facing strings are centralised in gui/locales.py; views
retranslate in-place on language switch without restarting the app.

- gui/locales.py: new file — _EN/_VI dicts, t() lookup, set_language(),
  get_language(); assert guard ensures EN/VI key parity
- gui/app.py: switch_language(), _apply_startup_language(),
  _save_language_pref(), _rebuild_nav_labels(); language stored in
  config.json under ui.language; pipeline step headers and run_tool
  status lines translated
- gui/views/settings.py: Language dropdown card (English / Tiếng Việt)
- gui/views/dashboard.py: all strings via t(); static header widgets
  stored and retranslated in refresh()
- gui/views/mods.py: all strings via t(); _STATUS dict built at call
  time so server status labels update on language switch
- gui/views/tools.py: all strings via _translatable registry; tab names
  and segmented-button values kept in English (CTkTabview constraint)
- gui/views/logs.py: title + Copy/Clear buttons stored, retranslated
- gui/wizard.py: all 3 pages fully translated
- docs/huong-dan-su-dung.md: full Vietnamese user guide
- CLAUDE.md: documents localization architecture and constraints
2026-04-08 16:58:41 +07:00

75 lines
3.0 KiB
Python

from __future__ import annotations
from typing import TYPE_CHECKING
import customtkinter as ctk
from gui._constants import COLOR_ERROR
from gui.locales import t
from gui.views.base import BaseView
if TYPE_CHECKING:
from gui.app import ArmaModManagerApp
class LogsView(BaseView):
"""
Monospace textbox showing captured stdout/stderr from pipeline and tools.
The app's poll loop appends text by calling append() directly on this view.
Log content persists across navigation — the textbox is built once in build()
and never recreated.
"""
def build(self) -> None:
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(1, weight=1)
# ── Header ────────────────────────────────────────────────────────────
hdr = ctk.CTkFrame(self, fg_color="transparent")
hdr.grid(row=0, column=0, sticky="ew", padx=24, pady=(20, 8))
self._title_lbl = ctk.CTkLabel(hdr, text=t("logs.title"),
font=ctk.CTkFont(size=22, weight="bold"))
self._title_lbl.pack(side="left")
btn_row = ctk.CTkFrame(hdr, fg_color="transparent")
btn_row.pack(side="right")
self._copy_btn = ctk.CTkButton(btn_row, text=t("logs.copy_btn"), width=72,
command=self._copy)
self._copy_btn.pack(side="left", padx=4)
self._clear_btn = ctk.CTkButton(btn_row, text=t("logs.clear_btn"), width=72,
fg_color=COLOR_ERROR, hover_color="#c62828",
command=self._clear)
self._clear_btn.pack(side="left")
# ── Log textbox (persistent) ──────────────────────────────────────────
self._log_box = ctk.CTkTextbox(
self, state="disabled",
font=ctk.CTkFont(family="Consolas", size=12))
self._log_box.grid(row=1, column=0, sticky="nsew", padx=24, pady=(0, 12))
def refresh(self) -> None:
# Retranslate header widgets (log content intentionally preserved)
self._title_lbl.configure(text=t("logs.title"))
self._copy_btn.configure(text=t("logs.copy_btn"))
self._clear_btn.configure(text=t("logs.clear_btn"))
def append(self, text: str) -> None:
"""Thread-safe-ish: called from the app's after() poll loop (main thread)."""
try:
self._log_box.configure(state="normal")
self._log_box.insert("end", text)
self._log_box.see("end")
self._log_box.configure(state="disabled")
except Exception:
pass
def _copy(self) -> None:
self.clipboard_clear()
self.clipboard_append(self._log_box.get("1.0", "end"))
def _clear(self) -> None:
self._log_box.configure(state="normal")
self._log_box.delete("1.0", "end")
self._log_box.configure(state="disabled")