171: self.error.emit(str(exc))
210: self.error.emit(str(exc))
267: self._lbl_selected = QLabel(tr("No image selected"))
278: self._btn_select = QPushButton(tr("Γ£ö Select this image"))
314: f"Γ£à {tr('Auto-selected')}: {d}  {cloud_str}")
334: self._lbl_selected.setText(f"Γ£à {tr('Selected')}: {d}  {cloud_str}")
358: self.setWindowTitle(tr("≡ƒî┐ Forest Change Detective ΓÇö LVT4U"))
372: self.setWindowTitle(tr("≡ƒî┐ Forest Change Detective ΓÇö LVT4U"))
374: "≡ƒî┐  <b>" + tr("≡ƒî┐ Forest Change Detective").replace("≡ƒî┐ ", "") + "</b>"
375: "  ΓÇö  <span style='color:#74C69D'>" + tr("Forest Change Detection & Deforestation Alert") + "</span>"
376: "  <span style='color:#95D5B2; font-size:11px;'>  |  " + tr("V4 Workflow") + "</span>"
378: self.tabs.setTabText(0, tr("≡ƒ¢░∩╕Å Data"))
379: self.tabs.setTabText(1, tr("≡ƒôè Analysis"))
380: self.tabs.setTabText(2, tr("≡ƒôï Results"))
381: self.tabs.setTabText(3, tr("ΓÜÖ∩╕Å Settings"))
383: self.btn_analyze.setText(tr("Γû╢  Analyze Changes"))
384: self.btn_cancel.setText(tr("Γ£û Cancel"))
385: self.btn_close.setText(tr("Close"))
386: self.btn_help.setText(tr("Help"))
388: self.grp_roi.setTitle(tr("Study Area (ROI)"))
389: if hasattr(self, 'roi_layout') and self.cb_roi_layer:
390: self.roi_layout.labelForField(self.cb_roi_layer).setText(tr("Layer from QGIS:"))
391: self.btn_use_extent.setText(tr("≡ƒôÉ Use current canvas extent"))
392: if hasattr(self, 'roi_layout') and self._lbl_roi:
393: self.roi_layout.labelForField(self._lbl_roi).setText(tr("ROI selected:"))
395: self.grp_sensor.setTitle(tr("Satellite Sensor"))
396: if hasattr(self, 'sensor_layout') and self.cb_sensor:
397: self.sensor_layout.labelForField(self.cb_sensor).setText(tr("Sensor:"))
399: self.cb_sensor.setItemText(0, tr("Sentinel-2 L2A (Optical, 10m)"))
400: self.cb_sensor.setItemText(1, tr("Sentinel-2 + Sentinel-1 SAR (Combined)"))
401: self.cb_sensor.setItemText(2, tr("Landsat 8/9 (30m)"))
403: if hasattr(self, 'sensor_layout') and self.spin_max_cloud:
404: self.sensor_layout.labelForField(self.spin_max_cloud).setText(tr("Max cloud cover:"))
406: self.grp_mode.setTitle(tr("Select TΓéü and TΓéé Images"))
407: self.rb_mode_a.setText(tr("A ΓÇö Manual search from catalog"))
408: self.rb_mode_b.setText(tr("B ΓÇö Enter date, system auto-selects best image"))
409: self.rb_mode_c.setText(tr("C ΓÇö Combined (TΓéü auto, TΓéé manual or vice versa)"))
411: if hasattr(self, 'lbl_date_from'): self.lbl_date_from.setText(tr("From:"))
412: if hasattr(self, 'lbl_date_to'): self.lbl_date_to.setText(tr("To:"))
414: self.btn_search.setText(tr("≡ƒöì Search Images"))
416: self._img_t1._lbl_title.setText(f"<b>{tr('≡ƒôà Image TΓéü (Before)')}</b>")
417: self._img_t2._lbl_title.setText(f"<b>{tr('≡ƒôà Image TΓéé (After)')}</b>")
418: self._img_t1._btn_select.setText(tr("Γ£ö Select this image"))
419: self._img_t2._btn_select.setText(tr("Γ£ö Select this image"))
421: if hasattr(self, 'page_b_layout') and self.de_t1_auto:
422: self.page_b_layout.labelForField(self.de_t1_auto).setText(tr("TΓéü date (target):"))
423: if hasattr(self, 'page_b_layout') and self.de_t2_auto:
424: self.page_b_layout.labelForField(self.de_t2_auto).setText(tr("TΓéé date (target):"))
425: if hasattr(self, 'page_b_layout') and self.spin_window_days:
426: self.page_b_layout.labelForField(self.spin_window_days).setText(tr("Search window ┬▒:"))
427: self.spin_window_days.setSuffix(tr(" days"))
430: self.btn_auto_find.setText(tr("≡ƒñû Auto-find best images"))
432: if hasattr(self, 'page_b_layout') and self._lbl_auto_t1:
433: self.page_b_layout.labelForField(self._lbl_auto_t1).setText(tr("TΓéü selected:"))
434: if hasattr(self, 'page_b_layout') and self._lbl_auto_t2:
435: self.page_b_layout.labelForField(self._lbl_auto_t2).setText(tr("TΓéé selected:"))
437: self.grp_forest.setTitle(tr("Forest Reference Layer (optional)"))
438: self.le_forest_path.setPlaceholderText(tr("Leave blank if no forest type classification needed..."))
440: self.grp_change_type.setTitle(tr("Change Type to Detect"))
441: if "DEFORESTATION" in self._mode_radios: self._mode_radios["DEFORESTATION"].setText(tr("≡ƒî▓ Deforestation / Forest Loss"))
442: if "FIRE" in self._mode_radios: self._mode_radios["FIRE"].setText(tr("≡ƒöÑ Fire / Burn Scar"))
443: if "DEGRADATION" in self._mode_radios: self._mode_radios["DEGRADATION"].setText(tr("≡ƒôë Degradation / Vegetation Recovery"))
444: if "MOISTURE" in self._mode_radios: self._mode_radios["MOISTURE"].setText(tr("≡ƒÆº Canopy Moisture Change"))
446: self.grp_thresh.setTitle(tr("Threshold Settings (editable)"))
447: self._thresh_table.setHorizontalHeaderLabels([tr("Index"), tr("Direction"), tr("Threshold"), tr("Unit")])
448: self.btn_reset_thresh.setText(tr("Γå║ Reset to defaults"))
454: dir_item.setText(tr("Giß║úm ΓëÑ"))
456: dir_item.setText(tr("T─âng ΓëÑ"))
458: self.grp_mmu.setTitle(tr("Filter Settings"))
459: if hasattr(self, 'mmu_layout') and self.spin_mmu:
460: self.mmu_layout.labelForField(self.spin_mmu).setText(tr("MMU (minimum mapping unit):"))
461: self.cb_use_s1.setText(tr("Include Sentinel-1 SAR (cloud-penetrating)"))
462: if hasattr(self, 'mmu_layout') and self.spin_dem_slope:
463: self.mmu_layout.labelForField(self.spin_dem_slope).setText(tr("DEM slope threshold (SAR mountain mask):"))
465: self.grp_firms.setTitle(tr("≡ƒöÑ FIRMS Fire Hotspot Validation (NASA)"))
466: self.cb_use_firms.setText(tr("Enable cross-validation with FIRMS Hotspots"))
467: self.le_firms_key.setPlaceholderText(tr("NASA FIRMS API Key (free at firms.modaps.eosdis.nasa.gov)"))
468: if hasattr(self, 'firms_layout') and self.le_firms_key:
469: self.firms_layout.labelForField(self.le_firms_key).setText(tr("API Key:"))
470: if hasattr(self, 'firms_layout') and self.spin_firms_buffer:
471: self.firms_layout.labelForField(self.spin_firms_buffer).setText(tr("Confirmation buffer:"))
473: self.grp_stats.setTitle(tr("Change Statistics"))
474: self._tbl_stats.setHorizontalHeaderLabels([tr("Index"), tr("Value")])
476: self.grp_log.setTitle(tr("Processing Log"))
478: self.grp_export.setTitle(tr("Export Data"))
479: self.btn_open_gpkg.setText(tr("≡ƒôé Open GeoPackage"))
480: self.btn_export_kml.setText(tr("≡ƒù║ Export KML"))
481: self.btn_export_json.setText(tr("≡ƒôä Export GeoJSON"))
482: self.btn_load_canvas.setText(tr("≡ƒù║ Add to QGIS"))
485: self.settings_layout.itemAt(0, QFormLayout.ItemRole.LabelRole).widget().setText("<b>" + tr("Copernicus Data Space (CDSE):") + "</b>")
487: self.settings_layout.itemAt(3, QFormLayout.ItemRole.LabelRole).widget().setText("<b>" + tr("DEM (for Mountain Mask):") + "</b>")
489: self.settings_layout.labelForField(self.le_cdse_user).setText(tr("CDSE Username:"))
490: self.settings_layout.labelForField(self.le_cdse_pass).setText(tr("CDSE Password:"))
492: if hasattr(self.le_dem_path, 'parentWidget'):
495: self.settings_layout.labelForField(pw).setText(tr("DEM (GeoTIFF):"))
497: if hasattr(self.le_output_dir, 'parentWidget'):
500: self.settings_layout.labelForField(pw2).setText(tr("Output folder:"))
502: self.le_dem_path.setPlaceholderText(tr("Leave blank = skip mountain mask"))
503: self.btn_save.setText(tr("≡ƒÆ╛ Save settings"))
606: "≡ƒî┐  <b>" + tr("≡ƒî┐ Forest Change Detective").replace("≡ƒî┐ ", "") + "</b>"
607: "  ΓÇö  <span style='color:#74C69D'>" + tr("Forest Change Detection & Deforestation Alert") + "</span>"
608: "  <span style='color:#95D5B2; font-size:11px;'>  |  " + tr("V4 Workflow") + "</span>"
619: self.tabs.addTab(self._build_tab_data(),     tr("≡ƒ¢░∩╕Å Data"))
620: self.tabs.addTab(self._build_tab_analysis(), tr("≡ƒôè Analysis"))
621: self.tabs.addTab(self._build_tab_results(),  tr("≡ƒôï Results"))
622: self.tabs.addTab(self._build_tab_settings(), tr("ΓÜÖ∩╕Å Settings"))
639: self.btn_analyze = QPushButton(tr("Γû╢  Analyze Changes"))
648: self.btn_cancel = QPushButton(tr("Γ£û Cancel"))
652: self.btn_close = QPushButton(tr("Close"))
655: self.btn_help = QPushButton(tr("≡ƒôû H╞░ß╗¢ng dß║½n"))
656: self.btn_help.setToolTip(tr(
678: self.grp_roi = QGroupBox(tr("Study Area (ROI)"))
694: tr("Chß╗ìn v├╣ng nghi├¬n cß╗⌐u (ROI) ΓÇö Region of Interest\n\n"
700: self.roi_layout.addRow(tr("Layer tß╗½ QGIS:"), self.cb_roi_layer)
702: self.btn_use_extent = QPushButton(tr("≡ƒôÉ D├╣ng v├╣ng nh├¼n thß║Ñy tr├¬n bß║ún ─æß╗ô"))
704: tr("D├╣ng v├╣ng hiß╗ân thß╗ï hiß╗çn tß║íi tr├¬n QGIS l├ám ROI\n\n"
710: self._lbl_roi = QLabel(tr("ROI not defined"))
712: self.roi_layout.addRow(tr("ROI selected:"), self._lbl_roi)
717: self.grp_sensor = QGroupBox(tr("Satellite Sensor"))
722: tr("Sentinel-2 L2A (Quang hß╗ìc, 10m ΓÇö Khuy├¬n d├╣ng)"),
723: tr("Sentinel-2 + Sentinel-1 SAR (Kß║┐t hß╗úp radar)"),
724: tr("Landsat 8/9 (30m ΓÇö V├╣ng nhß╗Å h╞ín 500 ha)"),
727: tr("Chß╗ìn nguß╗ôn ß║únh vß╗ç tinh\n\n"
737: self.sensor_layout.addRow(tr("ß║ónh vß╗ç tinh:"), self.cb_sensor)
744: tr("Tß╗ë lß╗ç m├óy tß╗æi ─æa ΓÇö Max Cloud Cover\n\n"
752: self.sensor_layout.addRow(tr("Γÿü∩╕Å M├óy tß╗æi ─æa:"), self.spin_max_cloud)
757: self.grp_mode = QGroupBox(tr("Select TΓéü and TΓéé Images"))
761: self.rb_mode_a = QRadioButton(tr("A ΓÇö Manual search from catalog"))
762: self.rb_mode_b = QRadioButton(tr("B ΓÇö Enter date, system auto-selects best image"))
763: self.rb_mode_c = QRadioButton(tr("C ΓÇö Combined (TΓéü auto, TΓéé manual or vice versa)"))
783: self.lbl_date_from = QLabel(tr("From:"))
791: self.lbl_date_to = QLabel(tr("To:"))
799: self.btn_search = QPushButton(tr("≡ƒöì Search Images"))
811: self._img_t1 = _ImageListWidget(tr("≡ƒôà Image TΓéü (Before)"))
812: self._img_t2 = _ImageListWidget(tr("≡ƒôà Image TΓéé (After)"))
814: lambda p: setattr(self, "_product_t1", p))
816: lambda p: setattr(self, "_product_t2", p))
841: self.spin_window_days.setSuffix(tr(" days"))
843: self.page_b_layout.addRow(tr("TΓéü date (target):"), self.de_t1_auto)
844: self.page_b_layout.addRow(tr("TΓéé date (target):"), self.de_t2_auto)
845: self.page_b_layout.addRow(tr("Search window ┬▒:"), self.spin_window_days)
847: self.btn_auto_find = QPushButton(tr("≡ƒñû Auto-find best images"))
855: self._lbl_auto_t1 = QLabel(tr("TΓéü: not searched yet"))
856: self._lbl_auto_t2 = QLabel(tr("TΓéé: not searched yet"))
857: self.page_b_layout.addRow(tr("TΓéü selected:"), self._lbl_auto_t1)
858: self.page_b_layout.addRow(tr("TΓéé selected:"), self._lbl_auto_t2)
866: self.grp_forest = QGroupBox(tr("Forest Reference Layer (optional)"))
871: tr("Leave blank if no forest type classification needed..."))
874: btn_browse_forest = QPushButton(tr("≡ƒôé"))
890: self.grp_change_type = QGroupBox(tr("Change Type to Detect"))
901: "DEFORESTATION": tr(
914: "FIRE": tr(
927: "DEGRADATION": tr(
941: "MOISTURE": tr(
956: rb = QRadioButton(tr(label))
966: self.grp_thresh = QGroupBox(tr("Threshold Settings ΓÇö Ng╞░ß╗íng ph├ít hiß╗çn"))
969: tr("Ng╞░ß╗íng chß╗ë sß╗æ (editable)\n\n"
980: tr("Chß╗ë sß╗æ"), tr("Chiß╗üu biß║┐n ─æß╗Öng"), tr("Ng╞░ß╗íng tß╗æi thiß╗âu"), tr("─É╞ín vß╗ï")])
984: tr("Chß╗ë sß╗æ: T├¬n chß╗ë sß╗æ (NDVI, EVI2, dNBR...)\n"
994: tr("╬ö = Chi so(T2) ΓêÆ Chi so(T1)\n"
1004: tr("Vi du cu the:\n"
1013: self.btn_reset_thresh = QPushButton(tr("\u21ba ─Éß║╖t lß║íi mß║╖c ─æß╗ïnh"))
1014: self.btn_reset_thresh.setToolTip(tr("Kh├┤i phß╗Ñc c├íc ng╞░ß╗íng vß╗ü gi├í trß╗ï mß║╖c ─æß╗ïnh khoa hß╗ìc"))
1021: self.grp_mmu = QGroupBox(tr("Filter Settings ΓÇö Lß╗ìc kß║┐t quß║ú"))
1030: tr("─É╞ín vß╗ï Lß║¡p Bß║ún ─Éß╗ô Tß╗æi Thiß╗âu (MMU)\n\n"
1038: self.mmu_layout.addRow(tr("≡ƒôì Diß╗çn t├¡ch tß╗æi thiß╗âu (MMU):"), self.spin_mmu)
1040: self.cb_use_s1 = QCheckBox(tr("Kß║┐t hß╗úp Sentinel-1 SAR (Radar xuy├¬n m├óy)"))
1042: tr("Th├¬m dß╗» liß╗çu Sentinel-1 SAR v├áo ph├ón t├¡ch\n\n"
1057: tr("Ng╞░ß╗íng ─æß╗Ö dß╗æc DEM ΓÇö lß╗ìc nhiß╗àu SAR tr├¬n n├║i\n\n"
1064: self.mmu_layout.addRow(tr("Γ¢░∩╕Å Lß╗ìc dß╗æc DEM (SAR):"),
1070: self.grp_firms = QGroupBox(tr("≡ƒöÑ FIRMS Fire Hotspot Validation (NASA)"))
1073: self.cb_use_firms = QCheckBox(tr("Enable cross-validation with FIRMS Hotspots"))
1080: tr("NASA FIRMS API Key (free at firms.modaps.eosdis.nasa.gov)"))
1083: self.firms_layout.addRow(tr("API Key:"), self.le_firms_key)
1089: self.firms_layout.addRow(tr("Confirmation buffer:"), self.spin_firms_buffer)
1107: self.grp_stats = QGroupBox(tr("Change Statistics"))
1111: self._tbl_stats.setHorizontalHeaderLabels([tr("Index"), tr("Value")])
1120: self.grp_log = QGroupBox(tr("Processing Log"))
1133: self.btn_preview = QPushButton(tr("≡ƒö┤  Xem Preview ΓÇö Nhß║Ñp nh├íy v├╣ng biß║┐n ─æß╗Öng"))
1146: self.grp_export = QGroupBox(tr("Export Data"))
1150: self.btn_open_gpkg = QPushButton(tr("≡ƒôé Open GeoPackage"))
1151: self.btn_export_kml = QPushButton(tr("≡ƒù║ Export KML"))
1152: self.btn_export_json = QPushButton(tr("≡ƒôä Export GeoJSON"))
1153: self.btn_load_canvas = QPushButton(tr("≡ƒù║ Add to QGIS"))
1185: self.settings_layout.addRow(tr("CDSE Username:"), self.le_cdse_user)
1190: self.settings_layout.addRow(tr("CDSE Password:"), self.le_cdse_pass)
1198: tr("Leave blank = skip mountain mask"))
1200: btn_dem = QPushButton(tr("≡ƒôé"))
1204: self.settings_layout.addRow(tr("DEM (GeoTIFF):"), dem_row)
1214: btn_out = QPushButton(tr("≡ƒôé"))
1218: self.settings_layout.addRow(tr("Output folder:"), out_row)
1221: self.btn_save = QPushButton(tr("≡ƒÆ╛ Save settings"))
1257: dir_label = tr("Giß║úm ΓëÑ") if direction == "below" else tr("T─âng ΓëÑ")
1266: tr("Nhß║¡p sß╗æ d╞░╞íng. Hß╗ç thß╗æng sß║╜ tß╗▒ ├íp dß╗Ñng dß║Ñu theo chiß╗üu biß║┐n ─æß╗Öng.\n"
1290: if tr("Giß║úm") in dir_text or "Decrease" in dir_text or "below" in dir_text:
1333: if hasattr(self, "_roi_geojson") and self._roi_geojson:
1372: self, tr("Missing ROI"),
1373: tr("Please select an ROI layer or use current canvas extent."))
1384: self.btn_search.setText(tr("≡ƒöä Searching..."))
1400: self.btn_search.setText(tr("≡ƒöì Search Images"))
1413: self.btn_search.setText(tr("≡ƒöì Search Images"))
1416: QMessageBox.critical(self, tr("Error"), msg)
1426: self, tr("Missing ROI"),
1427: tr("Please select an ROI first."))
1438: self.btn_auto_find.setText(tr("≡ƒñû Searching TΓéü..."))
1467: self.btn_auto_find.setText(tr("≡ƒñû Searching TΓéé..."))
1481: self.btn_auto_find.setText(tr("≡ƒñû Auto-find best images"))
1505: self, tr("Missing Image TΓéü"),
1506: tr("Please select or auto-find image TΓéü first."))
1511: self, tr("Missing Image TΓéé"),
1512: tr("Please select or auto-find image TΓéé first."))
1518: self, tr("Missing ROI"),
1519: tr("Please define the study area (ROI)."))
1566: "dry": tr("m├╣a kh├┤ (th├íng 11ΓÇô4)"),
1567: "wet": tr("m├╣a m╞░a (th├íng 5ΓÇô10)"),
1570: tr("ΓÜá∩╕Å  Cß║óNH B├üO: HAI ß║óNH ß╗₧ 2 M├ÖA KH├üC NHAU!\n\n")
1571: + tr(f"TΓéü ({t1_str}): {_s_label[s1]}\n")
1572: + tr(f"TΓéé ({t2_str}): {_s_label[s2]}\n\n")
1573: + tr("Vß║ñN ─Éß╗Ç:\n")
1574: + tr("M├╣a kh├┤: l├í rß╗Ñng, NDVI thß║Ñp (~0.45)\n")
1575: + tr("M├╣a m╞░a: l├í xanh ─æß║ºy, NDVI cao (~0.65)\n")
1576: + tr("ΓåÆ ╬öNDVI = +0.20 (d╞░╞íng) ΓåÆ Rß╗½ng 't─âng' ΓåÆ Che khuß║Ñt khai th├íc!\n\n")
1577: + tr("GIß║óI PH├üP Tß╗ÉT NHß║ñT:\n")
1578: + tr("  Chß╗ìn TΓéü v├á TΓéé C├ÖNG M├ÖA (c├╣ng th├íng, kh├íc n─âm):\n")
1579: + tr("  V├¡ dß╗Ñ: TΓéü = Th├íng 1/2025, TΓéé = Th├íng 1/2026\n\n")
1580: + tr("Bß║Ñm 'Tiß║┐p tß╗Ñc' ─æß╗â chß║íy d├╣ c├│ cß║únh b├ío\n"
1586: tr("ΓÜá∩╕Å Cß║únh b├ío M├╣a vß╗Ñ"),
1608: self, tr("ΓÜá∩╕Å Khoß║úng c├ích qu├í ngß║»n"),
1609: tr(f"T1 v├á T2 chß╗ë c├ích nhau {interval_days} ng├áy!\n\n"
1660: self._lbl_status.setText(tr("Γ£à Analysis complete!"))
1697: QMessageBox.critical(self, tr("Pipeline Error"), msg)
1705: "total_polygons": tr("Total polygons"),
1706: "total_area_ha":  tr("Total area (ha)"),
1707: "critical_count": tr("CRITICAL (ΓëÑ1 ha)"),
1708: "warning_count":  tr("WARNING (0.05ΓÇô1 ha)"),
1709: "info_count":     tr("INFO (< 0.05 ha)"),
1710: "sensor_type":    tr("Sensor type"),
1711: "index_used":     tr("Index used"),
1719: item_val = QTableWidgetItem(str(val))
2098: dlg.setWindowTitle(tr("≡ƒôû Forest Change Detective ΓÇö User Guide"))
2100: dlg.setWindowTitle(tr("≡ƒôû Forest Change Detective ΓÇö H╞░ß╗¢ng dß║½n sß╗¡ dß╗Ñng"))
2123: tabs.addTab(_browser(tips_html),  tr("ΓÜí Quick Tips"))
2124: tabs.addTab(_browser(guide_html), tr("≡ƒôÜ Full Guide"))
2125: tabs.addTab(_browser(param_html), tr("≡ƒôï Parameter Reference"))
2127: tabs.addTab(_browser(tips_html),  tr("ΓÜí Quick Tips"))
2128: tabs.addTab(_browser(guide_html), tr("≡ƒôÜ H╞░ß╗¢ng dß║½n ─æß║ºy ─æß╗º"))
2129: tabs.addTab(_browser(param_html), tr("≡ƒôï Bß║úng tham sß╗æ"))
2151: QMessageBox.warning(self, tr("No result"),
2152: tr("Run analysis first."))
2174: self, tr("Kh├┤ng c├│ kß║┐t quß║ú"),
2175: tr(f"File kß║┐t quß║ú kh├┤ng tß╗ôn tß║íi:\n{gpkg}\n\nH├úy chß║íy ph├ón t├¡ch tr╞░ß╗¢c."))
2184: self, tr("File lß╗ùi"),
2185: tr(f"Kh├┤ng thß╗â mß╗ƒ file GPKG:\n{gpkg}\n\n"
2200: self, tr("Kß║┐t quß║ú rß╗ùng"),
2201: tr("Kß║┐t quß║ú ph├ón t├¡ch c├│ 0 polygon.\n\n"
2208: layer_display_name = tr(f"Forest Change ΓÇö {mode}")
2238: self, tr("Thß║Ñt bß║íi"),
2239: tr(f"Kh├┤ng thß╗â th├¬m layer tß╗▒ ─æß╗Öng.\n\n"
2245: if "currentFrame" in str(ae) or "inspect" in str(ae):
2248: self, tr("Python 3.14 Compatibility"),
2249: tr(f"Lß╗ùi t╞░╞íng th├¡ch Python 3.14 (inspect.currentFrame).\n\n"
2253: QMessageBox.critical(self, tr("Lß╗ùi"), tr(f"AttributeError: {ae}\nFile: {gpkg}"))
2257: self, tr("Lß╗ùi Add to QGIS"),
2258: tr(f"Lß╗ùi: {e}\n\nFile: {gpkg}"))
2266: QMessageBox.warning(self, tr("No result"), tr("Run analysis first."))
2284: QMessageBox.warning(self, tr("Export KML"),
2285: tr("Export failed. Check log."))
2293: QMessageBox.warning(self, tr("No result"), tr("Run analysis first."))
2311: QMessageBox.warning(self, tr("Export GeoJSON"),
2312: tr("Export failed. Check log."))
2322: self, tr("Select forest reference layer"),
2323: "", tr("Vector (*.shp *.gpkg *.geojson)"))
2329: self, tr("Select DEM"), "", tr("GeoTIFF (*.tif *.tiff)"))
2335: self, tr("Select output folder"), self.le_output_dir.text())
