55from typing import Any
66
77from PyQt6 .QtCore import Qt
8- from PyQt6 .QtGui import QColor , QDragEnterEvent , QDropEvent , QPalette
8+ from PyQt6 .QtGui import QColor , QDragEnterEvent , QDropEvent , QIcon , QPalette
99from PyQt6 .QtWidgets import (
1010 QApplication ,
11+ QComboBox ,
1112 QFileDialog ,
1213 QHBoxLayout ,
1314 QHeaderView ,
2627from bookmark_checker .core .exporters import export_dedupe_report_csv , export_netscape_html
2728from bookmark_checker .core .merge import merge_collections
2829from bookmark_checker .core .parsers import parse_many
30+ from bookmark_checker .i18n .translations import TRANSLATIONS , get_translation
2931
3032
3133class MainWindow (QMainWindow ):
@@ -34,18 +36,24 @@ class MainWindow(QMainWindow):
3436 def __init__ (self ) -> None :
3537 """Initialize the main window."""
3638 super ().__init__ ()
37- self .setWindowTitle ("BrowserBookmarkChecker" )
3839 self .setMinimumSize (1000 , 640 )
3940
4041 # Data
4142 from bookmark_checker .core .models import BookmarkCollection
4243
4344 self .current_collection : BookmarkCollection | None = None
4445 self .current_report : list [dict [str , Any ]] = []
46+ self .current_language = "en"
47+
48+ # Set window icon
49+ icon_path = Path (__file__ ).parent .parent .parent / "assets" / "icon.svg"
50+ if icon_path .exists ():
51+ self .setWindowIcon (QIcon (str (icon_path )))
4552
4653 # Setup UI
4754 self ._setup_ui ()
4855 self ._setup_style ()
56+ self ._update_ui_texts ()
4957
5058 # Enable drag and drop
5159 self .setAcceptDrops (True )
@@ -60,25 +68,49 @@ def _setup_ui(self) -> None:
6068 top_bar = QWidget ()
6169 top_layout = QHBoxLayout (top_bar )
6270
63- self .btn_import = QPushButton ("Import Files" )
71+ # Language selector
72+ lang_label = QLabel ("Language:" )
73+ top_layout .addWidget (lang_label )
74+ self .language_combo = QComboBox ()
75+ self .language_combo .addItems (
76+ [
77+ "English" ,
78+ "Русский" ,
79+ "Português" ,
80+ "Español" ,
81+ "Eesti" ,
82+ "Français" ,
83+ "Deutsch" ,
84+ "日本語" ,
85+ "中文" ,
86+ "한국어" ,
87+ "Bahasa Indonesia" ,
88+ ]
89+ )
90+ self .language_combo .setCurrentIndex (0 )
91+ self .language_combo .currentIndexChanged .connect (self ._on_language_changed )
92+ top_layout .addWidget (self .language_combo )
93+ top_layout .addSpacing (10 )
94+
95+ self .btn_import = QPushButton ()
6496 self .btn_import .clicked .connect (self ._import_files )
6597 top_layout .addWidget (self .btn_import )
6698
67- self .btn_merge = QPushButton ("Find & Merge" )
99+ self .btn_merge = QPushButton ()
68100 self .btn_merge .clicked .connect (self ._find_and_merge )
69101 self .btn_merge .setEnabled (False )
70102 top_layout .addWidget (self .btn_merge )
71103
72- self .btn_export = QPushButton ("Export Merged" )
104+ self .btn_export = QPushButton ()
73105 self .btn_export .clicked .connect (self ._export_merged )
74106 self .btn_export .setEnabled (False )
75107 top_layout .addWidget (self .btn_export )
76108
77109 top_layout .addStretch ()
78110
79111 # Similarity slider
80- similarity_label = QLabel ("Similarity:" )
81- top_layout .addWidget (similarity_label )
112+ self . similarity_label = QLabel ()
113+ top_layout .addWidget (self . similarity_label )
82114
83115 self .similarity_slider = QSlider (Qt .Orientation .Horizontal )
84116 self .similarity_slider .setMinimum (0 )
@@ -102,9 +134,6 @@ def _setup_ui(self) -> None:
102134 # Table
103135 self .table = QTableWidget ()
104136 self .table .setColumnCount (5 )
105- self .table .setHorizontalHeaderLabels (
106- ["Title" , "URL (canonical)" , "Folder" , "Source" , "Count" ]
107- )
108137 header = self .table .horizontalHeader ()
109138 if header :
110139 header .setSectionResizeMode (0 , QHeaderView .ResizeMode .Stretch )
@@ -124,11 +153,51 @@ def _setup_ui(self) -> None:
124153 self .progress_bar .setVisible (False )
125154 bottom_layout .addWidget (self .progress_bar )
126155
127- self .status_label = QLabel ("Ready" )
156+ self .status_label = QLabel ()
128157 bottom_layout .addWidget (self .status_label )
129158
130159 layout .addWidget (bottom_widget )
131160
161+ def _update_ui_texts (self ) -> None :
162+ """Update all UI texts based on current language."""
163+ lang_codes = ["en" , "ru" , "pt" , "es" , "et" , "fr" , "de" , "ja" , "zh" , "ko" , "id" ]
164+ lang = lang_codes [self .language_combo .currentIndex ()]
165+
166+ self .setWindowTitle (get_translation (lang , "app_title" , "BrowserBookmarkChecker" ))
167+ self .btn_import .setText (get_translation (lang , "import_files" , "Import Files" ))
168+ self .btn_merge .setText (get_translation (lang , "find_merge" , "Find & Merge" ))
169+ self .btn_export .setText (get_translation (lang , "export_merged" , "Export Merged" ))
170+ self .similarity_label .setText (get_translation (lang , "similarity" , "Similarity" ) + ":" )
171+
172+ # Update table headers
173+ self .table .setHorizontalHeaderLabels (
174+ [
175+ get_translation (lang , "title" , "Title" ),
176+ get_translation (lang , "url_canonical" , "URL (canonical)" ),
177+ get_translation (lang , "folder" , "Folder" ),
178+ get_translation (lang , "source" , "Source" ),
179+ get_translation (lang , "count" , "Count" ),
180+ ]
181+ )
182+
183+ # Update status if it's the default "Ready" message
184+ if (
185+ self .status_label .text ()
186+ in [
187+ get_translation (old_lang , "ready" , "Ready" )
188+ for old_lang in lang_codes
189+ if old_lang != lang
190+ ]
191+ or self .status_label .text () == "Ready"
192+ ):
193+ self .status_label .setText (get_translation (lang , "ready" , "Ready" ))
194+
195+ self .current_language = lang
196+
197+ def _on_language_changed (self , index : int ) -> None :
198+ """Handle language change."""
199+ self ._update_ui_texts ()
200+
132201 def _setup_style (self ) -> None :
133202 """Set up dark Fusion style."""
134203 self .setStyleSheet (
@@ -215,7 +284,9 @@ def _import_files(self) -> None:
215284
216285 def _process_files (self , files : list [str ]) -> None :
217286 """Process imported files."""
218- self .status_label .setText ("Parsing files..." )
287+ self .status_label .setText (
288+ get_translation (self .current_language , "parsing_files" , "Parsing files..." )
289+ )
219290 self .progress_bar .setVisible (True )
220291 self .progress_bar .setRange (0 , 0 ) # Indeterminate
221292
@@ -225,7 +296,7 @@ def _process_files(self, files: list[str]) -> None:
225296 if len (collection .bookmarks ) == 0 :
226297 QMessageBox .warning (
227298 self ,
228- "No Bookmarks Found" ,
299+ get_translation ( self . current_language , "error" , "Error" ) ,
229300 f"No bookmarks were found in the selected files.\n \n Files: { ', ' .join (files )} " ,
230301 )
231302 self .status_label .setText ("No bookmarks found in files" )
@@ -241,7 +312,9 @@ def _process_files(self, files: list[str]) -> None:
241312 import traceback
242313
243314 error_msg = f"Failed to parse files:\n { str (e )} \n \n { traceback .format_exc ()} "
244- QMessageBox .critical (self , "Error" , error_msg )
315+ QMessageBox .critical (
316+ self , get_translation (self .current_language , "error" , "Error" ), error_msg
317+ )
245318 self .status_label .setText ("Error loading files" )
246319 self .btn_merge .setEnabled (False )
247320 finally :
@@ -270,7 +343,11 @@ def _find_and_merge(self) -> None:
270343 )
271344 self .btn_export .setEnabled (True )
272345 except Exception as e :
273- QMessageBox .critical (self , "Error" , f"Failed to merge:\n { e } " )
346+ QMessageBox .critical (
347+ self ,
348+ get_translation (self .current_language , "error" , "Error" ),
349+ f"Failed to merge:\n { e } " ,
350+ )
274351 self .status_label .setText ("Error during merge" )
275352 finally :
276353 self .progress_bar .setVisible (False )
@@ -345,10 +422,16 @@ def _export_merged(self) -> None:
345422
346423 self .status_label .setText (f"Exported to { output_path } and { csv_path } " )
347424 QMessageBox .information (
348- self , "Success" , f"Exported successfully!\n \n { output_path } \n { csv_path } "
425+ self ,
426+ get_translation (self .current_language , "success" , "Success" ),
427+ f"{ get_translation (self .current_language , 'exported_successfully' , 'Exported successfully!' )} \n \n { output_path } \n { csv_path } " ,
349428 )
350429 except Exception as e :
351- QMessageBox .critical (self , "Error" , f"Failed to export:\n { e } " )
430+ QMessageBox .critical (
431+ self ,
432+ get_translation (self .current_language , "error" , "Error" ),
433+ f"Failed to export:\n { e } " ,
434+ )
352435 self .status_label .setText ("Error during export" )
353436 finally :
354437 self .progress_bar .setVisible (False )
@@ -359,6 +442,11 @@ def launch_gui() -> None:
359442 app = QApplication (sys .argv )
360443 app .setStyle ("Fusion" )
361444
445+ # Set application icon
446+ icon_path = Path (__file__ ).parent .parent .parent / "assets" / "icon.svg"
447+ if icon_path .exists ():
448+ app .setWindowIcon (QIcon (str (icon_path )))
449+
362450 # Set dark palette
363451 palette = QPalette ()
364452 palette .setColor (QPalette .ColorRole .Window , QColor (43 , 43 , 43 ))
0 commit comments