ai.translate5_GUI のソースコード

"""
translate5_GUI.py

概要: translate5のGUIランナーアプリケーション。
詳細説明: translate5_comモジュールと連携し、ユーザーが翻訳設定を視覚的に操作できるようにするTkinterベースのGUIを提供する。
          設定ファイルの読み書き、翻訳実行、エラー表示などの機能を持つ。
関連リンク: :doc:`translate5_GUI_usage`
"""
import os
import threading
import configparser
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import traceback

try:
    from ttkthemes import ThemedTk
except Exception:
    ThemedTk = None

#from translate5 import (
from translate5_com import (
    initialize,
    update_variables,
    execute,
)

PROGRAM_NAME = "translate5 GUI Runner"

# translate5.py に合わせた API 一覧
API_CHOICES = ["openai5", "openai", "google", "gemini", "deepl"]

OPENAI_MODELS_V4 = ["gpt-4o", "gpt-4.5", "gpt-3.5-turbo"]
OPENAI_MODELS_V5 = ["gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-5-chat-latest"]
GOOGLE_MODELS = ["gemini-2.5-flash", "gemini-2.5-pro", "gemini-2.5-light", "gemini-3-pro-preview", "gemini-3.0-flash", "gemini-3.0-pro"]
DEEPL_MODELS = ["(auto)"]

REASONING_EFFORTS = ["low", "medium", "high"]
PROCESS_UNITS = ["paragraph", "run", "md"]  # md は GUI 専用で use_md フラグにマップ
MODES = [("日本語→英語 (je)", "je"), ("英語→日本語 (ej)", "ej")]

CONFIG_INI = os.path.splitext(os.path.abspath(__file__))[0] + ".ini"


[ドキュメント] class CopyableErrorDialog(tk.Toplevel): """ 概要: エラーメッセージをコピー可能な形で表示するカスタムダイアログ。 詳細説明: TkinterのToplevelウィジェットを継承し、表示されたエラーテキストを選択・コピーできるようにする。 親ウィンドウの中央に表示され、モーダルな動作をする。 """ def __init__(self, parent, title, message): """ 概要: CopyableErrorDialogのコンストラクタ。 詳細説明: ダイアログのUIを構築し、エラーメッセージを設定する。モーダルな動作を実現するためにgrab_set()を呼び出し、親ウィンドウの中央に配置する。 :param parent: tk.Widget: 親となるTkinterウィジェット。 :param title: str: ダイアログのタイトル。 :param message: str: ダイアログに表示するエラーメッセージ。 """ super().__init__(parent) self.title(title) self.geometry("700x400") self.transient(parent) self.grab_set() text_widget = tk.Text(self, wrap="word", font=("Courier", 10)) text_widget.pack(padx=10, pady=10, fill="both", expand=True) text_widget.insert("1.0", message) text_widget.config(state="disabled") button = ttk.Button(self, text="Close", command=self.destroy) button.pack(pady=10) self.update_idletasks() x = parent.winfo_x() + (parent.winfo_width() // 2) - (self.winfo_width() // 2) y = parent.winfo_y() + (parent.winfo_height() // 2) - (self.winfo_height() // 2) self.geometry(f"+{x}+{y}") button.focus_set() self.wait_window(self)
[ドキュメント] class App: """ 概要: translate5 GUIアプリケーションのメインクラス。 詳細説明: Tkinterを使用してユーザーインターフェースを構築し、translate5翻訳エンジンの設定を管理し、 翻訳処理を実行する。設定の読み込み、保存、ファイル選択、APIとモデルの動的な切り替え、 エラー処理、ステータス表示などの機能を提供する。 :param master: tk.Tk or ttkthemes.ThemedTk: アプリケーションのルートウィンドウ。 """ def __init__(self, master): """ 概要: Appクラスのコンストラクタ。 詳細説明: ルートウィンドウを設定し、translate5の設定を初期化し、GUIを構築する。 既存の設定ファイルをロードし、APIごとのモデル候補を適用する。 ウィンドウクローズ時のハンドラも設定する。 :param master: tk.Tk or ttkthemes.ThemedTk: アプリケーションのルートウィンドウ。 """ self.master = master master.title(PROGRAM_NAME) master.geometry("960x800") self.config = configparser.ConfigParser(comment_prefixes=('~',)) print("--- [INFO] Initialize translate5 config ---") self.cfg, self.parser = initialize() self.cfg = update_variables(self.cfg, self.parser) print("endpoint (from env/args) =", getattr(self.cfg, "endpoint", None)) self._busy = False self._build_ui() self._load_config() self._apply_api_to_models() master.protocol("WM_DELETE_WINDOW", self.on_close) # ---------------- UI 構築 ---------------- def _build_ui(self): """ 概要: アプリケーションのユーザーインターフェースを構築する。 詳細説明: 入力ファイル、HTMLテンプレート、翻訳モード、API設定、モデル、生成パラメータ、 翻訳ルール、プロンプトなどの各種ウィジェット(LabelFrame, Entry, Button, Radiobutton, Combobox, Text, Checkbutton)を配置する。 :returns: None """ # Input / Template io_frame = ttk.LabelFrame(self.master, text="Input / Template") io_frame.pack(fill="x", padx=10, pady=8) ttk.Label(io_frame, text="Input file (.docx/.pptx/.pdf/.html/.md/.txt):").grid( row=0, column=0, sticky="w", padx=6, pady=6 ) self.infile_var = tk.StringVar( value=self.cfg.infile if hasattr(self.cfg, "infile") else "" ) e_in = ttk.Entry(io_frame, textvariable=self.infile_var) e_in.grid(row=0, column=1, sticky="ew", padx=6, pady=6) ttk.Button(io_frame, text="Browse", command=self._pick_infile).grid( row=0, column=2, padx=6, pady=6 ) ttk.Label(io_frame, text="HTML template (template_translate.html):").grid( row=1, column=0, sticky="w", padx=6, pady=6 ) self.tpl_var = tk.StringVar( value=getattr(self.cfg, "html_template_path", "template_translate.html") ) e_tpl = ttk.Entry(io_frame, textvariable=self.tpl_var) e_tpl.grid(row=1, column=1, sticky="ew", padx=6, pady=6) ttk.Button(io_frame, text="Browse", command=self._pick_template).grid( row=1, column=2, padx=6, pady=6 ) io_frame.grid_columnconfigure(1, weight=1) # Mode / Options mode_frame = ttk.LabelFrame(self.master, text="Mode / Options") mode_frame.pack(fill="x", padx=10, pady=8) self.mode_var = tk.StringVar(value=getattr(self.cfg, "mode", "je")) col = 0 for label, val in MODES: ttk.Radiobutton( mode_frame, text=label, variable=self.mode_var, value=val ).grid(row=0, column=col, padx=6, pady=6, sticky="w") col += 1 ttk.Label(mode_frame, text="Process unit:").grid( row=0, column=col, padx=12, pady=6, sticky="e" ) self.proc_unit_var = tk.StringVar( value=getattr(self.cfg, "process_unit", "paragraph") ) cb_proc = ttk.Combobox( mode_frame, state="readonly", values=PROCESS_UNITS, textvariable=self.proc_unit_var, width=10, ) cb_proc.grid(row=0, column=col + 1, padx=6, pady=6, sticky="w") # API api_frame = ttk.LabelFrame(self.master, text="API") api_frame.pack(fill="x", padx=10, pady=8) ttk.Label(api_frame, text="API:").grid( row=0, column=0, padx=6, pady=6, sticky="w" ) self.api_var = tk.StringVar( value=getattr(self.cfg, "api", "openai5") ) self.api_cb = ttk.Combobox( api_frame, state="readonly", values=API_CHOICES, textvariable=self.api_var, width=10, ) self.api_cb.grid(row=0, column=1, padx=6, pady=6, sticky="w") self.api_cb.bind("<<ComboboxSelected>>", lambda e: self._apply_api_to_models()) ttk.Label(api_frame, text="Model:").grid( row=0, column=2, padx=12, pady=6, sticky="e" ) self.model_var = tk.StringVar(value="") self.model_cb = ttk.Combobox( api_frame, state="readonly", values=[], textvariable=self.model_var, width=24, ) self.model_cb.grid(row=0, column=3, padx=6, pady=6, sticky="w") # Endpointは DeepL/custom 用のみ ttk.Label(api_frame, text="Endpoint (DeepL / custom):").grid( row=0, column=4, padx=12, pady=6, sticky="e" ) endpoint_default = os.environ.get("endpoint", "") print("endpoint_default(env) =", endpoint_default) self.endpoint_var = tk.StringVar( value=endpoint_default if endpoint_default else "" ) ttk.Entry(api_frame, textvariable=self.endpoint_var, width=50).grid( row=0, column=5, padx=6, pady=6, sticky="w" ) api_frame.grid_columnconfigure(6, weight=1) # Generation Parameters gen_frame = ttk.LabelFrame(self.master, text="Generation Parameters") gen_frame.pack(fill="x", padx=10, pady=8) ttk.Label(gen_frame, text="temperature (OpenAI/Google/Gemini):").grid( row=0, column=0, padx=6, pady=6, sticky="e" ) self.temp_var = tk.StringVar( value=str(getattr(self.cfg, "temperature", 0.3)) ) ttk.Entry(gen_frame, textvariable=self.temp_var, width=8).grid( row=0, column=1, padx=6, pady=6, sticky="w" ) ttk.Label(gen_frame, text="max_tokens (or max_output_tokens):").grid( row=0, column=2, padx=12, pady=6, sticky="e" ) self.max_tok_var = tk.StringVar( value=str(getattr(self.cfg, "max_tokens", 2000)) ) ttk.Entry(gen_frame, textvariable=self.max_tok_var, width=10).grid( row=0, column=3, padx=6, pady=6, sticky="w" ) ttk.Label(gen_frame, text="reasoning_effort (OpenAI5):").grid( row=0, column=4, padx=12, pady=6, sticky="e" ) self.effort_var = tk.StringVar( value=getattr(self.cfg, "reasoning_effort", "low") ) ttk.Combobox( gen_frame, state="readonly", values=REASONING_EFFORTS, textvariable=self.effort_var, width=8, ).grid(row=0, column=5, padx=6, pady=6, sticky="w") # Translation Rules rule_frame = ttk.LabelFrame(self.master, text="Translation Rules") rule_frame.pack(fill="x", padx=10, pady=8) ttk.Label(rule_frame, text="min_translate_length:").grid( row=0, column=0, padx=6, pady=6, sticky="e" ) self.min_len_var = tk.StringVar( value=str(getattr(self.cfg, "min_translate_length", 5)) ) ttk.Entry(rule_frame, textvariable=self.min_len_var, width=6).grid( row=0, column=1, padx=6, pady=6, sticky="w" ) ttk.Label(rule_frame, text="allowed_translation_length_ratio:").grid( row=0, column=2, padx=12, pady=6, sticky="e" ) self.len_ratio_var = tk.StringVar( value=str(getattr(self.cfg, "allowed_translation_length_ratio", 5.0)) ) ttk.Entry(rule_frame, textvariable=self.len_ratio_var, width=6).grid( row=0, column=3, padx=6, pady=6, sticky="w" ) self.limit_mb_var = tk.BooleanVar( value=bool( getattr( self.cfg, "limit_to_multibyte_str", self._mode_implies_mb(getattr(self.cfg, "mode", "je")), ) ) ) ttk.Checkbutton( rule_frame, text="Limit to multibyte strings (JA source)", variable=self.limit_mb_var, ).grid(row=0, column=4, padx=12, pady=6, sticky="w") # Prompts(translate_prompt / reformat_prompt のみ) rp_frame = ttk.LabelFrame(self.master, text="Prompts") rp_frame.pack(fill="both", expand=True, padx=10, pady=8) ttk.Label(rp_frame, text="Translate Prompt (full instructions):").grid( row=0, column=0, padx=6, pady=6, sticky="nw" ) self.tr_prompt = tk.Text(rp_frame, height=8, wrap="word") self.tr_prompt.grid(row=0, column=1, padx=6, pady=6, sticky="nsew") self.tr_prompt.insert( "1.0", getattr(self.cfg, "translate_prompt", ""), ) ttk.Label(rp_frame, text="Reformat Prompt (PDF→MD etc.):").grid( row=1, column=0, padx=6, pady=6, sticky="nw" ) self.rf_prompt = tk.Text(rp_frame, height=6, wrap="word") self.rf_prompt.grid(row=1, column=1, padx=6, pady=6, sticky="nsew") self.rf_prompt.insert( "1.0", getattr(self.cfg, "reformat_prompt", ""), ) rp_frame.grid_columnconfigure(1, weight=1) rp_frame.grid_rowconfigure(0, weight=1) rp_frame.grid_rowconfigure(1, weight=1) # Buttons btn_frame = ttk.Frame(self.master) btn_frame.pack(pady=10) self.run_btn = ttk.Button( btn_frame, text="Run execute()", command=self._run_execute_thread ) self.run_btn.grid(row=0, column=0, padx=6) ttk.Button(btn_frame, text="Close", command=self.on_close).grid( row=0, column=1, padx=6 ) # Status bar self.status = ttk.Label( self.master, text="Ready", relief="sunken", anchor="w" ) self.status.pack(fill="x", padx=0, pady=(4, 0), ipady=2) def _mode_implies_mb(self, mode): """ 概要: 翻訳モードがマルチバイト文字列(日本語)を意味するかどうかを判定する。 詳細説明: モード文字列が"j"で始まる場合、日本語ソースであるとみなし、Trueを返す。 :param mode: str: 翻訳モード文字列(例: "je", "ej")。 :returns: bool: マルチバイト文字列を意味する場合はTrue、それ以外はFalse。 """ return True if mode and mode.startswith("j") else False def _pick_infile(self): """ 概要: 入力ファイルを選択するためのファイルダイアログを開く。 詳細説明: ユーザーがサポートされているファイルタイプ(.docx, .pptx, .pdf, .html, .md, .txt)の中から ファイルを選択できるようにする。選択されたファイルのパスを `infile_var` に設定する。 :returns: None """ fp = filedialog.askopenfilename( title="Select input file", filetypes=[ ( "Supported", "*.docx;*.pptx;*.pdf;*.html;*.htm;*.md;*.txt", ), ("All files", "*.*"), ], ) if fp: self.infile_var.set(fp) def _pick_template(self): """ 概要: HTMLテンプレートファイルを選択するためのファイルダイアログを開く。 詳細説明: ユーザーがHTMLファイル(.html, .htm)の中からファイルを選択できるようにする。 選択されたファイルのパスを `tpl_var` に設定する。 :returns: None """ fp = filedialog.askopenfilename( title="Select HTML template", filetypes=[("HTML", "*.html;*.htm"), ("All files", "*.*")], ) if fp: self.tpl_var.set(fp) def _apply_api_to_models(self): """ 概要: 現在選択されているAPIに基づいて、利用可能なモデルの選択肢を更新する。 詳細説明: `api_var` に設定されているAPIの種類(openai, openai5, google, gemini, deepl)に応じて、 `model_cb` コンボボックスの`values`を適切なモデルリストに更新し、デフォルト値を選択する。 :returns: None """ api = self.api_var.get() if api == "openai": self.model_cb["values"] = OPENAI_MODELS_V4 default = getattr(self.cfg, "openai_model", "gpt-4o") self.model_var.set( default if default in OPENAI_MODELS_V4 else "gpt-4o" ) elif api == "openai5": self.model_cb["values"] = OPENAI_MODELS_V5 default = getattr(self.cfg, "openai_model5", "gpt-5-nano") self.model_var.set( default if default in OPENAI_MODELS_V5 else "gpt-5-nano" ) elif api in ("google", "gemini"): self.model_cb["values"] = GOOGLE_MODELS new_default = GOOGLE_MODELS[0] if GOOGLE_MODELS else "" default = getattr(self.cfg, "google_model", new_default) self.model_var.set( default if default in GOOGLE_MODELS else new_default ) elif api == "deepl": self.model_cb["values"] = DEEPL_MODELS self.model_var.set("(auto)") else: self.model_cb["values"] = [] self.model_var.set("") def _worker_run(self): """ 概要: 翻訳処理 `execute()` を別スレッドで実行するワーカー関数。 詳細説明: `translate5_com.execute()` を呼び出し、処理の成否に応じてステータスバーを更新する。 エラーが発生した場合は、コピー可能なエラーダイアログを表示する。 処理完了後、Runボタンを再度有効にする。 :returns: None """ try: execute(self.cfg) self._set_status("Done.") except SystemExit: self._set_status("Finished (SystemExit).") except Exception as e: error_details = traceback.format_exc() self._set_status(f"Error: {e}") self._show_error_async("Execution Error", error_details) finally: self.master.after( 0, lambda: self.run_btn.config(state="normal") ) self._busy = False def _show_error_async(self, title, msg): """ 概要: 非同期でエラーダイアログを表示する。 詳細説明: GUIスレッドで `CopyableErrorDialog` を表示するようにスケジュールする。 これは、バックグラウンドスレッドからGUIを安全に更新するために使用される。 :param title: str: エラーダイアログのタイトル。 :param msg: str: エラーダイアログに表示するメッセージ。 :returns: None """ self.master.after( 0, lambda: CopyableErrorDialog(self.master, title, msg) ) def _run_execute_thread(self): """ 概要: 翻訳処理 `execute()` を新しいスレッドで開始する。 詳細説明: 実行中の場合や設定エラー、入力ファイルが見つからない場合は処理を中断する。 UIから設定を更新し、ステータスバーを「Running」に設定し、Runボタンを無効にして、 翻訳処理を実行する新しいデーモンスレッドを開始する。 :returns: None """ if self._busy: return try: self._update_cfg_from_ui() except Exception as e: messagebox.showerror( "設定エラー", f"設定の読み取りに失敗しました:\n{e}", ) return if not os.path.isfile(self.cfg.infile): messagebox.showerror( "入力エラー", f"入力ファイルが見つかりません:\n{self.cfg.infile}", ) return self._busy = True self.status.config(text="Running execute() ...") self.run_btn.config(state="disabled") t = threading.Thread(target=self._worker_run) t.daemon = True t.start() def _set_status(self, text): """ 概要: ステータスバーのテキストを更新する。 詳細説明: GUIスレッドでステータスバーのテキストを安全に更新するために `master.after()` を使用する。 :param text: str: ステータスバーに表示するテキスト。 :returns: None """ self.master.after(0, lambda: self.status.config(text=text)) def _update_cfg_from_ui(self): """ 概要: GUIの各入力値に基づいて、translate5の設定オブジェクト (`self.cfg`) を更新する。 詳細説明: 入力ファイルパス、テンプレートパス、翻訳モード、プロセス単位、API、モデル、 各種パラメータ(temperature, max_tokensなど)、プロンプト、APIキー(環境変数から)、 言語方向などを設定オブジェクトに反映させる。 :returns: None """ # 基本情報 self.cfg.infile = self.infile_var.get().strip() self.cfg.html_template_path = ( self.tpl_var.get().strip() or "template_translate.html" ) self.cfg.mode = self.mode_var.get() # process_unit / use_md (md は GUI 独自指定として use_md=True にマップ) proc_unit = self.proc_unit_var.get() if proc_unit == "md": self.cfg.use_md = True # 実際の処理で使わないが一応 paragraph にしておく self.cfg.process_unit = "paragraph" else: self.cfg.use_md = False self.cfg.process_unit = proc_unit or "paragraph" # API / model api = self.api_var.get() self.cfg.api = api model = self.model_var.get() if api == "openai": self.cfg.openai_model = model self.cfg.openai_model5 = None self.cfg.google_model = None elif api == "openai5": self.cfg.openai_model5 = model self.cfg.openai_model = None self.cfg.google_model = None elif api in ("google", "gemini"): self.cfg.google_model = model self.cfg.openai_model = None self.cfg.openai_model5 = None elif api == "deepl": self.cfg.openai_model = None self.cfg.openai_model5 = None self.cfg.google_model = None else: self.cfg.openai_model = None self.cfg.openai_model5 = None self.cfg.google_model = None # Endpoint は DeepL / custom のみ使用 if api == "deepl": self.cfg.endpoint = ( self.endpoint_var.get().strip() or getattr(self.cfg, "endpoint", None) ) else: self.cfg.endpoint = None # 各種パラメータ self.cfg.temperature = float(self.temp_var.get()) self.cfg.max_tokens = int(float(self.max_tok_var.get())) self.cfg.reasoning_effort = self.effort_var.get() self.cfg.min_translate_length = int(float(self.min_len_var.get())) self.cfg.allowed_translation_length_ratio = float( self.len_ratio_var.get() ) self.cfg.limit_to_multibyte_str = bool(self.limit_mb_var.get()) # translate5 新仕様: prompt は一体型のみ self.cfg.translate_prompt = ( self.tr_prompt.get("1.0", "end").strip() or getattr(self.cfg, "translate_prompt", "") ) self.cfg.reformat_prompt = ( self.rf_prompt.get("1.0", "end").strip() or getattr(self.cfg, "reformat_prompt", "") ) # APIキーなど(環境変数から) self.cfg.openai_api_key = os.getenv("OPENAI_API_KEY") self.cfg.google_api_key = os.getenv("GOOGLE_API_KEY") self.cfg.deepl_api_key = os.getenv("DEEPL_API_KEY") # その他固定値(translate5 と同等の既定値) self.cfg.output_html_path = None self.cfg.force_server_charcode = "utf-8" self.cfg.tsleep_rpm = getattr(self.cfg, "tsleep_rpm", 0.0) self.cfg.top_p = getattr(self.cfg, "top_p", 0.8) self.cfg.top_k = getattr(self.cfg, "top_k", 40) # mode から言語方向(translate5 と同じロジックに合わせて再計算) m = self.cfg.mode if m[0] == "j": self.cfg.source_lang = "JA" # GUIのチェック優先だが、je系ならデフォルトTrueでもよい if not hasattr(self.cfg, "limit_to_multibyte_str"): self.cfg.limit_to_multibyte_str = True else: self.cfg.source_lang = "EN" if m[1] == "j": self.cfg.target_lang = "JA" else: self.cfg.target_lang = "EN" def _load_config(self): """ 概要: 設定ファイル (`CONFIG_INI`) からアプリケーション設定を読み込む。 詳細説明: `CONFIG_INI` ファイルが存在する場合、その内容を読み込み、 GUIの各ウィジェット(Entry, Combobox, Textなど)に値を設定する。 読み込みに失敗してもエラーは表示せず、デフォルト値で続行する。 :returns: None """ if os.path.exists(CONFIG_INI): try: self.config.read(CONFIG_INI, encoding="utf-8") s = self.config["Settings"] if "geometry" in s: self.master.geometry(s.get("geometry")) self.infile_var.set( s.get("infile", self.infile_var.get()) ) self.tpl_var.set( s.get("html_template_path", self.tpl_var.get()) ) self.mode_var.set( s.get("mode", self.mode_var.get()) ) self.proc_unit_var.set( s.get("process_unit", self.proc_unit_var.get()) ) self.api_var.set( s.get("api", self.api_var.get()) ) self.model_var.set( s.get("model", self.model_var.get()) ) self.endpoint_var.set( s.get("endpoint", self.endpoint_var.get()) ) self.temp_var.set( s.get("temperature", self.temp_var.get()) ) self.max_tok_var.set( s.get("max_tokens", self.max_tok_var.get()) ) self.effort_var.set( s.get("reasoning_effort", self.effort_var.get()) ) self.min_len_var.set( s.get( "min_translate_length", self.min_len_var.get(), ) ) self.len_ratio_var.set( s.get( "allowed_translation_length_ratio", self.len_ratio_var.get(), ) ) self.limit_mb_var.set( s.getboolean( "limit_to_multibyte_str", self.limit_mb_var.get(), ) ) # promptのみ tr_p = s.get("translate_prompt", "").strip() if tr_p: self.tr_prompt.delete("1.0", "end") self.tr_prompt.insert("1.0", tr_p) rf_p = s.get("reformat_prompt", "").strip() if rf_p: self.rf_prompt.delete("1.0", "end") self.rf_prompt.insert("1.0", rf_p) except Exception: # 壊れていても無視してデフォルトで続行 pass def _save_config(self): """ 概要: アプリケーションの現在の設定をINIファイルに保存する。 詳細説明: GUIの各ウィジェットから現在の値を取得し、それを `CONFIG_INI` ファイルに書き込む。 設定保存中にエラーが発生した場合は警告メッセージを表示する。 :returns: None """ if "Settings" not in self.config: self.config["Settings"] = {} s = self.config["Settings"] s["geometry"] = self.master.winfo_geometry() s["infile"] = self.infile_var.get() s["html_template_path"] = self.tpl_var.get() s["mode"] = self.mode_var.get() s["process_unit"] = self.proc_unit_var.get() s["api"] = self.api_var.get() s["model"] = self.model_var.get() s["endpoint"] = self.endpoint_var.get() s["temperature"] = self.temp_var.get() s["max_tokens"] = self.max_tok_var.get() s["reasoning_effort"] = self.effort_var.get() s["min_translate_length"] = self.min_len_var.get() s["allowed_translation_length_ratio"] = self.len_ratio_var.get() s["limit_to_multibyte_str"] = str(bool(self.limit_mb_var.get())) s["translate_prompt"] = self.tr_prompt.get("1.0", "end").strip() s["reformat_prompt"] = self.rf_prompt.get("1.0", "end").strip() try: with open(CONFIG_INI, "w", encoding="utf-8") as f: self.config.write(f) except Exception as e: messagebox.showwarning( "保存警告", f"設定保存に失敗しました: {e}", )
[ドキュメント] def on_close(self): """ 概要: アプリケーションが閉じられる際に呼び出されるハンドラ。 詳細説明: 現在の設定をINIファイルに保存し、その後、ルートウィンドウを破棄する。 :returns: None """ try: self._save_config() finally: self.master.destroy()
[ドキュメント] def main(): """ 概要: アプリケーションのエントリポイント。 詳細説明: Tkinterのルートウィンドウを初期化し、可能であれば `ttkthemes` を使用してテーマを適用する。 `App` クラスのインスタンスを作成し、GUIアプリケーションのメインループを開始する。 :returns: None """ root = ThemedTk(theme="plastik") if ThemedTk else tk.Tk() if not ThemedTk: try: style = ttk.Style() style.theme_use("clam") except Exception: pass App(root) root.mainloop()
if __name__ == "__main__": main()