"""Simple two-language localization (EN / RU) for the LF-MRI GUI.""" from __future__ import annotations _lang: str = "en" _listeners: list = [] def add_listener(callback) -> None: """Register a callable to be invoked on every language change.""" if callback not in _listeners: _listeners.append(callback) def remove_listener(callback) -> None: """Unregister a previously added listener (silently ignores unknown callbacks).""" try: _listeners.remove(callback) except ValueError: pass _T: dict[str, dict[str, str]] = { # --- nav bar --- "tab_nav_sequence": {"en": "Sequence", "ru": "Последовательность"}, "tab_nav_scanner": {"en": "Scanner", "ru": "Сканнер"}, "tab_nav_fid": {"en": "FID", "ru": "FID"}, "tab_nav_scanning": {"en": "Scanning", "ru": "Сканирование"}, "tab_nav_spectro": {"en": "Spectroscopy", "ru": "Спектроскопия"}, "active_tab": {"en": "Active", "ru": "Активно"}, # --- scanner_tab --- "url_label": {"en": "Orchestrator URL:", "ru": "URL оркестратора:"}, "btn_connect": {"en": "Connect", "ru": "Подключить"}, "offline": {"en": "● Offline", "ru": "● Не подключён"}, "online": {"en": "● Online", "ru": "● Подключён"}, "conn_error": {"en": "● Error", "ru": "● Ошибка"}, "grp_scenario": {"en": "Scenario", "ru": "Сценарий"}, "btn_refresh": {"en": "Refresh", "ru": "Обновить"}, "grp_seq_info": {"en": "Sequence Info", "ru": "Информация о посл-ти"}, "no_seq_loaded": {"en": "No sequence loaded", "ru": "Посл-ть не загружена"}, "grp_job": {"en": "Job", "ru": "Задание"}, "no_job": {"en": "- no job -", "ru": "- нет задания -"}, "btn_load_scenario": {"en": "Load Scenario", "ru": "Загрузить сценарий"}, "btn_build_scenario": {"en": "Build custom…", "ru": "Собрать вручную…"}, "btn_run_all": {"en": "Run All", "ru": "Запустить всё"}, "btn_next_step": {"en": "Next Step", "ru": "Следующий шаг"}, "btn_abort": {"en": "Abort", "ru": "Прервать"}, "col_step": {"en": "Step", "ru": "Шаг"}, "col_status": {"en": "Status", "ru": "Статус"}, "col_result": {"en": "Result", "ru": "Результат"}, "tab_step_result": {"en": "Step Result", "ru": "Результат шага"}, "tab_step_params": {"en": "Step Params", "ru": "Параметры шага"}, "tab_seq_info_view": {"en": "Sequence Info", "ru": "Инф. о посл-ти"}, "tab_log": {"en": "Log", "ru": "Журнал"}, # --- seq_interp_tab --- "btn_load_seq": {"en": "Load .seq", "ru": "Загрузить .seq"}, "btn_hw_config": {"en": "HW Config", "ru": "Конфиг ЖО"}, "btn_out_dir": {"en": "Output Dir", "ru": "Папка вывода"}, "btn_run": {"en": "Run", "ru": "Запустить"}, "btn_export": {"en": "Export", "ru": "Экспорт"}, "btn_fit_all": {"en": "Fit All", "ru": "Вписать всё"}, "blocks_closed": {"en": "Blocks ▾", "ru": "Блоки ▾"}, "blocks_open": {"en": "Blocks ▴", "ru": "Блоки ▴"}, "btn_send_scanner": {"en": "Send to Scanner", "ru": "На сканнер"}, "grp_seq_metadata": {"en": "Sequence Metadata", "ru": "Метаданные посл-ти"}, "grp_warnings": {"en": "Warnings", "ru": "Предупреждения"}, "status_ready": {"en": "Ready", "ru": "Готово"}, "no_file_selected": {"en": "No file selected", "ru": "Файл не выбран"}, # --- fid_tab --- "btn_generate": {"en": "Generate", "ru": "Генерировать"}, "btn_save_seq": {"en": "Save .seq", "ru": "Сохранить .seq"}, "btn_load_seq_tab": {"en": "→ Load in Sequence Tab", "ru": "→ В Sequence Tab"}, "grp_fid_params": {"en": "FID Parameters", "ru": "Параметры FID"}, "grp_pulse_type": {"en": "Pulse type", "ru": "Тип импульса"}, "grp_out_filename": {"en": "Output filename (no extension)", "ru": "Имя файла (без расширения)"}, "fid_status_init": { "en": "Ready - set parameters and click Generate", "ru": "Готово - задайте параметры и нажмите Генерировать", }, # --- scanning_tab --- "protocols_header": {"en": "Protocols", "ru": "Протоколы"}, "tab_main": {"en": "Main", "ru": "Основные"}, "tab_contrast": {"en": "Contrast", "ru": "Контраст"}, "tab_resolution": {"en": "Resolution", "ru": "Разрешение"}, "tab_system": {"en": "System", "ru": "Система"}, "tab_geometry": {"en": "Geometry", "ru": "Геометрия"}, "grp_orientation": {"en": "Orientation", "ru": "Ориентация"}, "grp_rotation": {"en": "Rotation", "ru": "Поворот"}, "grp_rot_matrix": {"en": "Rotation Matrix", "ru": "Матрица поворота"}, "no_data": {"en": "No data", "ru": "Нет данных"}, "btn_scan": {"en": "Scan", "ru": "Сканировать"}, "btn_stop_scan": {"en": "Stop", "ru": "Стоп"}, "scanning_status": {"en": "Scanning...", "ru": "Сканирование..."}, "ready_to_scan": {"en": "Ready to scan", "ru": "Готово к сканированию"}, "dlg_no_data_title": {"en": "No data", "ru": "Нет данных"}, "dlg_no_seq_msg": { "en": "First load and export a sequence\nin the \"Sequence\" tab.", "ru": "Сначала загрузите и экспортируйте последовательность\nво вкладке \"Sequence\".", }, "done_status": {"en": "Done", "ru": "Готово"}, "error_prefix": {"en": "Error", "ru": "Ошибка"}, # --- spectroscopy_tab --- "btn_load_json": {"en": "Load JSON...", "ru": "Загрузить JSON..."}, "btn_analyze": {"en": "Analyze", "ru": "Анализировать"}, "btn_batch": {"en": "Batch...", "ru": "Пакетный..."}, "btn_export_sp": {"en": "Export...", "ru": "Экспорт..."}, "btn_clear": {"en": "Clear", "ru": "Очистить"}, "grp_nmr_params": {"en": "NMR Parameters", "ru": "Параметры ЯМР"}, "grp_metadata": {"en": "Metadata", "ru": "Метаданные"}, "grp_metrics": {"en": "Metrics", "ru": "Метрики"}, "spec_status_init": { "en": "Click 'Load JSON...' to open a hardware JSON file and start NMR analysis", "ru": "Нажмите 'Загрузить JSON...' для открытия JSON-файла и запуска анализа ЯМР", }, "batch_title": {"en": "Batch NMR Analysis", "ru": "ЯМР анализ набора данных"}, "folder_label": {"en": "Folder:", "ru": "Папка:"}, "btn_browse": {"en": "Browse...", "ru": "Обзор..."}, "cb_use_current": { "en": "Use current parameters from the Spectroscopy tab", "ru": "Использовать текущие параметры вкладки Spectroscopy", }, "btn_run_batch": {"en": "Run Batch", "ru": "Исследовать набор"}, "btn_export_csv": {"en": "Export CSV...", "ru": "Экспорт CSV..."}, "btn_close": {"en": "Close", "ru": "Закрыть"}, "batch_status_init": { "en": "Select a folder and click Run Batch.", "ru": "Выберите папку и нажмите Исследовать набор.", }, # --- mode selector --- "mode_plug": {"en": "PLUG", "ru": "ИМИТАТОР"}, "mode_real": {"en": "HACK RF", "ru": "HACK RF"}, "mode_confirm_title": {"en": "Switch Mode", "ru": "Смена режима"}, "mode_confirm_to_real": { "en": "Switch connection to Hack RF mode?\n\nPhysical devices will be used for acquisition.", "ru": "Переключиться в режим подключения к спектрометру Hack RF?\n\nДля сбора данных будет использоваться реальное оборудование.", }, "mode_confirm_to_plug": { "en": "Switch to PLUG (stub) mode?\n\nPhysical devices will NOT be used.", "ru": "Переключиться в режим имитатора?\n\nРеальное оборудование использоваться не будет.", }, "mode_error": {"en": "Mode switch failed", "ru": "Ошибка смены режима"}, # --- controls_panel --- "grp_hw_delays": {"en": "Hardware Delays / Rasters", "ru": "Задержки / Растры ЖО"}, "btn_apply_rerun": {"en": "Apply && Rerun", "ru": "Применить && Перезапуск"}, "btn_reset": {"en": "Reset", "ru": "Сброс"}, "btn_reload_config": {"en": "Reload Config", "ru": "Перезагрузить конфиг"}, "btn_save_hw": {"en": "Save HW Config", "ru": "Сохранить конфиг ЖО"}, } _TAB_NAV_KEYS = [ "tab_nav_sequence", "tab_nav_scanner", "tab_nav_fid", "tab_nav_scanning", "tab_nav_spectro", ] _PARAM_TABS_KEYS = [ "tab_main", "tab_contrast", "tab_resolution", "tab_system", "tab_geometry", ] def tr(key: str) -> str: """Return translated string for *key* in the current language.""" entry = _T.get(key) if entry is None: return key return entry.get(_lang, entry.get("en", key)) def current_language() -> str: return _lang def set_language(lang: str) -> None: global _lang if lang in ("en", "ru"): _lang = lang for cb in list(_listeners): try: cb() except Exception: pass