-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathenhanced_comprehensive_processing.py
More file actions
2239 lines (1883 loc) · 106 KB
/
enhanced_comprehensive_processing.py
File metadata and controls
2239 lines (1883 loc) · 106 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import tkinter as tk
from tkinter import ttk as tk_ttk
from tkinter import messagebox, simpledialog, filedialog
import threading
import logging
import copy
import itertools
from collections import defaultdict
import math
import ttkbootstrap as ttk
from utils import config_manager
from services.ai_translator import AITranslator
from concurrent.futures import ThreadPoolExecutor, as_completed
from gui.custom_widgets import ToolTip
class EnhancedComprehensiveProcessing(tk.Frame):
def __init__(self, parent, workbench_instance):
super().__init__(parent)
self.workbench = workbench_instance
# 初始化变量
self.processing = False
self.settings = config_manager.load_config()
# 配置变量
self.mixed_translation_var = tk.BooleanVar(value=self.settings.get('mixed_translation', False))
# 添加变量追踪,实现自动保存
self.mixed_translation_var.trace_add("write", lambda *args: self._save_config())
# 创建主容器
main_frame = ttk.Frame(self)
main_frame.pack(fill="both", expand=True)
# 右侧:配置和操作面板(不再需要主面板和模组管理面板)
self.operation_panel = ttk.LabelFrame(main_frame, text="操作配置")
self.operation_panel.pack(fill="both", expand=True)
# 初始化操作配置面板
self._init_operation_panel()
# 绑定事件
self.operation_var.trace_add("write", self._on_operation_change)
# 绑定事件 - 这些绑定会在进入翻译控制台模式时生效
# 选择事件用于更新开始按钮状态和批次预览
# 点击事件用于处理翻译控制台模式下的模组选择
# 长按拖拽事件用于处理模组的拖拽选择
# 初始化长按拖拽相关变量
self._long_press_started = False
self._long_press_item = None
self._last_dragged_item = None
self._processed_items = set() # 跟踪本次拖拽中已处理的模组
# AI 翻译部分应用:线程与主线程共享的批次结果上下文
self._ai_apply_lock = threading.Lock()
self._ai_apply_context = None
# 初始化时显示默认选项面板
self._on_operation_change()
# 初始化时检查选中的模组,更新开始按钮状态和批次预览
self._check_module_selection()
self._update_batch_preview()
def _init_operation_panel(self):
"""初始化操作配置面板"""
# 使用更现代的卡片式设计
# 操作类型选择卡片
operation_card = ttk.LabelFrame(self.operation_panel, text="操作类型")
operation_card.pack(fill="x", padx=15, pady=(0, 15))
# 添加内部容器来实现 padding 效果
operation_inner = ttk.Frame(operation_card)
operation_inner.pack(fill="x", padx=10, pady=(10, 15))
# 操作类型映射
self.operations_values = {
"AI 翻译": "ai_translate",
"导出文本": "export",
"导入翻译": "import",
"标点修正": "punctuation_correct",
"去除空格": "remove_space"
}
self.operations_reverse_values = {
"ai_translate": "AI 翻译",
"export": "导出文本",
"import": "导入翻译",
"punctuation_correct": "标点修正",
"remove_space": "去除空格"
}
self.operation_var = tk.StringVar(value=self.operations_reverse_values.get("ai_translate"))
operations = [
"AI 翻译",
"导出文本",
"导入翻译",
"标点修正",
"去除空格"
]
# 使用更现代的Combobox样式
operation_combo_frame = ttk.Frame(operation_inner)
operation_combo_frame.pack(fill="x")
ttk.Label(operation_combo_frame, text="选择操作类型:", font=("Microsoft YaHei UI", 10)).pack(anchor="w", pady=(0, 5), padx=10)
self.operation_combo = ttk.Combobox(operation_combo_frame, textvariable=self.operation_var, values=operations, state="readonly", bootstyle="primary")
# 设置当前选中项的索引,确保组件正确显示选中状态
if self.operation_var.get() in operations:
self.operation_combo.current(operations.index(self.operation_var.get()))
self.operation_combo.pack(fill="x", pady=(0, 10), padx=10)
# 添加事件绑定
self.operation_combo.bind('<<ComboboxSelected>>', lambda e: [self._on_operation_change(e), self._on_combobox_selected(e, self.operation_combo), self._update_operation_comment()])
self.operation_combo.bind('<FocusOut>', lambda e: self._on_combobox_focus_out(e, self.operation_combo))
self.operation_combo.bind('<FocusIn>', lambda e: self._on_combobox_focus_in(e, self.operation_combo))
# 操作类型注释文本,使用更醒目的样式
self.operation_comment_var = tk.StringVar()
self._update_operation_comment()
self.operation_comment = ttk.Label(operation_inner, textvariable=self.operation_comment_var,
font=("Microsoft YaHei UI", 9), foreground="#555555",
wraplength=600, justify="left")
self.operation_comment.pack(anchor="w", pady=(5, 0))
# AI 翻译选项卡片
self.ai_options_frame = ttk.LabelFrame(self.operation_panel, text="AI 翻译选项")
self.ai_options_frame.pack(fill="x", padx=15, pady=(0, 10))
# AI 翻译工作模式选择
mode_card = ttk.LabelFrame(self.ai_options_frame, text="翻译模式")
mode_card.pack(fill="x", padx=15, pady=(0, 10))
# 翻译模式映射
self.modes_values = {
"基础翻译模式": "basic",
"翻译润色模式": "polish",
"混合翻译模式": "hybrid"
}
self.modes_reverse_values = {
"basic": "基础翻译模式",
"polish": "翻译润色模式",
"hybrid": "混合翻译模式"
}
# 获取当前设置的内部值,转换为显示值
current_mode = self.settings.get('translation_mode', 'hybrid')
self.translation_mode_var = tk.StringVar(value=self.modes_reverse_values.get(current_mode))
modes = [
"基础翻译模式",
"翻译润色模式",
"混合翻译模式"
]
# 翻译模式选择区域(删除了"选择翻译模式:"文本)
self.mode_combo = ttk.Combobox(mode_card, textvariable=self.translation_mode_var, values=modes, state="readonly", bootstyle="success")
# 设置当前选中项的索引,确保组件正确显示选中状态
if self.translation_mode_var.get() in modes:
self.mode_combo.current(modes.index(self.translation_mode_var.get()))
self.mode_combo.pack(fill="x", pady=(5, 10), padx=10)
# 添加事件绑定
self.mode_combo.bind('<<ComboboxSelected>>', lambda e: self._on_combobox_selected(e, self.mode_combo))
self.mode_combo.bind('<FocusOut>', lambda e: self._on_combobox_focus_out(e, self.mode_combo))
self.mode_combo.bind('<FocusIn>', lambda e: self._on_combobox_focus_in(e, self.mode_combo))
# 添加模式切换注释,使用更现代的样式
self.mode_comment_var = tk.StringVar()
self._update_mode_comment()
self.mode_comment = ttk.Label(mode_card, textvariable=self.mode_comment_var,
font=("Microsoft YaHei UI", 9), foreground="#555555",
wraplength=800, justify="left")
self.mode_comment.pack(anchor="w", pady=(5, 5), padx=10)
# 绑定模式切换事件
self.translation_mode_var.trace_add("write", lambda *args: [self._save_config(), self._update_mode_comment(), self._update_batch_preview()])
# 智能翻译算法卡片
algorithm_card = ttk.LabelFrame(self.ai_options_frame, text="翻译控制台设置")
algorithm_card.pack(fill="x", padx=15, pady=(0, 10))
# 优化布局:使用网格布局,充分利用宽度
algorithm_card.columnconfigure(0, weight=1)
algorithm_card.columnconfigure(1, weight=1)
algorithm_card.columnconfigure(2, weight=1)
algorithm_card.columnconfigure(3, weight=1)
# 批次处理模式选择
batch_mode_frame = ttk.Frame(algorithm_card)
batch_mode_frame.grid(row=0, column=0, columnspan=1, sticky="w", padx=5, pady=5)
ttk.Label(batch_mode_frame, text="批次处理模式: ", font=("Microsoft YaHei UI", 10), width=12).pack(side="left", padx=5)
# 从设置中加载batch_processing_mode值,如果不存在或格式不正确则使用默认值
batch_processing_mode = self.settings.get('batch_processing_mode', 'words')
# 验证格式是否正确
if batch_processing_mode not in ['words', 'batch', 'items']:
batch_processing_mode = 'words'
self.batch_mode_var = tk.StringVar(value=batch_processing_mode)
# 批次处理模式选项:单词、批次和条目
mode_options = [
"单词",
"批次",
"条目"
]
# 映射显示值到内部值
self.mode_values = {
"单词": "words",
"批次": "batch",
"条目": "items"
}
self.mode_reverse_values = {v: k for k, v in self.mode_values.items()}
# 创建显示值变量,用于绑定到Combobox
self.batch_mode_display_var = tk.StringVar()
# 设置初始显示值
self.batch_mode_display_var.set(self.mode_reverse_values[batch_processing_mode])
mode_combo = ttk.Combobox(batch_mode_frame, textvariable=self.batch_mode_display_var,
values=mode_options, state="readonly", bootstyle="success", width=8)
# 设置默认选择
mode_combo.current(mode_options.index(self.mode_reverse_values[batch_processing_mode]))
mode_combo.pack(side="left", padx=(0, 10))
# 每批次数量设置
batch_value_frame = ttk.Frame(algorithm_card)
batch_value_frame.grid(row=0, column=1, sticky="w", padx=5, pady=5)
# 创建三个独立的变量,用于存储每种模式的数值
self.words_batch_var = tk.IntVar(value=self.settings.get('ai_batch_words', 2000))
self.items_batch_var = tk.IntVar(value=self.settings.get('ai_batch_items', 10))
self.batch_count_var = tk.IntVar(value=self.settings.get('ai_batch_count', 10))
# 动态标签:根据批次处理模式显示不同的标签文本
if batch_processing_mode == "words":
label_text = "每批次单词数: "
current_var = self.words_batch_var
elif batch_processing_mode == "items":
label_text = "每批次条目数: "
current_var = self.items_batch_var
else:
label_text = "批次数: "
current_var = self.batch_count_var
self.batch_value_label = ttk.Label(batch_value_frame, text=label_text, font=("Microsoft YaHei UI", 10), width=12)
self.batch_value_label.pack(side="left", padx=5)
# 根据模式设置不同的数值范围
if batch_processing_mode == "batch":
spinbox_from = 1
spinbox_to = 100
else:
spinbox_from = 100
spinbox_to = 50000
# 创建spinbox
value_spinbox = ttk.Spinbox(batch_value_frame, from_=spinbox_from, to=spinbox_to,
textvariable=current_var, width=8, bootstyle="success")
value_spinbox.pack(side="left")
# 保存当前的spinbox引用和当前模式
self.current_spinbox = value_spinbox
self.current_batch_var = current_var
# 保存初始模式,用于模式切换时的比较
self._current_batch_mode = batch_processing_mode
# 添加事件处理,切换批次处理模式时更新标签文本、数值范围和绑定的变量
def on_batch_mode_change(*args):
# 获取当前选择的新模式
new_batch_mode = self.batch_mode_var.get()
# 保存当前spinbox的值到旧模式对应的变量
try:
current_value = value_spinbox.get()
current_value = int(current_value)
if self._current_batch_mode == "words":
self.words_batch_var.set(current_value)
elif self._current_batch_mode == "items":
self.items_batch_var.set(current_value)
else:
self.batch_count_var.set(current_value)
except (ValueError, tk.TclError):
# 如果当前值无效,不保存
pass
# 更新当前模式记录
self._current_batch_mode = new_batch_mode
# 根据新模式更新UI
if new_batch_mode == "words":
self.batch_value_label.config(text="每批次单词数: ")
value_spinbox.config(from_=100, to=50000)
new_var = self.words_batch_var
elif new_batch_mode == "items":
self.batch_value_label.config(text="每批次条目数: ")
value_spinbox.config(from_=100, to=50000)
new_var = self.items_batch_var
else:
self.batch_value_label.config(text="批次数: ")
value_spinbox.config(from_=1, to=100)
new_var = self.batch_count_var
# 切换spinbox绑定的变量
value_spinbox.configure(textvariable=new_var)
self.current_batch_var = new_var
# 保存配置并更新批次预览和UI
self._save_config()
self._update_batch_preview()
self._update_batch_ui()
# 添加下拉选择事件处理,解决文字选中问题
def on_combo_selected(e):
# 当选择变化时,直接从Combobox获取用户选择的显示值
display_value = e.widget.get()
# 转换为内部值
internal_value = self.mode_values.get(display_value, 'words')
# 只有当内部值真正变化时才触发更新
if internal_value != self.batch_mode_var.get():
self.batch_mode_var.set(internal_value)
on_batch_mode_change()
self._on_combobox_selected(e, mode_combo)
mode_combo.bind('<<ComboboxSelected>>', on_combo_selected)
mode_combo.bind('<FocusOut>', lambda e: self._on_combobox_focus_out(e, mode_combo))
mode_combo.bind('<FocusIn>', lambda e: self._on_combobox_focus_in(e, mode_combo))
# 绑定批次计算模式变化事件
self.batch_mode_var.trace_add("write", on_batch_mode_change)
# 绑定数值变化事件
self.words_batch_var.trace_add("write", lambda *args: [self._save_config(), self._update_batch_preview()])
self.items_batch_var.trace_add("write", lambda *args: [self._save_config(), self._update_batch_preview()])
self.batch_count_var.trace_add("write", lambda *args: [self._save_config(), self._update_batch_preview()])
# 批次预览:移到右侧
self.preview_frame = ttk.Frame(algorithm_card)
self.preview_frame.grid(row=0, column=3, sticky="e", padx=5, pady=5)
ttk.Label(self.preview_frame, text="预计批次:", font=("Microsoft YaHei UI", 10), foreground="#666666").pack(side="left", padx=(0, 5))
self.batch_preview_var = tk.StringVar(value="0")
batch_preview_label = ttk.Label(self.preview_frame, textvariable=self.batch_preview_var,
font=("Microsoft YaHei UI", 12, "bold"), foreground="#28a745")
batch_preview_label.pack(side="left")
# 模组隔离选项
isolation_frame = ttk.Frame(algorithm_card)
isolation_frame.grid(row=1, column=0, columnspan=4, sticky="w", padx=5, pady=(10, 10))
self.module_isolation_var = tk.BooleanVar(value=self.settings.get('module_isolation', False))
isolation_check = ttk.Checkbutton(
isolation_frame,
text="模组隔离(不同模组的语言文件不会放入同一批次)",
variable=self.module_isolation_var,
bootstyle="primary"
)
isolation_check.pack(side="left", padx=5)
self.module_isolation_var.trace_add("write", lambda *args: [self._save_config(), self._update_batch_preview()])
# 初始化UI状态
self._update_batch_ui()
# 按钮框架 - 设计更现代的按钮布局
self.button_frame = ttk.Frame(self.operation_panel)
self.button_frame.pack(fill="x", padx=15, pady=(10, 0))
# 使用更现代的按钮样式和布局
button_container = ttk.Frame(self.button_frame)
button_container.pack(fill="x", anchor="e", padx=10, pady=(5, 0))
self.cancel_button = ttk.Button(button_container, text="取消", command=self._on_cancel, bootstyle="outline-secondary", width=12)
self.cancel_button.pack(side="right", padx=(10, 0))
self.apply_completed_ai_btn = ttk.Button(
button_container,
text="应用已完成",
command=self._on_apply_completed_ai_batches,
bootstyle="info-outline",
width=12,
)
self.start_button = ttk.Button(button_container, text="开始处理", command=self._on_start, bootstyle="success-outline", width=15)
self.start_button.pack(side="right", padx=5, before=self.cancel_button)
# 导入选项卡片(仅在导入操作时显示)
self.import_options_frame = ttk.LabelFrame(self.operation_panel, text="导入选项")
self.import_options_frame.pack(fill="x", padx=15, pady=(0, 15))
self.import_options_frame.pack_forget()
# 导入来源映射
self.import_sources_values = {
"从文件导入": "file",
"从剪贴板导入": "clipboard"
}
self.import_sources_reverse_values = {
"file": "从文件导入",
"clipboard": "从剪贴板导入"
}
self.import_source_var = tk.StringVar(value=self.import_sources_reverse_values.get("file"))
import_sources = [
"从文件导入",
"从剪贴板导入"
]
# 导入来源选择区域
import_source_frame = ttk.Frame(self.import_options_frame)
import_source_frame.pack(fill="x", padx=10, pady=(0, 10))
ttk.Label(import_source_frame, text="选择导入来源: ", font=("Microsoft YaHei UI", 10, "bold")).pack(anchor="w", pady=(0, 5), padx=5)
self.import_source_combo = ttk.Combobox(import_source_frame, textvariable=self.import_source_var, values=import_sources, state="readonly", bootstyle="info")
# 设置当前选中项的索引,确保组件正确显示选中状态
if self.import_source_var.get() in import_sources:
self.import_source_combo.current(import_sources.index(self.import_source_var.get()))
self.import_source_combo.pack(fill="x", pady=(0, 10), padx=5)
# 添加事件绑定
self.import_source_combo.bind('<<ComboboxSelected>>', lambda e: self._on_combobox_selected(e, self.import_source_combo))
self.import_source_combo.bind('<FocusOut>', lambda e: self._on_combobox_focus_out(e, self.import_source_combo))
self.import_source_combo.bind('<FocusIn>', lambda e: self._on_combobox_focus_in(e, self.import_source_combo))
# 导出选项卡片(仅在导出操作时显示)
self.export_options_frame = ttk.LabelFrame(self.operation_panel, text="导出选项")
self.export_options_frame.pack(fill="x", padx=15, pady=(0, 15))
self.export_options_frame.pack_forget()
# 导出范围选项
export_scope_card = ttk.LabelFrame(self.export_options_frame, text="导出范围")
export_scope_card.pack(fill="x", padx=15, pady=(0, 10))
# 导出范围映射
self.export_scopes_values = {
"全部文本": "all",
"仅待翻译": "pending",
"仅已翻译": "completed"
}
self.export_scopes_reverse_values = {
"all": "全部文本",
"pending": "仅待翻译",
"completed": "仅已翻译"
}
self.export_scope_var = tk.StringVar(value=self.export_scopes_reverse_values.get("all"))
export_scopes = [
"全部文本",
"仅待翻译",
"仅已翻译"
]
ttk.Label(export_scope_card, text="选择导出范围:", font=("Microsoft YaHei UI", 10, "bold")).pack(anchor="w", pady=(0, 5), padx=10)
self.export_scope_combo = ttk.Combobox(export_scope_card, textvariable=self.export_scope_var, values=export_scopes, state="readonly", bootstyle="warning")
# 设置当前选中项的索引,确保组件正确显示选中状态
if self.export_scope_var.get() in export_scopes:
self.export_scope_combo.current(export_scopes.index(self.export_scope_var.get()))
self.export_scope_combo.pack(fill="x", pady=(0, 10), padx=10)
# 添加事件绑定
self.export_scope_combo.bind('<<ComboboxSelected>>', lambda e: self._on_combobox_selected(e, self.export_scope_combo))
self.export_scope_combo.bind('<FocusOut>', lambda e: self._on_combobox_focus_out(e, self.export_scope_combo))
self.export_scope_combo.bind('<FocusIn>', lambda e: self._on_combobox_focus_in(e, self.export_scope_combo))
# 导出方式选项
export_method_card = ttk.LabelFrame(self.export_options_frame, text="导出方式")
export_method_card.pack(fill="x", padx=15, pady=(0, 10))
self.export_method_var = tk.StringVar(value="导出到文件")
export_methods = [
"导出到文件",
"导出到剪贴板"
]
self.export_method_values = {
"导出到文件": "file",
"导出到剪贴板": "clipboard"
}
self.export_method_reverse_values = {
"file": "导出到文件",
"clipboard": "导出到剪贴板"
}
ttk.Label(export_method_card, text="选择导出方式: ", font=("Microsoft YaHei UI", 10, "bold")).pack(anchor="w", pady=(0, 5), padx=10)
self.export_method_combo = ttk.Combobox(export_method_card, textvariable=self.export_method_var, values=export_methods, state="readonly", bootstyle="warning")
# 设置当前选中项的索引,确保组件正确显示选中状态
if self.export_method_var.get() in export_methods:
self.export_method_combo.current(export_methods.index(self.export_method_var.get()))
self.export_method_combo.pack(fill="x", pady=(0, 10), padx=10)
# 添加事件绑定
self.export_method_combo.bind('<<ComboboxSelected>>', lambda e: self._on_combobox_selected(e, self.export_method_combo))
self.export_method_combo.bind('<FocusOut>', lambda e: self._on_combobox_focus_out(e, self.export_method_combo))
self.export_method_combo.bind('<FocusIn>', lambda e: self._on_combobox_focus_in(e, self.export_method_combo))
# 配置会自动保存,无需手动保存按钮
def _count_words(self, text: str) -> int:
"""计算文本的单词数量
Args:
text: 要计算单词数量的文本
Returns:
int: 文本的单词数量
"""
if not text:
return 0
# 使用split()简单计算单词数量,消耗更低
return len(text.split())
def _update_mode_comment(self):
"""更新模式切换注释"""
mode = self.translation_mode_var.get()
if mode == "基础翻译模式":
self.mode_comment_var.set("仅对用户提供的待翻译文本进行直接翻译,不涉及任何已翻译内容的参考或润色处理")
elif mode == "翻译润色模式":
self.mode_comment_var.set("专门针对已完成翻译的文本进行质量优化,包括语言流畅度提升、专业术语统一等")
elif mode == "混合翻译模式":
self.mode_comment_var.set("在处理待翻译文本时,结合已翻译内容进行综合翻译,确保翻译风格一致性")
else:
self.mode_comment_var.set("")
def _update_operation_comment(self):
"""更新操作类型注释"""
operation = self.operation_var.get()
if operation == "AI 翻译":
self.operation_comment_var.set("使用 AI 对选中模组中的待翻译文本进行批量翻译,支持多种翻译模式")
elif operation == "导出文本":
self.operation_comment_var.set("将选中模组中的文本导出到文件或剪贴板,可选择导出范围和方式")
elif operation == "导入翻译":
self.operation_comment_var.set("从文件或剪贴板导入翻译结果,更新选中模组中的翻译内容")
elif operation == "标点修正":
self.operation_comment_var.set("自动检测和修正翻译文本中的标点符号,确保中英文标点的一致性和正确性")
elif operation == "去除空格":
self.operation_comment_var.set("移除翻译文本中多余的空格,清理中文标点符号前后的空格,保持文本整洁")
else:
self.operation_comment_var.set("")
def _on_operation_change(self, *args):
"""操作类型变化事件处理"""
# 将显示值转换为内部值
operation_display = self.operation_var.get()
operation = self.operations_values.get(operation_display, "ai_translate")
# 隐藏所有选项
self.ai_options_frame.pack_forget()
self.import_options_frame.pack_forget()
self.export_options_frame.pack_forget()
self.button_frame.pack_forget()
# 根据操作类型显示相应选项
if operation == "ai_translate":
self.ai_options_frame.pack(fill="x", pady=(0, 15))
self.button_frame.pack(fill="x", anchor="e", pady=(0, 15))
self.cancel_button.pack(side="right", padx=(10, 0))
elif operation == "import":
self.import_options_frame.pack(fill="x", pady=(0, 15))
self.button_frame.pack(fill="x", anchor="e", pady=(0, 15))
self.cancel_button.pack_forget()
elif operation == "export":
self.export_options_frame.pack(fill="x", pady=(0, 15))
self.button_frame.pack(fill="x", anchor="e", pady=(0, 15))
self.cancel_button.pack_forget()
elif operation == "punctuation_correct" or operation == "remove_space":
# 标点修正和去除空格操作不需要特殊选项,只显示按钮
self.button_frame.pack(fill="x", anchor="e", pady=(0, 15))
self.cancel_button.pack_forget()
self.update_idletasks()
def _on_threshold_change(self, *args):
"""阈值变化事件处理"""
# 字符串值已经在下拉框中显示,无需更新标签
# 但需要确保配置保存的是浮点数
pass
def _on_combobox_focus_out(self, event, combobox):
"""处理Combobox焦点移出事件,取消文字选中状态"""
# 取消文字选中状态
combobox.selection_clear()
# 确保光标位置在文本末尾
combobox.icursor(tk.END)
def _on_combobox_selected(self, event, combobox):
"""处理Combobox选项选中事件,立即取消文字选中状态"""
# 立即取消文字选中状态,不使用延迟
combobox.selection_clear()
combobox.icursor(tk.END)
def _on_combobox_focus_in(self, event, combobox):
"""处理Combobox焦点进入事件,取消文字选中状态"""
# 立即取消文字选中状态,不使用延迟
combobox.selection_clear()
combobox.icursor(tk.END)
def _check_module_selection(self):
"""检查选中的模组,并更新开始按钮的状态"""
selected_modules = self.workbench.ns_tree.selection()
if selected_modules:
self.start_button.config(state="normal")
else:
self.start_button.config(state="disabled")
# 更新批次预览
self._update_batch_preview()
def _calculate_total_batches(self):
"""计算预计的批次数量(与实际批次计算逻辑完全一致)"""
# 获取选中的模组
selected_modules = self.workbench.ns_tree.selection()
if not selected_modules:
return 0
# 获取翻译模式
translation_mode_display = self.translation_mode_var.get()
translation_mode = self.modes_values.get(translation_mode_display, "hybrid")
batch_mode = self.batch_mode_var.get()
# 如果是批次数模式,直接返回用户指定的批次数
if batch_mode == "batch":
try:
return self.batch_count_var.get()
except (tk.TclError, ValueError):
return self.settings.get('ai_batch_count', 10)
# 1. 首先进行去重处理(与实际批次计算完全一致)
# 根据模式筛选待处理条目,并对 en_text 去重
# 只保留每个 en_text 的第一个待翻译条目进行翻译
seen_en_texts = set()
deduplicated_items = []
all_item_mapping = []
for ns in selected_modules:
items = self.workbench.translation_data.get(ns, {}).get("items", [])
for idx, item in enumerate(items):
en_text = item.get("en", "").strip()
zh_text = item.get("zh", "").strip()
source = item.get("source", "")
if en_text:
if translation_mode == "basic" or translation_mode == "hybrid":
# 基础翻译模式和混合翻译模式:仅处理待翻译文本
if not zh_text and en_text not in seen_en_texts:
seen_en_texts.add(en_text)
deduplicated_items.append((ns, idx, item))
elif translation_mode == "polish":
# 翻译润色模式:仅处理已翻译文本,跳过"原文复制"来源
if zh_text and source != "原文复制" and en_text not in seen_en_texts:
seen_en_texts.add(en_text)
deduplicated_items.append((ns, idx, item))
if not deduplicated_items:
return 0
# 2. 准备翻译数据(与实际批次计算一致)
all_translation_inputs = []
for ns, idx, item in deduplicated_items:
en_text = item.get("en", "").strip()
zh_text = item.get("zh", "").strip()
if translation_mode == "polish" and zh_text:
# 润色模式:传递英文原文和中文译文,格式为 "原文 -> 译文"
combined_text = f"{en_text} -> {zh_text}"
all_translation_inputs.append({"text": combined_text, "key": item.get("key", "")})
else:
# 其他模式:仅传递英文原文
all_translation_inputs.append({"text": en_text, "key": item.get("key", "")})
all_item_mapping.append((ns, idx, item))
# 3. 获取批次处理模式和值
try:
if batch_mode == "words":
batch_value = self.words_batch_var.get()
elif batch_mode == "items":
batch_value = self.items_batch_var.get()
else:
batch_value = self.batch_count_var.get()
except (tk.TclError, ValueError):
# 使用保存的默认值
if batch_mode == "words":
batch_value = self.settings.get('ai_batch_words', 2000)
elif batch_mode == "items":
batch_value = self.settings.get('ai_batch_items', 10)
else:
batch_value = self.settings.get('ai_batch_count', 10)
# 4. 批次划分逻辑(与实际批次计算完全一致)
module_isolation = self.module_isolation_var.get()
total_batches = 0
if module_isolation:
# 模组隔离模式:按命名空间分别创建批次
ns_to_items = defaultdict(list)
for i, (ns, idx, item) in enumerate(all_item_mapping):
ns_to_items[ns].append(i)
for ns, indices in ns_to_items.items():
ns_inputs = [all_translation_inputs[i] for i in indices]
if batch_mode == "words":
ns_words = sum(self._count_words(item["text"]) for item in ns_inputs)
ns_batches_count = max(1, (ns_words + batch_value - 1) // batch_value)
total_batches += ns_batches_count
elif batch_mode == "items":
ns_items_count = len(ns_inputs)
ns_batches_count = max(1, (ns_items_count + batch_value - 1) // batch_value)
total_batches += ns_batches_count
else:
ns_items_count = len(ns_inputs)
ns_batches_count = batch_value
total_batches += ns_batches_count
elif batch_mode == "words":
# 基于单词数量的批次划分
total_words = sum(self._count_words(item["text"]) for item in all_translation_inputs)
total_batches = max(1, (total_words + batch_value - 1) // batch_value)
elif batch_mode == "items":
# 基于条目数量的批次划分
total_items = len(all_translation_inputs)
total_batches = max(1, (total_items + batch_value - 1) // batch_value)
else:
# 基于批次数量的批次划分
total_batches = batch_value
return max(1, total_batches)
def _update_batch_preview(self):
"""更新批次预览显示"""
total_batches = self._calculate_total_batches()
self.batch_preview_var.set(f"{total_batches}")
def _on_module_click(self, event):
"""处理模组点击事件,实现点击已选中模组取消选中的功能"""
# 只有在翻译控制台模式下才执行自定义点击逻辑
if self.workbench._current_mode != "comprehensive":
return
# 获取点击的模组
region = self.workbench.ns_tree.identify_region(event.x, event.y)
if region == "cell" or region == "text":
# 获取当前点击的模组ID
clicked_item = self.workbench.ns_tree.identify_row(event.y)
if clicked_item:
# 获取所有选中的模组
all_selected = self.workbench.ns_tree.selection()
# 临时解绑选择事件,避免递归调用
self.workbench.ns_tree.unbind("<<TreeviewSelect>>")
try:
if clicked_item in all_selected:
# 如果点击的是已选中的模组,取消其选中状态
self.workbench.ns_tree.selection_remove(clicked_item)
else:
# 如果点击的是未选中的模组,添加到选中列表
self.workbench.ns_tree.selection_add(clicked_item)
finally:
# 重新绑定选择事件
self.workbench.ns_tree.bind("<<TreeviewSelect>>", self._on_module_selection_change)
# 更新状态栏
selection = self.workbench.ns_tree.selection()
if selection:
self.workbench.status_label.config(text=f"已选择 {len(selection)} 个项目")
else:
self.workbench.status_label.config(text="未选择任何项目")
# 更新开始按钮状态和批次预览
self._check_module_selection()
self._update_batch_preview()
def _on_module_press(self, event):
"""处理鼠标按下事件,立即开始长按操作"""
# 只有在翻译控制台模式下才执行长按操作
if self.workbench._current_mode != "comprehensive":
return
region = self.workbench.ns_tree.identify_region(event.x, event.y)
if region == "cell" or region == "text":
# 获取当前点击的模组ID
clicked_item = self.workbench.ns_tree.identify_row(event.y)
if clicked_item:
# 清空已处理项目集合
self._processed_items.clear()
# 保存起始位置
self._long_press_item = clicked_item
# 将起始模组添加到已处理集合中
self._processed_items.add(clicked_item)
# 立即开始长按操作
self._long_press_started = True
self._last_dragged_item = self._long_press_item
def _on_module_drag(self, event):
"""处理鼠标拖拽事件,切换经过的模组选中状态"""
# 只有在翻译控制台模式下才执行拖拽操作
if self.workbench._current_mode != "comprehensive":
return
if self._long_press_started:
region = self.workbench.ns_tree.identify_region(event.x, event.y)
if region == "cell" or region == "text":
# 获取当前拖拽到的模组ID
dragged_item = self.workbench.ns_tree.identify_row(event.y)
if dragged_item and dragged_item != self._last_dragged_item:
# 获取所有模组的列表
all_items = self.workbench.ns_tree.get_children()
# 找到起始和结束模组的索引
if self._last_dragged_item in all_items and dragged_item in all_items:
start_idx = all_items.index(self._last_dragged_item)
end_idx = all_items.index(dragged_item)
# 确定处理范围(包括起始和结束模组)
if start_idx < end_idx:
# 向下拖拽
range_start = start_idx + 1
range_end = end_idx + 1
else:
# 向上拖拽
range_start = end_idx
range_end = start_idx
# 处理范围内的所有模组
for idx in range(range_start, range_end):
item = all_items[idx]
# 如果该模组已经在本次拖拽中处理过,跳过
if item in self._processed_items:
continue
# 将模组添加到已处理集合
self._processed_items.add(item)
# 获取所有选中的模组
all_selected = self.workbench.ns_tree.selection()
# 临时解绑选择事件,避免递归调用
self.workbench.ns_tree.unbind("<<TreeviewSelect>>")
try:
if item in all_selected:
# 如果是已选中的模组,取消其选中状态
self.workbench.ns_tree.selection_remove(item)
else:
# 如果是未选中的模组,添加到选中列表
self.workbench.ns_tree.selection_add(item)
finally:
# 重新绑定选择事件
self.workbench.ns_tree.bind("<<TreeviewSelect>>", self._on_module_selection_change)
# 更新状态栏
selection = self.workbench.ns_tree.selection()
if selection:
self.workbench.status_label.config(text=f"已选择 {len(selection)} 个项目")
else:
self.workbench.status_label.config(text="未选择任何项目")
# 更新开始按钮状态
self._check_module_selection()
# 更新批次预览
self._update_batch_preview()
# 保存当前拖拽位置
self._last_dragged_item = dragged_item
def _on_module_release(self, event):
"""处理鼠标释放事件,结束长按操作"""
# 重置长按状态
self._long_press_started = False
self._long_press_item = None
self._last_dragged_item = None
def _on_module_selection_change(self, event=None):
"""处理模组选择变化事件,更新开始按钮状态和批次预览"""
self._check_module_selection()
self._update_batch_preview()
def _update_batch_ui(self):
"""根据批次设置状态更新UI"""
# 从批次处理模式中提取信息
batch_mode = self.batch_mode_var.get()
# 移除智能批处理设置相关UI,简化界面
if hasattr(self, 'smart_batching_frame'):
self.smart_batching_frame.pack_forget()
if hasattr(self, 'smart_batching_settings_frame'):
self.smart_batching_settings_frame.pack_forget()
if hasattr(self, 'traditional_batch_frame'):
self.traditional_batch_frame.pack_forget()
if hasattr(self, 'count_mode_frame'):
self.count_mode_frame.pack_forget()
if hasattr(self, 'value_mode_frame'):
self.value_mode_frame.pack_forget()
# 控制预计批次预览的显示/隐藏
if hasattr(self, 'preview_frame'):
if batch_mode == "batch":
# 当选择批次模式时,隐藏预计批次预览
self.preview_frame.grid_remove()
else:
# 其他模式下显示预计批次预览
self.preview_frame.grid()
# 更新批次预览
self._update_batch_preview()
def _save_config(self):
"""保存配置(自动调用,无需手动触发)"""
# 更新配置,将显示值转换为内部值
self.settings['translation_mode'] = self.modes_values.get(self.translation_mode_var.get(), 'hybrid')
# 保存合并后的批次处理模式
self.settings['batch_processing_mode'] = self.batch_mode_var.get()
# 保存三种模式的独立数值设置
try:
# 保存每批次单词数
self.settings['ai_batch_words'] = self.words_batch_var.get()
# 保存每批次条目数
self.settings['ai_batch_items'] = self.items_batch_var.get()
# 保存批次数
self.settings['ai_batch_count'] = self.batch_count_var.get()
except (tk.TclError, ValueError):
# 使用默认值
self.settings['ai_batch_words'] = self.settings.get('ai_batch_words', 2000)
self.settings['ai_batch_items'] = self.settings.get('ai_batch_items', 10)
self.settings['ai_batch_count'] = self.settings.get('ai_batch_count', 10)
# 保存模组隔离设置
self.settings['module_isolation'] = self.module_isolation_var.get()
# 保存到文件
config_manager.save_config(self.settings)
# 更新workbench的当前设置
self.workbench.current_settings = self.settings
logging.info("配置已自动保存")
def _on_start(self):
"""开始处理"""
if self.processing:
return
# 将显示值转换为内部值
operation_display = self.operation_var.get()
operation = self.operations_values.get(operation_display, "ai_translate")
# 保存当前编辑
self.workbench._save_current_edit()
# 禁用界面
self.processing = True
self.start_button.config(state="disabled")
self.cancel_button.config(text="取消")
if operation != "ai_translate":
with self._ai_apply_lock:
self._ai_apply_context = None
self._hide_ai_apply_completed_btn()
# 获取选中的模组
selected_modules = self.workbench.ns_tree.selection()
# 根据操作类型执行不同的处理
if operation == "ai_translate":
self._start_ai_translation(selected_modules)
elif operation == "export":
self._start_export(selected_modules)
elif operation == "import":
self._start_import(selected_modules)
elif operation == "punctuation_correct":
self._start_punctuation_correction(selected_modules)
elif operation == "remove_space":
self._start_space_removal(selected_modules)
def _on_exit(self):
"""退出翻译控制台模式,返回翻译工作台"""
# 检查是否有任务正在处理