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
17 changes: 13 additions & 4 deletions je_editor/pyside_ui/browser/browser_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,20 @@

from PySide6.QtCore import QObject
from PySide6.QtGui import QCloseEvent
from PySide6.QtWebEngineCore import QWebEngineDownloadRequest
from PySide6.QtWebEngineWidgets import QWebEngineView

from je_editor.pyside_ui.browser.browser_download_window import BrowserDownloadWindow
from je_editor.utils.logging.loggin_instance import jeditor_logger
from je_editor.utils.browser.chromium_flags import quiet_chromium_logging

# 必須在 QtWebEngine 初始化之前設定,因此排在匯入它之前
# This has to be set before QtWebEngine initialises, so it comes before the import
quiet_chromium_logging()

from PySide6.QtWebEngineCore import QWebEngineDownloadRequest # noqa: E402
from PySide6.QtWebEngineWidgets import QWebEngineView # noqa: E402

from je_editor.pyside_ui.browser.browser_download_window import ( # noqa: E402
BrowserDownloadWindow
)
from je_editor.utils.logging.loggin_instance import jeditor_logger # noqa: E402


class BrowserView(QWebEngineView):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
# 匯入 Qt 動作與訊息框
# Import QAction and QMessageBox from PySide6
from PySide6.QtGui import QAction
from PySide6.QtWidgets import QMessageBox

# 匯入使用者設定字典,用來保存語言設定
# Import user settings dictionary for saving language preferences
Expand Down Expand Up @@ -78,6 +77,11 @@ def set_language(language: str, ui_we_want_to_set: EditorMain) -> None:
f"language: {language} "
f"ui_we_want_to_set: {ui_we_want_to_set}")

# 先留下舊字典:重新標示畫面時要靠它反查哪些字是翻譯來的
# Keep the old dictionary: relabelling needs it to tell which words on screen
# came from a translation and which are file names
previous_words = dict(language_wrapper.language_word_dict)

# 重設語言 (更新多語言字典)
# Reset language (update multi-language dictionary)
language_wrapper.reset_language(language)
Expand All @@ -86,8 +90,7 @@ def set_language(language: str, ui_we_want_to_set: EditorMain) -> None:
# Update user settings dictionary to persist language preference
user_setting_dict.update({"language": language})

# 顯示提示訊息,提醒使用者需要重新啟動程式
# Show a message box to remind user to restart the application
message_box = QMessageBox(ui_we_want_to_set)
message_box.setText(language_wrapper.language_word_dict.get("language_menu_bar_please_restart_messagebox"))
message_box.show()
# 立即重新標示整個介面,不需要重新啟動
# Relabel the whole interface at once, with no restart
from je_editor.pyside_ui.main_ui.retranslate import retranslate_ui
retranslate_ui(ui_we_want_to_set, previous_words)
14 changes: 14 additions & 0 deletions je_editor/pyside_ui/main_ui/outline_panel/outline_panel_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ def __init__(self, main_window=None) -> None:

self.refresh()

def retranslate(self) -> None:
"""
換語言後重新標示自己
Relabel after the language changes.
"""
word = language_wrapper.language_word_dict
self.refresh_button.setText(word.get("outline_panel_refresh"))
self.tree.setHeaderLabels([
word.get("outline_panel_col_symbol"),
word.get("outline_panel_col_kind"),
word.get("outline_panel_col_line"),
])
self.refresh()

def current_code_edit(self):
"""
取得目前分頁的程式碼編輯器
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,30 @@ def diagnostics(self) -> list[Diagnostic]:
"""取得面板目前顯示的診斷 / The diagnostics currently listed."""
return list(self._diagnostics)

def retranslate(self) -> None:
"""
換語言後重新標示自己
Relabel after the language changes.

面板記著目前的診斷,因此不能整個拆掉重建;改字之後重畫清單,狀態列的文字
也會跟著換成新語言。
The panel is holding the current diagnostics and so cannot be rebuilt from
scratch; relabelling and then redrawing the list also moves the status
text to the new language.
"""
word = language_wrapper.language_word_dict
self.refresh_button.setText(word.get("problems_panel_refresh"))
self.project_check.setText(word.get("problems_panel_whole_project"))
self.fix_button.setText(word.get("problems_panel_fix"))
self.severity_filter.setItemText(0, word.get("problems_panel_all_severities"))
self.result_tree.setHeaderLabels([
word.get("problems_panel_col_code"),
word.get("problems_panel_col_message"),
word.get("problems_panel_col_line"),
word.get("problems_panel_col_file"),
])
self._render_items()

def refresh(self) -> None:
"""
重新讀取診斷並重畫清單
Expand Down
111 changes: 111 additions & 0 deletions je_editor/pyside_ui/main_ui/retranslate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"""
換語言後把整個介面重新標示一次
Relabel the whole interface after the language changes.

原本換語言只會跳出「請重新啟動」——設定改了,但畫面上一個字也沒變。選單與工具列
是啟動時建好的,重建它們最省事也最完整;面板各自有狀態,因此由它們自己重新標示。
Changing the language used to do nothing but ask for a restart: the setting
moved and not one word on screen did. The menus and the toolbar are built at
startup and rebuilding them is both the simplest and the most complete way to
relabel them; the panels hold state, so each relabels itself.
"""
from __future__ import annotations

from typing import Dict

from je_editor.pyside_ui.main_ui.save_settings.shortcut_setting import reload_bound_shortcuts
from je_editor.utils.logging.loggin_instance import jeditor_logger
from je_editor.utils.multi_language.multi_language_wrapper import language_wrapper
from je_editor.utils.multi_language.retranslate_text import TITLE_KEYS, translated_again


def retranslate_ui(main_window, previous_words: Dict[str, str]) -> None:
"""
依目前語言重新標示整個介面
Relabel the whole interface in the language now in use.

:param main_window: 主編輯器視窗 / the main editor window
:param previous_words: 換語言之前的字典 / the dictionary from before the change
"""
jeditor_logger.info("retranslate.py retranslate_ui")
words = language_wrapper.language_word_dict
main_window.setWindowTitle(words.get("application_name", ""))
main_window.setToolTip(words.get("application_name", ""))
_rebuild_menu_bar(main_window)
_rebuild_toolbar(main_window)
_retranslate_tabs(main_window, previous_words, words)
_retranslate_docks(main_window, previous_words, words)
refresh = getattr(main_window, "refresh_status_bar", None)
if callable(refresh):
refresh()
# 舊選單與工具列的動作已經消失,把它們從快捷鍵表中清掉
# The old menu and toolbar actions are gone; drop them from the shortcut table
reload_bound_shortcuts()


def _rebuild_menu_bar(main_window) -> None:
"""重建選單列 / Build the menu bar again."""
from je_editor.pyside_ui.main_ui.menu.set_menu_bar import set_menu_bar
# setMenuBar 會接手並刪掉舊的那一個 / setMenuBar takes over and deletes the old one
set_menu_bar(main_window)


def _rebuild_toolbar(main_window) -> None:
"""
重建工具列
Build the toolbar again.

先等舊工具列的背景工作結束:git 分支掃描完成後要去填那個下拉選單,而下拉選單
正要被刪掉。不等的話 Qt 會直接讓程序中止。
The outgoing toolbar's background work is waited for first: the git branch
scan finishes by filling that combo box, and the combo box is about to be
deleted. Without the wait, Qt aborts the process.
"""
from je_editor.pyside_ui.main_ui.toolbar.toolbar_builder import (
build_toolbar, stop_background_threads
)
stop_background_threads()
old = getattr(main_window, "main_toolbar", None)
if old is not None:
main_window.removeToolBar(old)
old.deleteLater()
build_toolbar(main_window)


def _retranslate_tabs(main_window, previous: Dict[str, str], current: Dict[str, str]) -> None:
"""
重新標示分頁標題,檔名保持原樣
Relabel the tab titles, leaving file names alone.
"""
tab_widget = getattr(main_window, "tab_widget", None)
if tab_widget is None:
return
for index in range(tab_widget.count()):
tab_widget.setTabText(
index, translated_again(tab_widget.tabText(index), previous, current, TITLE_KEYS))
_retranslate_widget(tab_widget.widget(index))


def _retranslate_docks(main_window, previous: Dict[str, str], current: Dict[str, str]) -> None:
"""重新標示浮動視窗的標題與內容 / Relabel the docks and what they hold."""
from PySide6.QtWidgets import QDockWidget
for dock in main_window.findChildren(QDockWidget):
dock.setWindowTitle(
translated_again(dock.windowTitle(), previous, current, TITLE_KEYS))
_retranslate_widget(dock.widget())


def _retranslate_widget(widget) -> None:
"""
請一個元件重新標示自己
Ask a widget to relabel itself.

有 ``retranslate`` 就呼叫它。沒有的元件維持原本的字,直到重新開啟為止——這比
硬把它拆掉重建安全,因為那會連同它記著的狀態一起丟掉。
A widget with a ``retranslate`` is asked to. One without keeps its wording
until it is reopened, which is safer than tearing it down and rebuilding it:
that would throw away whatever state it is holding.
"""
retranslate = getattr(widget, "retranslate", None)
if callable(retranslate):
retranslate()
25 changes: 25 additions & 0 deletions je_editor/pyside_ui/main_ui/test_panel/test_panel_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,31 @@ def results(self) -> list[PytestResult]:
"""取得目前顯示的結果 / The results currently listed."""
return list(self._results)

def retranslate(self) -> None:
"""
換語言後重新標示自己
Relabel after the language changes.

面板記著上一次的測試結果,因此只換文字再重畫清單,不重建整個面板。
The panel is holding the last run's results, so it relabels and redraws
rather than being rebuilt.
"""
word = language_wrapper.language_word_dict
self.run_button.setText(word.get("test_panel_run"))
self.run_selected_button.setText(word.get("test_panel_run_selected"))
self.rerun_failures_button.setText(word.get("test_panel_rerun_failures"))
self.filter_edit.setPlaceholderText(word.get("test_panel_filter_placeholder"))
self.coverage_check.setText(word.get("test_panel_coverage"))
self.traceback_view.setPlaceholderText(word.get("test_panel_traceback_placeholder"))
self.result_tree.setHeaderLabels([
word.get("test_panel_col_outcome"),
word.get("test_panel_col_test"),
word.get("test_panel_col_file"),
])
if not self._results:
self.status_label.setText(word.get("test_panel_ready"))
self._render_items()

def start_run(self, node_ids: list[str] | None = None) -> bool:
"""
啟動一次測試執行
Expand Down
22 changes: 22 additions & 0 deletions je_editor/pyside_ui/main_ui/todo_panel/todo_panel_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,28 @@ def _on_scanned(self, items: list) -> None:
self.refresh_button.setEnabled(True)
self._render_items()

def retranslate(self) -> None:
"""
換語言後重新標示自己
Relabel after the language changes.

標籤篩選的第一項是「所有標籤」,其餘是掃到的標籤本身(TODO、FIXME),那些
不是翻譯來的,不能動。
The first entry in the tag filter is "all tags" and the rest are the tags
as found in the code (TODO, FIXME); those did not come from a translation
and are left alone.
"""
word = language_wrapper.language_word_dict
self.refresh_button.setText(word.get("todo_panel_refresh"))
self.tag_filter.setItemText(0, word.get("todo_panel_all_tags"))
self.result_tree.setHeaderLabels([
word.get("todo_panel_col_tag"),
word.get("todo_panel_col_message"),
word.get("todo_panel_col_file"),
word.get("todo_panel_col_line"),
])
self._render_items()

def _render_items(self) -> None:
"""依篩選條件重建清單 / Rebuild the tree for the current filter."""
visible = self.visible_items()
Expand Down
7 changes: 7 additions & 0 deletions je_editor/start_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from je_editor.pyside_ui.main_ui.main_editor import EditorMain
from je_editor.plugins.plugin_loader import load_external_plugins
from je_editor.utils.browser.chromium_flags import quiet_chromium_logging


def start_editor(debug_mode: bool = False) -> None:
Expand All @@ -18,6 +19,12 @@ def start_editor(debug_mode: bool = False) -> None:
:param debug_mode: 是否啟用除錯模式 / whether to enable debug mode
"""

# 壓下內嵌 Chromium 的啟動雜訊;標準錯誤會被導進輸出面板,那些訊息會直接
# 出現在使用者眼前
# Quiet the embedded Chromium's startup noise: standard error is piped into
# the output pane, so its messages land in front of the user
quiet_chromium_logging()

# 嘗試取得現有的 QCoreApplication 實例
# Try to get an existing QCoreApplication instance
new_editor = QCoreApplication.instance()
Expand Down
Empty file.
64 changes: 64 additions & 0 deletions je_editor/utils/browser/chromium_flags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""
壓下內嵌瀏覽器的啟動雜訊
Quiet the embedded browser's startup noise.

QtWebEngine 裡包著一整個 Chromium,它會把自己的訊息寫到標準錯誤,例如探測硬體
視訊編碼器失敗的 ``mf_video_encoder_util.cc: Set output type failed``。那些訊息
對編輯器的使用者沒有意義——Chromium 自己會退回可用的路徑——但這個編輯器把標準
錯誤導進輸出面板,於是它們就出現在使用者眼前。
QtWebEngine embeds a whole Chromium, which writes its own messages to standard
error -- such as ``mf_video_encoder_util.cc: Set output type failed`` when it
probes a hardware video encoder that will not take the settings. None of that
means anything to someone using a text editor, since Chromium falls back on its
own, but this editor pipes standard error into its output pane, so it lands in
front of the user.

純邏輯:只組出環境變數的值,不啟動任何東西。
Pure logic: it only assembles an environment variable, starting nothing.
"""
from __future__ import annotations

import os
from typing import Dict, Optional

# Chromium 讀取旗標的環境變數 / The variable Chromium reads its flags from
FLAGS_VARIABLE = "QTWEBENGINE_CHROMIUM_FLAGS"
# 只保留致命錯誤:0=INFO、1=WARNING、2=ERROR、3=FATAL
# Keep fatal messages only: 0=INFO, 1=WARNING, 2=ERROR, 3=FATAL
QUIET_FLAG = "--log-level=3"


def flags_with_quiet_logging(existing: Optional[str]) -> str:
"""
把「安靜」旗標加進既有的旗標字串
Add the quiet flag to whatever flags are already set.

使用者自己設過 log level 就不動它:他顯然是想看那些訊息。
A log level the user set is left alone: they evidently want to see them.

:param existing: 目前的旗標字串 / the flags as they stand
:return: 加上之後的旗標字串 / the flags afterwards
"""
current = (existing or "").strip()
if "--log-level" in current:
return current
return f"{current} {QUIET_FLAG}".strip()


def quiet_chromium_logging(environment: Optional[Dict[str, str]] = None) -> str:
"""
設定環境變數,讓內嵌的 Chromium 少說話
Set the environment variable that keeps the embedded Chromium quiet.

必須在 QtWebEngine 初始化之前呼叫,也就是第一個瀏覽器分頁被建立之前。
This has to run before QtWebEngine initialises, which is before the first
browser tab is created.

:param environment: 要設定的環境,省略時用行程本身的
/ the environment to set, or the process's own when omitted
:return: 設定後的旗標字串 / the flags as they now stand
"""
target = os.environ if environment is None else environment
flags = flags_with_quiet_logging(target.get(FLAGS_VARIABLE))
target[FLAGS_VARIABLE] = flags
return flags
Loading
Loading