GameSentenceMiner 2.12.12__py3-none-any.whl → 2.13.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import json
2
3
  import subprocess
3
4
  import time
4
5
  import tkinter as tk
@@ -19,6 +20,21 @@ on_save = []
19
20
  exit_func = None
20
21
 
21
22
 
23
+ # It's assumed that a file named 'en_us.json' exists in the same directory
24
+ # or a path that Python can find.
25
+ def load_localization(locale=Locale.English):
26
+ """Loads the localization file."""
27
+ try:
28
+ # Use a path relative to this script file
29
+ script_dir = os.path.dirname(os.path.abspath(__file__))
30
+ lang_file = os.path.join(script_dir, 'locales', f'{locale.value}.json')
31
+ with open(lang_file, 'r', encoding='utf-8') as f:
32
+ return json.load(f)['python']['config']
33
+ except (FileNotFoundError, json.JSONDecodeError) as e:
34
+ print(f"Warning: Could not load localization file '{locale.value}.json'. Error: {e}. Falling back to empty dict.")
35
+ return {}
36
+
37
+
22
38
  def new_tab(func):
23
39
  def wrapper(self, *args, **kwargs):
24
40
  self.current_row = 0 # Resetting row for the new tab
@@ -35,6 +51,15 @@ class HoverInfoWidget:
35
51
  self.info_icon.bind("<Enter>", lambda e: self.show_info_box(text))
36
52
  self.info_icon.bind("<Leave>", lambda e: self.hide_info_box())
37
53
  self.tooltip = None
54
+
55
+ def change_text(self, text):
56
+ """
57
+ Change the text of the info icon.
58
+ """
59
+ if self.tooltip:
60
+ self.tooltip.destroy()
61
+ self.tooltip = None
62
+ self.info_icon.config(text=text)
38
63
 
39
64
  def show_info_box(self, text):
40
65
  x, y, _, _ = self.info_icon.bbox("insert")
@@ -55,9 +80,10 @@ class HoverInfoWidget:
55
80
 
56
81
  class HoverInfoLabelWidget:
57
82
  def __init__(self, parent, text, tooltip, row, column, padx=5, pady=2, foreground="white", sticky='W',
58
- bootstyle=None, font=("Arial", 10, "normal")):
59
- self.label = ttk.Label(parent, text=text, foreground=foreground, cursor="hand2", bootstyle=bootstyle, font=font)
60
- self.label.grid(row=row, column=column, padx=(0, padx), pady=0, sticky=sticky)
83
+ bootstyle=None, font=("Arial", 10, "normal"), apply_to_parent=False, columnspan=1):
84
+ self.label_text_value = ttk.StringVar(value=text)
85
+ self.label = ttk.Label(parent, textvariable=self.label_text_value, foreground=foreground, cursor="hand2", bootstyle=bootstyle, font=font)
86
+ self.label.grid(row=row, column=column, padx=(0, padx), pady=0, sticky=sticky, columnspan=columnspan)
61
87
  self.label.bind("<Enter>", lambda e: self.show_info_box(tooltip))
62
88
  self.label.bind("<Leave>", lambda e: self.hide_info_box())
63
89
  self.tooltip = None
@@ -80,8 +106,9 @@ class HoverInfoLabelWidget:
80
106
 
81
107
 
82
108
  class ResetToDefaultButton(ttk.Button):
83
- def __init__(self, parent, command, text="Reset to Default", bootstyle="danger", **kwargs):
109
+ def __init__(self, parent, command, text="Reset to Default", tooltip_text="Reset settings", bootstyle="danger", **kwargs):
84
110
  super().__init__(parent, text=text, command=command, bootstyle=bootstyle, **kwargs)
111
+ self.tooltip_text = tooltip_text
85
112
  self.tooltip = None
86
113
  self.bind("<Enter>", self.show_tooltip)
87
114
  self.bind("<Leave>", self.hide_tooltip)
@@ -93,7 +120,7 @@ class ResetToDefaultButton(ttk.Button):
93
120
  self.tooltip = tk.Toplevel(self)
94
121
  self.tooltip.wm_overrideredirect(True)
95
122
  self.tooltip.wm_geometry(f"+{x}+{y}")
96
- label = ttk.Label(self.tooltip, text="Reset Current Tab Settings to default values.", relief="solid",
123
+ label = ttk.Label(self.tooltip, text=self.tooltip_text, relief="solid",
97
124
  borderwidth=1,
98
125
  font=("tahoma", "12", "normal"))
99
126
  label.pack(ipadx=1)
@@ -109,16 +136,16 @@ class ConfigApp:
109
136
  self.window = root
110
137
  self.on_exit = None
111
138
  self.window.tk.call('tk', 'scaling', 1.5) # Set DPI scaling factor
112
- # self.window = ttk.Window(themename='darkly')
113
- self.window.title('GameSentenceMiner Configuration')
114
139
  self.window.protocol("WM_DELETE_WINDOW", self.hide)
115
140
  self.obs_scene_listbox_changed = False
116
141
  self.test_func = None
117
142
 
118
- # self.window.geometry("800x500")
119
143
  self.current_row = 0
120
144
 
121
145
  self.master_config: Config = configuration.load_config()
146
+ self.i18n = load_localization(self.master_config.locale)
147
+
148
+ self.window.title(self.i18n.get('app', {}).get('title', 'GameSentenceMiner Configuration'))
122
149
 
123
150
  self.settings = self.master_config.get_config()
124
151
  self.default_master_settings = Config.new()
@@ -126,7 +153,9 @@ class ConfigApp:
126
153
 
127
154
  self.notebook = ttk.Notebook(self.window)
128
155
  self.notebook.pack(pady=10, expand=True, fill='both')
129
-
156
+
157
+ self.required_settings_frame = None
158
+ self.starter_tab = None
130
159
  self.general_tab = None
131
160
  self.paths_tab = None
132
161
  self.anki_tab = None
@@ -144,40 +173,195 @@ class ConfigApp:
144
173
  try:
145
174
  import mss as mss
146
175
  self.monitors = [f"Monitor {i}: width: {monitor['width']}, height: {monitor['height']}" for i, monitor in enumerate(mss.mss().monitors[1:], start=1)]
147
- print(self.monitors)
148
176
  if len(self.monitors) == 0:
149
177
  self.monitors = [1]
150
178
  except ImportError:
151
179
  self.monitors = []
152
180
 
181
+ self.create_vars()
153
182
  self.create_tabs()
154
-
155
- # self.create_help_tab()
156
-
157
183
  self.notebook.bind("<<NotebookTabChanged>>", self.on_profiles_tab_selected)
158
184
 
159
185
  button_frame = ttk.Frame(self.window)
160
186
  button_frame.pack(side="bottom", pady=20, anchor="center")
161
-
162
- ttk.Button(button_frame, text="Save Settings", command=self.save_settings, bootstyle="success").grid(row=0,
163
- column=0,
164
- padx=10)
187
+
188
+ buttons_i18n = self.i18n.get('buttons', {})
189
+ self.save_button = ttk.Button(button_frame, text=buttons_i18n.get('save', 'Save Settings'), command=self.save_settings, bootstyle="success")
190
+ self.save_button.grid(row=0, column=0, padx=10)
191
+
165
192
  if len(self.master_config.configs) > 1:
166
- ttk.Button(button_frame, text="Save and Sync Changes",
167
- command=lambda: self.save_settings(profile_change=False, sync_changes=True),
168
- bootstyle="info").grid(row=0, column=1, padx=10)
169
- HoverInfoWidget(button_frame,
170
- "Saves Settings and Syncs CHANGED SETTINGS to all profiles.", row=0,
171
- column=2)
193
+ self.sync_changes_var = tk.BooleanVar(value=False)
194
+ sync_btn_i18n = buttons_i18n.get('sync_changes', {})
195
+ self.sync_changes_checkbutton = ttk.Checkbutton(
196
+ button_frame,
197
+ text=sync_btn_i18n.get('label', 'Sync Changes to Profiles'),
198
+ variable=self.sync_changes_var,
199
+ bootstyle="info"
200
+ )
201
+ self.sync_changes_checkbutton.grid(row=0, column=1, padx=10)
202
+ self.sync_changes_hover_info = HoverInfoWidget(
203
+ button_frame,
204
+ sync_btn_i18n.get('tooltip', 'Syncs CHANGED SETTINGS to all profiles.'),
205
+ row=0,
206
+ column=2
207
+ )
172
208
 
173
209
  self.window.update_idletasks()
174
210
  self.window.geometry("")
175
211
  self.window.withdraw()
212
+
213
+ def change_locale(self):
214
+ """Change the locale of the application."""
215
+ if self.locale_value.get() == self.master_config.locale.name:
216
+ return
217
+ self.i18n = load_localization(Locale[self.locale_value.get()])
218
+ self.save_settings()
219
+ self.reload_settings(force_refresh=True)
176
220
 
221
+ self.window.title(self.i18n.get('app', {}).get('title', 'GameSentenceMiner Configuration'))
222
+ self.save_button.config(text=self.i18n.get('buttons', {}).get('save', 'Save Settings'))
223
+ if hasattr(self, 'sync_changes_checkbutton'):
224
+ self.sync_changes_checkbutton.config(text=self.i18n.get('buttons', {}).get('sync_changes', {}).get('label', 'Sync Changes to Profiles'))
225
+
226
+ logger.info(f"Locale changed to {self.locale_value.get()}.")
227
+
177
228
  def set_test_func(self, func):
178
229
  self.test_func = func
230
+
231
+ def create_vars(self):
232
+ """
233
+ Initializes all the tkinter variables used in the configuration GUI.
234
+ """
235
+ # General Settings
236
+ self.websocket_enabled_value = tk.BooleanVar(value=self.settings.general.use_websocket)
237
+ self.clipboard_enabled_value = tk.BooleanVar(value=self.settings.general.use_clipboard)
238
+ self.use_both_clipboard_and_websocket_value = tk.BooleanVar(value=self.settings.general.use_both_clipboard_and_websocket)
239
+ self.merge_matching_sequential_text_value = tk.BooleanVar(value=self.settings.general.merge_matching_sequential_text)
240
+ self.websocket_uri_value = tk.StringVar(value=self.settings.general.websocket_uri)
241
+ self.texthook_replacement_regex_value = tk.StringVar(value=self.settings.general.texthook_replacement_regex)
242
+ self.open_config_on_startup_value = tk.BooleanVar(value=self.settings.general.open_config_on_startup)
243
+ self.open_multimine_on_startup_value = tk.BooleanVar(value=self.settings.general.open_multimine_on_startup)
244
+ self.texthooker_port_value = tk.StringVar(value=str(self.settings.general.texthooker_port))
245
+ self.native_language_value = tk.StringVar(value=CommonLanguages.from_code(self.settings.general.native_language).name.replace('_', ' ').title())
246
+
247
+ # OBS Settings
248
+ self.obs_enabled_value = tk.BooleanVar(value=self.settings.obs.enabled)
249
+ self.obs_websocket_port_value = tk.StringVar(value=str(self.settings.obs.port))
250
+ self.obs_host_value = tk.StringVar(value=self.settings.obs.host)
251
+ self.obs_port_value = tk.StringVar(value=str(self.settings.obs.port))
252
+ self.obs_password_value = tk.StringVar(value=self.settings.obs.password)
253
+ self.obs_open_obs_value = tk.BooleanVar(value=self.settings.obs.open_obs)
254
+ self.obs_close_obs_value = tk.BooleanVar(value=self.settings.obs.close_obs)
255
+ self.obs_get_game_from_scene_name_value = tk.BooleanVar(value=self.settings.obs.get_game_from_scene)
256
+ self.obs_minimum_replay_size_value = tk.StringVar(value=str(self.settings.obs.minimum_replay_size))
257
+
258
+ # Paths Settings
259
+ self.folder_to_watch_value = tk.StringVar(value=self.settings.paths.folder_to_watch)
260
+ self.audio_destination_value = tk.StringVar(value=self.settings.paths.audio_destination)
261
+ self.screenshot_destination_value = tk.StringVar(value=self.settings.paths.screenshot_destination)
262
+ self.remove_video_value = tk.BooleanVar(value=self.settings.paths.remove_video)
263
+ self.remove_audio_value = tk.BooleanVar(value=self.settings.paths.remove_audio)
264
+ self.remove_screenshot_value = tk.BooleanVar(value=self.settings.paths.remove_screenshot)
265
+
266
+ # Anki Settings
267
+ self.update_anki_value = tk.BooleanVar(value=self.settings.anki.update_anki)
268
+ self.anki_url_value = tk.StringVar(value=self.settings.anki.url)
269
+ self.sentence_field_value = tk.StringVar(value=self.settings.anki.sentence_field)
270
+ self.sentence_audio_field_value = tk.StringVar(value=self.settings.anki.sentence_audio_field)
271
+ self.picture_field_value = tk.StringVar(value=self.settings.anki.picture_field)
272
+ self.word_field_value = tk.StringVar(value=self.settings.anki.word_field)
273
+ self.previous_sentence_field_value = tk.StringVar(value=self.settings.anki.previous_sentence_field)
274
+ self.previous_image_field_value = tk.StringVar(value=self.settings.anki.previous_image_field)
275
+ self.custom_tags_value = tk.StringVar(value=', '.join(self.settings.anki.custom_tags))
276
+ self.tags_to_check_value = tk.StringVar(value=', '.join(self.settings.anki.tags_to_check))
277
+ self.add_game_tag_value = tk.BooleanVar(value=self.settings.anki.add_game_tag)
278
+ self.polling_rate_value = tk.StringVar(value=str(self.settings.anki.polling_rate))
279
+ self.overwrite_audio_value = tk.BooleanVar(value=self.settings.anki.overwrite_audio)
280
+ self.overwrite_picture_value = tk.BooleanVar(value=self.settings.anki.overwrite_picture)
281
+ self.multi_overwrites_sentence_value = tk.BooleanVar(value=self.settings.anki.multi_overwrites_sentence)
282
+ self.parent_tag_value = tk.StringVar(value=self.settings.anki.parent_tag)
283
+
284
+ # Features Settings
285
+ self.full_auto_value = tk.BooleanVar(value=self.settings.features.full_auto)
286
+ self.notify_on_update_value = tk.BooleanVar(value=self.settings.features.notify_on_update)
287
+ self.open_anki_edit_value = tk.BooleanVar(value=self.settings.features.open_anki_edit)
288
+ self.open_anki_browser_value = tk.BooleanVar(value=self.settings.features.open_anki_in_browser)
289
+ self.backfill_audio_value = tk.BooleanVar(value=self.settings.features.backfill_audio)
290
+ self.browser_query_value = tk.StringVar(value=self.settings.features.browser_query)
291
+
292
+ # Screenshot Settings
293
+ self.screenshot_enabled_value = tk.BooleanVar(value=self.settings.screenshot.enabled)
294
+ self.screenshot_width_value = tk.StringVar(value=str(self.settings.screenshot.width))
295
+ self.screenshot_height_value = tk.StringVar(value=str(self.settings.screenshot.height))
296
+ self.screenshot_quality_value = tk.StringVar(value=str(self.settings.screenshot.quality))
297
+ self.screenshot_extension_value = tk.StringVar(value=self.settings.screenshot.extension)
298
+ self.screenshot_custom_ffmpeg_settings_value = tk.StringVar(value=self.settings.screenshot.custom_ffmpeg_settings)
299
+ self.screenshot_hotkey_update_anki_value = tk.BooleanVar(value=self.settings.screenshot.screenshot_hotkey_updates_anki)
300
+ self.seconds_after_line_value = tk.StringVar(value=str(self.settings.screenshot.seconds_after_line))
301
+ self.screenshot_timing_value = tk.StringVar(value=self.settings.screenshot.screenshot_timing_setting)
302
+ self.use_screenshot_selector_value = tk.BooleanVar(value=self.settings.screenshot.use_screenshot_selector)
303
+
304
+ # Audio Settings
305
+ self.audio_enabled_value = tk.BooleanVar(value=self.settings.audio.enabled)
306
+ self.audio_extension_value = tk.StringVar(value=self.settings.audio.extension)
307
+ self.beginning_offset_value = tk.StringVar(value=str(self.settings.audio.beginning_offset))
308
+ self.end_offset_value = tk.StringVar(value=str(self.settings.audio.end_offset))
309
+ self.audio_ffmpeg_reencode_options_value = tk.StringVar(value=self.settings.audio.ffmpeg_reencode_options)
310
+ self.external_tool_value = tk.StringVar(value=self.settings.audio.external_tool)
311
+ self.anki_media_collection_value = tk.StringVar(value=self.settings.audio.anki_media_collection)
312
+ self.external_tool_enabled_value = tk.BooleanVar(value=self.settings.audio.external_tool_enabled)
313
+ self.pre_vad_audio_offset_value = tk.StringVar(value=str(self.settings.audio.pre_vad_end_offset))
314
+
315
+ # Hotkeys Settings
316
+ self.reset_line_hotkey_value = tk.StringVar(value=self.settings.hotkeys.reset_line)
317
+ self.take_screenshot_hotkey_value = tk.StringVar(value=self.settings.hotkeys.take_screenshot)
318
+ self.play_latest_audio_hotkey_value = tk.StringVar(value=self.settings.hotkeys.play_latest_audio)
319
+
320
+ # VAD Settings
321
+ self.whisper_model_value = tk.StringVar(value=self.settings.vad.whisper_model)
322
+ self.do_vad_postprocessing_value = tk.BooleanVar(value=self.settings.vad.do_vad_postprocessing)
323
+ self.selected_vad_model_value = tk.StringVar(value=self.settings.vad.selected_vad_model)
324
+ self.backup_vad_model_value = tk.StringVar(value=self.settings.vad.backup_vad_model)
325
+ self.vad_trim_beginning_value = tk.BooleanVar(value=self.settings.vad.trim_beginning)
326
+ self.vad_beginning_offset_value = tk.StringVar(value=str(self.settings.vad.beginning_offset))
327
+ self.add_audio_on_no_results_value = tk.BooleanVar(value=self.settings.vad.add_audio_on_no_results)
328
+ self.language_value = tk.StringVar(value=self.settings.vad.language)
329
+ self.cut_and_splice_segments_value = tk.BooleanVar(value=self.settings.vad.cut_and_splice_segments)
330
+ self.splice_padding_value = tk.StringVar(value=str(self.settings.vad.splice_padding) if self.settings.vad.splice_padding else "")
331
+
332
+ # Advanced Settings
333
+ self.audio_player_path_value = tk.StringVar(value=self.settings.advanced.audio_player_path)
334
+ self.video_player_path_value = tk.StringVar(value=self.settings.advanced.video_player_path)
335
+ self.multi_line_line_break_value = tk.StringVar(value=self.settings.advanced.multi_line_line_break)
336
+ self.multi_line_sentence_storage_field_value = tk.StringVar(value=self.settings.advanced.multi_line_sentence_storage_field)
337
+ self.ocr_websocket_port_value = tk.StringVar(value=str(self.settings.advanced.ocr_websocket_port))
338
+ self.texthooker_communication_websocket_port_value = tk.StringVar(value=str(self.settings.advanced.texthooker_communication_websocket_port))
339
+ self.plaintext_websocket_export_port_value = tk.StringVar(value=str(self.settings.advanced.plaintext_websocket_port))
340
+
341
+ # AI Settings
342
+ self.ai_enabled_value = tk.BooleanVar(value=self.settings.ai.enabled)
343
+ self.ai_provider_value = tk.StringVar(value=self.settings.ai.provider)
344
+ self.gemini_model_value = tk.StringVar(value=self.settings.ai.gemini_model)
345
+ self.groq_model_value = tk.StringVar(value=self.settings.ai.groq_model)
346
+ self.gemini_api_key_value = tk.StringVar(value=self.settings.ai.gemini_api_key)
347
+ self.groq_api_key_value = tk.StringVar(value=self.settings.ai.groq_api_key)
348
+ self.local_ai_model_value = tk.StringVar(value=self.settings.ai.local_model)
349
+ self.ai_anki_field_value = tk.StringVar(value=self.settings.ai.anki_field)
350
+ self.use_canned_translation_prompt_value = tk.BooleanVar(value=self.settings.ai.use_canned_translation_prompt)
351
+ self.use_canned_context_prompt_value = tk.BooleanVar(value=self.settings.ai.use_canned_context_prompt)
352
+ self.ai_dialogue_context_length_value = tk.StringVar(value=str(self.settings.ai.dialogue_context_length))
353
+
354
+ # WIP Settings
355
+ self.overlay_websocket_port_value = tk.StringVar(value=str(self.settings.wip.overlay_websocket_port))
356
+ self.overlay_websocket_send_value = tk.BooleanVar(value=self.settings.wip.overlay_websocket_send)
357
+
358
+ # Master Config Settings
359
+ self.switch_to_default_if_not_found_value = tk.BooleanVar(value=self.master_config.switch_to_default_if_not_found)
360
+ self.locale_value = tk.StringVar(value=self.master_config.locale.name)
361
+
179
362
 
180
363
  def create_tabs(self):
364
+ self.create_required_settings_tab()
181
365
  self.create_general_tab()
182
366
  self.create_paths_tab()
183
367
  self.create_anki_tab()
@@ -195,18 +379,20 @@ class ConfigApp:
195
379
  """
196
380
  Adds a reset button to the given frame that resets the settings in the frame to default values.
197
381
  """
382
+ reset_btn_i18n = self.i18n.get('buttons', {}).get('reset_to_default', {})
198
383
  reset_button = ResetToDefaultButton(frame, command=lambda: self.reset_to_default(category, recreate_tab),
199
- text="Reset to Default")
384
+ text=reset_btn_i18n.get('text', 'Reset to Default'),
385
+ tooltip_text=reset_btn_i18n.get('tooltip', 'Reset current tab to default.'))
200
386
  reset_button.grid(row=row, column=column, sticky='W', padx=5, pady=5)
201
387
  return reset_button
202
388
 
203
- # Category is the dataclass name of the settings being reset, default is a default instance of that dataclass
204
389
  def reset_to_default(self, category, recreate_tab):
205
390
  """
206
391
  Resets the settings in the current tab to default values.
207
392
  """
208
- if not messagebox.askyesno("Reset to Default",
209
- "Are you sure you want to reset all settings in this tab to default?"):
393
+ dialog_i18n = self.i18n.get('dialogs', {}).get('reset_to_default', {})
394
+ if not messagebox.askyesno(dialog_i18n.get('title', 'Reset to Default'),
395
+ dialog_i18n.get('message', 'Are you sure you want to reset all settings in this tab to default?')):
210
396
  return
211
397
 
212
398
  default_category_config = getattr(self.default_settings, category)
@@ -219,22 +405,25 @@ class ConfigApp:
219
405
  def show_scene_selection(self, matched_configs):
220
406
  selected_scene = None
221
407
  if matched_configs:
408
+ dialog_i18n = self.i18n.get('dialogs', {}).get('select_profile', {})
409
+ buttons_i18n = self.i18n.get('buttons', {})
410
+
222
411
  selection_window = tk.Toplevel(self.window)
223
- selection_window.title("Select Profile")
224
- selection_window.transient(self.window) # Make it modal relative to the main window
225
- selection_window.grab_set() # Grab all events for this window
412
+ selection_window.title(dialog_i18n.get('title', 'Select Profile'))
413
+ selection_window.transient(self.window)
414
+ selection_window.grab_set()
226
415
 
227
416
  ttk.Label(selection_window,
228
- text="Multiple profiles match the current scene. Please select the profile:").pack(pady=10)
417
+ text=dialog_i18n.get('message', 'Multiple profiles match... Please select:')).pack(pady=10)
229
418
  profile_var = tk.StringVar(value=matched_configs[0])
230
419
  profile_dropdown = ttk.Combobox(selection_window, textvariable=profile_var, values=matched_configs,
231
420
  state="readonly")
232
421
  profile_dropdown.pack(pady=5)
233
- ttk.Button(selection_window, text="OK",
422
+ ttk.Button(selection_window, text=buttons_i18n.get('ok', 'OK'),
234
423
  command=lambda: [selection_window.destroy(), setattr(self, 'selected_scene', profile_var.get())],
235
424
  bootstyle="primary").pack(pady=10)
236
425
 
237
- self.window.wait_window(selection_window) # Wait for selection_window to close
426
+ self.window.wait_window(selection_window)
238
427
  selected_scene = self.selected_scene
239
428
  return selected_scene
240
429
 
@@ -257,165 +446,176 @@ class ConfigApp:
257
446
 
258
447
  def save_settings(self, profile_change=False, sync_changes=False):
259
448
  global settings_saved
449
+
450
+ sync_changes = self.sync_changes_checkbutton.instate(['selected']) if hasattr(self, 'sync_changes_checkbutton') else sync_changes
451
+ # reset checkbox
452
+ if hasattr(self, 'sync_changes_checkbutton'):
453
+ self.sync_changes_checkbutton.state(['!selected'])
260
454
 
261
455
  # Create a new Config instance
262
456
  config = ProfileConfig(
263
457
  scenes=self.settings.scenes,
264
458
  general=General(
265
- use_websocket=self.websocket_enabled.get(),
266
- use_clipboard=self.clipboard_enabled.get(),
267
- websocket_uri=self.websocket_uri.get(),
268
- merge_matching_sequential_text= self.merge_matching_sequential_text.get(),
269
- open_config_on_startup=self.open_config_on_startup.get(),
270
- open_multimine_on_startup=self.open_multimine_on_startup.get(),
271
- texthook_replacement_regex=self.texthook_replacement_regex.get(),
272
- use_both_clipboard_and_websocket=self.use_both_clipboard_and_websocket.get(),
273
- texthooker_port=int(self.texthooker_port.get()),
274
- native_language=CommonLanguages.from_name(self.native_language.get()) if self.native_language.get() else CommonLanguages.ENGLISH.value,
459
+ use_websocket=self.websocket_enabled_value.get(),
460
+ use_clipboard=self.clipboard_enabled_value.get(),
461
+ websocket_uri=self.websocket_uri_value.get(),
462
+ merge_matching_sequential_text= self.merge_matching_sequential_text_value.get(),
463
+ open_config_on_startup=self.open_config_on_startup_value.get(),
464
+ open_multimine_on_startup=self.open_multimine_on_startup_value.get(),
465
+ texthook_replacement_regex=self.texthook_replacement_regex_value.get(),
466
+ use_both_clipboard_and_websocket=self.use_both_clipboard_and_websocket_value.get(),
467
+ texthooker_port=int(self.texthooker_port_value.get()),
468
+ native_language=CommonLanguages.from_name(self.native_language_value.get()) if self.native_language_value.get() else CommonLanguages.ENGLISH.value,
275
469
  ),
276
470
  paths=Paths(
277
- folder_to_watch=self.folder_to_watch.get(),
278
- audio_destination=self.audio_destination.get(),
279
- screenshot_destination=self.screenshot_destination.get(),
280
- remove_video=self.remove_video.get(),
281
- remove_audio=self.remove_audio.get(),
282
- remove_screenshot=self.remove_screenshot.get()
471
+ folder_to_watch=self.folder_to_watch_value.get(),
472
+ audio_destination=self.audio_destination_value.get(),
473
+ screenshot_destination=self.screenshot_destination_value.get(),
474
+ remove_video=self.remove_video_value.get(),
475
+ remove_audio=self.remove_audio_value.get(),
476
+ remove_screenshot=self.remove_screenshot_value.get()
283
477
  ),
284
478
  anki=Anki(
285
- update_anki=self.update_anki.get(),
286
- url=self.anki_url.get(),
287
- sentence_field=self.sentence_field.get(),
288
- sentence_audio_field=self.sentence_audio_field.get(),
289
- picture_field=self.picture_field.get(),
290
- word_field=self.word_field.get(),
291
- previous_sentence_field=self.previous_sentence_field.get(),
292
- previous_image_field=self.previous_image_field.get(),
293
- custom_tags=[tag.strip() for tag in self.custom_tags.get().split(',') if tag.strip()],
294
- tags_to_check=[tag.strip().lower() for tag in self.tags_to_check.get().split(',') if tag.strip()],
295
- add_game_tag=self.add_game_tag.get(),
296
- polling_rate=int(self.polling_rate.get()),
297
- overwrite_audio=self.overwrite_audio.get(),
298
- overwrite_picture=self.overwrite_picture.get(),
299
- multi_overwrites_sentence=self.multi_overwrites_sentence.get(),
300
- parent_tag=self.parent_tag.get(),
479
+ update_anki=self.update_anki_value.get(),
480
+ url=self.anki_url_value.get(),
481
+ sentence_field=self.sentence_field_value.get(),
482
+ sentence_audio_field=self.sentence_audio_field_value.get(),
483
+ picture_field=self.picture_field_value.get(),
484
+ word_field=self.word_field_value.get(),
485
+ previous_sentence_field=self.previous_sentence_field_value.get(),
486
+ previous_image_field=self.previous_image_field_value.get(),
487
+ custom_tags=[tag.strip() for tag in self.custom_tags_value.get().split(',') if tag.strip()],
488
+ tags_to_check=[tag.strip().lower() for tag in self.tags_to_check_value.get().split(',') if tag.strip()],
489
+ add_game_tag=self.add_game_tag_value.get(),
490
+ polling_rate=int(self.polling_rate_value.get()),
491
+ overwrite_audio=self.overwrite_audio_value.get(),
492
+ overwrite_picture=self.overwrite_picture_value.get(),
493
+ multi_overwrites_sentence=self.multi_overwrites_sentence_value.get(),
494
+ parent_tag=self.parent_tag_value.get(),
301
495
  ),
302
496
  features=Features(
303
- full_auto=self.full_auto.get(),
304
- notify_on_update=self.notify_on_update.get(),
305
- open_anki_edit=self.open_anki_edit.get(),
306
- open_anki_in_browser=self.open_anki_browser.get(),
307
- backfill_audio=self.backfill_audio.get(),
308
- browser_query=self.browser_query.get(),
497
+ full_auto=self.full_auto_value.get(),
498
+ notify_on_update=self.notify_on_update_value.get(),
499
+ open_anki_edit=self.open_anki_edit_value.get(),
500
+ open_anki_in_browser=self.open_anki_browser_value.get(),
501
+ backfill_audio=self.backfill_audio_value.get(),
502
+ browser_query=self.browser_query_value.get(),
309
503
  ),
310
504
  screenshot=Screenshot(
311
- enabled=self.screenshot_enabled.get(),
312
- width=self.screenshot_width.get(),
313
- height=self.screenshot_height.get(),
314
- quality=self.screenshot_quality.get(),
315
- extension=self.screenshot_extension.get(),
316
- custom_ffmpeg_settings=self.screenshot_custom_ffmpeg_settings.get(),
317
- screenshot_hotkey_updates_anki=self.screenshot_hotkey_update_anki.get(),
318
- seconds_after_line=float(self.seconds_after_line.get()) if self.seconds_after_line.get() else 0.0,
319
- screenshot_timing_setting=self.screenshot_timing.get(),
320
- use_screenshot_selector=self.use_screenshot_selector.get(),
505
+ enabled=self.screenshot_enabled_value.get(),
506
+ width=self.screenshot_width_value.get(),
507
+ height=self.screenshot_height_value.get(),
508
+ quality=self.screenshot_quality_value.get(),
509
+ extension=self.screenshot_extension_value.get(),
510
+ custom_ffmpeg_settings=self.screenshot_custom_ffmpeg_settings_value.get(),
511
+ screenshot_hotkey_updates_anki=self.screenshot_hotkey_update_anki_value.get(),
512
+ seconds_after_line=float(self.seconds_after_line_value.get()) if self.seconds_after_line_value.get() else 0.0,
513
+ screenshot_timing_setting=self.screenshot_timing_value.get(),
514
+ use_screenshot_selector=self.use_screenshot_selector_value.get(),
321
515
  ),
322
516
  audio=Audio(
323
- enabled=self.audio_enabled.get(),
324
- extension=self.audio_extension.get(),
325
- beginning_offset=float(self.beginning_offset.get()),
326
- end_offset=float(self.end_offset.get()),
327
- ffmpeg_reencode_options=self.audio_ffmpeg_reencode_options.get(),
328
- external_tool=self.external_tool.get(),
329
- anki_media_collection=self.anki_media_collection.get(),
330
- external_tool_enabled=self.external_tool_enabled.get(),
331
- pre_vad_end_offset=float(self.pre_vad_audio_offset.get()),
517
+ enabled=self.audio_enabled_value.get(),
518
+ extension=self.audio_extension_value.get(),
519
+ beginning_offset=float(self.beginning_offset_value.get()),
520
+ end_offset=float(self.end_offset_value.get()),
521
+ ffmpeg_reencode_options=self.audio_ffmpeg_reencode_options_value.get(),
522
+ external_tool=self.external_tool_value.get(),
523
+ anki_media_collection=self.anki_media_collection_value.get(),
524
+ external_tool_enabled=self.external_tool_enabled_value.get(),
525
+ pre_vad_end_offset=float(self.pre_vad_audio_offset_value.get()),
332
526
  ),
333
527
  obs=OBS(
334
- enabled=self.obs_enabled.get(),
335
- open_obs=self.open_obs.get(),
336
- close_obs=self.close_obs.get(),
337
- host=self.obs_host.get(),
338
- port=int(self.obs_port.get()),
339
- password=self.obs_password.get(),
340
- get_game_from_scene=self.get_game_from_scene_name.get(),
341
- minimum_replay_size=int(self.minimum_replay_size.get())
528
+ enabled=self.obs_enabled_value.get(),
529
+ open_obs=self.obs_open_obs_value.get(),
530
+ close_obs=self.obs_close_obs_value.get(),
531
+ host=self.obs_host_value.get(),
532
+ port=int(self.obs_port_value.get()),
533
+ password=self.obs_password_value.get(),
534
+ get_game_from_scene=self.obs_get_game_from_scene_name_value.get(),
535
+ minimum_replay_size=int(self.obs_minimum_replay_size_value.get())
342
536
  ),
343
537
  hotkeys=Hotkeys(
344
- reset_line=self.reset_line_hotkey.get(),
345
- take_screenshot=self.take_screenshot_hotkey.get(),
346
- # open_utility=self.open_utility_hotkey.get(),
347
- play_latest_audio=self.play_latest_audio_hotkey.get()
538
+ reset_line=self.reset_line_hotkey_value.get(),
539
+ take_screenshot=self.take_screenshot_hotkey_value.get(),
540
+ play_latest_audio=self.play_latest_audio_hotkey_value.get()
348
541
  ),
349
542
  vad=VAD(
350
- whisper_model=self.whisper_model.get(),
351
- do_vad_postprocessing=self.do_vad_postprocessing.get(),
352
- # vosk_url='https://alphacephei.com/vosk/models/vosk-model-ja-0.22.zip' if self.vosk_url.get() == VOSK_BASE else "https://alphacephei.com/vosk/models/vosk-model-small-ja-0.22.zip",
353
- selected_vad_model=self.selected_vad_model.get(),
354
- backup_vad_model=self.backup_vad_model.get(),
355
- trim_beginning=self.vad_trim_beginning.get(),
356
- beginning_offset=float(self.vad_beginning_offset.get()),
357
- add_audio_on_no_results=self.add_audio_on_no_results.get(),
358
- language=self.language.get(),
359
- cut_and_splice_segments=self.cut_and_splice_segments.get(),
360
- splice_padding=float(self.splice_padding.get()) if self.splice_padding.get() else 0.0,
543
+ whisper_model=self.whisper_model_value.get(),
544
+ do_vad_postprocessing=self.do_vad_postprocessing_value.get(),
545
+ selected_vad_model=self.selected_vad_model_value.get(),
546
+ backup_vad_model=self.backup_vad_model_value.get(),
547
+ trim_beginning=self.vad_trim_beginning_value.get(),
548
+ beginning_offset=float(self.vad_beginning_offset_value.get()),
549
+ add_audio_on_no_results=self.add_audio_on_no_results_value.get(),
550
+ language=self.language_value.get(),
551
+ cut_and_splice_segments=self.cut_and_splice_segments_value.get(),
552
+ splice_padding=float(self.splice_padding_value.get()) if self.splice_padding_value.get() else 0.0,
361
553
  ),
362
554
  advanced=Advanced(
363
- audio_player_path=self.audio_player_path.get(),
364
- video_player_path=self.video_player_path.get(),
365
- multi_line_line_break=self.multi_line_line_break.get(),
366
- multi_line_sentence_storage_field=self.multi_line_sentence_storage_field.get(),
367
- # use_anki_note_creation_time=self.use_anki_note_creation_time.get(),
368
- ocr_websocket_port=int(self.ocr_websocket_port.get()),
369
- texthooker_communication_websocket_port=int(self.texthooker_communication_websocket_port.get()),
370
- plaintext_websocket_port=int(self.plaintext_websocket_export_port.get()),
555
+ audio_player_path=self.audio_player_path_value.get(),
556
+ video_player_path=self.video_player_path_value.get(),
557
+ multi_line_line_break=self.multi_line_line_break_value.get(),
558
+ multi_line_sentence_storage_field=self.multi_line_sentence_storage_field_value.get(),
559
+ ocr_websocket_port=int(self.ocr_websocket_port_value.get()),
560
+ texthooker_communication_websocket_port=int(self.texthooker_communication_websocket_port_value.get()),
561
+ plaintext_websocket_port=int(self.plaintext_websocket_export_port_value.get()),
371
562
  ),
372
563
  ai=Ai(
373
- enabled=self.ai_enabled.get(),
374
- provider=self.ai_provider.get(),
375
- gemini_model=self.gemini_model.get(),
376
- groq_model=self.groq_model.get(),
377
- gemini_api_key=self.gemini_api_key.get(),
378
- api_key=self.gemini_api_key.get(),
379
- groq_api_key=self.groq_api_key.get(),
380
- local_model=self.local_ai_model.get(),
381
- anki_field=self.ai_anki_field.get(),
382
- use_canned_translation_prompt=self.use_canned_translation_prompt.get(),
383
- use_canned_context_prompt=self.use_canned_context_prompt.get(),
384
- custom_prompt=self.custom_prompt.get("1.0", tk.END),
385
- dialogue_context_length=int(self.ai_dialogue_context_length.get()),
564
+ enabled=self.ai_enabled_value.get(),
565
+ provider=self.ai_provider_value.get(),
566
+ gemini_model=self.gemini_model_value.get(),
567
+ groq_model=self.groq_model_value.get(),
568
+ gemini_api_key=self.gemini_api_key_value.get(),
569
+ api_key=self.gemini_api_key_value.get(),
570
+ groq_api_key=self.groq_api_key_value.get(),
571
+ local_model=self.local_ai_model_value.get(),
572
+ anki_field=self.ai_anki_field_value.get(),
573
+ use_canned_translation_prompt=self.use_canned_translation_prompt_value.get(),
574
+ use_canned_context_prompt=self.use_canned_context_prompt_value.get(),
575
+ custom_prompt=self.custom_prompt.get("1.0", tk.END).strip(),
576
+ dialogue_context_length=int(self.ai_dialogue_context_length_value.get()),
386
577
  ),
387
578
  wip=WIP(
388
- overlay_websocket_port=int(self.overlay_websocket_port.get()),
389
- overlay_websocket_send=self.overlay_websocket_send.get(),
390
- monitor_to_capture=self.monitor_to_capture.current()
579
+ overlay_websocket_port=int(self.overlay_websocket_port_value.get()),
580
+ overlay_websocket_send=self.overlay_websocket_send_value.get(),
581
+ monitor_to_capture=self.monitor_to_capture.current() if self.monitors else 0
391
582
  )
392
583
  )
393
584
 
394
- if self.ffmpeg_audio_preset_options.get() == "Custom":
395
- config.audio.custom_encode_settings = self.audio_ffmpeg_reencode_options.get()
585
+ # Find the display name for "Custom" to check against
586
+ audio_i18n = self.i18n.get('tabs', {}).get('audio', {})
587
+ ffmpeg_preset_i18n = audio_i18n.get('ffmpeg_preset', {}).get('options', {})
588
+ custom_display_name = ffmpeg_preset_i18n.get('custom', 'Custom')
589
+
590
+ if self.ffmpeg_audio_preset_options.get() == custom_display_name:
591
+ config.audio.custom_encode_settings = self.audio_ffmpeg_reencode_options_value.get()
592
+
593
+ dialog_i18n = self.i18n.get('dialogs', {}).get('config_error', {})
594
+ error_title = dialog_i18n.get('title', 'Configuration Error')
396
595
 
397
596
  if config.features.backfill_audio and config.features.full_auto:
398
- messagebox.showerror("Configuration Error",
399
- "Cannot have Full Auto and Backfill mode on at the same time! Note: Backfill is a very niche workflow.")
597
+ messagebox.showerror(error_title,
598
+ dialog_i18n.get('full_auto_and_backfill', 'Cannot have Full Auto and Backfill...'))
400
599
  return
401
600
 
402
601
  if not config.general.use_websocket and not config.general.use_clipboard:
403
- messagebox.showerror("Configuration Error", "Cannot have both Clipboard and Websocket Disabled.")
602
+ messagebox.showerror(error_title, dialog_i18n.get('no_input_method', 'Cannot have both...'))
404
603
  return
405
604
 
406
605
  current_profile = self.profile_combobox.get()
407
606
  prev_config = self.master_config.get_config()
408
- self.master_config.switch_to_default_if_not_found = self.switch_to_default_if_not_found.get()
607
+ self.master_config.switch_to_default_if_not_found = self.switch_to_default_if_not_found_value.get()
409
608
  if profile_change:
410
609
  self.master_config.current_profile = current_profile
411
610
  else:
412
611
  self.master_config.current_profile = current_profile
413
612
  self.master_config.set_config_for_profile(current_profile, config)
613
+
614
+ self.master_config.locale = Locale[self.locale_value.get()].value
414
615
 
415
616
 
416
617
  config_backup_folder = os.path.join(get_app_directory(), "backup", "config")
417
618
  os.makedirs(config_backup_folder, exist_ok=True)
418
- # write a timesstamped backup of the current config before saving
419
619
  timestamp = time.strftime("%Y-%m-%d_%H-%M-%S")
420
620
  with open(os.path.join(config_backup_folder, f"config_backup_{timestamp}.json"), 'w') as backup_file:
421
621
  backup_file.write(self.master_config.to_json(indent=4))
@@ -425,7 +625,6 @@ class ConfigApp:
425
625
  if sync_changes:
426
626
  self.master_config.sync_changed_fields(prev_config)
427
627
 
428
- # Serialize the config instance to JSON
429
628
  with open(get_config_path(), 'w') as file:
430
629
  file.write(self.master_config.to_json(indent=4))
431
630
 
@@ -445,7 +644,8 @@ class ConfigApp:
445
644
  new_config = configuration.load_config()
446
645
  current_config = new_config.get_config()
447
646
 
448
- self.window.title("GameSentenceMiner Configuration - " + current_config.name)
647
+ title_template = self.i18n.get('app', {}).get('title_with_profile', 'GameSentenceMiner Configuration - {profile_name}')
648
+ self.window.title(title_template.format(profile_name=current_config.name))
449
649
 
450
650
  if current_config.name != self.settings.name or self.settings.config_changed(current_config) or force_refresh:
451
651
  logger.info("Config changed, reloading settings.")
@@ -454,7 +654,7 @@ class ConfigApp:
454
654
  for frame in self.notebook.winfo_children():
455
655
  frame.destroy()
456
656
 
457
- # Reset tab frames so they are recreated
657
+ self.required_settings_frame = None
458
658
  self.general_tab = None
459
659
  self.paths_tab = None
460
660
  self.anki_tab = None
@@ -466,161 +666,138 @@ class ConfigApp:
466
666
  self.profiles_tab = None
467
667
  self.ai_tab = None
468
668
  self.advanced_tab = None
669
+ self.wip_tab = None
469
670
 
671
+ self.create_vars()
470
672
  self.create_tabs()
471
673
 
472
674
  def increment_row(self):
473
- """Increment the current row index and return the new value."""
474
675
  self.current_row += 1
475
676
  return self.current_row
476
677
 
477
- def add_label_and_increment_row(self, root, label, row=0, column=0):
478
- HoverInfoWidget(root, label, row=self.current_row, column=column)
479
- self.increment_row()
480
-
481
- def add_label_without_row_increment(self, root, label, row=0, column=0):
482
- HoverInfoWidget(root, label, row=self.current_row, column=column)
483
-
484
678
  @new_tab
485
679
  def create_general_tab(self):
486
680
  if self.general_tab is None:
681
+ general_i18n = self.i18n.get('tabs', {}).get('general', {})
487
682
  self.general_tab = ttk.Frame(self.notebook, padding=15)
488
- self.notebook.add(self.general_tab, text='General')
683
+ self.notebook.add(self.general_tab, text=general_i18n.get('title', 'General'))
489
684
  else:
490
685
  for widget in self.general_tab.winfo_children():
491
686
  widget.destroy()
492
687
 
493
- HoverInfoLabelWidget(self.general_tab, text="Websocket Enabled:",
688
+ general_i18n = self.i18n.get('tabs', {}).get('general', {})
689
+
690
+ ws_i18n = general_i18n.get('websocket_enabled', {})
691
+ HoverInfoLabelWidget(self.general_tab, text=ws_i18n.get('label', 'Websocket Enabled:'),
494
692
  foreground="dark orange", font=("Helvetica", 10, "bold"),
495
- tooltip="Enable or disable WebSocket communication. Enabling this will disable the clipboard monitor.",
693
+ tooltip=ws_i18n.get('tooltip', '...'),
496
694
  row=self.current_row, column=0)
497
- self.websocket_enabled = tk.BooleanVar(value=self.settings.general.use_websocket)
498
- ttk.Checkbutton(self.general_tab, variable=self.websocket_enabled, bootstyle="round-toggle").grid(
499
- row=self.current_row, column=1,
500
- sticky='W', pady=2)
695
+ ttk.Checkbutton(self.general_tab, variable=self.websocket_enabled_value, bootstyle="round-toggle").grid(
696
+ row=self.current_row, column=1, sticky='W', pady=2)
501
697
  self.current_row += 1
502
698
 
503
- HoverInfoLabelWidget(self.general_tab, text="Clipboard Enabled:",
699
+ clip_i18n = general_i18n.get('clipboard_enabled', {})
700
+ HoverInfoLabelWidget(self.general_tab, text=clip_i18n.get('label', 'Clipboard Enabled:'),
504
701
  foreground="dark orange", font=("Helvetica", 10, "bold"),
505
- tooltip="Enable or disable Clipboard monitoring.", row=self.current_row, column=0)
506
- self.clipboard_enabled = tk.BooleanVar(value=self.settings.general.use_clipboard)
507
- ttk.Checkbutton(self.general_tab, variable=self.clipboard_enabled, bootstyle="round-toggle").grid(
508
- row=self.current_row, column=1,
509
- sticky='W', pady=2)
702
+ tooltip=clip_i18n.get('tooltip', '...'), row=self.current_row, column=0)
703
+ ttk.Checkbutton(self.general_tab, variable=self.clipboard_enabled_value, bootstyle="round-toggle").grid(
704
+ row=self.current_row, column=1, sticky='W', pady=2)
510
705
  self.current_row += 1
511
706
 
512
- HoverInfoLabelWidget(self.general_tab, text="Allow Both Simultaneously:",
707
+ both_i18n = general_i18n.get('allow_both_simultaneously', {})
708
+ HoverInfoLabelWidget(self.general_tab, text=both_i18n.get('label', 'Allow Both Simultaneously:'),
513
709
  foreground="red", font=("Helvetica", 10, "bold"),
514
- tooltip="Enable to allow GSM to accept both clipboard and websocket input at the same time.",
710
+ tooltip=both_i18n.get('tooltip', '...'),
515
711
  row=self.current_row, column=0)
516
- self.use_both_clipboard_and_websocket = tk.BooleanVar(
517
- value=self.settings.general.use_both_clipboard_and_websocket)
518
- ttk.Checkbutton(self.general_tab, variable=self.use_both_clipboard_and_websocket,
519
- bootstyle="round-toggle").grid(
520
- row=self.current_row, column=1,
521
- sticky='W', pady=2)
712
+ ttk.Checkbutton(self.general_tab, variable=self.use_both_clipboard_and_websocket_value, bootstyle="round-toggle").grid(
713
+ row=self.current_row, column=1, sticky='W', pady=2)
522
714
  self.current_row += 1
523
715
 
524
- HoverInfoLabelWidget(self.general_tab, text="Merge Matching Sequential Text:",
716
+ merge_i18n = general_i18n.get('merge_sequential_text', {})
717
+ HoverInfoLabelWidget(self.general_tab, text=merge_i18n.get('label', 'Merge Matching Sequential Text:'),
525
718
  foreground="red", font=("Helvetica", 10, "bold"),
526
- tooltip="Enable to merge matching sequential text into a single entry. Designed for Luna's Speech Recognition feature. Very niche.",
719
+ tooltip=merge_i18n.get('tooltip', '...'),
527
720
  row=self.current_row, column=0)
528
-
529
- self.merge_matching_sequential_text = tk.BooleanVar(
530
- value=self.settings.general.merge_matching_sequential_text)
531
- ttk.Checkbutton(self.general_tab, variable=self.merge_matching_sequential_text,
532
- bootstyle="round-toggle").grid(
533
- row=self.current_row, column=1,
534
- sticky='W', pady=2)
721
+ ttk.Checkbutton(self.general_tab, variable=self.merge_matching_sequential_text_value, bootstyle="round-toggle").grid(
722
+ row=self.current_row, column=1, sticky='W', pady=2)
535
723
  self.current_row += 1
536
724
 
537
- HoverInfoLabelWidget(self.general_tab, text="Websocket URI(s):",
538
- tooltip="WebSocket URI for connecting. Allows Comma Separated Values for Connecting Multiple.",
725
+ uri_i18n = general_i18n.get('websocket_uri', {})
726
+ HoverInfoLabelWidget(self.general_tab, text=uri_i18n.get('label', 'Websocket URI(s):'),
727
+ tooltip=uri_i18n.get('tooltip', '...'),
539
728
  row=self.current_row, column=0)
540
- self.websocket_uri = ttk.Entry(self.general_tab, width=50)
541
- self.websocket_uri.insert(0, self.settings.general.websocket_uri)
542
- self.websocket_uri.grid(row=self.current_row, column=1, sticky='EW', pady=2)
729
+ ttk.Entry(self.general_tab, width=50, textvariable=self.websocket_uri_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
543
730
  self.current_row += 1
544
731
 
545
- HoverInfoLabelWidget(self.general_tab, text="TextHook Replacement Regex:",
546
- tooltip="Regex to run replacement on texthook input, set this to the same as what you may have in your texthook page.",
732
+ regex_i18n = general_i18n.get('texthook_regex', {})
733
+ HoverInfoLabelWidget(self.general_tab, text=regex_i18n.get('label', 'TextHook Replacement Regex:'),
734
+ tooltip=regex_i18n.get('tooltip', '...'),
547
735
  row=self.current_row, column=0)
548
- self.texthook_replacement_regex = ttk.Entry(self.general_tab)
549
- self.texthook_replacement_regex.insert(0, self.settings.general.texthook_replacement_regex)
550
- self.texthook_replacement_regex.grid(row=self.current_row, column=1, sticky='EW', pady=2)
736
+ ttk.Entry(self.general_tab, textvariable=self.texthook_replacement_regex_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
551
737
  self.current_row += 1
552
738
 
553
- HoverInfoLabelWidget(self.general_tab, text="Open Config on Startup:",
554
- tooltip="Whether to open config when the script starts.", row=self.current_row, column=0)
555
- self.open_config_on_startup = tk.BooleanVar(value=self.settings.general.open_config_on_startup)
556
- ttk.Checkbutton(self.general_tab, variable=self.open_config_on_startup, bootstyle="round-toggle").grid(
557
- row=self.current_row, column=1,
558
- sticky='W', pady=2)
739
+ open_config_i18n = general_i18n.get('open_config_on_startup', {})
740
+ HoverInfoLabelWidget(self.general_tab, text=open_config_i18n.get('label', 'Open Config on Startup:'),
741
+ tooltip=open_config_i18n.get('tooltip', '...'), row=self.current_row, column=0)
742
+ ttk.Checkbutton(self.general_tab, variable=self.open_config_on_startup_value, bootstyle="round-toggle").grid(
743
+ row=self.current_row, column=1, sticky='W', pady=2)
559
744
  self.current_row += 1
560
745
 
561
- HoverInfoLabelWidget(self.general_tab, text="Open GSM Texthooker on Startup:",
562
- tooltip="Whether to open Texthooking page when the script starts.", row=self.current_row,
563
- column=0)
564
- self.open_multimine_on_startup = tk.BooleanVar(value=self.settings.general.open_multimine_on_startup)
565
- ttk.Checkbutton(self.general_tab, variable=self.open_multimine_on_startup, bootstyle="round-toggle").grid(
566
- row=self.current_row, column=1,
567
- sticky='W', pady=2)
746
+ open_texthooker_i18n = general_i18n.get('open_texthooker_on_startup', {})
747
+ HoverInfoLabelWidget(self.general_tab, text=open_texthooker_i18n.get('label', 'Open GSM Texthooker on Startup:'),
748
+ tooltip=open_texthooker_i18n.get('tooltip', '...'), row=self.current_row, column=0)
749
+ ttk.Checkbutton(self.general_tab, variable=self.open_multimine_on_startup_value, bootstyle="round-toggle").grid(
750
+ row=self.current_row, column=1, sticky='W', pady=2)
568
751
  self.current_row += 1
569
752
 
570
- HoverInfoLabelWidget(self.general_tab, text="GSM Texthooker Port:",
571
- tooltip="Port for the Texthooker to run on. Only change if you know what you are doing.",
753
+ port_i18n = general_i18n.get('texthooker_port', {})
754
+ HoverInfoLabelWidget(self.general_tab, text=port_i18n.get('label', 'GSM Texthooker Port:'),
755
+ tooltip=port_i18n.get('tooltip', '...'),
572
756
  row=self.current_row, column=0)
573
- self.texthooker_port = ttk.Entry(self.general_tab)
574
- self.texthooker_port.insert(0, str(self.settings.general.texthooker_port))
575
- self.texthooker_port.grid(row=self.current_row, column=1, sticky='EW', pady=2)
576
- self.current_row += 1
577
-
578
- HoverInfoLabelWidget(self.general_tab, text="Current Version:", bootstyle="secondary",
579
- tooltip="The current version of the application.", row=self.current_row, column=0)
580
- self.current_version = ttk.Label(self.general_tab, text=get_current_version(), bootstyle="secondary")
581
- self.current_version.grid(row=self.current_row, column=1, sticky='W', pady=2)
582
- self.current_row += 1
583
-
584
- HoverInfoLabelWidget(self.general_tab, text="Latest Version:", bootstyle="secondary",
585
- tooltip="The latest available version of the application.", row=self.current_row, column=0)
586
- self.latest_version = ttk.Label(self.general_tab, text=get_latest_version(), bootstyle="secondary")
587
- self.latest_version.grid(row=self.current_row, column=1, sticky='W', pady=2)
757
+ ttk.Entry(self.general_tab, textvariable=self.texthooker_port_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
588
758
  self.current_row += 1
589
759
 
590
- # Native Language Selection
591
- HoverInfoLabelWidget(self.general_tab, text="Native Language:",
592
- tooltip="Select your native language. This is used for various features, but will not change the look of GSM.",
760
+ # locale_i18n = general_i18n.get('locale', {})
761
+ # HoverInfoLabelWidget(self.general_tab, text=locale_i18n.get('label', 'Locale:'),
762
+ # tooltip=locale_i18n.get('tooltip', '...'),
763
+ # row=self.current_row, column=0)
764
+ # locale_combobox = ttk.Combobox(self.general_tab, textvariable=self.locale_value, values=[Locale.English.name, Locale.日本語.name, Locale.中文.name], state="readonly")
765
+ # locale_combobox.grid(row=self.current_row, column=1, sticky='EW', pady=2)
766
+ # locale_combobox.bind("<<ComboboxSelected>>", lambda e: self.change_locale())
767
+ # self.current_row += 1
768
+
769
+ lang_i18n = general_i18n.get('native_language', {})
770
+ HoverInfoLabelWidget(self.general_tab, text=lang_i18n.get('label', 'Native Language:'),
771
+ tooltip=lang_i18n.get('tooltip', '...'),
593
772
  row=self.current_row, column=0)
594
- self.native_language = ttk.Combobox(self.general_tab, values=CommonLanguages.get_all_names_pretty(), state="readonly")
595
- self.native_language.set(CommonLanguages.from_code(self.settings.general.native_language).name.replace('_', ' ').title())
596
- self.native_language.grid(row=self.current_row, column=1, sticky='EW', pady=2)
773
+ ttk.Combobox(self.general_tab, textvariable=self.native_language_value, values=CommonLanguages.get_all_names_pretty(), state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
597
774
  self.current_row += 1
598
775
 
599
- ttk.Label(self.general_tab, text="Indicates important/required settings.", foreground="dark orange",
776
+ legend_i18n = general_i18n.get('legend', {})
777
+ ttk.Label(self.general_tab, text=legend_i18n.get('important', '...'), foreground="dark orange",
600
778
  font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
601
779
  self.current_row += 1
602
- ttk.Label(self.general_tab, text="Highlights Advanced Features that may break things.", foreground="red",
780
+ ttk.Label(self.general_tab, text=legend_i18n.get('advanced', '...'), foreground="red",
603
781
  font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
604
782
  self.current_row += 1
605
- ttk.Label(self.general_tab, text="Indicates Recommended, but completely optional settings.", foreground="green",
783
+ ttk.Label(self.general_tab, text=legend_i18n.get('recommended', '...'), foreground="green",
606
784
  font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
607
785
  self.current_row += 1
608
786
  ttk.Label(self.general_tab,
609
- text="Every Label in settings has a tooltip with more information if you hover over them.",
787
+ text=legend_i18n.get('tooltip_info', '...'),
610
788
  font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
611
789
  self.current_row += 1
612
790
 
613
791
  if is_beangate:
614
- ttk.Button(self.general_tab, text="Run Function", command=self.test_func, bootstyle="info").grid(
792
+ ttk.Button(self.general_tab, text=self.i18n.get('buttons', {}).get('run_function', 'Run Function'), command=self.test_func, bootstyle="info").grid(
615
793
  row=self.current_row, column=0, pady=5
616
794
  )
617
795
  self.current_row += 1
618
796
 
619
- # Add Reset to Default button
620
797
  self.add_reset_button(self.general_tab, "general", self.current_row, column=0, recreate_tab=self.create_general_tab)
621
798
 
622
- self.general_tab.grid_columnconfigure(0, weight=0) # No expansion for the label column
623
- self.general_tab.grid_columnconfigure(1, weight=0) # Entry column gets more space
799
+ self.general_tab.grid_columnconfigure(0, weight=0)
800
+ self.general_tab.grid_columnconfigure(1, weight=0)
624
801
  for row in range(self.current_row):
625
802
  self.general_tab.grid_rowconfigure(row, minsize=30)
626
803
 
@@ -628,351 +805,484 @@ class ConfigApp:
628
805
 
629
806
  @new_tab
630
807
  def create_required_settings_tab(self):
631
- required_settings_frame = ttk.Frame(self.notebook)
632
- self.notebook.add(required_settings_frame, text='Required Settings')
808
+ if self.required_settings_frame is None:
809
+ self.required_settings_frame = ttk.Frame(self.notebook, padding=15)
810
+ self.notebook.add(self.required_settings_frame, text=self.i18n.get('tabs', {}).get('key_settings', {}).get('title', 'Key Settings'))
811
+ else:
812
+ for widget in self.required_settings_frame.winfo_children():
813
+ widget.destroy()
814
+ required_settings_frame = self.required_settings_frame
815
+
816
+ simple_i18n = self.i18n.get('tabs', {}).get('simple', {})
817
+
818
+ # key_settings_i18n = simple_i18n.get('key_settings', {})
819
+ # HoverInfoLabelWidget(required_settings_frame, text=key_settings_i18n.get('label', 'Key Settings'),
820
+ # tooltip=key_settings_i18n.get('tooltip', "These settings are important..."),
821
+ # row=self.current_row, column=0, columnspan=4, font=("Helvetica", 12, "bold"))
822
+ # self.current_row += 1
823
+
824
+ # --- General Settings ---
825
+ general_i18n = self.i18n.get('tabs', {}).get('general', {})
826
+ input_frame = ttk.Frame(required_settings_frame)
827
+ input_frame.grid(row=self.current_row, column=0, columnspan=4, sticky='W', pady=2)
828
+
829
+ ws_i18n = general_i18n.get('websocket_enabled', {})
830
+ HoverInfoLabelWidget(input_frame, text=ws_i18n.get('label', '...'),
831
+ tooltip=ws_i18n.get('tooltip', '...'), row=0, column=0)
832
+ ttk.Checkbutton(input_frame, variable=self.websocket_enabled_value, bootstyle="round-toggle").grid(row=0, column=1, sticky='W', pady=2)
833
+
834
+ clip_i18n = general_i18n.get('clipboard_enabled', {})
835
+ HoverInfoLabelWidget(input_frame, text=clip_i18n.get('label', '...'),
836
+ tooltip=clip_i18n.get('tooltip', '...'), row=0, column=2, padx=5)
837
+ ttk.Checkbutton(input_frame, variable=self.clipboard_enabled_value, bootstyle="round-toggle").grid(row=0, column=3, sticky='W', pady=2)
838
+ self.current_row += 1
839
+
840
+ uri_i18n = general_i18n.get('websocket_uri', {})
841
+ HoverInfoLabelWidget(required_settings_frame, text=uri_i18n.get('label', '...'),
842
+ tooltip=uri_i18n.get('tooltip', '...'), row=self.current_row, column=0)
843
+ ttk.Entry(required_settings_frame, width=50, textvariable=self.websocket_uri_value).grid(row=self.current_row, column=1, columnspan=3, sticky='EW', pady=2)
844
+ self.current_row += 1
845
+
846
+ locale_i18n = general_i18n.get('locale', {})
847
+ HoverInfoLabelWidget(required_settings_frame, text=locale_i18n.get('label', '...'),
848
+ tooltip=locale_i18n.get('tooltip', '...'), row=self.current_row, column=0)
849
+ locale_combobox_simple = ttk.Combobox(required_settings_frame, textvariable=self.locale_value, values=[Locale.English.name, Locale.日本語.name, Locale.中文.name], state="readonly")
850
+ locale_combobox_simple.grid(row=self.current_row, column=1, columnspan=3, sticky='EW', pady=2)
851
+ locale_combobox_simple.bind("<<ComboboxSelected>>", lambda e: self.change_locale())
852
+ self.current_row += 1
853
+
854
+ # --- Paths Settings ---
855
+ paths_i18n = self.i18n.get('tabs', {}).get('paths', {})
856
+ browse_text = self.i18n.get('buttons', {}).get('browse', 'Browse')
857
+ watch_i18n = paths_i18n.get('folder_to_watch', {})
858
+ HoverInfoLabelWidget(required_settings_frame, text=watch_i18n.get('label', '...'),
859
+ tooltip=watch_i18n.get('tooltip', '...'), row=self.current_row, column=0)
860
+ folder_watch_entry = ttk.Entry(required_settings_frame, width=50, textvariable=self.folder_to_watch_value)
861
+ folder_watch_entry.grid(row=self.current_row, column=1, columnspan=2, sticky='EW', pady=2)
862
+ ttk.Button(required_settings_frame, text=browse_text, command=lambda: self.browse_folder(folder_watch_entry),
863
+ bootstyle="outline").grid(row=self.current_row, column=3, padx=5, pady=2)
864
+ self.current_row += 1
865
+
866
+ # --- Anki Settings ---
867
+ anki_i18n = self.i18n.get('tabs', {}).get('anki', {})
868
+ sentence_i18n = anki_i18n.get('sentence_field', {})
869
+ HoverInfoLabelWidget(required_settings_frame, text=sentence_i18n.get('label', '...'),
870
+ tooltip=sentence_i18n.get('tooltip', '...'), row=self.current_row, column=0)
871
+ ttk.Entry(required_settings_frame, textvariable=self.sentence_field_value).grid(row=self.current_row, column=1, columnspan=3, sticky='EW', pady=2)
872
+ self.current_row += 1
873
+
874
+ audio_i18n = anki_i18n.get('sentence_audio_field', {})
875
+ HoverInfoLabelWidget(required_settings_frame, text=audio_i18n.get('label', '...'),
876
+ tooltip=audio_i18n.get('tooltip', '...'), row=self.current_row, column=0)
877
+ ttk.Entry(required_settings_frame, textvariable=self.sentence_audio_field_value).grid(row=self.current_row, column=1, columnspan=3, sticky='EW', pady=2)
878
+ self.current_row += 1
879
+
880
+ pic_i18n = anki_i18n.get('picture_field', {})
881
+ HoverInfoLabelWidget(required_settings_frame, text=pic_i18n.get('label', '...'),
882
+ tooltip=pic_i18n.get('tooltip', '...'), row=self.current_row, column=0)
883
+ ttk.Entry(required_settings_frame, textvariable=self.picture_field_value).grid(row=self.current_row, column=1, columnspan=3, sticky='EW', pady=2)
884
+ self.current_row += 1
885
+
886
+ word_i18n = anki_i18n.get('word_field', {})
887
+ HoverInfoLabelWidget(required_settings_frame, text=word_i18n.get('label', '...'),
888
+ tooltip=word_i18n.get('tooltip', '...'), row=self.current_row, column=0)
889
+ ttk.Entry(required_settings_frame, textvariable=self.word_field_value).grid(row=self.current_row, column=1, columnspan=3, sticky='EW', pady=2)
890
+ self.current_row += 1
891
+
892
+ # --- Audio Settings ---
893
+ audio_tab_i18n = self.i18n.get('tabs', {}).get('audio', {})
894
+ begin_offset_i18n = audio_tab_i18n.get('beginning_offset', {})
895
+ HoverInfoLabelWidget(required_settings_frame, text=begin_offset_i18n.get('label', '...'),
896
+ tooltip=begin_offset_i18n.get('tooltip', '...'), row=self.current_row, column=0)
897
+ ttk.Entry(required_settings_frame, textvariable=self.beginning_offset_value).grid(row=self.current_row, column=1, columnspan=3, sticky='EW', pady=2)
898
+ self.current_row += 1
899
+
900
+ # Vad end offset
901
+
902
+ vad_i18n = self.i18n.get('tabs', {}).get('vad', {})
903
+ vad_end_offset_i18n = vad_i18n.get('audio_end_offset', {})
904
+ HoverInfoLabelWidget(required_settings_frame, text=vad_end_offset_i18n.get('label', '...'),
905
+ tooltip=vad_end_offset_i18n.get('tooltip', '...'), row=self.current_row, column=0)
906
+ ttk.Entry(required_settings_frame, textvariable=self.end_offset_value).grid(row=self.current_row, column=1, columnspan=3, sticky='EW', pady=2)
907
+ self.current_row += 1
908
+
909
+ # --- Features Settings ---
910
+ features_i18n = self.i18n.get('tabs', {}).get('features', {})
911
+ # action_frame = ttk.Frame(required_settings_frame)
912
+ # action_frame.grid(row=self.current_row, column=0, columnspan=4, sticky='W', pady=2)
913
+
914
+ # Feature Toggles, Anki, OBS, Audio, VAD, AI, WIP
915
+ feature_frame = ttk.Frame(required_settings_frame)
916
+ feature_frame.grid(row=self.current_row, column=0, columnspan=5, sticky='W', pady=2)
917
+
918
+ # anki_enabled_i18n = simple_i18n.get('anki_enabled', {})
919
+ # HoverInfoLabelWidget(feature_frame, text=anki_enabled_i18n.get('label', '...'),
920
+ # tooltip=anki_enabled_i18n.get('tooltip', '...'), row=self.current_row, column=0)
921
+ # ttk.Checkbutton(feature_frame, variable=self.update_anki_value, bootstyle="round-toggle").grid(
922
+ # row=self.current_row, column=1, sticky='W', pady=2)
923
+
924
+ open_edit_i18n = features_i18n.get('open_anki_edit', {})
925
+ HoverInfoLabelWidget(feature_frame, text=open_edit_i18n.get('label', '...'),
926
+ tooltip=open_edit_i18n.get('tooltip', '...'), row=self.current_row, column=0)
927
+ ttk.Checkbutton(feature_frame, variable=self.open_anki_edit_value, bootstyle="round-toggle").grid(
928
+ row=self.current_row, column=1, sticky='W', pady=2)
929
+
930
+ open_browser_i18n = features_i18n.get('open_anki_browser', {})
931
+ HoverInfoLabelWidget(feature_frame, text=open_browser_i18n.get('label', '...'),
932
+ tooltip=open_browser_i18n.get('tooltip', '...'), row=self.current_row, column=2, padx=5)
933
+ ttk.Checkbutton(feature_frame, variable=self.open_anki_browser_value, bootstyle="round-toggle").grid(
934
+ row=self.current_row, column=3, sticky='W', pady=2)
935
+ self.current_row += 1
936
+
937
+ # obs_enabled_i18n = simple_i18n.get('obs_enabled', {})
938
+ # HoverInfoLabelWidget(feature_frame, text=obs_enabled_i18n.get('label', '...'),
939
+ # tooltip=obs_enabled_i18n.get('tooltip', '...'), row=self.current_row, column=0)
940
+ # ttk.Checkbutton(feature_frame, variable=self.obs_enabled_value, bootstyle="round-toggle").grid(
941
+ # row=self.current_row, column=1, sticky='W', pady=2)
942
+ # self.current_row += 1
943
+
944
+ # screenshot_i18n = simple_i18n.get('screenshot_enabled', {})
945
+ # HoverInfoLabelWidget(feature_frame, text=screenshot_i18n.get('label', '...'),
946
+ # tooltip=screenshot_i18n.get('tooltip', '...'), row=self.current_row, column=0)
947
+ # ttk.Checkbutton(feature_frame, variable=self.screenshot_enabled_value, bootstyle="round-toggle").grid(
948
+ # row=self.current_row, column=1, sticky='W', pady=2)
949
+ # self.current_row += 1
950
+
951
+ # audio_i18n = self.i18n.get('tabs', {}).get('audio', {})
952
+ # audio_enabled_i18n = simple_i18n.get('audio_enabled', {})
953
+ # HoverInfoLabelWidget(feature_frame, text=audio_enabled_i18n.get('label', '...'),
954
+ # tooltip=audio_enabled_i18n.get('tooltip', '...'), row=self.current_row, column=0)
955
+ # ttk.Checkbutton(feature_frame, variable=self.audio_enabled_value, bootstyle="round-toggle").grid(
956
+ # row=self.current_row, column=1, sticky='W', pady=2)
957
+ # self.current_row += 1
958
+
959
+ # vad_i18n = self.i18n.get('tabs', {}).get('vad', {})
960
+ # vad_enabled_i18n = simple_i18n.get('vad_enabled', {})
961
+ # HoverInfoLabelWidget(feature_frame, text=vad_enabled_i18n.get('label', '...'),
962
+ # tooltip=vad_enabled_i18n.get('tooltip', '...'), row=self.current_row, column=0)
963
+ # ttk.Checkbutton(feature_frame, variable=self.do_vad_postprocessing_value, bootstyle="round-toggle").grid(
964
+ # row=self.current_row, column=1, sticky='W', pady=2)
965
+ # self.current_row += 1
966
+
967
+ # ai_i18n = self.i18n.get('tabs', {}).get('ai', {})
968
+ # ai_enabled_i18n = simple_i18n.get('ai_enabled', {})
969
+ # HoverInfoLabelWidget(feature_frame, text=ai_enabled_i18n.get('label', '...'),
970
+ # tooltip=ai_enabled_i18n.get('tooltip', '...'), row=self.current_row, column=0)
971
+ # ttk.Checkbutton(feature_frame, variable=self.ai_enabled_value, bootstyle="round-toggle").grid(
972
+ # row=self.current_row, column=1, sticky='W', pady=2)
973
+
974
+ required_settings_frame.grid_columnconfigure(1, weight=1)
975
+
633
976
  return required_settings_frame
634
977
 
635
978
  @new_tab
636
979
  def create_vad_tab(self):
637
980
  if self.vad_tab is None:
981
+ vad_i18n = self.i18n.get('tabs', {}).get('vad', {})
638
982
  self.vad_tab = ttk.Frame(self.notebook, padding=15)
639
- self.notebook.add(self.vad_tab, text='VAD')
983
+ self.notebook.add(self.vad_tab, text=vad_i18n.get('title', 'VAD'))
640
984
  else:
641
985
  for widget in self.vad_tab.winfo_children():
642
986
  widget.destroy()
643
987
 
644
988
  vad_frame = self.vad_tab
989
+ vad_i18n = self.i18n.get('tabs', {}).get('vad', {})
645
990
 
646
- HoverInfoLabelWidget(vad_frame, text="Voice Detection Postprocessing:",
647
- tooltip="Enable post-processing of audio to trim just the voiceline.",
991
+ postproc_i18n = vad_i18n.get('do_postprocessing', {})
992
+ HoverInfoLabelWidget(vad_frame, text=postproc_i18n.get('label', '...'),
993
+ tooltip=postproc_i18n.get('tooltip', '...'),
648
994
  row=self.current_row, column=0)
649
- self.do_vad_postprocessing = tk.BooleanVar(
650
- value=self.settings.vad.do_vad_postprocessing)
651
- ttk.Checkbutton(vad_frame, variable=self.do_vad_postprocessing, bootstyle="round-toggle").grid(
995
+ ttk.Checkbutton(vad_frame, variable=self.do_vad_postprocessing_value, bootstyle="round-toggle").grid(
652
996
  row=self.current_row, column=1, sticky='W', pady=2)
653
997
  self.current_row += 1
654
998
 
655
- HoverInfoLabelWidget(vad_frame, text="Language:",
656
- tooltip="Select the language for VAD. This is used for Whisper Only.",
999
+ lang_i18n = vad_i18n.get('language', {})
1000
+ HoverInfoLabelWidget(vad_frame, text=lang_i18n.get('label', '...'),
1001
+ tooltip=lang_i18n.get('tooltip', '...'),
657
1002
  row=self.current_row, column=0)
658
- self.language = ttk.Combobox(vad_frame, values=AVAILABLE_LANGUAGES, state="readonly")
659
- self.language.set(self.settings.vad.language)
660
- self.language.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1003
+ ttk.Combobox(vad_frame, textvariable=self.language_value, values=AVAILABLE_LANGUAGES, state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
661
1004
  self.current_row += 1
662
1005
 
663
- HoverInfoLabelWidget(vad_frame, text="Whisper Model:", tooltip="Select the Whisper model size for VAD.",
1006
+ whisper_i18n = vad_i18n.get('whisper_model', {})
1007
+ HoverInfoLabelWidget(vad_frame, text=whisper_i18n.get('label', '...'), tooltip=whisper_i18n.get('tooltip', '...'),
664
1008
  row=self.current_row, column=0)
665
- self.whisper_model = ttk.Combobox(vad_frame, values=[WHISPER_TINY, WHISPER_BASE, WHISPER_SMALL, WHISPER_MEDIUM,
666
- WHSIPER_LARGE, WHISPER_TURBO], state="readonly")
667
- self.whisper_model.set(self.settings.vad.whisper_model)
668
- self.whisper_model.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1009
+ ttk.Combobox(vad_frame, textvariable=self.whisper_model_value, values=[WHISPER_TINY, WHISPER_BASE, WHISPER_SMALL, WHISPER_MEDIUM,
1010
+ WHSIPER_LARGE, WHISPER_TURBO], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
669
1011
  self.current_row += 1
670
1012
 
671
- HoverInfoLabelWidget(vad_frame, text="Select VAD Model:", tooltip="Select which VAD model to use.",
1013
+ selected_model_i18n = vad_i18n.get('selected_model', {})
1014
+ HoverInfoLabelWidget(vad_frame, text=selected_model_i18n.get('label', '...'), tooltip=selected_model_i18n.get('tooltip', '...'),
672
1015
  foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
673
- self.selected_vad_model = ttk.Combobox(vad_frame, values=[SILERO, WHISPER], state="readonly")
674
- self.selected_vad_model.set(self.settings.vad.selected_vad_model)
675
- self.selected_vad_model.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1016
+ ttk.Combobox(vad_frame, textvariable=self.selected_vad_model_value, values=[SILERO, WHISPER], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
676
1017
  self.current_row += 1
677
1018
 
678
- HoverInfoLabelWidget(vad_frame, text="Backup VAD Model:",
679
- tooltip="Select which model to use as a backup if no audio is found.",
1019
+ backup_model_i18n = vad_i18n.get('backup_model', {})
1020
+ HoverInfoLabelWidget(vad_frame, text=backup_model_i18n.get('label', '...'),
1021
+ tooltip=backup_model_i18n.get('tooltip', '...'),
680
1022
  row=self.current_row, column=0)
681
- self.backup_vad_model = ttk.Combobox(vad_frame, values=[OFF, SILERO, WHISPER], state="readonly")
682
- self.backup_vad_model.set(self.settings.vad.backup_vad_model)
683
- self.backup_vad_model.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1023
+ ttk.Combobox(vad_frame, textvariable=self.backup_vad_model_value, values=[OFF, SILERO, WHISPER], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
684
1024
  self.current_row += 1
685
1025
 
686
- HoverInfoLabelWidget(vad_frame, text="Add Audio on No Results:",
687
- tooltip="Add audio even if no results are found by VAD.", row=self.current_row, column=0)
688
- self.add_audio_on_no_results = tk.BooleanVar(value=self.settings.vad.add_audio_on_no_results)
689
- ttk.Checkbutton(vad_frame, variable=self.add_audio_on_no_results, bootstyle="round-toggle").grid(
1026
+ no_results_i18n = vad_i18n.get('add_on_no_results', {})
1027
+ HoverInfoLabelWidget(vad_frame, text=no_results_i18n.get('label', '...'),
1028
+ tooltip=no_results_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1029
+ ttk.Checkbutton(vad_frame, variable=self.add_audio_on_no_results_value, bootstyle="round-toggle").grid(
690
1030
  row=self.current_row, column=1, sticky='W', pady=2)
691
1031
  self.current_row += 1
692
1032
 
693
- HoverInfoLabelWidget(vad_frame, text="Audio End Offset:",
694
- tooltip="Offset in seconds from end of the video to extract.", foreground="dark orange",
1033
+ end_offset_i18n = vad_i18n.get('audio_end_offset', {})
1034
+ HoverInfoLabelWidget(vad_frame, text=end_offset_i18n.get('label', '...'),
1035
+ tooltip=end_offset_i18n.get('tooltip', '...'), foreground="dark orange",
695
1036
  font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
696
- self.end_offset = ttk.Entry(vad_frame)
697
- self.end_offset.insert(0, str(self.settings.audio.end_offset))
698
- self.end_offset.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1037
+ ttk.Entry(vad_frame, textvariable=self.end_offset_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
699
1038
  self.current_row += 1
700
1039
 
701
- HoverInfoLabelWidget(vad_frame, text="Trim Beginning:",
702
- tooltip='Beginning offset after VAD Trim, Only active if "Trim Beginning" is ON. Negative values = more time at the beginning',
1040
+ trim_begin_i18n = vad_i18n.get('trim_beginning', {})
1041
+ HoverInfoLabelWidget(vad_frame, text=trim_begin_i18n.get('label', '...'),
1042
+ tooltip=trim_begin_i18n.get('tooltip', '...'),
703
1043
  row=self.current_row, column=0)
704
- self.vad_trim_beginning = tk.BooleanVar(
705
- value=self.settings.vad.trim_beginning)
706
- ttk.Checkbutton(vad_frame, variable=self.vad_trim_beginning, bootstyle="round-toggle").grid(
1044
+ ttk.Checkbutton(vad_frame, variable=self.vad_trim_beginning_value, bootstyle="round-toggle").grid(
707
1045
  row=self.current_row, column=1, sticky='W', pady=2)
708
1046
 
709
- HoverInfoLabelWidget(vad_frame, text="Beginning Offset:",
710
- tooltip='Beginning offset after VAD Trim, Only active if "Trim Beginning" is ON. Negative values = more time at the beginning',
1047
+ begin_offset_i18n = vad_i18n.get('beginning_offset', {})
1048
+ HoverInfoLabelWidget(vad_frame, text=begin_offset_i18n.get('label', '...'),
1049
+ tooltip=begin_offset_i18n.get('tooltip', '...'),
711
1050
  row=self.current_row, column=2)
712
- self.vad_beginning_offset = ttk.Entry(vad_frame)
713
- self.vad_beginning_offset.insert(0, str(self.settings.vad.beginning_offset))
714
- self.vad_beginning_offset.grid(row=self.current_row, column=3, sticky='EW', pady=2)
1051
+ ttk.Entry(vad_frame, textvariable=self.vad_beginning_offset_value).grid(row=self.current_row, column=3, sticky='EW', pady=2)
715
1052
  self.current_row += 1
716
1053
 
717
- HoverInfoLabelWidget(vad_frame, text="Cut and Splice Segments:",
718
- tooltip="Cut Detected Voice Segments and Paste them back together. More Padding = More Space between voicelines.",
1054
+ splice_i18n = vad_i18n.get('cut_and_splice', {})
1055
+ HoverInfoLabelWidget(vad_frame, text=splice_i18n.get('label', '...'),
1056
+ tooltip=splice_i18n.get('tooltip', '...'),
719
1057
  row=self.current_row, column=0)
720
- self.cut_and_splice_segments = tk.BooleanVar(value=self.settings.vad.cut_and_splice_segments)
721
- ttk.Checkbutton(vad_frame, variable=self.cut_and_splice_segments, bootstyle="round-toggle").grid(
1058
+ ttk.Checkbutton(vad_frame, variable=self.cut_and_splice_segments_value, bootstyle="round-toggle").grid(
722
1059
  row=self.current_row, column=1, sticky='W', pady=2)
723
- HoverInfoLabelWidget(vad_frame, text="Padding:",
724
- tooltip="Cut Detected Voice Segments and Paste them back together. More Padding = More Space between voicelines.",
1060
+
1061
+ padding_i18n = vad_i18n.get('splice_padding', {})
1062
+ HoverInfoLabelWidget(vad_frame, text=padding_i18n.get('label', '...'),
1063
+ tooltip=padding_i18n.get('tooltip', '...'),
725
1064
  row=self.current_row, column=2)
726
- self.splice_padding = ttk.Entry(vad_frame)
727
- self.splice_padding.insert(0, str(self.settings.vad.splice_padding))
728
- self.splice_padding.grid(row=self.current_row, column=3, sticky='EW', pady=2)
1065
+ ttk.Entry(vad_frame, textvariable=self.splice_padding_value).grid(row=self.current_row, column=3, sticky='EW', pady=2)
729
1066
  self.current_row += 1
730
1067
 
731
1068
  self.add_reset_button(vad_frame, "vad", self.current_row, 0, self.create_vad_tab)
732
1069
 
733
- for col in range(5):
734
- vad_frame.grid_columnconfigure(col, weight=0)
735
-
736
- for row in range(self.current_row):
737
- vad_frame.grid_rowconfigure(row, minsize=30)
1070
+ for col in range(5): vad_frame.grid_columnconfigure(col, weight=0)
1071
+ for row in range(self.current_row): vad_frame.grid_rowconfigure(row, minsize=30)
738
1072
 
739
1073
  return vad_frame
740
1074
 
741
1075
  @new_tab
742
1076
  def create_paths_tab(self):
743
1077
  if self.paths_tab is None:
1078
+ paths_i18n = self.i18n.get('tabs', {}).get('paths', {})
744
1079
  self.paths_tab = ttk.Frame(self.notebook, padding=15)
745
- self.notebook.add(self.paths_tab, text='Paths')
1080
+ self.notebook.add(self.paths_tab, text=paths_i18n.get('title', 'Paths'))
746
1081
  else:
747
1082
  for widget in self.paths_tab.winfo_children():
748
1083
  widget.destroy()
749
1084
 
750
1085
  paths_frame = self.paths_tab
1086
+ paths_i18n = self.i18n.get('tabs', {}).get('paths', {})
1087
+ browse_text = self.i18n.get('buttons', {}).get('browse', 'Browse')
751
1088
 
752
- HoverInfoLabelWidget(paths_frame, text="Folder to Watch:", tooltip="Path where the OBS Replays will be saved.",
1089
+ watch_i18n = paths_i18n.get('folder_to_watch', {})
1090
+ HoverInfoLabelWidget(paths_frame, text=watch_i18n.get('label', '...'), tooltip=watch_i18n.get('tooltip', '...'),
753
1091
  foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
754
- self.folder_to_watch = ttk.Entry(paths_frame, width=50)
755
- self.folder_to_watch.insert(0, self.settings.paths.folder_to_watch)
756
- self.folder_to_watch.grid(row=self.current_row, column=1, sticky='W', pady=2)
757
- ttk.Button(paths_frame, text="Browse", command=lambda: self.browse_folder(self.folder_to_watch),
758
- bootstyle="outline").grid(
759
- row=self.current_row,
760
- column=2, padx=5, pady=2)
1092
+ folder_watch_entry = ttk.Entry(paths_frame, width=50, textvariable=self.folder_to_watch_value)
1093
+ folder_watch_entry.grid(row=self.current_row, column=1, sticky='W', pady=2)
1094
+ ttk.Button(paths_frame, text=browse_text, command=lambda: self.browse_folder(folder_watch_entry),
1095
+ bootstyle="outline").grid(row=self.current_row, column=2, padx=5, pady=2)
761
1096
  self.current_row += 1
762
1097
 
763
- HoverInfoLabelWidget(paths_frame, text="Audio Destination:", tooltip="Path where the cut Audio will be saved.",
1098
+ audio_dest_i18n = paths_i18n.get('audio_destination', {})
1099
+ HoverInfoLabelWidget(paths_frame, text=audio_dest_i18n.get('label', '...'), tooltip=audio_dest_i18n.get('tooltip', '...'),
764
1100
  foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
765
- self.audio_destination = ttk.Entry(paths_frame, width=50)
766
- self.audio_destination.insert(0, self.settings.paths.audio_destination)
767
- self.audio_destination.grid(row=self.current_row, column=1, sticky='W', pady=2)
768
- ttk.Button(paths_frame, text="Browse", command=lambda: self.browse_folder(self.audio_destination),
769
- bootstyle="outline").grid(
770
- row=self.current_row,
771
- column=2, padx=5, pady=2)
1101
+ audio_dest_entry = ttk.Entry(paths_frame, width=50, textvariable=self.audio_destination_value)
1102
+ audio_dest_entry.grid(row=self.current_row, column=1, sticky='W', pady=2)
1103
+ ttk.Button(paths_frame, text=browse_text, command=lambda: self.browse_folder(audio_dest_entry),
1104
+ bootstyle="outline").grid(row=self.current_row, column=2, padx=5, pady=2)
772
1105
  self.current_row += 1
773
1106
 
774
- HoverInfoLabelWidget(paths_frame, text="Screenshot Destination:",
775
- tooltip="Path where the Screenshot will be saved.", foreground="dark orange",
1107
+ ss_dest_i18n = paths_i18n.get('screenshot_destination', {})
1108
+ HoverInfoLabelWidget(paths_frame, text=ss_dest_i18n.get('label', '...'),
1109
+ tooltip=ss_dest_i18n.get('tooltip', '...'), foreground="dark orange",
776
1110
  font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
777
- self.screenshot_destination = ttk.Entry(paths_frame, width=50)
778
- self.screenshot_destination.insert(0, self.settings.paths.screenshot_destination)
779
- self.screenshot_destination.grid(row=self.current_row, column=1, sticky='W', pady=2)
780
- ttk.Button(paths_frame, text="Browse", command=lambda: self.browse_folder(self.screenshot_destination),
781
- bootstyle="outline").grid(
782
- row=self.current_row, column=2, padx=5, pady=2)
1111
+ ss_dest_entry = ttk.Entry(paths_frame, width=50, textvariable=self.screenshot_destination_value)
1112
+ ss_dest_entry.grid(row=self.current_row, column=1, sticky='W', pady=2)
1113
+ ttk.Button(paths_frame, text=browse_text, command=lambda: self.browse_folder(ss_dest_entry),
1114
+ bootstyle="outline").grid(row=self.current_row, column=2, padx=5, pady=2)
783
1115
  self.current_row += 1
784
1116
 
785
- HoverInfoLabelWidget(paths_frame, text="Remove Video:", tooltip="Remove video from the output.",
1117
+ rm_vid_i18n = paths_i18n.get('remove_video', {})
1118
+ HoverInfoLabelWidget(paths_frame, text=rm_vid_i18n.get('label', '...'), tooltip=rm_vid_i18n.get('tooltip', '...'),
786
1119
  row=self.current_row, column=0)
787
- self.remove_video = tk.BooleanVar(value=self.settings.paths.remove_video)
788
- ttk.Checkbutton(paths_frame, variable=self.remove_video, bootstyle="round-toggle").grid(row=self.current_row,
789
- column=1, sticky='W',
790
- pady=2)
1120
+ ttk.Checkbutton(paths_frame, variable=self.remove_video_value, bootstyle="round-toggle").grid(row=self.current_row,
1121
+ column=1, sticky='W', pady=2)
791
1122
  self.current_row += 1
792
1123
 
793
- HoverInfoLabelWidget(paths_frame, text="Remove Audio:", tooltip="Remove audio from the output.",
1124
+ rm_audio_i18n = paths_i18n.get('remove_audio', {})
1125
+ HoverInfoLabelWidget(paths_frame, text=rm_audio_i18n.get('label', '...'), tooltip=rm_audio_i18n.get('tooltip', '...'),
794
1126
  row=self.current_row, column=0)
795
- self.remove_audio = tk.BooleanVar(value=self.settings.paths.remove_audio)
796
- ttk.Checkbutton(paths_frame, variable=self.remove_audio, bootstyle="round-toggle").grid(row=self.current_row,
797
- column=1, sticky='W',
798
- pady=2)
1127
+ ttk.Checkbutton(paths_frame, variable=self.remove_audio_value, bootstyle="round-toggle").grid(row=self.current_row,
1128
+ column=1, sticky='W', pady=2)
799
1129
  self.current_row += 1
800
1130
 
801
- HoverInfoLabelWidget(paths_frame, text="Remove Screenshot:", tooltip="Remove screenshots after processing.",
1131
+ rm_ss_i18n = paths_i18n.get('remove_screenshot', {})
1132
+ HoverInfoLabelWidget(paths_frame, text=rm_ss_i18n.get('label', '...'), tooltip=rm_ss_i18n.get('tooltip', '...'),
802
1133
  row=self.current_row, column=0)
803
- self.remove_screenshot = tk.BooleanVar(value=self.settings.paths.remove_screenshot)
804
- ttk.Checkbutton(paths_frame, variable=self.remove_screenshot, bootstyle="round-toggle").grid(
1134
+ ttk.Checkbutton(paths_frame, variable=self.remove_screenshot_value, bootstyle="round-toggle").grid(
805
1135
  row=self.current_row, column=1, sticky='W', pady=2)
806
1136
  self.current_row += 1
807
1137
 
808
1138
  self.add_reset_button(paths_frame, "paths", self.current_row, 0, self.create_paths_tab)
809
1139
 
810
- paths_frame.grid_columnconfigure(0, weight=0)
811
- paths_frame.grid_columnconfigure(1, weight=0)
812
- paths_frame.grid_columnconfigure(2, weight=0)
813
-
814
- for row in range(self.current_row):
815
- paths_frame.grid_rowconfigure(row, minsize=30)
1140
+ for col in range(3): paths_frame.grid_columnconfigure(col, weight=0)
1141
+ for row in range(self.current_row): paths_frame.grid_rowconfigure(row, minsize=30)
816
1142
 
817
1143
  return paths_frame
818
1144
 
819
1145
  def browse_file(self, entry_widget):
820
1146
  file_selected = filedialog.askopenfilename()
821
1147
  if file_selected:
1148
+ # The entry widget's textvariable will be updated automatically
822
1149
  entry_widget.delete(0, tk.END)
823
1150
  entry_widget.insert(0, file_selected)
824
1151
 
825
1152
  def browse_folder(self, entry_widget):
826
1153
  folder_selected = filedialog.askdirectory()
827
1154
  if folder_selected:
1155
+ # The entry widget's textvariable will be updated automatically
828
1156
  entry_widget.delete(0, tk.END)
829
1157
  entry_widget.insert(0, folder_selected)
830
1158
 
831
1159
  @new_tab
832
1160
  def create_anki_tab(self):
833
1161
  if self.anki_tab is None:
1162
+ anki_i18n = self.i18n.get('tabs', {}).get('anki', {})
834
1163
  self.anki_tab = ttk.Frame(self.notebook, padding=15)
835
- self.notebook.add(self.anki_tab, text='Anki')
1164
+ self.notebook.add(self.anki_tab, text=anki_i18n.get('title', 'Anki'))
836
1165
  else:
837
1166
  for widget in self.anki_tab.winfo_children():
838
1167
  widget.destroy()
839
1168
 
840
1169
  anki_frame = self.anki_tab
1170
+ anki_i18n = self.i18n.get('tabs', {}).get('anki', {})
841
1171
 
842
- HoverInfoLabelWidget(anki_frame, text="Update Anki:", tooltip="Automatically update Anki with new data.",
1172
+ update_i18n = anki_i18n.get('update_anki', {})
1173
+ HoverInfoLabelWidget(anki_frame, text=update_i18n.get('label', '...'), tooltip=update_i18n.get('tooltip', '...'),
843
1174
  row=self.current_row, column=0)
844
- self.update_anki = tk.BooleanVar(value=self.settings.anki.update_anki)
845
- ttk.Checkbutton(anki_frame, variable=self.update_anki, bootstyle="round-toggle").grid(row=self.current_row,
846
- column=1, sticky='W',
847
- pady=2)
1175
+ ttk.Checkbutton(anki_frame, variable=self.update_anki_value, bootstyle="round-toggle").grid(row=self.current_row,
1176
+ column=1, sticky='W', pady=2)
848
1177
  self.current_row += 1
849
1178
 
850
- HoverInfoLabelWidget(anki_frame, text="Anki URL:", tooltip="The URL to connect to your Anki instance.",
1179
+ url_i18n = anki_i18n.get('url', {})
1180
+ HoverInfoLabelWidget(anki_frame, text=url_i18n.get('label', '...'), tooltip=url_i18n.get('tooltip', '...'),
851
1181
  foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
852
- self.anki_url = ttk.Entry(anki_frame, width=50)
853
- self.anki_url.insert(0, self.settings.anki.url)
854
- self.anki_url.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1182
+ ttk.Entry(anki_frame, width=50, textvariable=self.anki_url_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
855
1183
  self.current_row += 1
856
1184
 
857
- HoverInfoLabelWidget(anki_frame, text="Sentence Field:", tooltip="Field in Anki for the main sentence.",
1185
+ sentence_i18n = anki_i18n.get('sentence_field', {})
1186
+ HoverInfoLabelWidget(anki_frame, text=sentence_i18n.get('label', '...'), tooltip=sentence_i18n.get('tooltip', '...'),
858
1187
  foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
859
- self.sentence_field = ttk.Entry(anki_frame)
860
- self.sentence_field.insert(0, self.settings.anki.sentence_field)
861
- self.sentence_field.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1188
+ ttk.Entry(anki_frame, textvariable=self.sentence_field_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
862
1189
  self.current_row += 1
863
1190
 
864
- HoverInfoLabelWidget(anki_frame, text="Sentence Audio Field:",
865
- tooltip="Field in Anki for audio associated with the sentence. Leave Blank to Disable Audio Processing.",
1191
+ audio_i18n = anki_i18n.get('sentence_audio_field', {})
1192
+ HoverInfoLabelWidget(anki_frame, text=audio_i18n.get('label', '...'),
1193
+ tooltip=audio_i18n.get('tooltip', '...'),
866
1194
  foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
867
- self.sentence_audio_field = ttk.Entry(anki_frame)
868
- self.sentence_audio_field.insert(0, self.settings.anki.sentence_audio_field)
869
- self.sentence_audio_field.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1195
+ ttk.Entry(anki_frame, textvariable=self.sentence_audio_field_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
870
1196
  self.current_row += 1
871
1197
 
872
- HoverInfoLabelWidget(anki_frame, text="Picture Field:", tooltip="Field in Anki for associated pictures.",
1198
+ pic_i18n = anki_i18n.get('picture_field', {})
1199
+ HoverInfoLabelWidget(anki_frame, text=pic_i18n.get('label', '...'), tooltip=pic_i18n.get('tooltip', '...'),
873
1200
  foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
874
- self.picture_field = ttk.Entry(anki_frame)
875
- self.picture_field.insert(0, self.settings.anki.picture_field)
876
- self.picture_field.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1201
+ ttk.Entry(anki_frame, textvariable=self.picture_field_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
877
1202
  self.current_row += 1
878
1203
 
879
- HoverInfoLabelWidget(anki_frame, text="Word Field:", tooltip="Field in Anki for individual words.",
1204
+ word_i18n = anki_i18n.get('word_field', {})
1205
+ HoverInfoLabelWidget(anki_frame, text=word_i18n.get('label', '...'), tooltip=word_i18n.get('tooltip', '...'),
880
1206
  foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
881
- self.word_field = ttk.Entry(anki_frame)
882
- self.word_field.insert(0, self.settings.anki.word_field)
883
- self.word_field.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1207
+ ttk.Entry(anki_frame, textvariable=self.word_field_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
884
1208
  self.current_row += 1
885
1209
 
886
- HoverInfoLabelWidget(anki_frame, text="Previous Sentence Field:",
887
- tooltip="Field in Anki for the previous line of dialogue. If Empty, will not populate",
1210
+ prev_sent_i18n = anki_i18n.get('previous_sentence_field', {})
1211
+ HoverInfoLabelWidget(anki_frame, text=prev_sent_i18n.get('label', '...'),
1212
+ tooltip=prev_sent_i18n.get('tooltip', '...'),
888
1213
  row=self.current_row, column=0)
889
- self.previous_sentence_field = ttk.Entry(anki_frame)
890
- self.previous_sentence_field.insert(0, self.settings.anki.previous_sentence_field)
891
- self.previous_sentence_field.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1214
+ ttk.Entry(anki_frame, textvariable=self.previous_sentence_field_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
892
1215
  self.current_row += 1
893
1216
 
894
- HoverInfoLabelWidget(anki_frame, text="Previous VoiceLine SS Field:",
895
- tooltip="Field in Anki for the screenshot of previous line. If Empty, will not populate",
1217
+ prev_img_i18n = anki_i18n.get('previous_image_field', {})
1218
+ HoverInfoLabelWidget(anki_frame, text=prev_img_i18n.get('label', '...'),
1219
+ tooltip=prev_img_i18n.get('tooltip', '...'),
896
1220
  row=self.current_row, column=0)
897
- self.previous_image_field = ttk.Entry(anki_frame)
898
- self.previous_image_field.insert(0, self.settings.anki.previous_image_field)
899
- self.previous_image_field.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1221
+ ttk.Entry(anki_frame, textvariable=self.previous_image_field_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
900
1222
  self.current_row += 1
901
1223
 
902
- HoverInfoLabelWidget(anki_frame, text="Add Tags:", tooltip="Comma-separated custom tags for the Anki cards.",
1224
+ tags_i18n = anki_i18n.get('custom_tags', {})
1225
+ HoverInfoLabelWidget(anki_frame, text=tags_i18n.get('label', '...'), tooltip=tags_i18n.get('tooltip', '...'),
903
1226
  row=self.current_row, column=0)
904
- self.custom_tags = ttk.Entry(anki_frame, width=50)
905
- self.custom_tags.insert(0, ', '.join(self.settings.anki.custom_tags))
906
- self.custom_tags.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1227
+ ttk.Entry(anki_frame, width=50, textvariable=self.custom_tags_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
907
1228
  self.current_row += 1
908
1229
 
909
- HoverInfoLabelWidget(anki_frame, text="Tags to work on:",
910
- tooltip="Comma-separated Tags, script will only do 1-click on cards with these tags (Recommend keep empty, or use Yomitan Profile to add custom tag from texthooker page)",
1230
+ tags_check_i18n = anki_i18n.get('tags_to_check', {})
1231
+ HoverInfoLabelWidget(anki_frame, text=tags_check_i18n.get('label', '...'),
1232
+ tooltip=tags_check_i18n.get('tooltip', '...'),
911
1233
  foreground="green", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
912
- self.tags_to_check = ttk.Entry(anki_frame, width=50)
913
- self.tags_to_check.insert(0, ', '.join(self.settings.anki.tags_to_check))
914
- self.tags_to_check.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1234
+ ttk.Entry(anki_frame, width=50, textvariable=self.tags_to_check_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
915
1235
  self.current_row += 1
916
1236
 
917
- HoverInfoLabelWidget(anki_frame, text="Add Game as Tag:",
918
- tooltip="Include a tag for the game on the Anki card.", foreground="green",
1237
+ game_tag_i18n = anki_i18n.get('add_game_tag', {})
1238
+ HoverInfoLabelWidget(anki_frame, text=game_tag_i18n.get('label', '...'),
1239
+ tooltip=game_tag_i18n.get('tooltip', '...'), foreground="green",
919
1240
  font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
920
- self.add_game_tag = tk.BooleanVar(value=self.settings.anki.add_game_tag)
921
- ttk.Checkbutton(anki_frame, variable=self.add_game_tag, bootstyle="round-toggle").grid(row=self.current_row,
922
- column=1, sticky='W',
923
- pady=2)
924
-
1241
+ ttk.Checkbutton(anki_frame, variable=self.add_game_tag_value, bootstyle="round-toggle").grid(row=self.current_row,
1242
+ column=1, sticky='W', pady=2)
925
1243
  self.current_row += 1
926
1244
 
927
- HoverInfoLabelWidget(anki_frame, text="Game Parent Tag:",
1245
+ parent_tag_i18n = anki_i18n.get('parent_tag', {})
1246
+ HoverInfoLabelWidget(anki_frame, text=parent_tag_i18n.get('label', '...'),
928
1247
  foreground="green", font=("Helvetica", 10, "bold"),
929
- tooltip="Parent tag for the Game Tag. If empty, no parent tag will be added. i.e. Game::{Game_Title}. You can think of this as a \"Folder\" for your tags",
1248
+ tooltip=parent_tag_i18n.get('tooltip', '...'),
930
1249
  row=self.current_row, column=0)
931
- self.parent_tag = ttk.Entry(anki_frame, width=50)
932
- self.parent_tag.insert(0, self.settings.anki.parent_tag)
933
- self.parent_tag.grid(row=self.current_row, column=1, sticky='EW', pady=2)
934
-
1250
+ ttk.Entry(anki_frame, width=50, textvariable=self.parent_tag_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
935
1251
  self.current_row += 1
936
1252
 
937
- HoverInfoLabelWidget(anki_frame, text="Overwrite Audio:", tooltip="Overwrite existing audio in Anki cards.",
1253
+ ow_audio_i18n = anki_i18n.get('overwrite_audio', {})
1254
+ HoverInfoLabelWidget(anki_frame, text=ow_audio_i18n.get('label', '...'), tooltip=ow_audio_i18n.get('tooltip', '...'),
938
1255
  row=self.current_row, column=0)
939
- self.overwrite_audio = tk.BooleanVar(
940
- value=self.settings.anki.overwrite_audio)
941
- ttk.Checkbutton(anki_frame, variable=self.overwrite_audio, bootstyle="round-toggle").grid(row=self.current_row,
942
- column=1, sticky='W',
943
- pady=2)
944
- self.current_row += 1
945
-
946
- HoverInfoLabelWidget(anki_frame, text="Overwrite Picture:",
947
- tooltip="Overwrite existing pictures in Anki cards.", row=self.current_row, column=0)
948
- self.overwrite_picture = tk.BooleanVar(
949
- value=self.settings.anki.overwrite_picture)
950
- ttk.Checkbutton(anki_frame, variable=self.overwrite_picture, bootstyle="round-toggle").grid(
1256
+ ttk.Checkbutton(anki_frame, variable=self.overwrite_audio_value, bootstyle="round-toggle").grid(row=self.current_row,
1257
+ column=1, sticky='W', pady=2)
1258
+ self.current_row += 1
1259
+
1260
+ ow_pic_i18n = anki_i18n.get('overwrite_picture', {})
1261
+ HoverInfoLabelWidget(anki_frame, text=ow_pic_i18n.get('label', '...'),
1262
+ tooltip=ow_pic_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1263
+ ttk.Checkbutton(anki_frame, variable=self.overwrite_picture_value, bootstyle="round-toggle").grid(
951
1264
  row=self.current_row, column=1, sticky='W', pady=2)
952
1265
  self.current_row += 1
953
1266
 
954
- HoverInfoLabelWidget(anki_frame, text="Multi-line Mining Overwrite Sentence:",
955
- tooltip="When using Multi-line Mining, overwrite the sentence with a concatenation of the lines selected.",
1267
+ multi_ow_i18n = anki_i18n.get('multi_overwrites_sentence', {})
1268
+ HoverInfoLabelWidget(anki_frame, text=multi_ow_i18n.get('label', '...'),
1269
+ tooltip=multi_ow_i18n.get('tooltip', '...'),
956
1270
  row=self.current_row, column=0)
957
- self.multi_overwrites_sentence = tk.BooleanVar(
958
- value=self.settings.anki.multi_overwrites_sentence)
959
- ttk.Checkbutton(anki_frame, variable=self.multi_overwrites_sentence, bootstyle="round-toggle").grid(
1271
+ ttk.Checkbutton(anki_frame, variable=self.multi_overwrites_sentence_value, bootstyle="round-toggle").grid(
960
1272
  row=self.current_row, column=1, sticky='W', pady=2)
961
1273
  self.current_row += 1
962
1274
 
963
1275
  self.add_reset_button(anki_frame, "anki", self.current_row, 0, self.create_anki_tab)
964
1276
 
965
- anki_frame.grid_columnconfigure(0, weight=0)
966
- anki_frame.grid_columnconfigure(1, weight=0)
967
-
968
- for row in range(self.current_row):
969
- anki_frame.grid_rowconfigure(row, minsize=30)
1277
+ for col in range(2): anki_frame.grid_columnconfigure(col, weight=0)
1278
+ for row in range(self.current_row): anki_frame.grid_rowconfigure(row, minsize=30)
970
1279
 
971
1280
  return anki_frame
972
1281
 
973
1282
  def on_profiles_tab_selected(self, event):
974
1283
  try:
975
- if self.window.state() != "withdrawn" and self.notebook.tab(self.notebook.select(), "text") == "Profiles":
1284
+ profiles_i18n = self.i18n.get('tabs', {}).get('profiles', {})
1285
+ if self.window.state() != "withdrawn" and self.notebook.tab(self.notebook.select(), "text") == profiles_i18n.get('title', 'Profiles'):
976
1286
  self.refresh_obs_scenes()
977
1287
  except Exception as e:
978
1288
  logger.debug(e)
@@ -980,342 +1290,305 @@ class ConfigApp:
980
1290
  @new_tab
981
1291
  def create_features_tab(self):
982
1292
  if self.features_tab is None:
1293
+ features_i18n = self.i18n.get('tabs', {}).get('features', {})
983
1294
  self.features_tab = ttk.Frame(self.notebook, padding=15)
984
- self.notebook.add(self.features_tab, text='Features')
1295
+ self.notebook.add(self.features_tab, text=features_i18n.get('title', 'Features'))
985
1296
  else:
986
1297
  for widget in self.features_tab.winfo_children():
987
1298
  widget.destroy()
988
1299
 
989
1300
  features_frame = self.features_tab
1301
+ features_i18n = self.i18n.get('tabs', {}).get('features', {})
990
1302
 
991
- HoverInfoLabelWidget(features_frame, text="Notify on Update:", tooltip="Notify the user when an update occurs.",
1303
+ notify_i18n = features_i18n.get('notify_on_update', {})
1304
+ HoverInfoLabelWidget(features_frame, text=notify_i18n.get('label', '...'), tooltip=notify_i18n.get('tooltip', '...'),
992
1305
  row=self.current_row, column=0)
993
- self.notify_on_update = tk.BooleanVar(value=self.settings.features.notify_on_update)
994
- ttk.Checkbutton(features_frame, variable=self.notify_on_update, bootstyle="round-toggle").grid(
1306
+ ttk.Checkbutton(features_frame, variable=self.notify_on_update_value, bootstyle="round-toggle").grid(
995
1307
  row=self.current_row, column=1, sticky='W', pady=2)
996
1308
  self.current_row += 1
997
1309
 
998
- HoverInfoLabelWidget(features_frame, text="Open Anki Edit:",
999
- tooltip="Automatically open Anki for editing after updating.", row=self.current_row,
1000
- column=0)
1001
- self.open_anki_edit = tk.BooleanVar(value=self.settings.features.open_anki_edit)
1002
- ttk.Checkbutton(features_frame, variable=self.open_anki_edit, bootstyle="round-toggle").grid(
1310
+ open_edit_i18n = features_i18n.get('open_anki_edit', {})
1311
+ HoverInfoLabelWidget(features_frame, text=open_edit_i18n.get('label', '...'),
1312
+ tooltip=open_edit_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1313
+ ttk.Checkbutton(features_frame, variable=self.open_anki_edit_value, bootstyle="round-toggle").grid(
1003
1314
  row=self.current_row, column=1, sticky='W', pady=2)
1004
1315
  self.current_row += 1
1005
1316
 
1006
- HoverInfoLabelWidget(features_frame, text="Open Anki Note in Browser:",
1007
- tooltip="Open Anki note in browser after updating.", row=self.current_row, column=0)
1008
- self.open_anki_browser = tk.BooleanVar(value=self.settings.features.open_anki_in_browser)
1009
- ttk.Checkbutton(features_frame, variable=self.open_anki_browser, bootstyle="round-toggle").grid(
1317
+ open_browser_i18n = features_i18n.get('open_anki_browser', {})
1318
+ HoverInfoLabelWidget(features_frame, text=open_browser_i18n.get('label', '...'),
1319
+ tooltip=open_browser_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1320
+ ttk.Checkbutton(features_frame, variable=self.open_anki_browser_value, bootstyle="round-toggle").grid(
1010
1321
  row=self.current_row, column=1, sticky='W', pady=2)
1011
1322
  self.current_row += 1
1012
1323
 
1013
- HoverInfoLabelWidget(features_frame, text="Browser Query:",
1014
- tooltip="Query to use when opening Anki notes in the browser. Ex: 'Added:1'",
1324
+ query_i18n = features_i18n.get('browser_query', {})
1325
+ HoverInfoLabelWidget(features_frame, text=query_i18n.get('label', '...'),
1326
+ tooltip=query_i18n.get('tooltip', '...'),
1015
1327
  row=self.current_row, column=0)
1016
- self.browser_query = ttk.Entry(features_frame, width=50)
1017
- self.browser_query.insert(0, self.settings.features.browser_query)
1018
- self.browser_query.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1328
+ ttk.Entry(features_frame, width=50, textvariable=self.browser_query_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1019
1329
  self.current_row += 1
1020
1330
 
1021
- HoverInfoLabelWidget(features_frame, text="Backfill Audio:", tooltip="Fill in audio data for existing entries.",
1331
+ backfill_i18n = features_i18n.get('backfill_audio', {})
1332
+ HoverInfoLabelWidget(features_frame, text=backfill_i18n.get('label', '...'), tooltip=backfill_i18n.get('tooltip', '...'),
1022
1333
  row=self.current_row, column=0)
1023
- self.backfill_audio = tk.BooleanVar(value=self.settings.features.backfill_audio)
1024
- ttk.Checkbutton(features_frame, variable=self.backfill_audio, bootstyle="round-toggle").grid(
1334
+ ttk.Checkbutton(features_frame, variable=self.backfill_audio_value, bootstyle="round-toggle").grid(
1025
1335
  row=self.current_row, column=1, sticky='W', pady=2)
1026
1336
  self.current_row += 1
1027
1337
 
1028
- HoverInfoLabelWidget(features_frame, text="Full Auto Mode:", tooltip="Yomitan 1-click anki card creation.",
1338
+ full_auto_i18n = features_i18n.get('full_auto', {})
1339
+ HoverInfoLabelWidget(features_frame, text=full_auto_i18n.get('label', '...'), tooltip=full_auto_i18n.get('tooltip', '...'),
1029
1340
  row=self.current_row, column=0)
1030
- self.full_auto = tk.BooleanVar(
1031
- value=self.settings.features.full_auto)
1032
- ttk.Checkbutton(features_frame, variable=self.full_auto, bootstyle="round-toggle").grid(row=self.current_row,
1033
- column=1, sticky='W',
1034
- pady=2)
1341
+ ttk.Checkbutton(features_frame, variable=self.full_auto_value, bootstyle="round-toggle").grid(row=self.current_row,
1342
+ column=1, sticky='W', pady=2)
1035
1343
  self.current_row += 1
1036
1344
 
1037
1345
  self.add_reset_button(features_frame, "features", self.current_row, 0, self.create_features_tab)
1038
1346
 
1039
- for col in range(3):
1040
- features_frame.grid_columnconfigure(col, weight=0)
1041
-
1042
- for row in range(self.current_row):
1043
- features_frame.grid_rowconfigure(row, minsize=30)
1347
+ for col in range(3): features_frame.grid_columnconfigure(col, weight=0)
1348
+ for row in range(self.current_row): features_frame.grid_rowconfigure(row, minsize=30)
1044
1349
 
1045
1350
  return features_frame
1046
1351
 
1047
1352
  @new_tab
1048
1353
  def create_screenshot_tab(self):
1049
1354
  if self.screenshot_tab is None:
1355
+ ss_i18n = self.i18n.get('tabs', {}).get('screenshot', {})
1050
1356
  self.screenshot_tab = ttk.Frame(self.notebook, padding=15)
1051
- self.notebook.add(self.screenshot_tab, text='Screenshot')
1357
+ self.notebook.add(self.screenshot_tab, text=ss_i18n.get('title', 'Screenshot'))
1052
1358
  else:
1053
1359
  for widget in self.screenshot_tab.winfo_children():
1054
1360
  widget.destroy()
1055
1361
 
1056
1362
  screenshot_frame = self.screenshot_tab
1363
+ ss_i18n = self.i18n.get('tabs', {}).get('screenshot', {})
1057
1364
 
1058
- HoverInfoLabelWidget(screenshot_frame, text="Enabled:", tooltip="Enable or disable screenshot processing.",
1365
+ enabled_i18n = ss_i18n.get('enabled', {})
1366
+ HoverInfoLabelWidget(screenshot_frame, text=enabled_i18n.get('label', '...'), tooltip=enabled_i18n.get('tooltip', '...'),
1059
1367
  row=self.current_row, column=0)
1060
- self.screenshot_enabled = tk.BooleanVar(value=self.settings.screenshot.enabled)
1061
- ttk.Checkbutton(screenshot_frame, variable=self.screenshot_enabled, bootstyle="round-toggle").grid(
1368
+ ttk.Checkbutton(screenshot_frame, variable=self.screenshot_enabled_value, bootstyle="round-toggle").grid(
1062
1369
  row=self.current_row, column=1, sticky='W', pady=2)
1063
1370
  self.current_row += 1
1064
1371
 
1065
- HoverInfoLabelWidget(screenshot_frame, text="Width:", tooltip="Width of the screenshot in pixels.",
1372
+ width_i18n = ss_i18n.get('width', {})
1373
+ HoverInfoLabelWidget(screenshot_frame, text=width_i18n.get('label', '...'), tooltip=width_i18n.get('tooltip', '...'),
1066
1374
  row=self.current_row, column=0)
1067
- self.screenshot_width = ttk.Entry(screenshot_frame)
1068
- self.screenshot_width.insert(0, str(self.settings.screenshot.width))
1069
- self.screenshot_width.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1375
+ ttk.Entry(screenshot_frame, textvariable=self.screenshot_width_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1070
1376
  self.current_row += 1
1071
1377
 
1072
- HoverInfoLabelWidget(screenshot_frame, text="Height:", tooltip="Height of the screenshot in pixels.",
1378
+ height_i18n = ss_i18n.get('height', {})
1379
+ HoverInfoLabelWidget(screenshot_frame, text=height_i18n.get('label', '...'), tooltip=height_i18n.get('tooltip', '...'),
1073
1380
  row=self.current_row, column=0)
1074
- self.screenshot_height = ttk.Entry(screenshot_frame)
1075
- self.screenshot_height.insert(0, str(self.settings.screenshot.height))
1076
- self.screenshot_height.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1381
+ ttk.Entry(screenshot_frame, textvariable=self.screenshot_height_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1077
1382
  self.current_row += 1
1078
1383
 
1079
- HoverInfoLabelWidget(screenshot_frame, text="Quality:", tooltip="Quality of the screenshot (0-100).",
1384
+ quality_i18n = ss_i18n.get('quality', {})
1385
+ HoverInfoLabelWidget(screenshot_frame, text=quality_i18n.get('label', '...'), tooltip=quality_i18n.get('tooltip', '...'),
1080
1386
  row=self.current_row, column=0)
1081
- self.screenshot_quality = ttk.Entry(screenshot_frame)
1082
- self.screenshot_quality.insert(0, str(self.settings.screenshot.quality))
1083
- self.screenshot_quality.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1387
+ ttk.Entry(screenshot_frame, textvariable=self.screenshot_quality_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1084
1388
  self.current_row += 1
1085
1389
 
1086
- HoverInfoLabelWidget(screenshot_frame, text="Extension:", tooltip="File extension for the screenshot format.",
1390
+ ext_i18n = ss_i18n.get('extension', {})
1391
+ HoverInfoLabelWidget(screenshot_frame, text=ext_i18n.get('label', '...'), tooltip=ext_i18n.get('tooltip', '...'),
1087
1392
  row=self.current_row, column=0)
1088
- self.screenshot_extension = ttk.Combobox(screenshot_frame, values=['webp', 'avif', 'png', 'jpeg'],
1089
- state="readonly")
1090
- self.screenshot_extension.set(self.settings.screenshot.extension)
1091
- self.screenshot_extension.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1393
+ ttk.Combobox(screenshot_frame, textvariable=self.screenshot_extension_value, values=['webp', 'avif', 'png', 'jpeg'],
1394
+ state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
1092
1395
  self.current_row += 1
1093
1396
 
1094
- HoverInfoLabelWidget(screenshot_frame, text="FFmpeg Reencode Options:",
1095
- tooltip="Custom FFmpeg options for re-encoding screenshots.", foreground="red",
1397
+ ffmpeg_i18n = ss_i18n.get('ffmpeg_options', {})
1398
+ HoverInfoLabelWidget(screenshot_frame, text=ffmpeg_i18n.get('label', '...'),
1399
+ tooltip=ffmpeg_i18n.get('tooltip', '...'), foreground="red",
1096
1400
  font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1097
- self.screenshot_custom_ffmpeg_settings = ttk.Entry(screenshot_frame, width=50)
1098
- self.screenshot_custom_ffmpeg_settings.insert(0, self.settings.screenshot.custom_ffmpeg_settings)
1099
- self.screenshot_custom_ffmpeg_settings.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1401
+ ttk.Entry(screenshot_frame, width=50, textvariable=self.screenshot_custom_ffmpeg_settings_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1100
1402
  self.current_row += 1
1101
1403
 
1102
- HoverInfoLabelWidget(screenshot_frame, text="Screenshot Timing:",
1103
- tooltip="Select when to take the screenshot relative to the line: beginning, middle, or end.",
1404
+ timing_i18n = ss_i18n.get('timing', {})
1405
+ HoverInfoLabelWidget(screenshot_frame, text=timing_i18n.get('label', '...'),
1406
+ tooltip=timing_i18n.get('tooltip', '...'),
1104
1407
  row=self.current_row, column=0)
1105
- self.screenshot_timing = ttk.Combobox(screenshot_frame, values=['beginning', 'middle', 'end'], state="readonly")
1106
- self.screenshot_timing.set(self.settings.screenshot.screenshot_timing_setting)
1107
- self.screenshot_timing.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1408
+ ttk.Combobox(screenshot_frame, textvariable=self.screenshot_timing_value, values=['beginning', 'middle', 'end'], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
1108
1409
  self.current_row += 1
1109
1410
 
1110
- HoverInfoLabelWidget(screenshot_frame, text="Screenshot Offset:",
1111
- tooltip="Time in seconds to offset the screenshot based on the Timing setting above (should almost always be positive, can be negative if you use \"middle\")",
1411
+ offset_i18n = ss_i18n.get('offset', {})
1412
+ HoverInfoLabelWidget(screenshot_frame, text=offset_i18n.get('label', '...'),
1413
+ tooltip=offset_i18n.get('tooltip', '...'),
1112
1414
  foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1113
- self.seconds_after_line = ttk.Entry(screenshot_frame)
1114
- self.seconds_after_line.insert(0, str(self.settings.screenshot.seconds_after_line))
1115
- self.seconds_after_line.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1415
+ ttk.Entry(screenshot_frame, textvariable=self.seconds_after_line_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1116
1416
  self.current_row += 1
1117
1417
 
1118
- HoverInfoLabelWidget(screenshot_frame, text="Use Screenshot Selector for every card:",
1119
- tooltip="Enable to use the screenshot selector to choose the screenshot point on every card.",
1418
+ selector_i18n = ss_i18n.get('use_selector', {})
1419
+ HoverInfoLabelWidget(screenshot_frame, text=selector_i18n.get('label', '...'),
1420
+ tooltip=selector_i18n.get('tooltip', '...'),
1120
1421
  row=self.current_row, column=0)
1121
- self.use_screenshot_selector = tk.BooleanVar(value=self.settings.screenshot.use_screenshot_selector)
1122
- ttk.Checkbutton(screenshot_frame, variable=self.use_screenshot_selector, bootstyle="round-toggle").grid(
1422
+ ttk.Checkbutton(screenshot_frame, variable=self.use_screenshot_selector_value, bootstyle="round-toggle").grid(
1123
1423
  row=self.current_row, column=1, sticky='W', pady=2)
1124
1424
  self.current_row += 1
1125
1425
 
1126
- HoverInfoLabelWidget(screenshot_frame, text="Take Screenshot Hotkey:", tooltip="Hotkey to take a screenshot.",
1426
+ hotkey_i18n = ss_i18n.get('hotkey', {})
1427
+ HoverInfoLabelWidget(screenshot_frame, text=hotkey_i18n.get('label', '...'), tooltip=hotkey_i18n.get('tooltip', '...'),
1127
1428
  row=self.current_row, column=0)
1128
- self.take_screenshot_hotkey = ttk.Entry(screenshot_frame)
1129
- self.take_screenshot_hotkey.insert(0, self.settings.hotkeys.take_screenshot)
1130
- self.take_screenshot_hotkey.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1429
+ ttk.Entry(screenshot_frame, textvariable=self.take_screenshot_hotkey_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1131
1430
  self.current_row += 1
1132
1431
 
1133
- HoverInfoLabelWidget(screenshot_frame, text="Screenshot Hotkey Updates Anki:",
1134
- tooltip="Enable to allow Screenshot hotkey/button to update the latest anki card.",
1432
+ hotkey_update_i18n = ss_i18n.get('hotkey_updates_anki', {})
1433
+ HoverInfoLabelWidget(screenshot_frame, text=hotkey_update_i18n.get('label', '...'),
1434
+ tooltip=hotkey_update_i18n.get('tooltip', '...'),
1135
1435
  row=self.current_row, column=0)
1136
- self.screenshot_hotkey_update_anki = tk.BooleanVar(
1137
- value=self.settings.screenshot.screenshot_hotkey_updates_anki)
1138
- ttk.Checkbutton(screenshot_frame, variable=self.screenshot_hotkey_update_anki, bootstyle="round-toggle").grid(
1436
+ ttk.Checkbutton(screenshot_frame, variable=self.screenshot_hotkey_update_anki_value, bootstyle="round-toggle").grid(
1139
1437
  row=self.current_row, column=1, sticky='W', pady=2)
1140
1438
  self.current_row += 1
1141
1439
 
1142
1440
  self.add_reset_button(screenshot_frame, "screenshot", self.current_row, 0, self.create_screenshot_tab)
1143
1441
 
1144
- for col in range(3):
1145
- screenshot_frame.grid_columnconfigure(col, weight=0)
1146
-
1147
- for row in range(self.current_row):
1148
- screenshot_frame.grid_rowconfigure(row, minsize=30)
1442
+ for col in range(3): screenshot_frame.grid_columnconfigure(col, weight=0)
1443
+ for row in range(self.current_row): screenshot_frame.grid_rowconfigure(row, minsize=30)
1149
1444
 
1150
1445
  return screenshot_frame
1151
1446
 
1152
1447
  def update_audio_ffmpeg_settings(self, event):
1153
1448
  selected_option = self.ffmpeg_audio_preset_options.get()
1154
1449
  if selected_option in self.ffmpeg_audio_preset_options_map:
1155
- self.audio_ffmpeg_reencode_options.delete(0, tk.END)
1156
- self.audio_ffmpeg_reencode_options.insert(0, self.ffmpeg_audio_preset_options_map[selected_option])
1450
+ self.audio_ffmpeg_reencode_options_value.set(self.ffmpeg_audio_preset_options_map[selected_option])
1157
1451
  else:
1158
- self.audio_ffmpeg_reencode_options.delete(0, tk.END)
1159
- self.audio_ffmpeg_reencode_options.insert(0, "")
1452
+ self.audio_ffmpeg_reencode_options_value.set("")
1160
1453
 
1161
1454
  @new_tab
1162
1455
  def create_audio_tab(self):
1163
1456
  if self.audio_tab is None:
1457
+ audio_i18n = self.i18n.get('tabs', {}).get('audio', {})
1164
1458
  self.audio_tab = ttk.Frame(self.notebook, padding=15)
1165
- self.notebook.add(self.audio_tab, text='Audio')
1459
+ self.notebook.add(self.audio_tab, text=audio_i18n.get('title', 'Audio'))
1166
1460
  else:
1167
1461
  for widget in self.audio_tab.winfo_children():
1168
1462
  widget.destroy()
1169
1463
 
1170
1464
  audio_frame = self.audio_tab
1465
+ audio_i18n = self.i18n.get('tabs', {}).get('audio', {})
1171
1466
 
1172
- HoverInfoLabelWidget(audio_frame, text="Enabled:", tooltip="Enable or disable audio processing.",
1467
+ enabled_i18n = audio_i18n.get('enabled', {})
1468
+ HoverInfoLabelWidget(audio_frame, text=enabled_i18n.get('label', '...'), tooltip=enabled_i18n.get('tooltip', '...'),
1173
1469
  row=self.current_row, column=0)
1174
- self.audio_enabled = tk.BooleanVar(value=self.settings.audio.enabled)
1175
- ttk.Checkbutton(audio_frame, variable=self.audio_enabled, bootstyle="round-toggle").grid(row=self.current_row,
1176
- column=1, sticky='W',
1177
- pady=2)
1470
+ ttk.Checkbutton(audio_frame, variable=self.audio_enabled_value, bootstyle="round-toggle").grid(row=self.current_row,
1471
+ column=1, sticky='W', pady=2)
1178
1472
  self.current_row += 1
1179
1473
 
1180
- HoverInfoLabelWidget(audio_frame, text="Audio Extension:", tooltip="File extension for audio files.",
1474
+ ext_i18n = audio_i18n.get('extension', {})
1475
+ HoverInfoLabelWidget(audio_frame, text=ext_i18n.get('label', '...'), tooltip=ext_i18n.get('tooltip', '...'),
1181
1476
  row=self.current_row, column=0)
1182
- self.audio_extension = ttk.Combobox(audio_frame, values=['opus', 'mp3', 'ogg', 'aac', 'm4a'], state="readonly")
1183
- self.audio_extension.set(self.settings.audio.extension)
1184
- self.audio_extension.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1477
+ ttk.Combobox(audio_frame, textvariable=self.audio_extension_value, values=['opus', 'mp3', 'ogg', 'aac', 'm4a'], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
1185
1478
  self.current_row += 1
1186
1479
 
1187
- HoverInfoLabelWidget(audio_frame, text="Audio Extraction Beginning Offset:",
1188
- tooltip="Offset in seconds from beginning of the video to extract (Should Usually be negative or 0).",
1480
+ begin_offset_i18n = audio_i18n.get('beginning_offset', {})
1481
+ HoverInfoLabelWidget(audio_frame, text=begin_offset_i18n.get('label', '...'),
1482
+ tooltip=begin_offset_i18n.get('tooltip', '...'),
1189
1483
  foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1190
- self.beginning_offset = ttk.Entry(audio_frame)
1191
- self.beginning_offset.insert(0, str(self.settings.audio.beginning_offset))
1192
- self.beginning_offset.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1193
-
1194
- ttk.Button(audio_frame, text="Find Offset (WIP)", command=self.call_audio_offset_selector,
1195
- bootstyle="info").grid(
1196
- row=self.current_row, column=2, sticky='EW', pady=2, padx=5)
1484
+ ttk.Entry(audio_frame, textvariable=self.beginning_offset_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1197
1485
 
1486
+ ttk.Button(audio_frame, text=audio_i18n.get('find_offset_button', 'Find Offset (WIP)'), command=self.call_audio_offset_selector,
1487
+ bootstyle="info").grid(row=self.current_row, column=2, sticky='EW', pady=2, padx=5)
1198
1488
  self.current_row += 1
1199
1489
 
1200
- HoverInfoLabelWidget(audio_frame, text="Audio Extraction End Offset:",
1201
- tooltip="Offset in seconds to trim from the end before VAD processing starts. Warning: May Result in lost audio if negative.",
1490
+ end_offset_i18n = audio_i18n.get('end_offset', {})
1491
+ HoverInfoLabelWidget(audio_frame, text=end_offset_i18n.get('label', '...'),
1492
+ tooltip=end_offset_i18n.get('tooltip', '...'),
1202
1493
  foreground="red", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1203
- self.pre_vad_audio_offset = ttk.Entry(audio_frame)
1204
- self.pre_vad_audio_offset.insert(0, str(self.settings.audio.pre_vad_end_offset))
1205
- self.pre_vad_audio_offset.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1494
+ ttk.Entry(audio_frame, textvariable=self.pre_vad_audio_offset_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1206
1495
  self.current_row += 1
1207
1496
 
1208
- HoverInfoLabelWidget(audio_frame, text="FFmpeg Preset Options:",
1209
- tooltip="Select a preset FFmpeg option for re-encoding screenshots.", row=self.current_row,
1210
- column=0)
1497
+ ffmpeg_preset_i18n = audio_i18n.get('ffmpeg_preset', {})
1498
+ HoverInfoLabelWidget(audio_frame, text=ffmpeg_preset_i18n.get('label', '...'),
1499
+ tooltip=ffmpeg_preset_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1211
1500
 
1212
- # Define display names and their corresponding values
1501
+ preset_options_i18n = ffmpeg_preset_i18n.get('options', {})
1213
1502
  self.ffmpeg_audio_preset_options_map = {
1214
- "No Re-encode": "",
1215
- "Simple Fade-in, Avoids Audio Clipping (Default)": "-c:a {encoder} -f {format} -af \"afade=t=in:d=0.10\"",
1216
- "Simple loudness normalization (Simplest, Start Here)": "-c:a {encoder} -f {format} -af \"loudnorm=I=-23:TP=-2,afade=t=in:d=0.10\"",
1217
- "Downmix to mono with normalization (Recommended(?))": "-c:a {encoder} -ac 1 -f {format} -af \"loudnorm=I=-23:TP=-2:dual_mono=true,afade=t=in:d=0.10\"",
1218
- "Downmix to mono, 30kbps, normalized (Optimal(?))": "-c:a {encoder} -b:a 30k -ac 1 -f {format} -af \"loudnorm=I=-23:TP=-2:dual_mono=true,afade=t=in:d=0.10\"",
1219
- "Custom": get_config().audio.custom_encode_settings,
1503
+ preset_options_i18n.get('no_reencode', "No Re-encode"): "",
1504
+ preset_options_i18n.get('fade_in', "Simple Fade-in..."): "-c:a {encoder} -f {format} -af \"afade=t=in:d=0.10\"",
1505
+ preset_options_i18n.get('loudness_norm', "Simple loudness..."): "-c:a {encoder} -f {format} -af \"loudnorm=I=-23:TP=-2,afade=t=in:d=0.10\"",
1506
+ preset_options_i18n.get('downmix_norm', "Downmix to mono..."): "-c:a {encoder} -ac 1 -f {format} -af \"loudnorm=I=-23:TP=-2:dual_mono=true,afade=t=in:d=0.10\"",
1507
+ preset_options_i18n.get('downmix_norm_low_bitrate', "Downmix to mono, 30kbps..."): "-c:a {encoder} -b:a 30k -ac 1 -f {format} -af \"loudnorm=I=-23:TP=-2:dual_mono=true,afade=t=in:d=0.10\"",
1508
+ preset_options_i18n.get('custom', "Custom"): get_config().audio.custom_encode_settings,
1220
1509
  }
1221
1510
 
1222
- # Create a Combobox with display names
1223
1511
  self.ffmpeg_audio_preset_options = ttk.Combobox(audio_frame,
1224
1512
  values=list(self.ffmpeg_audio_preset_options_map.keys()),
1225
1513
  width=50, state="readonly")
1226
- # self.ffmpeg_preset_options.set("Downmix to mono with normalization") # Set default display name
1227
1514
  self.ffmpeg_audio_preset_options.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1228
-
1229
- # Bind selection to update settings
1230
1515
  self.ffmpeg_audio_preset_options.bind("<<ComboboxSelected>>", self.update_audio_ffmpeg_settings)
1231
1516
  self.current_row += 1
1232
1517
 
1233
- HoverInfoLabelWidget(audio_frame, text="FFmpeg Reencode Options:",
1234
- tooltip="Custom FFmpeg options for re-encoding audio files.", foreground="red",
1518
+ ffmpeg_options_i18n = audio_i18n.get('ffmpeg_options', {})
1519
+ HoverInfoLabelWidget(audio_frame, text=ffmpeg_options_i18n.get('label', '...'),
1520
+ tooltip=ffmpeg_options_i18n.get('tooltip', '...'), foreground="red",
1235
1521
  font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1236
- self.audio_ffmpeg_reencode_options = ttk.Entry(audio_frame, width=50)
1237
- self.audio_ffmpeg_reencode_options.insert(0, self.settings.audio.ffmpeg_reencode_options)
1238
- self.audio_ffmpeg_reencode_options.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1522
+ ttk.Entry(audio_frame, width=50, textvariable=self.audio_ffmpeg_reencode_options_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1239
1523
  self.current_row += 1
1240
1524
 
1241
- HoverInfoLabelWidget(audio_frame, text="Anki Media Collection:",
1242
- tooltip="Path of the Anki Media Collection, used for external Trimming tool. NO TRAILING SLASH",
1525
+ anki_media_i18n = audio_i18n.get('anki_media_collection', {})
1526
+ HoverInfoLabelWidget(audio_frame, text=anki_media_i18n.get('label', '...'),
1527
+ tooltip=anki_media_i18n.get('tooltip', '...'),
1243
1528
  foreground="green", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1244
- self.anki_media_collection = ttk.Entry(audio_frame)
1245
- self.anki_media_collection.insert(0, self.settings.audio.anki_media_collection)
1246
- self.anki_media_collection.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1529
+ self.anki_media_collection_entry = ttk.Entry(audio_frame, textvariable=self.anki_media_collection_value)
1530
+ self.anki_media_collection_entry.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1247
1531
  self.current_row += 1
1248
1532
 
1249
- HoverInfoLabelWidget(audio_frame, text="External Audio Editing Tool:",
1250
- tooltip="Path to External tool that opens the audio up for manual trimming. I recommend OcenAudio for in-place Editing.",
1533
+ ext_tool_i18n = audio_i18n.get('external_tool', {})
1534
+ HoverInfoLabelWidget(audio_frame, text=ext_tool_i18n.get('label', '...'),
1535
+ tooltip=ext_tool_i18n.get('tooltip', '...'),
1251
1536
  foreground="green", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1252
- self.external_tool = ttk.Entry(audio_frame)
1253
- self.external_tool.insert(0, self.settings.audio.external_tool)
1254
- self.external_tool.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1255
- self.external_tool_enabled = tk.BooleanVar(value=self.settings.audio.external_tool_enabled)
1256
- HoverInfoLabelWidget(audio_frame, text="Enabled:", tooltip="Send Audio to External Tool for Editing.",
1537
+ self.external_tool_entry = ttk.Entry(audio_frame, textvariable=self.external_tool_value)
1538
+ self.external_tool_entry.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1539
+
1540
+ ext_tool_enabled_i18n = audio_i18n.get('external_tool_enabled', {})
1541
+ HoverInfoLabelWidget(audio_frame, text=ext_tool_enabled_i18n.get('label', '...'), tooltip=ext_tool_enabled_i18n.get('tooltip', '...'),
1257
1542
  row=self.current_row, column=2, foreground="green", font=("Helvetica", 10, "bold"))
1258
- ttk.Checkbutton(audio_frame, variable=self.external_tool_enabled, bootstyle="round-toggle").grid(
1543
+ ttk.Checkbutton(audio_frame, variable=self.external_tool_enabled_value, bootstyle="round-toggle").grid(
1259
1544
  row=self.current_row, column=3, sticky='W', padx=10, pady=5)
1260
1545
  self.current_row += 1
1261
1546
 
1262
- ttk.Button(audio_frame, text="Install Ocenaudio", command=self.download_and_install_ocen,
1263
- bootstyle="info").grid(
1264
- row=self.current_row, column=0, pady=5)
1265
- ttk.Button(audio_frame, text="Get Anki Media Collection",
1547
+ ttk.Button(audio_frame, text=audio_i18n.get('install_ocenaudio_button', 'Install Ocenaudio'), command=self.download_and_install_ocen,
1548
+ bootstyle="info").grid(row=self.current_row, column=0, pady=5)
1549
+ ttk.Button(audio_frame, text=audio_i18n.get('get_anki_media_button', 'Get Anki Media Collection'),
1266
1550
  command=self.set_default_anki_media_collection, bootstyle="info").grid(row=self.current_row,
1267
1551
  column=1, pady=5)
1268
1552
  self.current_row += 1
1269
1553
 
1270
1554
  self.add_reset_button(audio_frame, "audio", self.current_row, 0, self.create_audio_tab)
1271
1555
 
1272
- for col in range(5):
1273
- audio_frame.grid_columnconfigure(col, weight=0)
1274
-
1275
- for row in range(self.current_row):
1276
- audio_frame.grid_rowconfigure(row, minsize=30)
1556
+ for col in range(5): audio_frame.grid_columnconfigure(col, weight=0)
1557
+ for row in range(self.current_row): audio_frame.grid_rowconfigure(row, minsize=30)
1277
1558
 
1278
1559
  return audio_frame
1279
1560
 
1280
1561
  def call_audio_offset_selector(self):
1281
1562
  try:
1282
- # if is_dev:
1283
- # path, beginning_offset, end_offset = r"C:\Users\Beangate\GSM\Electron App\test\tmphd01whan_untrimmed.opus", 500, 0
1284
- # else:
1285
1563
  path, beginning_offset, end_offset = gsm_state.previous_trim_args
1286
- # Get the directory of the current script
1287
1564
  current_dir = os.path.dirname(os.path.abspath(__file__))
1288
- # Construct the path to the audio offset selector script
1289
- script_path = os.path.join(current_dir,
1290
- "audio_offset_selector.py") # Replace with the actual script name if different
1565
+ script_path = os.path.join(current_dir, "audio_offset_selector.py")
1291
1566
 
1292
1567
  logger.info(' '.join([sys.executable, "-m", "GameSentenceMiner.util.audio_offset_selector",
1293
- "--path", path, "--beginning_offset", str(beginning_offset), "--end_offset",
1294
- str(end_offset)]))
1568
+ "--path", path, "--beginning_offset", str(beginning_offset), "--end_offset", str(end_offset)]))
1295
1569
 
1296
- # Run the script using subprocess.run()
1297
1570
  result = subprocess.run(
1298
1571
  [sys.executable, "-m", "GameSentenceMiner.util.audio_offset_selector",
1299
1572
  "--path", path, "--beginning_offset", str(beginning_offset), "--end_offset", str(end_offset)],
1300
- capture_output=True,
1301
- text=True, # Get output as text
1302
- check=False # Raise an exception for non-zero exit codes
1573
+ capture_output=True, text=True, check=False
1303
1574
  )
1304
1575
  if result.returncode != 0:
1305
1576
  logger.error(f"Script failed with return code: {result.returncode}")
1306
1577
  return None
1578
+
1307
1579
  logger.info(result)
1308
1580
  logger.info(f"Audio offset selector script output: {result.stdout.strip()}")
1309
- pyperclip.copy(result.stdout.strip()) # Copy the output to clipboard
1310
- messagebox.showinfo("Clipboard", "Offset copied to clipboard!")
1311
- return result.stdout.strip() # Return the output
1581
+ pyperclip.copy(result.stdout.strip())
1582
+
1583
+ dialog_i18n = self.i18n.get('dialogs', {}).get('offset_copied', {})
1584
+ messagebox.showinfo(dialog_i18n.get('title', 'Clipboard'), dialog_i18n.get('message', 'Offset copied!'))
1585
+ return result.stdout.strip()
1312
1586
 
1313
1587
  except subprocess.CalledProcessError as e:
1314
- logger.error(f"Error calling script: {e}")
1315
- logger.error(f"Script output (stderr): {e.stderr.strip()}")
1588
+ logger.error(f"Error calling script: {e}\nStderr: {e.stderr.strip()}")
1316
1589
  return None
1317
1590
  except FileNotFoundError:
1318
- logger.error(f"Error: Script not found at {script_path}. Make sure the script name is correct.")
1591
+ logger.error(f"Error: Script not found at {script_path}.")
1319
1592
  return None
1320
1593
  except Exception as e:
1321
1594
  logger.error(f"An unexpected error occurred: {e}")
@@ -1324,95 +1597,91 @@ class ConfigApp:
1324
1597
  @new_tab
1325
1598
  def create_obs_tab(self):
1326
1599
  if self.obs_tab is None:
1600
+ obs_i18n = self.i18n.get('tabs', {}).get('obs', {})
1327
1601
  self.obs_tab = ttk.Frame(self.notebook, padding=15)
1328
- self.notebook.add(self.obs_tab, text='OBS')
1602
+ self.notebook.add(self.obs_tab, text=obs_i18n.get('title', 'OBS'))
1329
1603
  else:
1330
1604
  for widget in self.obs_tab.winfo_children():
1331
1605
  widget.destroy()
1332
1606
 
1333
1607
  obs_frame = self.obs_tab
1608
+ obs_i18n = self.i18n.get('tabs', {}).get('obs', {})
1334
1609
 
1335
- HoverInfoLabelWidget(obs_frame, text="Enabled:", tooltip="Enable or disable OBS integration.",
1610
+ enabled_i18n = obs_i18n.get('enabled', {})
1611
+ HoverInfoLabelWidget(obs_frame, text=enabled_i18n.get('label', '...'), tooltip=enabled_i18n.get('tooltip', '...'),
1336
1612
  row=self.current_row, column=0)
1337
- self.obs_enabled = tk.BooleanVar(value=self.settings.obs.enabled)
1338
- ttk.Checkbutton(obs_frame, variable=self.obs_enabled, bootstyle="round-toggle").grid(row=self.current_row,
1339
- column=1, sticky='W',
1340
- pady=2)
1613
+ ttk.Checkbutton(obs_frame, variable=self.obs_enabled_value, bootstyle="round-toggle").grid(row=self.current_row,
1614
+ column=1, sticky='W', pady=2)
1341
1615
  self.current_row += 1
1342
1616
 
1343
- HoverInfoLabelWidget(obs_frame, text="Open OBS:", tooltip="Open OBS when the GSM starts.", row=self.current_row,
1617
+ open_i18n = obs_i18n.get('open_obs', {})
1618
+ HoverInfoLabelWidget(obs_frame, text=open_i18n.get('label', '...'), tooltip=open_i18n.get('tooltip', '...'), row=self.current_row,
1344
1619
  column=0)
1345
- self.open_obs = tk.BooleanVar(value=self.settings.obs.open_obs)
1346
- ttk.Checkbutton(obs_frame, variable=self.open_obs, bootstyle="round-toggle").grid(row=self.current_row,
1620
+ ttk.Checkbutton(obs_frame, variable=self.obs_open_obs_value, bootstyle="round-toggle").grid(row=self.current_row,
1347
1621
  column=1, sticky='W', pady=2)
1348
1622
  self.current_row += 1
1349
1623
 
1350
- HoverInfoLabelWidget(obs_frame, text="Close OBS:", tooltip="Close OBS when the GSM closes.",
1624
+ close_i18n = obs_i18n.get('close_obs', {})
1625
+ HoverInfoLabelWidget(obs_frame, text=close_i18n.get('label', '...'), tooltip=close_i18n.get('tooltip', '...'),
1351
1626
  row=self.current_row, column=0)
1352
- self.close_obs = tk.BooleanVar(value=self.settings.obs.close_obs)
1353
- ttk.Checkbutton(obs_frame, variable=self.close_obs, bootstyle="round-toggle").grid(row=self.current_row,
1627
+ ttk.Checkbutton(obs_frame, variable=self.obs_close_obs_value, bootstyle="round-toggle").grid(row=self.current_row,
1354
1628
  column=1, sticky='W', pady=2)
1355
1629
  self.current_row += 1
1356
1630
 
1357
- HoverInfoLabelWidget(obs_frame, text="Host:", tooltip="Host address for the OBS WebSocket server.",
1631
+ host_i18n = obs_i18n.get('host', {})
1632
+ HoverInfoLabelWidget(obs_frame, text=host_i18n.get('label', '...'), tooltip=host_i18n.get('tooltip', '...'),
1358
1633
  row=self.current_row, column=0)
1359
- self.obs_host = ttk.Entry(obs_frame)
1360
- self.obs_host.insert(0, self.settings.obs.host)
1361
- self.obs_host.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1634
+ ttk.Entry(obs_frame, textvariable=self.obs_host_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1362
1635
  self.current_row += 1
1363
1636
 
1364
- HoverInfoLabelWidget(obs_frame, text="Port:", tooltip="Port number for the OBS WebSocket server.",
1637
+ port_i18n = obs_i18n.get('port', {})
1638
+ HoverInfoLabelWidget(obs_frame, text=port_i18n.get('label', '...'), tooltip=port_i18n.get('tooltip', '...'),
1365
1639
  row=self.current_row, column=0)
1366
- self.obs_port = ttk.Entry(obs_frame)
1367
- self.obs_port.insert(0, str(self.settings.obs.port))
1368
- self.obs_port.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1640
+ ttk.Entry(obs_frame, textvariable=self.obs_port_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1369
1641
  self.current_row += 1
1370
1642
 
1371
- HoverInfoLabelWidget(obs_frame, text="Password:", tooltip="Password for the OBS WebSocket server.",
1643
+ pass_i18n = obs_i18n.get('password', {})
1644
+ HoverInfoLabelWidget(obs_frame, text=pass_i18n.get('label', '...'), tooltip=pass_i18n.get('tooltip', '...'),
1372
1645
  row=self.current_row, column=0)
1373
- self.obs_password = ttk.Entry(obs_frame, show="*")
1374
- self.obs_password.insert(0, self.settings.obs.password)
1375
- self.obs_password.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1646
+ ttk.Entry(obs_frame, show="*", textvariable=self.obs_password_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1376
1647
  self.current_row += 1
1377
1648
 
1378
- HoverInfoLabelWidget(obs_frame, text="Get Game From Scene Name:", tooltip="Changes Current Game to Scene Name",
1649
+ game_scene_i18n = obs_i18n.get('game_from_scene', {})
1650
+ HoverInfoLabelWidget(obs_frame, text=game_scene_i18n.get('label', '...'), tooltip=game_scene_i18n.get('tooltip', '...'),
1379
1651
  row=self.current_row, column=0)
1380
- self.get_game_from_scene_name = tk.BooleanVar(value=self.settings.obs.get_game_from_scene)
1381
- ttk.Checkbutton(obs_frame, variable=self.get_game_from_scene_name, bootstyle="round-toggle").grid(
1382
- row=self.current_row, column=1,
1383
- sticky='W', pady=2)
1652
+ ttk.Checkbutton(obs_frame, variable=self.obs_get_game_from_scene_name_value, bootstyle="round-toggle").grid(
1653
+ row=self.current_row, column=1, sticky='W', pady=2)
1384
1654
  self.current_row += 1
1385
1655
 
1386
- HoverInfoLabelWidget(obs_frame, text="Minimum Replay Size (KB):",
1387
- tooltip="Minimum Replay Size for OBS Replays in KB. If Replay is Under this, Audio/Screenshot Will not be grabbed.",
1656
+ min_size_i18n = obs_i18n.get('min_replay_size', {})
1657
+ HoverInfoLabelWidget(obs_frame, text=min_size_i18n.get('label', '...'),
1658
+ tooltip=min_size_i18n.get('tooltip', '...'),
1388
1659
  row=self.current_row, column=0)
1389
- self.minimum_replay_size = ttk.Entry(obs_frame)
1390
- self.minimum_replay_size.insert(0, str(self.settings.obs.minimum_replay_size))
1391
- self.minimum_replay_size.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1660
+ ttk.Entry(obs_frame, textvariable=self.obs_minimum_replay_size_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1392
1661
  self.current_row += 1
1393
1662
 
1394
1663
  self.add_reset_button(obs_frame, "obs", self.current_row, 0, self.create_obs_tab)
1395
1664
 
1396
- for col in range(3):
1397
- obs_frame.grid_columnconfigure(col, weight=0)
1398
-
1399
- for row in range(self.current_row):
1400
- obs_frame.grid_rowconfigure(row, minsize=30)
1665
+ for col in range(3): obs_frame.grid_columnconfigure(col, weight=0)
1666
+ for row in range(self.current_row): obs_frame.grid_rowconfigure(row, minsize=30)
1401
1667
 
1402
1668
  return obs_frame
1403
1669
 
1404
1670
  @new_tab
1405
1671
  def create_profiles_tab(self):
1406
1672
  if self.profiles_tab is None:
1673
+ profiles_i18n = self.i18n.get('tabs', {}).get('profiles', {})
1407
1674
  self.profiles_tab = ttk.Frame(self.notebook, padding=15)
1408
- self.notebook.add(self.profiles_tab, text='Profiles')
1675
+ self.notebook.add(self.profiles_tab, text=profiles_i18n.get('title', 'Profiles'))
1409
1676
  else:
1410
1677
  for widget in self.profiles_tab.winfo_children():
1411
1678
  widget.destroy()
1412
1679
 
1413
1680
  profiles_frame = self.profiles_tab
1681
+ profiles_i18n = self.i18n.get('tabs', {}).get('profiles', {})
1414
1682
 
1415
- HoverInfoLabelWidget(profiles_frame, text="Select Profile:", tooltip="Select a profile to load its settings.",
1683
+ select_i18n = profiles_i18n.get('select_profile', {})
1684
+ HoverInfoLabelWidget(profiles_frame, text=select_i18n.get('label', '...'), tooltip=select_i18n.get('tooltip', '...'),
1416
1685
  row=self.current_row, column=0)
1417
1686
  self.profile_var = tk.StringVar(value=self.settings.name)
1418
1687
  self.profile_combobox = ttk.Combobox(profiles_frame, textvariable=self.profile_var,
@@ -1422,11 +1691,11 @@ class ConfigApp:
1422
1691
  self.current_row += 1
1423
1692
 
1424
1693
  button_row = self.current_row
1425
- ttk.Button(profiles_frame, text="Add Profile", command=self.add_profile, bootstyle="primary").grid(
1694
+ ttk.Button(profiles_frame, text=profiles_i18n.get('add_button', 'Add Profile'), command=self.add_profile, bootstyle="primary").grid(
1426
1695
  row=button_row, column=0, pady=5)
1427
- ttk.Button(profiles_frame, text="Copy Profile", command=self.copy_profile, bootstyle="secondary").grid(
1696
+ ttk.Button(profiles_frame, text=profiles_i18n.get('copy_button', 'Copy Profile'), command=self.copy_profile, bootstyle="secondary").grid(
1428
1697
  row=button_row, column=1, pady=5)
1429
- self.delete_profile_button = ttk.Button(profiles_frame, text="Delete Config", command=self.delete_profile,
1698
+ self.delete_profile_button = ttk.Button(profiles_frame, text=profiles_i18n.get('delete_button', 'Delete Config'), command=self.delete_profile,
1430
1699
  bootstyle="danger")
1431
1700
  if self.master_config.current_profile != DEFAULT_CONFIG:
1432
1701
  self.delete_profile_button.grid(row=button_row, column=2, pady=5)
@@ -1434,31 +1703,29 @@ class ConfigApp:
1434
1703
  self.delete_profile_button.grid_remove()
1435
1704
  self.current_row += 1
1436
1705
 
1437
- HoverInfoLabelWidget(profiles_frame, text="OBS Scene (Auto Switch Profile):",
1438
- tooltip="Select an OBS scene to associate with this profile. (Optional)",
1706
+ scene_i18n = profiles_i18n.get('obs_scene', {})
1707
+ HoverInfoLabelWidget(profiles_frame, text=scene_i18n.get('label', '...'),
1708
+ tooltip=scene_i18n.get('tooltip', '...'),
1439
1709
  row=self.current_row, column=0)
1440
1710
  self.obs_scene_var = tk.StringVar(value="")
1441
1711
  self.obs_scene_listbox = tk.Listbox(profiles_frame, listvariable=self.obs_scene_var, selectmode=tk.MULTIPLE,
1442
1712
  height=10, width=50, selectbackground=ttk.Style().colors.primary)
1443
1713
  self.obs_scene_listbox.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1444
1714
  self.obs_scene_listbox.bind("<<ListboxSelect>>", self.on_obs_scene_select)
1445
- ttk.Button(profiles_frame, text="Refresh Scenes", command=self.refresh_obs_scenes, bootstyle="outline").grid(
1715
+ ttk.Button(profiles_frame, text=profiles_i18n.get('refresh_scenes_button', 'Refresh Scenes'), command=self.refresh_obs_scenes, bootstyle="outline").grid(
1446
1716
  row=self.current_row, column=2, pady=5)
1447
1717
  self.current_row += 1
1448
1718
 
1449
- HoverInfoLabelWidget(profiles_frame, text="Switch To Default If Not Found:",
1450
- tooltip="Enable to switch to the default profile if the selected OBS scene is not found.",
1719
+ switch_i18n = profiles_i18n.get('switch_to_default', {})
1720
+ HoverInfoLabelWidget(profiles_frame, text=switch_i18n.get('label', '...'),
1721
+ tooltip=switch_i18n.get('tooltip', '...'),
1451
1722
  row=self.current_row, column=0)
1452
- self.switch_to_default_if_not_found = tk.BooleanVar(value=self.master_config.switch_to_default_if_not_found)
1453
- ttk.Checkbutton(profiles_frame, variable=self.switch_to_default_if_not_found, bootstyle="round-toggle").grid(
1723
+ ttk.Checkbutton(profiles_frame, variable=self.switch_to_default_if_not_found_value, bootstyle="round-toggle").grid(
1454
1724
  row=self.current_row, column=1, sticky='W', pady=2)
1455
1725
  self.current_row += 1
1456
1726
 
1457
- for col in range(4):
1458
- profiles_frame.grid_columnconfigure(col, weight=0)
1459
-
1460
- for row in range(self.current_row):
1461
- profiles_frame.grid_rowconfigure(row, minsize=30)
1727
+ for col in range(4): profiles_frame.grid_columnconfigure(col, weight=0)
1728
+ for row in range(self.current_row): profiles_frame.grid_rowconfigure(row, minsize=30)
1462
1729
 
1463
1730
  return profiles_frame
1464
1731
 
@@ -1470,328 +1737,295 @@ class ConfigApp:
1470
1737
  def refresh_obs_scenes(self):
1471
1738
  scenes = obs.get_obs_scenes()
1472
1739
  obs_scene_names = [scene['sceneName'] for scene in scenes]
1473
- self.obs_scene_listbox.delete(0, tk.END) # Clear existing items
1740
+ self.obs_scene_listbox.delete(0, tk.END)
1474
1741
  for scene_name in obs_scene_names:
1475
- self.obs_scene_listbox.insert(tk.END, scene_name) # Add each scene to the Listbox
1476
- for i, scene in enumerate(obs_scene_names): # Iterate through actual scene names
1477
- if scene.strip() in self.settings.scenes: # Check if scene is in current settings
1478
- self.obs_scene_listbox.select_set(i) # Select the item in the Listbox
1742
+ self.obs_scene_listbox.insert(tk.END, scene_name)
1743
+ for i, scene in enumerate(obs_scene_names):
1744
+ if scene.strip() in self.settings.scenes:
1745
+ self.obs_scene_listbox.select_set(i)
1479
1746
  self.obs_scene_listbox.activate(i)
1480
- self.obs_scene_listbox.update_idletasks() # Ensure the GUI reflects the changes
1747
+ self.obs_scene_listbox.update_idletasks()
1481
1748
 
1482
1749
  @new_tab
1483
1750
  def create_advanced_tab(self):
1484
1751
  if self.advanced_tab is None:
1752
+ advanced_i18n = self.i18n.get('tabs', {}).get('advanced', {})
1485
1753
  self.advanced_tab = ttk.Frame(self.notebook, padding=15)
1486
- self.notebook.add(self.advanced_tab, text='Advanced')
1754
+ self.notebook.add(self.advanced_tab, text=advanced_i18n.get('title', 'Advanced'))
1487
1755
  else:
1488
1756
  for widget in self.advanced_tab.winfo_children():
1489
1757
  widget.destroy()
1490
1758
 
1491
1759
  advanced_frame = self.advanced_tab
1760
+ advanced_i18n = self.i18n.get('tabs', {}).get('advanced', {})
1761
+ browse_text = self.i18n.get('buttons', {}).get('browse', 'Browse')
1492
1762
 
1493
- ttk.Label(advanced_frame, text="Note: Only one of these will take effect, prioritizing audio.",
1763
+ ttk.Label(advanced_frame, text=advanced_i18n.get('player_note', '...'),
1494
1764
  foreground="red", font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=3,
1495
1765
  sticky='W', pady=5)
1496
1766
  self.current_row += 1
1497
1767
 
1498
- HoverInfoLabelWidget(advanced_frame, text="Audio Player Path:",
1499
- tooltip="Path to the audio player executable. Will open the trimmed Audio",
1768
+ audio_player_i18n = advanced_i18n.get('audio_player_path', {})
1769
+ HoverInfoLabelWidget(advanced_frame, text=audio_player_i18n.get('label', '...'),
1770
+ tooltip=audio_player_i18n.get('tooltip', '...'),
1500
1771
  row=self.current_row, column=0)
1501
- self.audio_player_path = ttk.Entry(advanced_frame, width=50)
1502
- self.audio_player_path.insert(0, self.settings.advanced.audio_player_path)
1503
- self.audio_player_path.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1504
- ttk.Button(advanced_frame, text="Browse", command=lambda: self.browse_file(self.audio_player_path),
1772
+ audio_player_entry = ttk.Entry(advanced_frame, width=50, textvariable=self.audio_player_path_value)
1773
+ audio_player_entry.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1774
+ ttk.Button(advanced_frame, text=browse_text, command=lambda: self.browse_file(audio_player_entry),
1505
1775
  bootstyle="outline").grid(row=self.current_row, column=2, padx=5, pady=2)
1506
1776
  self.current_row += 1
1507
1777
 
1508
- HoverInfoLabelWidget(advanced_frame, text="Video Player Path:",
1509
- tooltip="Path to the video player executable. Will seek to the location of the line in the replay",
1778
+ video_player_i18n = advanced_i18n.get('video_player_path', {})
1779
+ HoverInfoLabelWidget(advanced_frame, text=video_player_i18n.get('label', '...'),
1780
+ tooltip=video_player_i18n.get('tooltip', '...'),
1510
1781
  row=self.current_row, column=0)
1511
- self.video_player_path = ttk.Entry(advanced_frame, width=50)
1512
- self.video_player_path.insert(0, self.settings.advanced.video_player_path)
1513
- self.video_player_path.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1514
- ttk.Button(advanced_frame, text="Browse", command=lambda: self.browse_file(self.video_player_path),
1782
+ video_player_entry = ttk.Entry(advanced_frame, width=50, textvariable=self.video_player_path_value)
1783
+ video_player_entry.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1784
+ ttk.Button(advanced_frame, text=browse_text, command=lambda: self.browse_file(video_player_entry),
1515
1785
  bootstyle="outline").grid(row=self.current_row, column=2, padx=5, pady=2)
1516
1786
  self.current_row += 1
1517
1787
 
1518
- HoverInfoLabelWidget(advanced_frame, text="Play Latest Video/Audio Hotkey:",
1519
- tooltip="Hotkey to trim and play the latest audio.", row=self.current_row, column=0)
1520
- self.play_latest_audio_hotkey = ttk.Entry(advanced_frame)
1521
- self.play_latest_audio_hotkey.insert(0, self.settings.hotkeys.play_latest_audio)
1522
- self.play_latest_audio_hotkey.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1788
+ play_hotkey_i18n = advanced_i18n.get('play_latest_hotkey', {})
1789
+ HoverInfoLabelWidget(advanced_frame, text=play_hotkey_i18n.get('label', '...'),
1790
+ tooltip=play_hotkey_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1791
+ ttk.Entry(advanced_frame, textvariable=self.play_latest_audio_hotkey_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1523
1792
  self.current_row += 1
1524
1793
 
1525
- HoverInfoLabelWidget(advanced_frame, text="Multi-line Line-Break:",
1526
- tooltip="Line break for multi-line mining. This goes between each sentence",
1794
+ linebreak_i18n = advanced_i18n.get('multiline_linebreak', {})
1795
+ HoverInfoLabelWidget(advanced_frame, text=linebreak_i18n.get('label', '...'),
1796
+ tooltip=linebreak_i18n.get('tooltip', '...'),
1527
1797
  row=self.current_row, column=0)
1528
- self.multi_line_line_break = ttk.Entry(advanced_frame)
1529
- self.multi_line_line_break.insert(0, self.settings.advanced.multi_line_line_break)
1530
- self.multi_line_line_break.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1798
+ ttk.Entry(advanced_frame, textvariable=self.multi_line_line_break_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1531
1799
  self.current_row += 1
1532
1800
 
1533
- HoverInfoLabelWidget(advanced_frame, text="Multi-Line Sentence Storage Field:",
1534
- tooltip="Field in Anki for storing the multi-line sentence temporarily.",
1801
+ storage_field_i18n = advanced_i18n.get('multiline_storage_field', {})
1802
+ HoverInfoLabelWidget(advanced_frame, text=storage_field_i18n.get('label', '...'),
1803
+ tooltip=storage_field_i18n.get('tooltip', '...'),
1535
1804
  row=self.current_row, column=0)
1536
- self.multi_line_sentence_storage_field = ttk.Entry(advanced_frame)
1537
- self.multi_line_sentence_storage_field.insert(0, self.settings.advanced.multi_line_sentence_storage_field)
1538
- self.multi_line_sentence_storage_field.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1805
+ ttk.Entry(advanced_frame, textvariable=self.multi_line_sentence_storage_field_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1539
1806
  self.current_row += 1
1540
1807
 
1541
- HoverInfoLabelWidget(advanced_frame, text="OCR WebSocket Port:",
1542
- tooltip="Port for OCR WebSocket communication. GSM will also listen on this port",
1808
+ ocr_port_i18n = advanced_i18n.get('ocr_port', {})
1809
+ HoverInfoLabelWidget(advanced_frame, text=ocr_port_i18n.get('label', '...'),
1810
+ tooltip=ocr_port_i18n.get('tooltip', '...'),
1543
1811
  row=self.current_row, column=0)
1544
- self.ocr_websocket_port = ttk.Entry(advanced_frame)
1545
- self.ocr_websocket_port.insert(0, str(self.settings.advanced.ocr_websocket_port))
1546
- self.ocr_websocket_port.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1812
+ ttk.Entry(advanced_frame, textvariable=self.ocr_websocket_port_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1547
1813
  self.current_row += 1
1548
1814
 
1549
- HoverInfoLabelWidget(advanced_frame, text="Texthooker Communication WebSocket Port:",
1550
- tooltip="Port for GSM Texthooker WebSocket communication. Does nothing right now, hardcoded to 55001",
1815
+ comm_port_i18n = advanced_i18n.get('texthooker_comm_port', {})
1816
+ HoverInfoLabelWidget(advanced_frame, text=comm_port_i18n.get('label', '...'),
1817
+ tooltip=comm_port_i18n.get('tooltip', '...'),
1551
1818
  row=self.current_row, column=0)
1552
- self.texthooker_communication_websocket_port = ttk.Entry(advanced_frame)
1553
- self.texthooker_communication_websocket_port.insert(0,
1554
- str(self.settings.advanced.texthooker_communication_websocket_port))
1555
- self.texthooker_communication_websocket_port.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1819
+ ttk.Entry(advanced_frame, textvariable=self.texthooker_communication_websocket_port_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1556
1820
  self.current_row += 1
1557
1821
 
1558
- HoverInfoLabelWidget(advanced_frame, text="Plaintext Websocket Export Port:",
1559
- tooltip="Port for GSM Plaintext WebSocket Export communication. Does nothing right now, hardcoded to 55002",
1822
+ export_port_i18n = advanced_i18n.get('plaintext_export_port', {})
1823
+ HoverInfoLabelWidget(advanced_frame, text=export_port_i18n.get('label', '...'),
1824
+ tooltip=export_port_i18n.get('tooltip', '...'),
1560
1825
  row=self.current_row, column=0)
1561
- self.plaintext_websocket_export_port = ttk.Entry(advanced_frame)
1562
- self.plaintext_websocket_export_port.insert(0, str(self.settings.advanced.plaintext_websocket_port))
1563
- self.plaintext_websocket_export_port.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1826
+ ttk.Entry(advanced_frame, textvariable=self.plaintext_websocket_export_port_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1564
1827
  self.current_row += 1
1565
1828
 
1566
- # HoverInfoLabelWidget(advanced_frame, text="Use Anki Creation Date for Audio Timing:",
1567
- # tooltip="Use the Anki note creation date for audio timing instead of the OBS replay time.",
1568
- # row=self.current_row, column=0)
1569
- # self.use_anki_note_creation_time = tk.BooleanVar(value=self.settings.advanced.use_anki_note_creation_time)
1570
- # ttk.Checkbutton(advanced_frame, variable=self.use_anki_note_creation_time, bootstyle="round-toggle").grid(
1571
- # row=self.current_row, column=1, sticky='W', pady=2)
1572
- # self.current_row += 1
1573
-
1574
- HoverInfoLabelWidget(advanced_frame, text="Reset Line Hotkey:",
1575
- tooltip="Hotkey to reset the current line of dialogue.", row=self.current_row, column=0)
1576
- self.reset_line_hotkey = ttk.Entry(advanced_frame)
1577
- self.reset_line_hotkey.insert(0, self.settings.hotkeys.reset_line)
1578
- self.reset_line_hotkey.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1829
+ reset_hotkey_i18n = advanced_i18n.get('reset_line_hotkey', {})
1830
+ HoverInfoLabelWidget(advanced_frame, text=reset_hotkey_i18n.get('label', '...'),
1831
+ tooltip=reset_hotkey_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1832
+ ttk.Entry(advanced_frame, textvariable=self.reset_line_hotkey_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1579
1833
  self.current_row += 1
1580
1834
 
1581
- HoverInfoLabelWidget(advanced_frame, text="Polling Rate:",
1582
- tooltip="Rate at which Anki will check for updates (in milliseconds).",
1835
+ polling_i18n = advanced_i18n.get('polling_rate', {})
1836
+ HoverInfoLabelWidget(advanced_frame, text=polling_i18n.get('label', '...'),
1837
+ tooltip=polling_i18n.get('tooltip', '...'),
1583
1838
  row=self.current_row, column=0)
1584
- self.polling_rate = ttk.Entry(advanced_frame)
1585
- self.polling_rate.insert(0, str(self.settings.anki.polling_rate))
1586
- self.polling_rate.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1839
+ ttk.Entry(advanced_frame, textvariable=self.polling_rate_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1840
+ self.current_row += 1
1841
+
1842
+ current_ver_i18n = advanced_i18n.get('current_version', {})
1843
+ HoverInfoLabelWidget(advanced_frame, text=current_ver_i18n.get('label', 'Current Version:'), bootstyle="secondary",
1844
+ tooltip=current_ver_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1845
+ self.current_version = ttk.Label(advanced_frame, text=get_current_version(), bootstyle="secondary")
1846
+ self.current_version.grid(row=self.current_row, column=1, sticky='W', pady=2)
1587
1847
  self.current_row += 1
1588
1848
 
1589
- # HoverInfoLabelWidget(advanced_frame, text="Vosk URL:", tooltip="URL for connecting to the Vosk server.",
1590
- # row=self.current_row, column=0)
1591
- # self.vosk_url = ttk.Combobox(advanced_frame, values=[VOSK_BASE, VOSK_SMALL], state="readonly")
1592
- # self.vosk_url.set(
1593
- # VOSK_BASE if self.settings.vad.vosk_url == 'https://alphacephei.com/vosk/models/vosk-model-ja-0.22.zip' else VOSK_SMALL)
1594
- # self.vosk_url.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1595
- # self.current_row += 1
1849
+ latest_ver_i18n = advanced_i18n.get('latest_version', {})
1850
+ HoverInfoLabelWidget(advanced_frame, text=latest_ver_i18n.get('label', 'Latest Version:'), bootstyle="secondary",
1851
+ tooltip=latest_ver_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1852
+ self.latest_version = ttk.Label(advanced_frame, text=get_latest_version(), bootstyle="secondary")
1853
+ self.latest_version.grid(row=self.current_row, column=1, sticky='W', pady=2)
1854
+ self.current_row += 1
1596
1855
 
1597
1856
  self.add_reset_button(advanced_frame, "advanced", self.current_row, 0, self.create_advanced_tab)
1598
1857
 
1599
- for col in range(4):
1600
- advanced_frame.grid_columnconfigure(col, weight=0)
1601
-
1602
- for row in range(self.current_row):
1603
- advanced_frame.grid_rowconfigure(row, minsize=30)
1858
+ for col in range(4): advanced_frame.grid_columnconfigure(col, weight=0)
1859
+ for row in range(self.current_row): advanced_frame.grid_rowconfigure(row, minsize=30)
1604
1860
 
1605
1861
  return advanced_frame
1606
1862
 
1607
1863
  @new_tab
1608
1864
  def create_ai_tab(self):
1609
1865
  if self.ai_tab is None:
1866
+ ai_i18n = self.i18n.get('tabs', {}).get('ai', {})
1610
1867
  self.ai_tab = ttk.Frame(self.notebook, padding=15)
1611
- self.notebook.add(self.ai_tab, text='AI')
1868
+ self.notebook.add(self.ai_tab, text=ai_i18n.get('title', 'AI'))
1612
1869
  else:
1613
1870
  for widget in self.ai_tab.winfo_children():
1614
1871
  widget.destroy()
1615
1872
 
1616
1873
  ai_frame = self.ai_tab
1874
+ ai_i18n = self.i18n.get('tabs', {}).get('ai', {})
1617
1875
 
1618
- HoverInfoLabelWidget(ai_frame, text="Enabled:", tooltip="Enable or disable AI integration.",
1876
+ enabled_i18n = ai_i18n.get('enabled', {})
1877
+ HoverInfoLabelWidget(ai_frame, text=enabled_i18n.get('label', '...'), tooltip=enabled_i18n.get('tooltip', '...'),
1619
1878
  row=self.current_row, column=0)
1620
- self.ai_enabled = tk.BooleanVar(value=self.settings.ai.enabled)
1621
- ttk.Checkbutton(ai_frame, variable=self.ai_enabled, bootstyle="round-toggle").grid(row=self.current_row,
1879
+ ttk.Checkbutton(ai_frame, variable=self.ai_enabled_value, bootstyle="round-toggle").grid(row=self.current_row,
1622
1880
  column=1, sticky='W', pady=2)
1623
1881
  self.current_row += 1
1624
1882
 
1625
- HoverInfoLabelWidget(ai_frame, text="Provider:", tooltip="Select the AI provider.", row=self.current_row,
1883
+ provider_i18n = ai_i18n.get('provider', {})
1884
+ HoverInfoLabelWidget(ai_frame, text=provider_i18n.get('label', '...'), tooltip=provider_i18n.get('tooltip', '...'), row=self.current_row,
1626
1885
  column=0)
1627
- self.ai_provider = ttk.Combobox(ai_frame, values=[AI_GEMINI, AI_GROQ, AI_LOCAL], state="readonly")
1628
- self.ai_provider.set(self.settings.ai.provider)
1629
- self.ai_provider.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1886
+ ttk.Combobox(ai_frame, textvariable=self.ai_provider_value, values=[AI_GEMINI, AI_GROQ, AI_LOCAL], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
1630
1887
  self.current_row += 1
1631
1888
 
1632
- HoverInfoLabelWidget(ai_frame, text="Gemini AI Model:", tooltip="Select the AI model to use.",
1889
+ gemini_model_i18n = ai_i18n.get('gemini_model', {})
1890
+ HoverInfoLabelWidget(ai_frame, text=gemini_model_i18n.get('label', '...'), tooltip=gemini_model_i18n.get('tooltip', '...'),
1633
1891
  row=self.current_row, column=0)
1634
- self.gemini_model = ttk.Combobox(ai_frame, values=['gemma-3n-e4b-it', 'gemini-2.5-flash-lite-preview-06-17', 'gemini-2.5-flash','gemini-2.0-flash', 'gemini-2.0-flash-lite'], state="readonly")
1635
- try:
1636
- self.gemini_model.set(self.settings.ai.gemini_model)
1637
- except Exception:
1638
- self.gemini_model.set('gemini-2.5-flash')
1639
- self.gemini_model.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1892
+ ttk.Combobox(ai_frame, textvariable=self.gemini_model_value, values=['gemma-3n-e4b-it', 'gemini-2.5-flash-lite', 'gemini-2.5-flash','gemini-2.0-flash', 'gemini-2.0-flash-lite'], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
1640
1893
  self.current_row += 1
1641
1894
 
1642
- HoverInfoLabelWidget(ai_frame, text="Gemini API Key:",
1643
- tooltip="API key for the selected AI provider (Gemini only currently).",
1895
+ gemini_key_i18n = ai_i18n.get('gemini_api_key', {})
1896
+ HoverInfoLabelWidget(ai_frame, text=gemini_key_i18n.get('label', '...'),
1897
+ tooltip=gemini_key_i18n.get('tooltip', '...'),
1644
1898
  foreground="green", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1645
- self.gemini_api_key = ttk.Entry(ai_frame, show="*") # Mask the API key for security
1646
- self.gemini_api_key.insert(0, self.settings.ai.gemini_api_key)
1647
- self.gemini_api_key.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1899
+ ttk.Entry(ai_frame, show="*", textvariable=self.gemini_api_key_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1648
1900
  self.current_row += 1
1649
1901
 
1650
- HoverInfoLabelWidget(ai_frame, text="Groq AI Model:", tooltip="Select the Groq AI model to use.",
1902
+ groq_model_i18n = ai_i18n.get('groq_model', {})
1903
+ HoverInfoLabelWidget(ai_frame, text=groq_model_i18n.get('label', '...'), tooltip=groq_model_i18n.get('tooltip', '...'),
1651
1904
  row=self.current_row, column=0)
1652
- self.groq_model = ttk.Combobox(ai_frame, values=['meta-llama/llama-4-maverick-17b-128e-instruct',
1905
+ ttk.Combobox(ai_frame, textvariable=self.groq_model_value, values=['meta-llama/llama-4-maverick-17b-128e-instruct',
1653
1906
  'meta-llama/llama-4-scout-17b-16e-instruct',
1654
- 'llama-3.1-8b-instant'], state="readonly")
1655
- self.groq_model.set(self.settings.ai.groq_model)
1656
- self.groq_model.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1907
+ 'llama-3.1-8b-instant'], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
1657
1908
  self.current_row += 1
1658
1909
 
1659
- HoverInfoLabelWidget(ai_frame, text="Groq API Key:", tooltip="API key for Groq AI provider.",
1910
+ groq_key_i18n = ai_i18n.get('groq_api_key', {})
1911
+ HoverInfoLabelWidget(ai_frame, text=groq_key_i18n.get('label', '...'), tooltip=groq_key_i18n.get('tooltip', '...'),
1660
1912
  row=self.current_row, column=0)
1661
- self.groq_api_key = ttk.Entry(ai_frame, show="*") # Mask the API key for security
1662
- self.groq_api_key.insert(0, self.settings.ai.groq_api_key)
1663
- self.groq_api_key.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1913
+ ttk.Entry(ai_frame, show="*", textvariable=self.groq_api_key_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1664
1914
  self.current_row += 1
1665
1915
 
1666
- # red
1667
- HoverInfoLabelWidget(ai_frame, text="Local AI Model:", tooltip="Local AI Model to Use, Only very basic Translation is supported atm. May require some other setup, but idk."
1668
- ,foreground="red", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1669
- self.local_ai_model = ttk.Combobox(ai_frame, values=[OFF, 'facebook/nllb-200-distilled-600M', 'facebook/nllb-200-1.3B', 'facebook/nllb-200-3.3B'])
1670
- self.local_ai_model.set(self.settings.ai.local_model)
1671
- self.local_ai_model.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1916
+ local_model_i18n = ai_i18n.get('local_model', {})
1917
+ HoverInfoLabelWidget(ai_frame, text=local_model_i18n.get('label', '...'), tooltip=local_model_i18n.get('tooltip', '...'),
1918
+ foreground="red", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1919
+ ttk.Combobox(ai_frame, textvariable=self.local_ai_model_value, values=[OFF, 'facebook/nllb-200-distilled-600M', 'facebook/nllb-200-1.3B', 'facebook/nllb-200-3.3B']).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1672
1920
  self.current_row += 1
1673
1921
 
1674
- HoverInfoLabelWidget(ai_frame, text="Anki Field:", tooltip="Field in Anki for AI-generated content.",
1922
+ anki_field_i18n = ai_i18n.get('anki_field', {})
1923
+ HoverInfoLabelWidget(ai_frame, text=anki_field_i18n.get('label', '...'), tooltip=anki_field_i18n.get('tooltip', '...'),
1675
1924
  row=self.current_row, column=0)
1676
- self.ai_anki_field = ttk.Entry(ai_frame)
1677
- self.ai_anki_field.insert(0, self.settings.ai.anki_field)
1678
- self.ai_anki_field.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1925
+ ttk.Entry(ai_frame, textvariable=self.ai_anki_field_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1679
1926
  self.current_row += 1
1680
1927
 
1681
- HoverInfoLabelWidget(ai_frame, text="Dialogue Context Length:", tooltip="Number of previous/next lines to include as context for AI. 0 to disable. -1 for as many as possible (Use With Caution)",
1928
+ context_i18n = ai_i18n.get('context_length', {})
1929
+ HoverInfoLabelWidget(ai_frame, text=context_i18n.get('label', '...'), tooltip=context_i18n.get('tooltip', '...'),
1682
1930
  foreground="red", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1683
- self.ai_dialogue_context_length = ttk.Entry(ai_frame)
1684
- self.ai_dialogue_context_length.insert(0, str(self.settings.ai.dialogue_context_length))
1685
- self.ai_dialogue_context_length.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1931
+ ttk.Entry(ai_frame, textvariable=self.ai_dialogue_context_length_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1686
1932
  self.current_row += 1
1687
1933
 
1688
- HoverInfoLabelWidget(ai_frame, text="Use Canned Translation Prompt:",
1689
- tooltip="Use a pre-defined translation prompt for AI.", row=self.current_row, column=0)
1690
- self.use_canned_translation_prompt = tk.BooleanVar(value=self.settings.ai.use_canned_translation_prompt)
1691
- ttk.Checkbutton(ai_frame, variable=self.use_canned_translation_prompt, bootstyle="round-toggle").grid(
1692
- row=self.current_row, column=1,
1693
- sticky='W', pady=2)
1934
+ canned_trans_i18n = ai_i18n.get('use_canned_translation', {})
1935
+ HoverInfoLabelWidget(ai_frame, text=canned_trans_i18n.get('label', '...'),
1936
+ tooltip=canned_trans_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1937
+ ttk.Checkbutton(ai_frame, variable=self.use_canned_translation_prompt_value, bootstyle="round-toggle").grid(
1938
+ row=self.current_row, column=1, sticky='W', pady=2)
1694
1939
  self.current_row += 1
1695
1940
 
1696
- HoverInfoLabelWidget(ai_frame, text="Use Canned Context Prompt:",
1697
- tooltip="Use a pre-defined context prompt for AI.", row=self.current_row, column=0)
1698
- self.use_canned_context_prompt = tk.BooleanVar(value=self.settings.ai.use_canned_context_prompt)
1699
- ttk.Checkbutton(ai_frame, variable=self.use_canned_context_prompt, bootstyle="round-toggle").grid(
1700
- row=self.current_row, column=1,
1701
- sticky='W', pady=2)
1941
+ canned_context_i18n = ai_i18n.get('use_canned_context', {})
1942
+ HoverInfoLabelWidget(ai_frame, text=canned_context_i18n.get('label', '...'),
1943
+ tooltip=canned_context_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1944
+ ttk.Checkbutton(ai_frame, variable=self.use_canned_context_prompt_value, bootstyle="round-toggle").grid(
1945
+ row=self.current_row, column=1, sticky='W', pady=2)
1702
1946
  self.current_row += 1
1703
1947
 
1704
- HoverInfoLabelWidget(ai_frame, text="Custom Prompt:", tooltip="Custom prompt for AI processing.",
1948
+ custom_prompt_i18n = ai_i18n.get('custom_prompt', {})
1949
+ HoverInfoLabelWidget(ai_frame, text=custom_prompt_i18n.get('label', '...'), tooltip=custom_prompt_i18n.get('tooltip', '...'),
1705
1950
  row=self.current_row, column=0)
1706
1951
  self.custom_prompt = scrolledtext.ScrolledText(ai_frame, width=50, height=5, font=("TkDefaultFont", 9),
1707
1952
  relief="solid", borderwidth=1,
1708
- highlightbackground=ttk.Style().colors.border) # Adjust height as needed
1953
+ highlightbackground=ttk.Style().colors.border)
1709
1954
  self.custom_prompt.insert(tk.END, self.settings.ai.custom_prompt)
1710
1955
  self.custom_prompt.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1711
1956
  self.current_row += 1
1712
1957
 
1713
1958
  self.add_reset_button(ai_frame, "ai", self.current_row, 0, self.create_ai_tab)
1714
1959
 
1715
- for col in range(3):
1716
- ai_frame.grid_columnconfigure(col, weight=0)
1717
-
1718
- for row in range(self.current_row):
1719
- ai_frame.grid_rowconfigure(row, minsize=30)
1960
+ for col in range(3): ai_frame.grid_columnconfigure(col, weight=0)
1961
+ for row in range(self.current_row): ai_frame.grid_rowconfigure(row, minsize=30)
1720
1962
 
1721
1963
  return ai_frame
1722
-
1723
- # @new_tab
1724
- # def create_help_tab(self):
1725
- # help_frame = ttk.Frame(self.notebook, padding=15)
1726
- # self.notebook.add(help_frame, text='Help')
1727
- #
1728
- #
1729
- #
1730
- # help_frame.grid_columnconfigure(0, weight=1)
1731
1964
 
1732
1965
  @new_tab
1733
1966
  def create_wip_tab(self):
1734
1967
  if self.wip_tab is None:
1968
+ wip_i18n = self.i18n.get('tabs', {}).get('wip', {})
1735
1969
  self.wip_tab = ttk.Frame(self.notebook, padding=15)
1736
- self.notebook.add(self.wip_tab, text='WIP')
1970
+ self.notebook.add(self.wip_tab, text=wip_i18n.get('title', 'WIP'))
1737
1971
  else:
1738
1972
  for widget in self.wip_tab.winfo_children():
1739
1973
  widget.destroy()
1740
1974
 
1741
1975
  wip_frame = self.wip_tab
1976
+ wip_i18n = self.i18n.get('tabs', {}).get('wip', {})
1742
1977
  try:
1743
-
1744
- ttk.Label(wip_frame, text="Warning: These features are experimental and may not work as expected.",
1978
+ ttk.Label(wip_frame, text=wip_i18n.get('warning_experimental', '...'),
1745
1979
  foreground="red", font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2,
1746
1980
  sticky='W', pady=5)
1747
-
1748
1981
  self.current_row += 1
1749
1982
 
1750
- ttk.Label(wip_frame, text="Overlay requires OwOCR dependencies to be installed, and requires an external app to be running.",
1983
+ ttk.Label(wip_frame, text=wip_i18n.get('warning_overlay_deps', '...'),
1751
1984
  foreground="red", font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2,
1752
1985
  sticky='W', pady=5)
1753
-
1754
1986
  self.current_row += 1
1755
1987
 
1756
- HoverInfoLabelWidget(wip_frame, text="Overlay WebSocket Port:",
1757
- tooltip="Port for the overlay WebSocket communication. Used for experimental overlay features.",
1988
+ overlay_port_i18n = wip_i18n.get('overlay_port', {})
1989
+ HoverInfoLabelWidget(wip_frame, text=overlay_port_i18n.get('label', '...'),
1990
+ tooltip=overlay_port_i18n.get('tooltip', '...'),
1758
1991
  row=self.current_row, column=0)
1759
- self.overlay_websocket_port = ttk.Entry(wip_frame)
1760
- self.overlay_websocket_port.insert(0, str(self.settings.wip.overlay_websocket_port))
1761
- self.overlay_websocket_port.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1992
+ ttk.Entry(wip_frame, textvariable=self.overlay_websocket_port_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1762
1993
  self.current_row += 1
1763
1994
 
1764
- HoverInfoLabelWidget(wip_frame, text="Overlay WebSocket Send:",
1765
- tooltip="Enable to send overlay data via WebSocket. Experimental feature.",
1995
+ overlay_send_i18n = wip_i18n.get('overlay_send', {})
1996
+ HoverInfoLabelWidget(wip_frame, text=overlay_send_i18n.get('label', '...'),
1997
+ tooltip=overlay_send_i18n.get('tooltip', '...'),
1766
1998
  row=self.current_row, column=0)
1767
- self.overlay_websocket_send = tk.BooleanVar(value=self.settings.wip.overlay_websocket_send)
1768
- ttk.Checkbutton(wip_frame, variable=self.overlay_websocket_send, bootstyle="round-toggle").grid(
1999
+ ttk.Checkbutton(wip_frame, variable=self.overlay_websocket_send_value, bootstyle="round-toggle").grid(
1769
2000
  row=self.current_row, column=1, sticky='W', pady=2)
1770
2001
  self.current_row += 1
1771
2002
 
1772
- HoverInfoLabelWidget(wip_frame, text="Monitor to Capture:",
1773
- tooltip="Select the monitor to capture (1-based index).",
2003
+ monitor_i18n = wip_i18n.get('monitor_capture', {})
2004
+ HoverInfoLabelWidget(wip_frame, text=monitor_i18n.get('label', '...'),
2005
+ tooltip=monitor_i18n.get('tooltip', '...'),
1774
2006
  row=self.current_row, column=0)
1775
2007
  self.monitor_to_capture = ttk.Combobox(wip_frame, values=self.monitors, state="readonly")
1776
2008
 
1777
- # set index of monitor to capture, not the string
1778
2009
  if self.monitors:
1779
- self.monitor_to_capture.current(self.settings.wip.monitor_to_capture)
2010
+ # Ensure the index is valid
2011
+ monitor_index = self.settings.wip.monitor_to_capture
2012
+ if 0 <= monitor_index < len(self.monitors):
2013
+ self.monitor_to_capture.current(monitor_index)
2014
+ else:
2015
+ self.monitor_to_capture.current(0)
1780
2016
  else:
1781
- self.monitor_to_capture.set("OwOCR Not Detected")
2017
+ self.monitor_to_capture.set(monitor_i18n.get('not_detected', "OwOCR Not Detected"))
1782
2018
  self.monitor_to_capture.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1783
2019
  self.current_row += 1
1784
2020
 
1785
2021
  except Exception as e:
1786
2022
  logger.error(f"Error setting up wip tab to capture: {e}")
1787
- ttk.Label(wip_frame, text="Error setting up WIP tab", foreground="red").grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=5)
2023
+ ttk.Label(wip_frame, text=wip_i18n.get('error_setup', 'Error setting up WIP tab'), foreground="red").grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=5)
1788
2024
 
1789
2025
  self.add_reset_button(wip_frame, "wip", self.current_row, 0, self.create_wip_tab)
1790
2026
 
1791
- for col in range(2):
1792
- wip_frame.grid_columnconfigure(col, weight=0)
1793
- for row in range(self.current_row):
1794
- wip_frame.grid_rowconfigure(row, minsize=30)
2027
+ for col in range(2): wip_frame.grid_columnconfigure(col, weight=0)
2028
+ for row in range(self.current_row): wip_frame.grid_rowconfigure(row, minsize=30)
1795
2029
 
1796
2030
  return wip_frame
1797
2031
 
@@ -1805,7 +2039,8 @@ class ConfigApp:
1805
2039
  self.delete_profile_button.grid_remove()
1806
2040
 
1807
2041
  def add_profile(self):
1808
- new_profile_name = simpledialog.askstring("Input", "Enter new profile name:")
2042
+ dialog_i18n = self.i18n.get('dialogs', {}).get('add_profile', {})
2043
+ new_profile_name = simpledialog.askstring(dialog_i18n.get('title', 'Input'), dialog_i18n.get('prompt', 'Enter new profile name:'))
1809
2044
  if new_profile_name:
1810
2045
  self.master_config.configs[new_profile_name] = self.master_config.get_default_config()
1811
2046
  self.profile_combobox['values'] = list(self.master_config.configs.keys())
@@ -1815,12 +2050,12 @@ class ConfigApp:
1815
2050
 
1816
2051
  def copy_profile(self):
1817
2052
  source_profile = self.profile_combobox.get()
1818
- new_profile_name = simpledialog.askstring("Input", "Enter new profile name:", parent=self.window)
2053
+ dialog_i18n = self.i18n.get('dialogs', {}).get('copy_profile', {})
2054
+ new_profile_name = simpledialog.askstring(dialog_i18n.get('title', 'Input'), dialog_i18n.get('prompt', 'Enter new profile name:'), parent=self.window)
1819
2055
  if new_profile_name and source_profile in self.master_config.configs:
1820
- # Deep copy the configuration to avoid shared references
1821
2056
  import copy
1822
2057
  self.master_config.configs[new_profile_name] = copy.deepcopy(self.master_config.configs[source_profile])
1823
- self.master_config.configs[new_profile_name].name = new_profile_name # Update the name in the copied config
2058
+ self.master_config.configs[new_profile_name].name = new_profile_name
1824
2059
  self.profile_combobox['values'] = list(self.master_config.configs.keys())
1825
2060
  self.profile_combobox.set(new_profile_name)
1826
2061
  self.save_settings()
@@ -1828,13 +2063,16 @@ class ConfigApp:
1828
2063
 
1829
2064
  def delete_profile(self):
1830
2065
  profile_to_delete = self.profile_combobox.get()
2066
+ dialog_i18n = self.i18n.get('dialogs', {}).get('delete_profile', {})
2067
+ error_title = dialog_i18n.get('error_title', 'Error')
2068
+
1831
2069
  if profile_to_delete == "Default":
1832
- messagebox.showerror("Error", "Cannot delete the Default profile.")
2070
+ messagebox.showerror(error_title, dialog_i18n.get('error_cannot_delete_default', 'Cannot delete the Default profile.'))
1833
2071
  return
1834
2072
 
1835
2073
  if profile_to_delete and profile_to_delete in self.master_config.configs:
1836
- confirm = messagebox.askyesno("Confirm Delete",
1837
- f"Are you sure you want to delete the profile '{profile_to_delete}'?",
2074
+ confirm = messagebox.askyesno(dialog_i18n.get('title', 'Confirm Delete'),
2075
+ dialog_i18n.get('message', "Are you sure... '{profile_name}'?").format(profile_name=profile_to_delete),
1838
2076
  parent=self.window, icon='warning')
1839
2077
  if confirm:
1840
2078
  del self.master_config.configs[profile_to_delete]
@@ -1844,38 +2082,35 @@ class ConfigApp:
1844
2082
  save_full_config(self.master_config)
1845
2083
  self.reload_settings()
1846
2084
 
1847
- def show_error_box(self, title, message):
1848
- messagebox.showerror(title, message)
1849
-
1850
2085
  def download_and_install_ocen(self):
1851
- confirm = messagebox.askyesno("Download OcenAudio?",
1852
- "Would you like to download and install OcenAudio? It is a free audio editing software that works extremely well with GSM.",
2086
+ dialog_i18n = self.i18n.get('dialogs', {}).get('install_ocenaudio', {})
2087
+ confirm = messagebox.askyesno(dialog_i18n.get('title', 'Download OcenAudio?'),
2088
+ dialog_i18n.get('message', 'Would you like to download...?'),
1853
2089
  parent=self.window, icon='question')
1854
2090
  if confirm:
1855
- self.external_tool.delete(0, tk.END)
1856
- self.external_tool.insert(0, "Downloading OcenAudio...")
2091
+ self.external_tool_value.set(dialog_i18n.get('downloading_message', 'Downloading...'))
1857
2092
  exe_path = download_ocenaudio_if_needed()
1858
- messagebox.showinfo("OcenAudio Downloaded",
1859
- f"OcenAudio has been downloaded and installed. You can find it at {exe_path}.",
2093
+ messagebox.showinfo(dialog_i18n.get('success_title', 'Download Complete'),
2094
+ dialog_i18n.get('success_message', 'Downloaded to {path}').format(path=exe_path),
1860
2095
  parent=self.window)
1861
- self.external_tool.delete(0, tk.END)
1862
- self.external_tool.insert(0, exe_path)
2096
+ self.external_tool_value.set(exe_path)
1863
2097
  self.save_settings()
1864
2098
 
1865
2099
  def set_default_anki_media_collection(self):
1866
- confirm = messagebox.askyesno("Set Default Anki Media Collection?",
1867
- "Would you like to set the default Anki media collection path? This will help the script find the media collection for external trimming.\n\nDefault: %APPDATA%/Anki2/User 1/collection.media",
2100
+ dialog_i18n = self.i18n.get('dialogs', {}).get('set_anki_media', {})
2101
+ confirm = messagebox.askyesno(dialog_i18n.get('title', 'Set Default Path?'),
2102
+ dialog_i18n.get('message', 'Would you like to set...?'),
1868
2103
  parent=self.window, icon='question')
1869
2104
  if confirm:
1870
2105
  default_path = get_default_anki_media_collection_path()
1871
- if default_path != self.settings.audio.anki_media_collection: # Check against anki_media_collection
1872
- self.anki_media_collection.delete(0, tk.END)
1873
- self.anki_media_collection.insert(0, default_path)
2106
+ if default_path != self.anki_media_collection_value.get():
2107
+ self.anki_media_collection_value.set(default_path)
1874
2108
  self.save_settings()
1875
2109
 
1876
2110
 
1877
2111
  if __name__ == '__main__':
2112
+ # Ensure 'en_us.json' is in the same directory as this script to run this example
1878
2113
  root = ttk.Window(themename='darkly')
1879
2114
  window = ConfigApp(root)
1880
2115
  window.show()
1881
- window.window.mainloop()
2116
+ window.window.mainloop()