i18n.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. """Simple two-language localization (EN / RU) for the LF-MRI GUI."""
  2. from __future__ import annotations
  3. _lang: str = "en"
  4. _listeners: list = []
  5. def add_listener(callback) -> None:
  6. """Register a callable to be invoked on every language change."""
  7. if callback not in _listeners:
  8. _listeners.append(callback)
  9. def remove_listener(callback) -> None:
  10. """Unregister a previously added listener (silently ignores unknown callbacks)."""
  11. try:
  12. _listeners.remove(callback)
  13. except ValueError:
  14. pass
  15. _T: dict[str, dict[str, str]] = {
  16. # --- nav bar ---
  17. "tab_nav_sequence": {"en": "Sequence", "ru": "Последовательность"},
  18. "tab_nav_scanner": {"en": "Scanner", "ru": "Сканнер"},
  19. "tab_nav_fid": {"en": "FID", "ru": "FID"},
  20. "tab_nav_scanning": {"en": "Scanning", "ru": "Сканирование"},
  21. "tab_nav_spectro": {"en": "Spectroscopy", "ru": "Спектроскопия"},
  22. "active_tab": {"en": "Active", "ru": "Активно"},
  23. # --- scanner_tab ---
  24. "url_label": {"en": "Orchestrator URL:", "ru": "URL оркестратора:"},
  25. "btn_connect": {"en": "Connect", "ru": "Подключить"},
  26. "offline": {"en": "● Offline", "ru": "● Не подключён"},
  27. "online": {"en": "● Online", "ru": "● Подключён"},
  28. "conn_error": {"en": "● Error", "ru": "● Ошибка"},
  29. "grp_scenario": {"en": "Scenario", "ru": "Сценарий"},
  30. "btn_refresh": {"en": "Refresh", "ru": "Обновить"},
  31. "grp_seq_info": {"en": "Sequence Info", "ru": "Информация о посл-ти"},
  32. "no_seq_loaded": {"en": "No sequence loaded", "ru": "Посл-ть не загружена"},
  33. "grp_job": {"en": "Job", "ru": "Задание"},
  34. "no_job": {"en": "- no job -", "ru": "- нет задания -"},
  35. "btn_load_scenario": {"en": "Load Scenario", "ru": "Загрузить сценарий"},
  36. "btn_build_scenario": {"en": "Build custom…", "ru": "Собрать вручную…"},
  37. "btn_run_all": {"en": "Run All", "ru": "Запустить всё"},
  38. "btn_next_step": {"en": "Next Step", "ru": "Следующий шаг"},
  39. "btn_abort": {"en": "Abort", "ru": "Прервать"},
  40. "col_step": {"en": "Step", "ru": "Шаг"},
  41. "col_status": {"en": "Status", "ru": "Статус"},
  42. "col_result": {"en": "Result", "ru": "Результат"},
  43. "tab_step_result": {"en": "Step Result", "ru": "Результат шага"},
  44. "tab_step_params": {"en": "Step Params", "ru": "Параметры шага"},
  45. "tab_seq_info_view": {"en": "Sequence Info", "ru": "Инф. о посл-ти"},
  46. "tab_log": {"en": "Log", "ru": "Журнал"},
  47. # --- seq_interp_tab ---
  48. "btn_load_seq": {"en": "Load .seq", "ru": "Загрузить .seq"},
  49. "btn_hw_config": {"en": "HW Config", "ru": "Конфиг ЖО"},
  50. "btn_out_dir": {"en": "Output Dir", "ru": "Папка вывода"},
  51. "btn_run": {"en": "Run", "ru": "Запустить"},
  52. "btn_export": {"en": "Export", "ru": "Экспорт"},
  53. "btn_fit_all": {"en": "Fit All", "ru": "Вписать всё"},
  54. "blocks_closed": {"en": "Blocks ▾", "ru": "Блоки ▾"},
  55. "blocks_open": {"en": "Blocks ▴", "ru": "Блоки ▴"},
  56. "btn_send_scanner": {"en": "Send to Scanner", "ru": "На сканнер"},
  57. "grp_seq_metadata": {"en": "Sequence Metadata", "ru": "Метаданные посл-ти"},
  58. "grp_warnings": {"en": "Warnings", "ru": "Предупреждения"},
  59. "status_ready": {"en": "Ready", "ru": "Готово"},
  60. "no_file_selected": {"en": "No file selected", "ru": "Файл не выбран"},
  61. # --- fid_tab ---
  62. "btn_generate": {"en": "Generate", "ru": "Генерировать"},
  63. "btn_save_seq": {"en": "Save .seq", "ru": "Сохранить .seq"},
  64. "btn_load_seq_tab": {"en": "→ Load in Sequence Tab", "ru": "→ В Sequence Tab"},
  65. "grp_fid_params": {"en": "FID Parameters", "ru": "Параметры FID"},
  66. "grp_pulse_type": {"en": "Pulse type", "ru": "Тип импульса"},
  67. "grp_out_filename": {"en": "Output filename (no extension)", "ru": "Имя файла (без расширения)"},
  68. "fid_status_init": {
  69. "en": "Ready - set parameters and click Generate",
  70. "ru": "Готово - задайте параметры и нажмите Генерировать",
  71. },
  72. # --- scanning_tab ---
  73. "protocols_header": {"en": "Protocols", "ru": "Протоколы"},
  74. "tab_main": {"en": "Main", "ru": "Основные"},
  75. "tab_contrast": {"en": "Contrast", "ru": "Контраст"},
  76. "tab_resolution": {"en": "Resolution", "ru": "Разрешение"},
  77. "tab_system": {"en": "System", "ru": "Система"},
  78. "tab_geometry": {"en": "Geometry", "ru": "Геометрия"},
  79. "grp_orientation": {"en": "Orientation", "ru": "Ориентация"},
  80. "grp_rotation": {"en": "Rotation", "ru": "Поворот"},
  81. "grp_rot_matrix": {"en": "Rotation Matrix", "ru": "Матрица поворота"},
  82. "no_data": {"en": "No data", "ru": "Нет данных"},
  83. "btn_scan": {"en": "Scan", "ru": "Сканировать"},
  84. "btn_stop_scan": {"en": "Stop", "ru": "Стоп"},
  85. "scanning_status": {"en": "Scanning...", "ru": "Сканирование..."},
  86. "ready_to_scan": {"en": "Ready to scan", "ru": "Готово к сканированию"},
  87. "dlg_no_data_title": {"en": "No data", "ru": "Нет данных"},
  88. "dlg_no_seq_msg": {
  89. "en": "First load and export a sequence\nin the \"Sequence\" tab.",
  90. "ru": "Сначала загрузите и экспортируйте последовательность\nво вкладке \"Sequence\".",
  91. },
  92. "done_status": {"en": "Done", "ru": "Готово"},
  93. "error_prefix": {"en": "Error", "ru": "Ошибка"},
  94. # --- spectroscopy_tab ---
  95. "btn_load_json": {"en": "Load JSON...", "ru": "Загрузить JSON..."},
  96. "btn_analyze": {"en": "Analyze", "ru": "Анализировать"},
  97. "btn_batch": {"en": "Batch...", "ru": "Пакетный..."},
  98. "btn_export_sp": {"en": "Export...", "ru": "Экспорт..."},
  99. "btn_clear": {"en": "Clear", "ru": "Очистить"},
  100. "grp_nmr_params": {"en": "NMR Parameters", "ru": "Параметры ЯМР"},
  101. "grp_metadata": {"en": "Metadata", "ru": "Метаданные"},
  102. "grp_metrics": {"en": "Metrics", "ru": "Метрики"},
  103. "spec_status_init": {
  104. "en": "Click 'Load JSON...' to open a hardware JSON file and start NMR analysis",
  105. "ru": "Нажмите 'Загрузить JSON...' для открытия JSON-файла и запуска анализа ЯМР",
  106. },
  107. "batch_title": {"en": "Batch NMR Analysis", "ru": "ЯМР анализ набора данных"},
  108. "folder_label": {"en": "Folder:", "ru": "Папка:"},
  109. "btn_browse": {"en": "Browse...", "ru": "Обзор..."},
  110. "cb_use_current": {
  111. "en": "Use current parameters from the Spectroscopy tab",
  112. "ru": "Использовать текущие параметры вкладки Spectroscopy",
  113. },
  114. "btn_run_batch": {"en": "Run Batch", "ru": "Исследовать набор"},
  115. "btn_export_csv": {"en": "Export CSV...", "ru": "Экспорт CSV..."},
  116. "btn_close": {"en": "Close", "ru": "Закрыть"},
  117. "batch_status_init": {
  118. "en": "Select a folder and click Run Batch.",
  119. "ru": "Выберите папку и нажмите Исследовать набор.",
  120. },
  121. # --- mode selector ---
  122. "mode_plug": {"en": "PLUG", "ru": "ИМИТАТОР"},
  123. "mode_real": {"en": "HACK RF", "ru": "HACK RF"},
  124. "mode_confirm_title": {"en": "Switch Mode", "ru": "Смена режима"},
  125. "mode_confirm_to_real": {
  126. "en": "Switch connection to Hack RF mode?\n\nPhysical devices will be used for acquisition.",
  127. "ru": "Переключиться в режим подключения к спектрометру Hack RF?\n\nДля сбора данных будет использоваться реальное оборудование.",
  128. },
  129. "mode_confirm_to_plug": {
  130. "en": "Switch to PLUG (stub) mode?\n\nPhysical devices will NOT be used.",
  131. "ru": "Переключиться в режим имитатора?\n\nРеальное оборудование использоваться не будет.",
  132. },
  133. "mode_error": {"en": "Mode switch failed", "ru": "Ошибка смены режима"},
  134. # --- controls_panel ---
  135. "grp_hw_delays": {"en": "Hardware Delays / Rasters", "ru": "Задержки / Растры ЖО"},
  136. "btn_apply_rerun": {"en": "Apply && Rerun", "ru": "Применить && Перезапуск"},
  137. "btn_reset": {"en": "Reset", "ru": "Сброс"},
  138. "btn_reload_config": {"en": "Reload Config", "ru": "Перезагрузить конфиг"},
  139. "btn_save_hw": {"en": "Save HW Config", "ru": "Сохранить конфиг ЖО"},
  140. }
  141. _TAB_NAV_KEYS = [
  142. "tab_nav_sequence",
  143. "tab_nav_scanner",
  144. "tab_nav_fid",
  145. "tab_nav_scanning",
  146. "tab_nav_spectro",
  147. ]
  148. _PARAM_TABS_KEYS = [
  149. "tab_main",
  150. "tab_contrast",
  151. "tab_resolution",
  152. "tab_system",
  153. "tab_geometry",
  154. ]
  155. def tr(key: str) -> str:
  156. """Return translated string for *key* in the current language."""
  157. entry = _T.get(key)
  158. if entry is None:
  159. return key
  160. return entry.get(_lang, entry.get("en", key))
  161. def current_language() -> str:
  162. return _lang
  163. def set_language(lang: str) -> None:
  164. global _lang
  165. if lang in ("en", "ru"):
  166. _lang = lang
  167. for cb in list(_listeners):
  168. try:
  169. cb()
  170. except Exception:
  171. pass