/** * i18n.js — minimal PL/EN string-switching layer. * * Static text: mark an element with data-i18n="key" (replaces textContent), * data-i18n-title="key" (replaces title attribute), or * data-i18n-placeholder="key" (replaces placeholder attribute). For strings * with inline HTML formatting (e.g. ) use data-i18n-html="key" instead of * data-i18n — sets innerHTML. Only for trusted, hardcoded dictionary strings, * never for anything derived from user input. Call I18n.apply() after * injecting new HTML dynamically (e.g. after building a list of rows) to * translate the new nodes too. * * Dynamic text (toasts, JS-built strings): call I18n.t('key') directly. * * Language source of truth: localStorage 'ham_lang'. Falls back to the * value the installer wrote at first run (window.__HAM_INITIAL_LANG__, set * by a small inline script in index.html reading it from /api/config), then * to 'pl' if neither is present (matches this app's original language). */ window.I18N_STRINGS = { pl: { tab_radio: "RADIO", tab_settings: "USTAWIENIA", tab_profile: "👤 PROFIL", tab_log: "LOG QSO", tab_config: "KONFIGURACJA", tab_admin: "ADMIN", logout_btn: "⏻ WYLOGUJ", logout_title: "Wyloguj", // ── RADIO tab ── radio_mode_hdr: "TRYB", radio_band_hdr: "PASMO", radio_freq_history: "HISTORIA FREQ", radio_trx_functions: "TRX FUNKCJE", radio_meter_indicator: "WSKAŹNIK (bargraf pod S-metrem)", radio_filter_lbl: "FILTR (FIL1/2/3 — ustawienia z menu radia)", vfo_b_tooltip: "Klik: przełącz na VFO-B | Prawy klik: wpisz częstotliwość | Scroll: zmieniaj co 1kHz", ws_badge_title: "Opóźnienie połączenia sterującego (ping/pong) — NIE opóźnienie samego audio. Kliknij aby odświeżyć.", audio_badge_title: "Rzeczywiste opóźnienie odsłuchu RX: ile audio jest zbuforowane między odbiorem z serwera a odtworzeniem. Cel adaptacyjny 180-300ms (rośnie po niedoborach, opada gdy łącze spokojne), twardy limit 400ms.", vfoab_title: "Zamień VFO A i B (CI-V 07 B0)", vfoa2b_title: "Skopiuj A do B (CI-V 07 A0)", autotune_title: "Autotune — cykl dopasowania (CI-V 1C 01 02, generuje TX)", peak_hold_title: "Peak-hold: pokazuje najwyższą wartość w każdym paśmie", wf_collapse_title: "Zwiń/rozwiń wodospad (np. gdy radio nie ma scope'a i animacja przeszkadza)", rotator_target_lbl: "CEL (stopnie lub lokator)", cw_custom_placeholder: "Wpisz tekst CW…", operators_hdr: "OPERATORZY", chat_hdr: "CZAT", chat_input_ph: "Wiadomość…", sent_title: "RST wysłane", rcvd_title: "RST odebrane", rotator_target_placeholder: "272 lub KO02", no_connections: "Brak połączeń", // Radio disconnected-duration badge (status row, next to SIM) rig_disconnected_badge: "rozłączone", // RadioLock (TRX lock panel — buttons + dynamic status text) radio_free: "🔓 RADIO WOLNE", you_have_trx: "🎙 MASZ TRX", take_trx_btn: "▶ PRZEJMIJ TRX", release_trx_btn: "✕ ODDAJ TRX", request_trx_btn: "🙋 POPROŚ O TRX", request_trx_again_btn: "🙋 POPROŚ PONOWNIE", request_sending: "✋ Wysyłanie…", request_sent_short: "✋ Prośba wysłana…", force_release_btn: "⚡ WYMUŚ (admin)", toast_trx_granted: "✓ Masz teraz dostęp do TRX!", toast_request_sent: "✋ Prośba wysłana — operator zostanie powiadomiony", toast_request_error: "✗ Błąd wysyłania prośby", toast_release_error: "✗ Błąd zwalniania radia", toast_reject_error: "✗ Błąd odrzucania prośby", toast_request_rejected: "✗ Prośba o TRX odrzucona przez {by}", tx_mic_start: "🎤 Nadawanie TX — mikrofon", tx_mic_stop: "⏹ Zatrzymaj TX mikrofon", cw_edit_macro_title: "Edytuj makro", rotator_none: "✗ brak", rotator_moving: "↻ ruch", // ── USTAWIENIA tab ── settings_save_btn: "ZAPISZ", settings_loading: "Ładowanie…", status_checking: "sprawdzanie…", status_error_generic: "błąd", status_no_response: "brak odpowiedzi", settings_saved_short: "✓ zapisano", settings_save_error_plain: "błąd zapisu", // COM BRIDGE settings_combridge_hdr: "🔌 ZDALNY DOSTĘP CAT — CW Skimmer, HRD, Logger32", settings_combridge_desc: "Pobierz aplikację HAM RADIO CTRL i używaj zdalnego radia w programach CAT tak jakby było podłączone lokalnie. Masz 2 niezależne porty — np. CW Skimmer i HRD jednocześnie.", settings_combridge_warn: "Uwaga: zmiana częstotliwości/trybu przez CAT wymaga przejęcia radia (blokady). Bez blokady możesz tylko odczytywać stan radia (podgląd freq w programie). Numery COM na Twoim PC mogą się różnić — sprawdź w oknie aplikacji.", settings_combridge_download: "⬇️ POBIERZ MOST COM (Windows)", settings_combridge_winreq: "Windows 10/11 · ~25 MB · zawiera com0com", // CLOUDLOG / WAVELOG settings_cloudlog_status_title: "Kliknij aby przetestować połączenie", settings_cloudlog_status_default: "nie sprawdzono", settings_cl_url_lbl: "Adres serwera", settings_cl_apikey_qso_lbl: "API Key — logowanie QSO", settings_cl_apikey_qso_ph: "klucz API dla QSO", settings_cl_stationid_ph: "ID stacji", settings_cl_apikey_radio_lbl: "API Key — częstotliwość live", settings_cl_apikey_radio_ph: "klucz API dla live freq/mode", settings_cl_live_checkbox: "Wysyłaj freq+mode automatycznie (co 5s)", settings_test_connection_btn: "TEST POŁĄCZENIA", toast_cloudlog_saved: "✓ CloudLog: ustawienia zapisane", toast_cloudlog_save_error: "✗ Błąd zapisu ustawień", cloudlog_fill_url_key: "Uzupełnij adres i API Key QSO", cloudlog_connected_default: "połączono", toast_cloudlog_qso_sent: "✓ QSO wysłane do CloudLog", toast_cloudlog_no_connection: "✗ CloudLog: brak połączenia", // WYSZUKIWANIE ZNAKÓW (QRZ.com / HamQTH) settings_lookup_hdr: "🔍 WYSZUKIWANIE ZNAKÓW — QRZ.com / HamQTH", settings_lookup_desc: "Przy logowaniu QSO ikonka 🔍 przy polu ZNAK spróbuje pobrać imię/QTH/kraj drugiej stacji — najpierw z QRZ.com (jeśli skonfigurowane), potem HamQTH. Wymaga Twojego własnego konta w tych serwisach (QRZ: płatna subskrypcja \"XML Data\" · HamQTH: darmowe konto).", settings_qrz_login_lbl: "QRZ.com — login", settings_qrz_pass_lbl: "QRZ.com — hasło", settings_hamqth_login_lbl: "HamQTH — login", settings_hamqth_pass_lbl: "HamQTH — hasło", ph_qrz_login: "login QRZ.com", ph_qrz_pass: "hasło QRZ.com", ph_hamqth_login: "login HamQTH", ph_hamqth_pass: "hasło HamQTH", cb_fill_login_pass: "uzupełnij login/hasło", cb_connected: "✓ połączono", cb_no_response: "✗ brak odpowiedzi", toast_lookup_saved: "✓ Ustawienia lookupu zapisane", // WIRTUALNE RADIO (Hamlib NET rigctl) settings_virtual_radio_hdr: "WIRTUALNE RADIO — ZEWNĘTRZNE PROGRAMY", settings_virtual_radio_desc: "Emulacja Hamlib NET rigctl (rigctld) — 3 niezależne serwery TCP.
Każdy port to osobne \"wirtualne radio\" do którego podłączasz zewnętrzny program.
WSJT-X / Log4OM / N1MM / CW Skimmer / DXKeeper / HRD: Rig → Hamlib NET rigctl → host: <IP serwera> → port: 4532", settings_virtual_radio_save_btn: "ZAPISZ KONFIGURACJĘ", settings_virtual_radio_refresh_btn: "↻ ODŚWIEŻ STATUS", settings_virtual_radio_howto_hdr: "Jak podłączyć program zewnętrzny:", hamlib_load_error: "Błąd ładowania statusu", hamlib_no_ports: "Brak skonfigurowanych portów.", hamlib_active: "aktywny", hamlib_client_singular: "klient", hamlib_client_plural: "klientów", hamlib_disabled: "— wyłączony —", hamlib_toggle_port_title: "Włącz/wyłącz ten port", hamlib_saving: "zapisywanie…", // ── PROFIL tab ── profile_myprofile_hdr: "👤 MÓJ PROFIL", profile_name_lbl: "IMIĘ", profile_name_ph: "Jan", profile_callsign_lbl: "ZNAK WYWOŁAWCZY", profile_locator_lbl: "LOKATOR QTH (np. JO82)", profile_save_btn: "💾 ZAPISZ PROFIL", profile_logged_in_as: "Zalogowany jako: {user} ({role})", profile_changepwd_hdr: "🔑 ZMIANA HASŁA", profile_old_pwd_lbl: "STARE HASŁO", profile_new_pwd_lbl: "NOWE HASŁO (min. 8 znaków)", profile_new_pwd2_lbl: "POWTÓRZ NOWE HASŁO", profile_changepwd_btn: "🔑 ZMIEŃ HASŁO", profile_fill_fields: "Uzupełnij pola", profile_pwd_mismatch: "Hasła nie pasują", profile_pwd_min_len: "Min. 8 znaków", profile_pwd_changed_short: "✓ Hasło zmienione", profile_pwd_changed_toast: "✓ Hasło zmienione (inne urządzenia wylogowane)", profile_saved_short: "✓ Profil zapisany", profile_error_fallback: "Błąd", profile_audio_hdr: "🔊 AUDIO — MOJE URZĄDZENIA (przeglądarka)", profile_audio_desc: "Wybierz gdzie słyszysz odbiór (RX) i skąd nadajesz głos (TX mikrofon). Ustawienia zapisywane lokalnie w Twojej przeglądarce.", profile_audio_out_lbl: "GŁOŚNIK RX (odbiór radia → Twoje słuchawki)", profile_loading_option: "— ładowanie… —", profile_audio_test_out_title: "Odtwórz ton testowy", profile_audio_in_lbl: "MIKROFON TX (Twój głos → radio)", profile_audio_test_in_title: "Zmierz poziom mikrofonu (5s)", profile_mic_level_lbl: "POZIOM MIKROFONU:", profile_audio_footnote: "💡 Unikaj urządzeń \"CABLE\"/\"Virtual\" — to wirtualne kable dla programów (WSJT-X), nie Twój sprzęt.", profile_no_devices: "brak urządzeń", profile_device_fallback: "Urządzenie", profile_mic_fallback: "Mikrofon", profile_toast_speaker_saved: "✓ Głośnik RX zapisany", profile_toast_mic_saved: "✓ Mikrofon TX zapisany (aktywny przy następnym TX)", profile_toast_test_tone: "🔉 Ton testowy 1 kHz", profile_toast_speaker_test_err: "✕ Błąd testu głośnika: ", profile_mic_hint_speaking: "Mów do mikrofonu… ({s}s) — szczyt: {peak}%", profile_mic_hint_low: "⚠ Szczyt tylko {peak}% — mikrofon za cichy lub zły wybór urządzenia", profile_mic_hint_high: "⚠ Szczyt {peak}% — za głośno, może przesterować", profile_mic_hint_ok: "✓ Szczyt {peak}% — poziom OK", profile_mic_hint_error: "✕ Błąd: ", profile_toast_mic_open_err: "✕ Nie można otworzyć mikrofonu: ", profile_theme_hdr: "🎨 WYGLĄD (motyw kolorów)", profile_theme_desc: "Motyw zmienia kolory akcentów w interfejsie. Zapisywany lokalnie w przeglądarce.", profile_theme_lbl: "MOTYW", profile_theme_opt_default: "Zielony + bursztyn (klasyczny IC-7300)", profile_theme_opt_blue: "Niebieski (nowoczesny)", profile_theme_opt_mono: "Monochromatyczny (dyskretny)", profile_theme_opt_amber: "Bursztyn klasyczny (retro CRT)", profile_eq_hdr: "🎤 TX MIKROFON — KOREKCJA AUDIO (EQ)", profile_eq_desc: "Korekcja audio dopasowuje sygnał mikrofonu do pasma SSB (300-2700 Hz). Wybierz preset pasujący do Twojego głosu i sprawdź odsłuchem jak będzie brzmieć w eterze. Ustawienia zapisują się lokalnie w przeglądarce.", profile_eq_preset_lbl: "PRESET EQ", profile_eq_opt_default: "Standardowy (zbalansowany)", profile_eq_opt_dark: "Ciemny głos (męski/gruby) — więcej klarowności", profile_eq_opt_bright: "Jasny głos (kobiecy/dziecięcy) — mniej wysokich", profile_eq_opt_dx: "DX/Contest — maksymalny punch", profile_eq_opt_ragchew: "Ragchew — naturalny, mniej agresywny", profile_eq_opt_flat: "Flat (bez EQ, tylko limiter)", profile_eq_opt_custom: "Custom (ręcznie)", profile_eq_custom_hdr: "CUSTOM EQ — ręczna regulacja", profile_eq_bass_lbl: "BAS (200 Hz):", profile_eq_mud_lbl: "MUD (700 Hz):", profile_eq_clarity_lbl: "KLAROWNOŚĆ (1800 Hz):", profile_eq_punch_lbl: "PUNCH (2400 Hz):", profile_eq_air_lbl: "POWIETRZE (2700 Hz):", profile_eq_monitor_hdr: "🎧 ODSŁUCH (Monitor) — usłysz jak EQ zmienia Twój głos", profile_eq_monitor_start_btn: "▶ START ODSŁUCH", profile_eq_monitor_stop_btn: "⏹ STOP ODSŁUCH", profile_eq_monitor_desc: "Odsłuch przepuszcza mikrofon przez EQ i odtwarza w słuchawkach lokalnie (nie nadaje przez radio). Używaj słuchawek żeby uniknąć sprzężenia!", profile_eq_monitor_vol_lbl: "Głośność:", profile_toast_mic_unavailable: "Mikrofon niedostępny w tej przeglądarce", profile_toast_mic_no_access: "Brak dostępu do mikrofonu: ", profile_toast_audioctx_unavailable: "AudioContext niedostępny - kliknij cokolwiek w UI żeby aktywować audio, potem spróbuj ponownie", // ── Wspólne (modale, przyciski używane w wielu miejscach) ── common_confirm_title: "POTWIERDŹ", common_cancel_btn: "ANULUJ", common_delete_btn: "USUŃ", // ── LOG QSO tab ── log_new_qso_btn: "+ NOWE QSO", log_delete_selected_btn: "USUŃ ZAZNACZONE", log_delete_all_btn: "⚠ KASUJ CAŁY LOG", log_import_title: "Importuj QSO z pliku ADIF (.adi/.adif)", log_filter_user_lbl: "UŻYTKOWNIK", log_filter_all_users: "Wszyscy", log_filter_from_lbl: "OD", log_filter_to_lbl: "DO", log_filter_call_lbl: "ZNAK", log_filter_call_ph: "np. SP3GSK", log_pasmo_lbl: "PASMO", log_filter_all: "Wszystkie", log_mode_lbl: "TRYB", log_search_btn: "SZUKAJ", log_clear_btn: "WYCZYŚĆ", log_th_date: "DATA ↕", log_sort_date_title: "Sortuj po dacie", log_th_time_utc: "CZAS UTC", log_sort_call_title: "Sortuj po znaku", log_th_locator: "LOKATOR", log_th_comment: "KOMENTARZ", log_th_actions: "AKCJE", log_select_all_title: "Zaznacz wszystkie (tylko ta strona)", log_select_all_page_warning: "Zaznaczono tylko biezaca strone ({shown} z {total} QSO). Aby wyeksportowac CALY log, kliknij Eksport bez zaznaczania zadnego wiersza.", log_prev_page_btn: "◀ POPRZEDNIA", log_next_page_btn: "NASTĘPNA ▶", log_per_page: "50 QSO na stronę", log_field_call_lbl: "ZNAK *", log_lookup_title: "Pobierz dane stacji z QRZ.com/HamQTH", log_locator_ph: "KO02 lub KO02ab", log_field_country_lbl: "KRAJ (DXCC)", log_country_ph: "auto ze znaku", log_field_date_lbl: "DATA *", log_field_time_lbl: "CZAS UTC *", log_field_band_lbl: "PASMO *", log_field_mode_lbl: "TRYB *", log_sat_checkbox: "🛰 Łączność przez satelitę", log_sat_name_lbl: "SATELITA", log_sat_name_ph: "np. SO-50, QO-100, ISS", log_sat_mode_lbl: "TRYB SATELITY", log_sat_mode_ph: "np. U/V, V/U, L/X", log_downlink_band_lbl: "DOWNLINK PASMO", log_downlink_band_default: "— jak uplink —", log_save_qso_btn: "ZAPISZ QSO", log_error_prefix: "Błąd: ", log_no_qso: "Brak QSO", log_sat_tooltip_prefix: "Satelita: ", log_row_edit_btn: "EDYTUJ", log_page_info_full: "Strona {page} z {total} ({count} QSO)", log_enter_callsign: "Wpisz znak", log_lookup_not_found: "✗ Nie znaleziono albo brak konfiguracji QRZ/HamQTH (USTAWIENIA)", log_data_fetched_from: "✓ Dane pobrane z {source}", log_modal_new_title: "NOWE QSO", log_modal_edit_title: "EDYTUJ QSO", log_load_qso_error: "Błąd ładowania QSO: ", log_missing_callsign: "Brak znaku wywoławczego", log_qso_updated: "✓ QSO zaktualizowane", log_qso_saved: "✓ QSO zapisane", log_unknown_fallback: "nieznany", log_save_error_prefix: "✗ Błąd zapisu: ", log_confirm_delete_one: "Usunąć to QSO?", log_qso_deleted: "✓ QSO usunięte", log_exported_selected: "✓ Wyeksportowano {n} zaznaczonych QSO", log_export_error_prefix: "✗ Export błąd: ", log_enter_callsign_excl: "Wpisz znak!", log_cant_read_file: "✗ Nie można odczytać pliku: ", log_no_qso_in_adif: "✗ Brak QSO w pliku ADIF", log_importing: "Importuję {n} QSO — proszę czekać…", log_importing_progress: "Importuję… {done}/{total} QSO", log_import_done: "✓ Import ADIF: {n} QSO wczytano", log_import_duplicates_skipped: ", {n} duplikatów pominięto", log_import_errors: ", {n} błędnych", log_select_qso_to_delete: "Zaznacz QSO do usunięcia", log_confirm_delete_selected: "Usunąć {n} zaznaczonych QSO?", log_deleted_count: "✓ Usunięto {n} QSO", log_delete_all_specific_user: "użytkownika {name}", log_delete_all_everyone: "WSZYSTKICH użytkowników", log_confirm_delete_all_1: "Usunąć WSZYSTKIE QSO {who}?\nTej operacji nie można cofnąć!", log_confirm_delete_all_2: "Jesteś PEWIEN? Log {who} zostanie skasowany!", log_yes_delete_btn: "TAK, KASUJ", // ── KONFIGURACJA tab ── cfg_loading_dash: "— ładowanie —", cfg_default_dash: "— domyślna —", cfg_yes: "TAK", cfg_no: "NIE", cfg_conn_error: "Błąd połączenia", cfg_refresh_btn: "🔍 ODŚWIEŻ", cfg_none_paren: "(brak)", cfg_name_lbl: "NAZWA", cfg_model_hamlib_lbl: "MODEL HAMLIB", cfg_port_com_lbl: "PORT COM", cfg_baud_lbl: "BAUD", // Widget przenoszenia (transfer widget) — wspólne dla PASMA/TRYBY/FUNKCJE RADIA tw_visible_lbl: "Widoczne", tw_available_lbl: "Dostępne", tw_hint_remove: "Kliknij dwukrotnie aby usunąć", tw_hint_add: "Kliknij dwukrotnie aby dodać", tw_btn_all_left: "«« Wszystkie", tw_btn_add: "« Dodaj", tw_btn_remove: "Usuń »", tw_btn_all_right: "Wszystkie »»", tw_btn_up: "▲ Wyżej", tw_btn_down: "▼ Niżej", tw_title_add_all: "Dodaj wszystkie", tw_title_add_sel: "Dodaj zaznaczone", tw_title_remove_sel: "Usuń zaznaczone", tw_title_remove_all: "Usuń wszystkie", tw_title_move_up: "Przesuń wyżej", tw_title_move_down: "Przesuń niżej", // AUDIO RADIA — AUTO-DETEKCJA cfg_audio_autodetect_hdr: "🎙 AUDIO RADIA — AUTO-DETEKCJA", cfg_audio_autodetect_desc: "Serwer automatycznie wykrywa kartę audio radia (IC-7300, IC-705, FT-991 itp.) po nazwie.
Standardowo nie musisz nic konfigurować — po podłączeniu radia przez USB karta zostanie znaleziona.", cfg_rescan_btn: "🔍 SKANUJ PONOWNIE", cfg_expert_mode_btn: "⚙ TRYB EKSPERT (ręczny wybór karty)", cfg_detected_badge: "✓ WYKRYTO", cfg_not_detected_badge: "✕ NIE WYKRYTO", cfg_scanning_badge: "⏳ SKANOWANIE…", cfg_card_detected_msg: "✓ Karta radia wykryta (wzorzec: {pattern})", cfg_rx_arrow_lbl: "RX (radio → serwer):", cfg_tx_arrow_lbl: "TX (serwer → radio):", cfg_other_cards_active: "⚠ Aktualnie użwane inne karty (ręczna konfiguracja aktywna):", cfg_not_detected_msg: "⚠ Karta radia nie wykryta automatycznie", cfg_not_detected_desc: "Serwer nie znalazł karty pasującej do znanych wzorców (IC-7300, IC-705, FT-991, SCU-17).
Sprawdź czy radio jest podłączone przez USB i włączone, następnie kliknij SKANUJ PONOWNIE.
Jeśli używasz nietypowego interfejsu — przełącz na TRYB EKSPERT i wybierz kartę ręcznie.", cfg_avail_capture_lbl: "Dostępne karty capture (RX):", cfg_avail_playback_lbl: "Dostępne karty playback (TX):", cfg_toast_detected: "✓ Wykryto: {pattern}", cfg_toast_not_detected: "⚠ Karta radia nie wykryta", cfg_toast_scan_error_prefix: "✕ Błąd skanowania: ", cfg_toast_network_error_prefix: "✕ Błąd sieci: ", // KARTY DŹWIĘKOWE — RĘCZNIE (tryb ekspert) cfg_manual_cards_hdr: "⚙ KARTY DŹWIĘKOWE — RĘCZNIE (tryb ekspert)", cfg_manual_cards_warn: "⚠ Zwykle nie potrzebujesz tego dotykać. Używaj gdy auto-detekcja nie znajduje karty lub masz kilka radi.", cfg_card_rx_lbl: "Karta RX (radio → serwer)", cfg_card_tx_lbl: "Karta TX (serwer → radio)", cfg_bitrate_lbl: "Bitrate Opus", cfg_txvol_ft8_lbl: "TX Volume FT8/FT4 (mnożnik tonu enkodera → radio)", cfg_txvol_ssb_lbl: "TX Volume SSB (mnożnik mikrofonu → radio)", cfg_txvol_ssb_sublabel: "(osobny od FT8/FT4 — inna charakterystyka sygnału, łatwiej trafić w ALC)", cfg_gain_mode_ssb_lbl: "Tryb wzmocnienia mikrofonu SSB", cfg_gain_mode_ssb_sublabel: "(globalne, dotyczy wszystkich operatorów — wybierz jedno z dwóch)", cfg_gain_mode_manual_opt: "Stały mnożnik (suwak obok)", cfg_gain_mode_agc_opt: "AGC (auto-wyrównanie poziomu)", cfg_save_apply_btn: "💾 ZAPISZ I ZASTOSUJ (karty + TX Volume)", cfg_refresh_cards_btn: "↺ ODŚWIEŻ KARTY", cfg_loading_cards: "Ładowanie kart...", cfg_empty_response_kept: "{source} — 0 RX, 0 TX (odpowiedz pusta, zachowano poprzednia liste)", cfg_cards_summary: "{source} — {rx} RX, {tx} TX", cfg_saving: "Zapisywanie...", cfg_saved_applied: "✓ Zapisano i zastosowano", cfg_toast_audio_saved: "Karta audio + TX Volume zapisane — Rust RX zrestartuje się", // KONFIGURACJA RADIA cfg_rig_config_hdr: "KONFIGURACJA RADIA", cfg_add_rig_btn: "+ DODAJ RADIO", cfg_radio_n_lbl: "RADIO {id}", cfg_default_paren: " (domyślne)", cfg_civ_lbl: "CI-V (Icom)", cfg_card_rx_full_lbl: "KARTA DŹWIĘKOWA RX — odbiór z radia", cfg_choose_rx_card: "-- wybierz kartę RX --", cfg_refresh_title: "Odśwież", cfg_card_rx_desc: "Dla IC-7300/IC-7610/IC-9700/IC-705 itp. wybierz \"USB Audio CODEC\" (wbudowany kodek audio radia, podlaczony przez USB — audio idzie przez USB, NIE przez zewnetrzne gniazda ACC/AF Out/Phones). Karta zewnetrzna (np. interfejs CAT/audio) potrzebna tylko dla radia bez wbudowanego USB Audio Codec.", cfg_card_tx_full_lbl: "KARTA DŹWIĘKOWA TX — nadawanie do radia", cfg_choose_tx_card: "-- wybierz kartę TX --", cfg_card_tx_desc: "Zwykle ta sama \"USB Audio CODEC\" co RX — jeden kabel USB przenosi audio w obie strony (RX = wejscie karty/wyjscie radia, TX = wyjscie karty/wejscie mikrofonowe radia). Wybranie innej karty (np. wbudowanego mikrofonu PC) NIE trafi do radia.", cfg_cw_keyer_hdr: "CW KEYER — DTR/RTS", cfg_dtr_desc: "Kluczowanie liną DTR lub RTS portu COM — dla nowszych radiów (IC-7300+) HAMCTRL sam włącza BK-IN i przypina wybraną linię jako klucz w radiu przez CI-V (bez grzebania w menu radia). Pole PORT puste = ten sam port co CI-V (typowe dla jednego kabla USB). Jeśli masz zewnętrzny adapter USB→COM/tranzystor wpięty w gniazdo KEY (starsze radia bez CI-V USB Keying), wpisz tu jego numer portu.", cfg_dtr_port_lbl: "PORT COM (pusty = ten sam co CI-V)", cfg_dtr_port_ph: "(taki sam jak port CI-V)", cfg_line_lbl: "LINIA", cfg_save_dtr_btn: "ZAPISZ I AKTYWUJ CW KEYER", cfg_connect_btn: "POŁĄCZ", cfg_rig1_note: "Wymagany: Hamlib rigctld.exe → instalator pobrał do C:\\hamlib\\bin\\
Jeśli tryb SIM: sprawdź HAMLIB_PATH= w pliku .env i kliknij POŁĄCZ", cfg_single_rig_notice: "Obecna wersja obsługuje jedno radio. Obsługa wielu radiów planowana w przyszłości.", cfg_choose_card_generic: "-- wybierz kartę --", cfg_toast_rig_saved: "✓ Radio {id} ({name}) zapisane", cfg_connecting: "Łączenie...", cfg_toast_connecting_radio: "Łączę z radiem...", cfg_sim_mode_msg: "⚠ Tryb symulacji — {msg}", cfg_connected_fallback: "Połączono", cfg_sim_no_rigctld: "SIM — brak rigctld ({path})", cfg_not_found: "nie znaleziono", cfg_connected_status: "✓ Połączono", cfg_configuring: "Konfiguruję...", // KONFIGURACJA ROTATORÓW cfg_rotator_config_hdr: "KONFIGURACJA ROTATORÓW", cfg_add_btn: "+ DODAJ", cfg_station_locator_lbl: "LOKATOR STACJI", cfg_station_locator_ph: "np. JO72", cfg_station_locator_desc: "Pozycja anteny — od niej liczony jest kierunek na korespondenta (widoczny w logu QSO po wpisaniu znaku). Wspólny dla wszystkich operatorów, niezależnie skąd się łączą.", cfg_save_restart_rotators_btn: "ZAPISZ I ZRESTARTUJ ROTATORY", cfg_rot_ph_lbl: "PH", cfg_rot_enabled_lbl: "WŁĄCZONY", cfg_no_rotators: "Brak rotatorów", cfg_toast_rotators_saved: "✓ Rotatory zapisane", cfg_save_config_first: "Najpierw zapisz konfigurację", cfg_testing: "Testowanie...", cfg_rot_test_ok: "✓ OK — az={az}° ({driver})", cfg_bad_locator_format: "✗ Zły format (np. JO72)", cfg_saved_capital: "✓ Zapisano", // DEKODER CW — MODEL DEEPCW cfg_deepcw_hdr: "DEKODER CW — MODEL DEEPCW", cfg_deepcw_desc: "Sieciowy dekoder CW (~15 MB). Pobierany raz, zapisywany w katalogu danych — przetrwa aktualizacje programu. Dekoder pracuje tylko gdy okno dekodera jest otwarte.", cfg_deepcw_download_btn: "POBIERZ / AKTUALIZUJ MODEL", cfg_deepcw_check_btn: "SPRAWDŹ STAN", cfg_deepcw_not_downloaded: "⚠ Model nie pobrany", cfg_deepcw_loading: "⏳ Model na dysku ({mb} MB), ładowanie...", cfg_deepcw_ready: "✓ Gotowy ({mb} MB)", cfg_deepcw_starting_download: "Rozpoczynam pobieranie...", cfg_deepcw_downloaded: "✓ Model pobrany ({mb} MB)", cfg_deepcw_download_error: "✗ Błąd pobierania", // FUNKCJE RADIA — DOSTĘP DLA UŻYTKOWNIKÓW cfg_rig_features_hdr: "FUNKCJE RADIA — DOSTĘP DLA UŻYTKOWNIKÓW", cfg_rig_features_desc: "Zaznacz funkcje dostępne dla zwykłych użytkowników (operator/viewer). Funkcje wyszarzone nie są wspierane przez aktualnie podłączone radio.", cfg_save_rig_features_btn: "ZAPISZ FUNKCJE RADIA", cfg_static_features_title: "FUNKCJE STATYCZNE — dostęp dla userów", cfg_buttons_title: "PRZYCISKI FUNKCJI — widoczne na ekranie głównym", cfg_sliders_title: "SLIDERY — widoczne w lewej kolumnie", cfg_toast_config_saved: "✓ Konfiguracja zapisana", // Ustawienia zaawansowane CI-V (admin-only, approved 2026-10-15) adv_txdelay_hdr: "TX DELAY — sekwencja PTT (wzmacniacz / przekaźnik antenowy)", adv_agc_hdr: "AGC", adv_agc_mode: "Tryb AGC", adv_tx_delay_hf: "TX Delay HF", adv_tx_delay_50m: "TX Delay 50MHz", adv_tx_delay_70m: "TX Delay 70MHz", // COM BRIDGE (KONFIGURACJA) cfg_combridge_hdr: "🔌 COM BRIDGE — zdalny dostęp CAT (CW Skimmer, HRD, Logger32)", cfg_combridge_no_client: "— brak podłączonego klienta —", cfg_combridge_desc: "Aplikacja HAM RADIO CTRL (klient Windows) tworzy wirtualne porty COM na Twoim komputerze i łączy je ze zdalnym radiem przez internet. Programy jak CW Skimmer, Ham Radio Deluxe czy Logger32 widzą radio tak jakby było podłączone lokalnie.", cfg_combridge_port1_lbl: "PORT 1 — pierwsza aplikacja", cfg_combridge_port2_lbl: "PORT 2 — druga aplikacja", cfg_combridge_note: "Masz 2 niezależne porty — możesz uruchomić dwie aplikacje jednocześnie (np. CW Skimmer na COM11 i HRD na COM21). Nie kolidują ze sobą ani z serwerem.
Uwaga: numery COM na Twoim komputerze mogą się różnić — sprawdź w oknie aplikacji HAM RADIO CTRL (kolumna „COM dla apki\").", cfg_combridge_howto_summary: "📖 Jak skonfigurować (kliknij)", cfg_combridge_steps: "1. Pobierz i uruchom Most COM (przycisk poniżej)
2. Wpisz adres serwera, swój login i hasło → POŁĄCZ
3. Aplikacja utworzy porty COM (przy pierwszym razie zezwól na UAC)
4. W CW Skimmer / HRD wybierz Icom IC-7300, CI-V adres 94, 19200 baud
5. Wpisz numer COM z okna aplikacji (kolumna „COM dla apki\")
6. Connect — radio działa jakby było lokalne!", cfg_combridge_download_btn: "⬇️ POBIERZ MOST COM (Windows .exe)", cfg_combridge_winreq: "Wymaga Windows 10/11 · ~25 MB · zawiera sterownik com0com", cfg_combridge_stats_hdr: "📊 COM BRIDGE — podłączeni klienci (ADMIN)", cfg_combridge_stats_hint: "Kliknij ODŚWIEŻ żeby zobaczyć podłączonych klientów.", // PASMA / TRYBY cfg_bands_hdr: "PASMA — WIDOCZNE W UI", cfg_bands_desc: "Wybierz które pasma będą widoczne użytkownikom w siatce PASMO.
Zmiana obowiązuje natychmiast dla wszystkich połączonych klientów.", cfg_bands_widget_title: "PASMA", cfg_save_bands_btn: "ZAPISZ PASMA", cfg_modes_hdr: "TRYBY PRACY — WIDOCZNE W UI + FILTRY", cfg_modes_desc: "Wybierz tryby widoczne w siatce TRYB. USB-D/LSB-D = tryby cyfrowe (DATA) — można przypisać domyślny filtr.", cfg_modes_widget_title: "TRYBY", cfg_mode_filter_hdr: "DOMYŚLNY FILTR DLA TRYBU", cfg_mode_filter_desc: "Gdy użytkownik przełączy tryb, serwer automatycznie ustawi wybrany filtr.
Puste = brak automatycznej zmiany filtra.", cfg_none_filter: "-- brak --", cfg_save_modes_btn: "ZAPISZ TRYBY I FILTRY", cfg_toast_bands_saved: "✓ Pasma zapisane", cfg_toast_modes_saved: "✓ Tryby zapisane", // COM BRIDGE stats (com_bridge.js) cfg_admin_only: "(tylko admin)", cfg_no_client_connected: "Brak podłączonych klientów.", cfg_client_online_1: "● {n} klient online", cfg_client_online_n: "● {n} klientów online", cfg_clients_connected_1: "● {n} klient podłączony", cfg_clients_connected_n: "● {n} klientów podłączonych", // ── FT8/WSJT-X tab ── wj_decode_mode_title: "Tryb dekodowania", wj_band_placeholder: "-- pasmo --", wj_band_select_title: "Wybierz pasmo i przestrój radio na standardową częstotliwość", wj_cq_only_title: "Pokazuj tylko wywołania CQ w Band Activity", wj_hound_title: "Fox/Hound DXpedition mode — wpisz znak Foxa w polu DX", wj_hound_confirm_btn: "✓ POTWIERDŹ", wj_period_title: "Okres nadawania (parzyste/nieparzyste okno 15s)", wj_tx_wait_title: "Nadawanie FT8/FT4 musi wystartować dokładnie na granicy okna 15s/7.5s UTC — to normalne oczekiwanie, nie zawieszenie. Sam TX (PTT) zaczyna się po zejściu licznika do 0.", wj_ref_title: "Reference: przesunięcie punktu 'typowego szumu' na palecie. Wyżej = wodospad jaśniejszy dla szumu.", wj_zero_title: "Zero: odcina najsłabsze sygnały (podnosi 'dół' palety). Wyżej = więcej szumu tła znika w czerń.", wj_gain_title: "Gain: kontrast krzywej. Wyżej = mocniejsze rozróżnienie między szumem a sygnałem.", wj_reset_palette_title: "Przywróć domyślne (REF=15, ZERO=0, GAIN=1.0)", wj_tune_title: "Nadaj stały ton dla strojenia ATU. Kliknij ponownie aby przerwać.", wj_clear_btn: "WYCZYŚĆ", wj_hide_worked_title: "Ukryj stacje już zaliczone w logu QSO", wj_own_rx_title: "Własny dekoder FT8 (audio z radia, bez zewnętrznego WSJT-X)", wj_own_rx_start: "▶ START (własny RX)", wj_own_rx_stop: "⏹ STOP (własny RX)", wj_rx_eq_tx_title: "Ustaw znacznik RX na bieżącą częstotliwość TX", wj_tx_eq_rx_title: "Ustaw znacznik TX na bieżącą częstotliwość RX", wj_halt_tx_title: "Zatrzymaj TX po bieżącym oknie", wj_tx_free_btn: "🔓 TX WOLNY", wj_tx_locked_btn: "📌 TX ZABLOKOWANY", wj_tx_freeze_title: "Zamróź znacznik TX w wybranym miejscu — kliknij na waterfallu aby ustawić pozycję TX, potem zamróź. RX nadal automatycznie podąża za wywołaniami adresowanymi do Ciebie.", wj_fake_split_off: "⭕ Fake Split: OFF", wj_fake_split_on: "🎯 FAKE SPLIT: ON", wj_fake_split_title: "Fake Split: przesuwa VFO na czas nadawania tak, żeby audio było blisko środka filtra SSB (~1500Hz) — pełna moc, brak splatterów na krawędziach filtra. Steruje radiem podczas TX, VFO wraca po nadaniu.", wj_band_activity_hdr: "BAND ACTIVITY", wj_country_col_title: "Kolumna Kraj: flaga (klik: przełącz na nazwę)", wj_clear_band_activity_title: "Wyczyść Band Activity", wj_th_message: "Wiadomość", wj_th_grid: "Siatka", wj_th_country: "Kraj", wj_no_decodes: "Brak dekodowań — uruchom WSJT-X i kliknij ▶ START", wj_no_cq: "Brak wywołań CQ", wj_rxfreq_hdr: "RX FREQUENCY", wj_clear_rxfreq_title: "Wyczyść Rx Frequency", wj_no_rxfreq_signal: "Brak sygnału na częstotliwości RX", wj_decode_count_default: "0 dekodowań", wj_decode_count_n: "{n} dekodowań", wj_cycle_utc_lbl: "UTC CYKL:", wj_cycle_phase_tx: "NADAWANIE (TX)", wj_cycle_phase_rx: "ODBIÓR (RX)", wj_cycle_phase_decoding: "DEKODOWANIE SYGNAŁÓW...", wj_cycle_phase_txprep: "PRZYGOTOWANIE NADAWANIA (TX PREP)", wj_cycle_marker_dec: "DEKODOWANIE", wj_autoqso_hdr: "AUTOMATYKA QSO", wj_auto_badge: "● AUTO", wj_auto_badge_title: "Automatyka jest zawsze aktywna. Klik na makro (F1-F6) w panelu TX wymusza ręczne nadanie tej wiadomości.", wj_autoqso_ready: "Automatyka gotowa — kliknij wiersz CQ aby rozpocząć automatyczne QSO • klik makro = ręczne wymuszenie", wj_skip_btn: "⏭ Pomiń", wj_skip_title: "Porzuć bieżącą stację (bez czekania na limit prób)", wj_tx_panel_hdr: "NADAWANIE TX", wj_timer_lbl: "TIMER:", wj_confirm_btn: "POTWIERDŹ ▶", wj_dx_call_ph: "-- KLIKNIJ WIERSZ LUB WPISZ --", wj_quicklog_hdr: "SZYBKI ZAPIS QSO", wj_antenna_hdr: "ANTENA I ROTOR", wj_minilog_hdr: "OSTATNIO ZALOGOWANE:", wj_antenna_lbl: "ANTENA", wj_rotor_sp_title: "Ustaw rotor na azymut SP (krótka droga)", wj_rotor_lp_title: "Ustaw rotor na azymut LP (długa droga)", wj_rotor_manual_title: "Ręcznie: wpisz azymut w stopniach lub lokator", wj_call_dx_ph: "CALLSIGN DX *", wj_grid_dx_ph: "Grid DX", wj_comment_ph: "Komentarz (opcjonalnie)", wj_addlog_btn: "+ LOG QSO", wj_adif_btn_title: "Eksport ADIF", wj_alltxt_btn_title: "Pobierz ALL.TXT - wszystko odebrane i nadane", wj_no_logged_qso: "Brak zalogowanych QSO", wj_color_legend_hdr: "LEGENDA KOLORÓW", wj_legend_cq: "CQ", wj_legend_dx: "Odpowiedź do DX", wj_legend_mycall: "Mój znak", wj_legend_73: "73 / RR73", wj_legend_worked: "Już w logu", wj_legend_owntx: "Moje TX", wj_rotor_modal_hdr: "ROTOR — RĘCZNA POZYCJA", wj_rotor_azimuth_lbl: "AZYMUT (°) LUB LOKATOR", wj_rotor_input_ph: "np. 272 lub KO02", wj_set_btn: "USTAW", // Dynamiczne (wsjtx.js / wsjtx_scope.js) wj_toast_monitor_active: "✓ WSJT-X monitor aktywny (UDP :{port})", wj_offline_autostart_failed: "○ OFFLINE (autostart nieudany?)", wj_decode_count_session: "{n} odebranych dekodowań (sesja)", wj_toast_monitor_on_port: "✓ Monitor aktywny na porcie {port}", wj_toast_monitor_stopped: "Monitor zatrzymany", wj_toast_tx_stopped: "⛔ TX zatrzymany", wj_toast_tx_paused: "TX wstrzymany", wj_toast_decode_mode: "Tryb dekodowania: {mode}", wj_toast_radio_busy: "⛔ Radio zajęte przez {holder} — przejmij TRX", wj_toast_tx_period_prefix: "Okres nadawania: ", wj_toast_own_rx_on: "✓ Własny dekoder FT8 RX aktywny", wj_toast_own_rx_off: "Własny dekoder FT8 RX zatrzymany", wj_toast_station_not_visible: "{call} nie widać aktualnie na paśmie", wj_toast_no_mycall: "Ustaw najpierw swój znak (MYCALL) w ustawieniach WSJT-X", wj_toast_no_dxcall: "Brak DX Call — wybierz stację z listy dekodowań", wj_toast_waiting_window: "Czekam na okno 15s ({s}s) — {text}", wj_toast_transmitting: "Nadaje: {text}", wj_toast_tx_error: "Błąd TX FT8: {error}", wj_tx_wait_countdown: "⏳ TX za {s}s — {text}", wj_tx_transmitting_status: "📡 NADAJE — {text}", wj_toast_enter_callsign_dx: "✗ Wpisz callsign DX", wj_toast_qso_logged: "✓ QSO zalogowane: {call}", wj_log_error_fallback: "Błąd logowania", wj_toast_log_error_generic: "✗ Błąd logowania", wj_toast_export_success: "✓ Wyeksportowano log (ADIF)", wj_toast_export_error: "✗ Błąd eksportu", wj_toast_qso_logged_full: "✓ QSO zalogowane: {call} {band} {mode}", wj_toast_timer_reset: "✓ Timer zresetowany", wj_toast_timer_warning: "⚠️ FT8 Timer: 1 minuta do zatrzymania TX!", wj_toast_timer_expired: "⛔ FT8 Timer: TX zatrzymany — potwierdź obecność!", wj_toast_no_foxcall: "Wpisz znak Foxa w polu DX!", wj_toast_hound_searching: "🦊 Hound mode: szukam {call} — TX {freq} Hz", wj_toast_hound_off: "Hound mode wyłączony", wj_toast_hound_confirm: "⚠️ Hound: potwierdź obecność (2 min) — TX wstrzymany", wj_toast_qso_complete_fox: "✓ QSO z {call} zaliczone! RR73 odebrane.", wj_toast_qso_complete: "✓ QSO z {call} zakończone — sprawdź i zatwierdź w \"MÓJ LOG QSO\"", wj_toast_no_rotor: "⚠ Brak rotora", wj_toast_rotor_moved: "↻ Rotor → {az}°{labelPart}", wj_toast_bad_format: "⚠ Nieprawidłowy format (stopnie lub lokator)", wj_toast_tx_frozen: "🧊 TX zamrożone — odmroź żeby ręcznie zmienić częstotliwość", wj_status_no_decoding: "Automatyka wyłączona — kliknij wiersz CQ aby odpowiedzieć ręcznie", wj_status_waiting_call1st: "Auto-Sequencing aktywne — czekam na wywołanie", wj_status_qso_done: "✓ QSO z {partner} zakończone", wj_status_calling: "wywołuje", wj_status_report_sent: "wysłano raport, czekam na potwierdzenie", wj_status_rrr_sent: "wysłano RRR, czekam na 73", wj_status_qso_with: "QSO z {partner}: {state}", wj_decoding_status: "⚡ DEKODUJE", wj_hound_step_calling: "Wołanie Foxa", wj_hound_step_report: "Wysyłam R+rpt", wj_hound_step_waiting: "Czekam na RR73", wj_hound_attempt: " | próba {n}", // ── DX CLUSTER tab ── dx_hdr: "📡 DX CLUSTER", dx_disconnected: "○ ROZŁĄCZONY", dx_connect_btn: "▶ POŁĄCZ", dx_disconnect_btn: "✕ ROZŁĄCZ", dx_config_btn: "⚙ KONFIG", dx_config_title: "Pokaż/ukryj konfigurację", dx_send_spot_btn: "📤 WYŚLIJ SPOT", dx_send_spot_title: "Wyślij spota na klaster (wypełni aktualną częstotliwość i tryb)", dx_band_lbl: "PASMO:", dx_mode_lbl: "TRYB:", dx_all_bands: "wszystkie", dx_search_ph: "szukaj (call/spotter)", dx_spots_lbl: "Spoty:", dx_clear_list_title: "Wyczyść listę", dx_config_desc: "Adres serwera Telnet (np. dxcluster.sq3mzm.ovh:7300). Login = znak. Hasło opcjonalne.", dx_address_lbl: "ADRES", dx_port_lbl: "PORT", dx_login_lbl: "LOGIN", dx_password_lbl: "HASŁO", dx_save_btn: "💾 ZAPISZ", dx_optional_ph: "opcjonalne", dx_autoconnect_lbl: "Auto-łącz przy logowaniu", dx_th_call: "CALL (DX)", dx_th_mode: "TRYB", dx_empty_initial: "Brak spotów. Kliknij ⚙ KONFIG żeby wpisać adres serwera, potem ▶ POŁĄCZ.", dx_cmd_lbl: "CMD:", dx_spot_modal_hdr: "📤 WYŚLIJ SPOT NA KLASTER", dx_spot_modal_desc: "Spot zostanie wysłany pod Twoim znakiem (loginem klastra) i będzie widoczny dla wszystkich operatorów na świecie.", dx_spot_call_lbl: "ZNAK STACJI *", dx_spot_freq_lbl: "CZĘSTOTLIWOŚĆ (kHz) *", dx_spot_comment_lbl: "KOMENTARZ (max 30 znaków)", dx_spot_comment_ph: "np. FT8 CQ DX / 599 TNX QSO", dx_preview_lbl: "PODGLĄD KOMENDY:", dx_send_spot_modal_btn: "📤 WYŚLIJ SPOT", // Dynamiczne (dxcluster.js) dx_saving: "⏳ Zapisywanie...", dx_saved: "✓ Zapisano", dx_cmd_send_fail: "Nie można wysłać komendy (brak połączenia?)", dx_empty_filtered: "Brak spotów pasujących do filtrów", dx_empty_connect: "Brak spotów. Połącz się z DX Cluster żeby zobaczyć na żywo.", dx_spot_count_title: "Widoczne {shown} z {total} — użyj filtrów żeby zawęzić", dx_invalid_freq: "✗ Nieprawidłowa częstotliwość spota", dx_trx_busy: "TRX jest zajęty przez {holder}", dx_take_trx_first: "Najpierw przejmij TRX (zakładka Radio → panel OPERATORZY → PRZEJMIJ TRX)", dx_cant_qsy: "⛔ Nie mogę wykonać QSY: {reason}", dx_connected: "● POŁĄCZONY", dx_connecting: "⏳ ŁĄCZENIE...", dx_error_prefix: "✕ BŁĄD: ", dx_unknown: "Nieznany", dx_spot_call_invalid: "Podaj poprawny znak stacji (litery, cyfry, /)", dx_spot_freq_invalid: "Podaj częstotliwość w kHz (np. 14074.0)", dx_sending: "WYSYŁANIE...", dx_spot_sent: "✓ Spot wysłany: {call} na {freq} kHz", dx_spot_send_fail: "Nie udało się wysłać spota", dx_spot_conn_error: "Błąd połączenia: {msg}", dx_password_saved_ph: "•••• (zapisane)", // ── INTERNET tab ── in_status_hdr: "STATUS DOSTĘPU ZDALNEGO", in_public_addr_lbl: "ADRES DO UDOSTĘPNIENIA", in_copy_btn: "📋 KOPIUJ", in_copy_title: "Kopiuj do schowka", in_cert_lbl: "🔒 CERT:", in_tunnel_lbl: "🌐 TUNEL:", in_lan_lbl: "🏠 LAN:", in_mode_hdr: "TRYB DOSTĘPU", in_autosave_note: "zmiany zapisują się automatycznie", in_mode_home_title: "TYLKO W DOMU", in_mode_home_desc: "LAN. Nie wymaga konfiguracji.", in_mode_quick_title: "SZYBKI TUNEL", in_mode_quick_desc: "Cloudflare bez rejestracji.\n Adres zmienny.", in_mode_duckdns_title: "DUCKDNS + HTTPS", in_mode_duckdns_desc: "Darmowa subdomena.\n Stały adres.", in_mode_named_title: "CLOUDFLARE + DOMENA", in_mode_named_desc: "Nazwana subdomena własnej domeny.", in_mode_staticip_title: "STAŁE IP + HTTPS", in_mode_staticip_desc: "Publiczne stałe IP + self-signed cert.", in_mode_customcert_title: "WŁASNA DOMENA + CERT", in_mode_customcert_desc: "Masz już domenę i wykupiony certyfikat.", in_named_instructions: "📋 dash.cloudflare.com → Zero Trust → Tunnels → Create tunnel → skopiuj token", in_your_domain_lbl: "TWOJA DOMENA", in_cf_token_lbl: "TOKEN CLOUDFLARE", in_duckdns_instructions: "📋 duckdns.org → zaloguj się → utwórz subdomenę → skopiuj token", in_subdomain_lbl: "SUBDOMENA", in_token_lbl: "TOKEN", in_your_address_lbl: "Twój adres:", in_gen_cert_btn: "🔒 GENERUJ CERTYFIKAT", in_letsencrypt_note: "Cert Let's Encrypt (90 dni, auto-renew). Wymaga otwartego portu 80 lub 443. Wymaga zainstalowanego win-acme (bez instalatora — rozpakuj do C:\\win-acme\\).", in_your_static_ip_lbl: "TWOJE STAŁE IP", in_port_lbl: "PORT", in_selfsigned_note: "Cert SSL self-signed generowany automatycznie. Przeglądarka ostrzeże raz — kliknij \"Zaawansowane → Przejdź dalej\".", in_customcert_instructions: "📋 Ten tryb NICZEGO nie generuje — musisz już mieć domenę (DNS wskazujący na Twoje stałe IP/router) i gotowy certyfikat SSL (plik cert.pem + key.pem, np. z zewnętrznego CA).", in_certpath_lbl: "ŚCIEŻKA DO cert.pem (opcjonalnie)", in_leave_empty_ph: "zostaw puste = domyślny katalog danych serwera", in_keypath_lbl: "ŚCIEŻKA DO key.pem (opcjonalnie)", in_customcert_note: "Zostawiając ścieżki puste, wystarczy podmienić pliki cert.pem i key.pem w katalogu danych serwera (obok EXE albo %APPDATA%\\HAMCTRL) — reszta zadzieje się automatycznie.", in_cf_checking: "cloudflared: sprawdzam...", in_cf_svc_active: "⚠ Usługa Windows aktywna", in_check_btn: "🔍 SPRAWDŹ", in_start_btn: "▶ URUCHOM", in_stop_btn: "■ ZATRZYMAJ", // Dynamiczne (tunnel.js) in_toast_tunnel_active: "✓ Tunel: {url}", in_no_cert: "brak certyfikatu", in_days_warn: "⚠ {n} dni", in_days_ok: "✓ {n} dni", in_active: "● AKTYWNY", in_connecting_dots: "◌ ŁĄCZENIE…", in_error_badge: "✗ BŁĄD", in_stopped_tunnel: "○ STOP", in_stopped_badge: "○ WYŁĄCZONY", in_waiting_url: "Oczekiwanie na adres URL…", in_toast_addr_copied: "✓ Adres skopiowany", in_toast_copied_plain: "✓ Skopiowano", in_toast_copied_prefix: "✓ Skopiowano: ", in_toast_copy_failed: "✗ Nie mogę skopiować: ", in_toast_no_addr: "⚠ Brak adresu do skopiowania - najpierw uruchom tunel", in_toast_paste_token: "⚠ Wklej token Cloudflare", in_toast_duckdns_fields: "⚠ Wpisz subdomenę i token DuckDNS", in_toast_enter_ip: "⚠ Wpisz adres IP", in_toast_enter_domain: "⚠ Wpisz domenę", in_connecting_btn: "Łączenie…", in_toast_tunnel_stopped: "■ Tunel zatrzymany", in_toast_config_saved: "✓ Konfiguracja tunelu zapisana", in_toast_save_error: "✗ Błąd zapisu", in_cf_available: "✓ cloudflared {ver}", in_cf_missing: "⚠ cloudflared brak — zostanie pobrany automatycznie", in_stale_procs: "⚠ Stare procesy: {n}", in_cf_check_error: "Błąd sprawdzania cloudflared", in_toast_generating_cert: "Generuję certyfikat — poczekaj ~2 min...", in_confirm_cleanup: "Wyczyścić stare procesy i usługę cloudflared?", in_toast_cleaned: "✓ Wyczyszczono: ", in_toast_cleanup_error: "✗ Błąd: ", // ── ADMIN tab ── adm_status_hdr: "📊 STATUS SERWERA", adm_refresh_btn: "↻ ODŚWIEŻ", adm_loading_status: "Ładowanie statusu...", adm_test_civ_btn: "🔌 TEST CI-V", adm_backup_hdr: "💾 BACKUP / RESTORE KONFIGURACJI", adm_backup_desc: "Backup zawiera config.json (radia, pasma, przekaźniki, SMTP) oraz users.json (konta, uprawnienia, hasła).
Trzymaj plik w bezpiecznym miejscu — zawiera hasła. Po restore zrestartuj serwer.", adm_download_backup_btn: "⬇ POBIERZ BACKUP", adm_upload_backup_btn: "⬆ WCZYTAJ BACKUP", adm_users_hdr: "UŻYTKOWNICY SYSTEMU", adm_new_user_btn: "+ NOWY UŻYTKOWNIK", adm_th_login_name: "LOGIN / IMIĘ", adm_th_role: "ROLA", adm_th_callsign: "ZNAK", adm_th_perms: "UPRAWNIENIA", adm_th_status: "STATUS", adm_ft8timer_hdr: "TIMER BEZPIECZEŃSTWA FT8/HOUND — GLOBALNY", adm_ft8timer_desc: "Maksymalny czas ciągłego nadawania FT8/FT4/Hound bez potwierdzenia operatora.
Po upływie czasu — TX zostaje automatycznie zatrzymany. Dotyczy wszystkich użytkowników. Zakres: 1–60 min.", adm_timer_duration_lbl: "CZAS TIMERA:", adm_minutes_word: "minut", adm_myprofile_hdr: "MÓJ PROFIL", adm_save_profile_btn: "ZAPISZ PROFIL", adm_change_pwd_hdr: "ZMIANA WŁASNEGO HASŁA", adm_change_pwd_btn: "ZMIEŃ HASŁO", adm_lock_settings_hdr: "BLOKADA RADIA — USTAWIENIA", adm_lock_timeout_lbl: "TIMEOUT BEZCZYNNOŚCI (minuty)", adm_lock_timeout_desc: "Jeśli aktywny operator nie wykona żadnej akcji przez ten czas, radio zostaje zwolnione automatycznie.", adm_save_timeout_btn: "ZAPISZ TIMEOUT", adm_force_release_btn: "⚡ WYMUŚ ZWOLNIENIE RADIA", adm_timeout_saved: "✓ Timeout zapisany", adm_relay_hdr: "🔌 PRZEKAŹNIKI ARDUINO (SP5IOU SDR220)", adm_relay_desc: "Sterowanie przekaźnikami przez Arduino z firmware SP5IOU (protokół SDR220) — firmware dołączony w arduino/relay_sp5iou/ w repozytorium HAMCTRL.
Każdy z 8 przekaźników można ustawić jako \"manual\" (toggle on/off) lub \"momentary\" (impuls z ustawianym czasem 0.1-10s).
Uprawnienia dostępu (kto ma widoczne przyciski) przydzielasz w sekcji Użytkownicy checkboxami \"Przekaźnik 0..7\".", adm_relay_enabled_lbl: "Włączony (podłączony do Arduino)", adm_serial_port_lbl: "PORT SZEREGOWY", adm_choose_port: "(wybierz port)", adm_baudrate_lbl: "BAUDRATE", adm_relay_config_hdr: "KONFIGURACJA 8 PRZEKAŹNIKÓW", adm_save_relay_config_btn: "💾 ZAPISZ KONFIGURACJĘ", adm_smtp_hdr: "EMAIL — KONFIGURACJA SMTP", adm_smtp_desc: "Wymagane do działania funkcji \"Zapomniałem hasła\". Obsługiwany STARTTLS (port 587) i SSL (port 465).
Przykład dla Gmail: host=smtp.gmail.com, port=587, user=twoj@gmail.com, hasło = App Password z Google.", adm_smtp_host_lbl: "SERWER SMTP (host)", adm_smtp_user_lbl: "UŻYTKOWNIK (login SMTP)", adm_smtp_pass_lbl: "HASŁO SMTP", adm_smtp_from_lbl: "ADRES NADAWCY (From)", adm_smtp_encryption_lbl: "SZYFROWANIE", adm_smtp_starttls_opt: "STARTTLS (port 587, zalecane)", adm_smtp_ssl_opt: "SSL/TLS (port 465)", adm_smtp_test_lbl: "EMAIL TESTOWY — WYŚLIJ NA", adm_send_test_btn: "WYŚLIJ TEST", adm_save_smtp_btn: "ZAPISZ SMTP", // user-modal adm_new_user_title: "NOWY UŻYTKOWNIK", adm_login_lbl: "LOGIN *", adm_role_lbl: "ROLA *", adm_pass_lbl: "HASŁO *", adm_min_chars_ph: "min. 8 znaków", adm_locator_grid_lbl: "LOKATOR (grid)", adm_perms_hdr: "UPRAWNIENIA GRANULARNE", adm_group_radio: "RADIO", adm_group_hw: "SPRZĘT", adm_group_system: "SYSTEM", adm_group_relay: "PRZEKAŹNIKI", // Uprawnienia (dzielone miedzy statycznym HTML modala a PERM_DEFS w admin.js) perm_ptt: "PTT — nadawanie", perm_rfpower: "RF Power — moc TX", perm_rfgain: "RF/AF Gain, SQL", perm_mode: "Zmiana trybu (USB/CW…)", perm_band: "Zmiana pasma", perm_freq: "Strojenie częstotliwości", perm_split: "Split VFO A/B", perm_cw: "CW Keyer — wysyłanie", perm_rotator: "Rotator — sterowanie", perm_log: "Log QSO — zapis", perm_settings: "Ustawienia serwera", perm_admin: "Panel administratora", perm_relay_n: "Przekaźnik {n}", // Dynamiczne (admin.js — uzytkownicy, ft8 timer) adm_no_perms: "brak", adm_edit_title: "Edytuj", adm_reset_pwd_title: "Reset hasła", adm_deactivate_title: "Dezaktywuj", adm_activate_title: "Aktywuj", adm_delete_title: "Usuń", adm_edit_user_title: "Edytuj: {username}", adm_no_access: "✗ Brak dostępu", adm_user_not_found: "✗ Nie znaleziono użytkownika", adm_enter_login: "Podaj login", adm_enter_password: "Podaj hasło", adm_network_error: "✗ Błąd sieci: ", adm_user_updated: "✓ Użytkownik zaktualizowany", adm_user_added: "✓ Użytkownik dodany", adm_account_deactivated: "Konto dezaktywowane", adm_account_activated: "Konto aktywowane", adm_confirm_delete_user: "Usunąć użytkownika {username}?", adm_user_deleted: "✓ Usunięto {username}", adm_new_pwd_for: "NOWE HASŁO DLA {username} (min. 8 znaków)", adm_pwd_reset_done: "✓ Hasło {username} zresetowane", adm_timer_active: "✓ Aktywny — {min} min dla wszystkich użytkowników", adm_timer_saved_toast: "✓ Timer FT8: {min} min dla wszystkich", adm_timer_save_error: "✗ Błąd zapisu timera", // Dynamiczne (admin_status.js) adm_stat_version: "Wersja", adm_stat_uptime: "Uptime", adm_stat_online: "Online", adm_stat_users_count: "{n} użytkowników", adm_stat_radio: "Radio", adm_stat_sim: "SYMULACJA", adm_stat_disconnected: "ROZŁĄCZONE", adm_stat_civ_connected: "CI-V połączone", adm_stat_model_port: "Model / Port", adm_stat_freq: "Częstotliwość", adm_stat_audio: "Audio", adm_stat_dxcluster: "DX Cluster", adm_stat_available: "dostępny", adm_stat_unavailable: "niedostępny", adm_stat_relays: "Przekaźniki", adm_stat_relays_connected: "podłączone", adm_stat_relays_configured: "skonfigurowane (offline)", adm_stat_no_module: "— brak modułu", adm_stat_perf_link: "📊 Otwórz szczegółową diagnostykę wydajności (/perf) →", adm_testing: "⏳ Testowanie...", adm_generating: "⏳ Generowanie...", adm_downloaded: "✓ Pobrano", adm_confirm_restore: "Przywrócić konfigurację z pliku \"{name}\"?\n\nTo NADPISZE obecną konfigurację i wszystkich użytkowników. Po restore zrestartuj serwer.", adm_restore_btn: "PRZYWRÓĆ", adm_loading_ellipsis: "⏳ Wczytywanie...", adm_restored: "✓ Przywrócono: {list}. Zrestartuj serwer!", adm_toast_restored: "✓ Backup przywrócony — zrestartuj serwer", // Dynamiczne (relay_admin.js) adm_port_unavailable: " (niedostepny)", adm_relay_connected_badge: "● PODŁĄCZONY", adm_relay_error_badge: "● BŁĄD POŁĄCZENIA", adm_relay_manual_opt: "Manual (toggle)", adm_relay_momentary_opt: "Momentary (impuls)", adm_relay_name_ph: "Nazwa...", adm_visible_lbl: "Widoczny", // Dynamiczne (AdminSmtp — inline w index.html) adm_smtp_pass_saved_ph: "●●●●●● (ustawione)", adm_smtp_saved: "✓ Konfiguracja SMTP zapisana", adm_smtp_enter_test_email: "✗ Wpisz adres email w polu \"EMAIL TESTOWY\"", adm_smtp_sending_test: "⏳ Wysyłam email testowy…", adm_smtp_sent: "✓ Wysłano", // Dynamiczne (AdminUpdate — inline w index.html) adm_update_hdr: "AKTUALIZACJE", adm_update_desc: "Sprawdza GitHub Releases raz przy starcie serwera (albo na żądanie poniżej). Niczego nie pobiera ani nie instaluje — tylko informuje.", adm_update_current_lbl: "TWOJA WERSJA", adm_update_status_lbl: "STATUS", adm_update_check_lbl: "Automatyczne sprawdzanie przy starcie", adm_update_email_lbl: "Powiadom adminów mailem (wymaga skonfigurowanego SMTP powyżej)", adm_update_check_now_btn: "SPRAWDŹ TERAZ", adm_update_no_repo: "Repozytorium nieskonfigurowane", adm_update_available: "Dostępna nowa wersja: {v}", adm_update_open_release: "otwórz release", adm_update_current_ok: "✓ Masz najnowszą wersję", adm_update_checking: "⏳ Sprawdzam…", adm_update_checked: "✓ Sprawdzono", // ── MOBILE (mobile.html / mobile.js / mobile_audio.js) ── m_lang_toggle_title: "Zmień język / Change language", m_audio_btn_title: "Dźwięk (odsłuch)", m_settings_title: "Ustawienia telefonu", m_logout_btn: "Wyloguj", m_lock_checking: "sprawdzam blokadę radia...", m_freq_hint: "przytrzymaj częstotliwość, żeby wpisać ręcznie", m_freq_strip_hint: "◄ przeciągnij, żeby stroić ►", m_functions_hdr: "Funkcje", m_sliders_hdr: "Suwaki radia", m_autoqso_ready: "Automatyka gotowa — dotknij wywołanie CQ na liście, żeby rozpocząć QSO", m_confirm_presence: "Potwierdzam obecność", m_skip_qso_btn: "Pomiń QSO", m_dx_card_title: "DX (znak korespondenta)", m_dx_call_ph: "-- dotknij wywołanie na liście lub wpisz --", m_tx_macros_hdr: "Makra TX", m_received_calls_hdr: "Odebrane wywołania", m_tx_settings_hdr: "Ustawienia TX", m_filters_hdr: "Filtry / widok", m_hide_worked_lbl: "Ukryj pracowane (HIDE WORKED)", m_reset_palette_btn: "↺ Reset palety", m_cw_hint: "dotknij = wyślij • przytrzymaj = edytuj", m_cw_custom_ph: "własny tekst CW...", m_send_btn: "Wyślij", m_quicklog_hdr: "Szybki log QSO", m_rotor_none_badge: "● brak", m_desktop_link: "Pełna wersja (desktop)", m_audio_toggle_lbl: "🔊 Dźwięk (odsłuch)", m_mic_lbl: "MIKROFON (nadawanie)", m_mic_default_opt: "-- domyślny --", m_close_btn: "Zamknij", m_freq_edit_ph: "np. 14.074 (MHz)", m_freq_vfo_title: "Częstotliwość VFO {vfo}", m_set_btn: "Ustaw", m_cw_label_ph: "etykieta (np. CQ)", m_cw_macro_text_ph: "treść makra (np. CQ CQ DE {MYCALL} K)", m_save_btn: "Zapisz", m_conn_connecting: "łączenie...", m_conn_connected: "połączono", m_radio_free_short: "Radio wolne", m_take_radio_btn: "Zajmij radio", m_radio_busy_you: "Radio zajęte przez Ciebie", m_release_btn: "Zwolnij", m_radio_busy_by: "Radio zajęte: {name}", m_request_btn: "Poproś", m_request_sent_short: "Prośba wysłana", m_network_error: "Błąd sieci", m_need_radio_mode: "Zajmij radio, żeby zmieniać tryb", m_need_radio_band: "Zajmij radio, żeby zmieniać pasmo", m_need_radio_tune: "Zajmij radio, żeby stroić", m_need_radio_tx: "Zajmij radio, żeby nadawać", m_need_radio_cw: "Zajmij radio, żeby nadawać CW", m_need_radio_freq: "Zajmij radio, żeby zmienić częstotliwość", m_invalid_freq: "Nieprawidłowa częstotliwość", m_log_empty: "brak wpisów", m_load_error: "błąd ładowania", m_mic_list_error: "Nie udało się odczytać listy mikrofonów", m_audio_no_webcodecs: "Ten telefon/przeglądarka nie obsługuje dekodera audio (WebCodecs)", m_no_webrtc: "WebRTC niedostępne w tej przeglądarce", m_mic_error_prefix: "Błąd mikrofonu: ", m_clear_btn: "wyczyść", m_log_hdr: "Log QSO", m_rotor_hdr: "Rotor", m_tab_radio: "Radio", m_tab_ft8: "FT8", m_tab_cw: "CW", m_tab_log: "Log", m_tab_rotor: "Rotor", m_radio_locked_generic: "Radio zablokowane", cw_status_ready: "● GOTOWY", }, en: { tab_radio: "RADIO", tab_settings: "SETTINGS", tab_profile: "👤 PROFILE", tab_log: "QSO LOG", tab_config: "CONFIGURATION", tab_admin: "ADMIN", logout_btn: "⏻ LOG OUT", logout_title: "Log out", // ── RADIO tab ── radio_mode_hdr: "MODE", radio_band_hdr: "BAND", radio_freq_history: "FREQ HISTORY", radio_trx_functions: "TRX FUNCTIONS", radio_meter_indicator: "METER (bargraph below S-meter)", radio_filter_lbl: "FILTER (FIL1/2/3 — radio menu settings)", vfo_b_tooltip: "Click: switch to VFO-B | Right-click: type frequency | Scroll: step 1kHz", ws_badge_title: "Control connection latency (ping/pong) — NOT audio latency itself. Click to refresh.", audio_badge_title: "Real RX listening latency: how much audio is buffered between receiving it from the server and playing it. Adaptive target 180-300ms (grows after underruns, decays when the link is quiet), hard limit 400ms.", vfoab_title: "Swap VFO A and B (CI-V 07 B0)", vfoa2b_title: "Copy A to B (CI-V 07 A0)", autotune_title: "Autotune — matching cycle (CI-V 1C 01 02, triggers TX)", peak_hold_title: "Peak-hold: shows the highest value in each band", wf_collapse_title: "Collapse/expand the waterfall (e.g. when the rig has no scope and the animation is distracting)", rotator_target_lbl: "TARGET (degrees or locator)", cw_custom_placeholder: "Type CW text…", operators_hdr: "OPERATORS", chat_hdr: "CHAT", chat_input_ph: "Message…", sent_title: "RST sent", rcvd_title: "RST received", rotator_target_placeholder: "272 or KO02", no_connections: "No connections", // Radio disconnected-duration badge (status row, next to SIM) rig_disconnected_badge: "disconnected", // RadioLock (TRX lock panel — buttons + dynamic status text) radio_free: "🔓 RADIO FREE", you_have_trx: "🎙 YOU HAVE TRX", take_trx_btn: "▶ TAKE OVER TRX", release_trx_btn: "✕ RELEASE TRX", request_trx_btn: "🙋 REQUEST TRX", request_trx_again_btn: "🙋 REQUEST AGAIN", request_sending: "✋ Sending…", request_sent_short: "✋ Request sent…", force_release_btn: "⚡ FORCE (admin)", toast_trx_granted: "✓ You now have TRX access!", toast_request_sent: "✋ Request sent — the operator will be notified", toast_request_error: "✗ Error sending request", toast_release_error: "✗ Error releasing radio", toast_reject_error: "✗ Error rejecting request", toast_request_rejected: "✗ TRX request rejected by {by}", tx_mic_start: "🎤 Start TX — microphone", tx_mic_stop: "⏹ Stop TX microphone", cw_edit_macro_title: "Edit macro", rotator_none: "✗ none", rotator_moving: "↻ moving", // ── SETTINGS tab ── settings_save_btn: "SAVE", settings_loading: "Loading…", status_checking: "checking…", status_error_generic: "error", status_no_response: "no response", settings_saved_short: "✓ saved", settings_save_error_plain: "save error", // COM BRIDGE settings_combridge_hdr: "🔌 REMOTE CAT ACCESS — CW Skimmer, HRD, Logger32", settings_combridge_desc: "Download the HAM RADIO CTRL app and use the remote radio in CAT programs as if it were connected locally. You get 2 independent ports — e.g. CW Skimmer and HRD at the same time.", settings_combridge_warn: "Note: changing frequency/mode via CAT requires taking over the radio (lock). Without the lock you can only read the radio state (freq preview in the program). COM numbers on your PC may differ — check the app window.", settings_combridge_download: "⬇️ DOWNLOAD COM BRIDGE (Windows)", settings_combridge_winreq: "Windows 10/11 · ~25 MB · includes com0com", // CLOUDLOG / WAVELOG settings_cloudlog_status_title: "Click to test the connection", settings_cloudlog_status_default: "not checked", settings_cl_url_lbl: "Server address", settings_cl_apikey_qso_lbl: "API Key — QSO logging", settings_cl_apikey_qso_ph: "API key for QSO", settings_cl_stationid_ph: "Station ID", settings_cl_apikey_radio_lbl: "API Key — live frequency", settings_cl_apikey_radio_ph: "API key for live freq/mode", settings_cl_live_checkbox: "Send freq+mode automatically (every 5s)", settings_test_connection_btn: "TEST CONNECTION", toast_cloudlog_saved: "✓ CloudLog: settings saved", toast_cloudlog_save_error: "✗ Error saving settings", cloudlog_fill_url_key: "Fill in the address and QSO API Key", cloudlog_connected_default: "connected", toast_cloudlog_qso_sent: "✓ QSO sent to CloudLog", toast_cloudlog_no_connection: "✗ CloudLog: no connection", // CALLSIGN LOOKUP (QRZ.com / HamQTH) settings_lookup_hdr: "🔍 CALLSIGN LOOKUP — QRZ.com / HamQTH", settings_lookup_desc: "When logging a QSO, the 🔍 icon next to the CALL field will try to fetch the other station's name/QTH/country — first from QRZ.com (if configured), then HamQTH. Requires your own account on these services (QRZ: paid \"XML Data\" subscription · HamQTH: free account).", settings_qrz_login_lbl: "QRZ.com — login", settings_qrz_pass_lbl: "QRZ.com — password", settings_hamqth_login_lbl: "HamQTH — login", settings_hamqth_pass_lbl: "HamQTH — password", ph_qrz_login: "QRZ.com login", ph_qrz_pass: "QRZ.com password", ph_hamqth_login: "HamQTH login", ph_hamqth_pass: "HamQTH password", cb_fill_login_pass: "fill in login/password", cb_connected: "✓ connected", cb_no_response: "✗ no response", toast_lookup_saved: "✓ Lookup settings saved", // VIRTUAL RADIO (Hamlib NET rigctl) settings_virtual_radio_hdr: "VIRTUAL RADIO — EXTERNAL PROGRAMS", settings_virtual_radio_desc: "Emulates Hamlib NET rigctl (rigctld) — 3 independent TCP servers.
Each port is a separate \"virtual radio\" you connect an external program to.
WSJT-X / Log4OM / N1MM / CW Skimmer / DXKeeper / HRD: Rig → Hamlib NET rigctl → host: <server IP> → port: 4532", settings_virtual_radio_save_btn: "SAVE CONFIGURATION", settings_virtual_radio_refresh_btn: "↻ REFRESH STATUS", settings_virtual_radio_howto_hdr: "How to connect an external program:", hamlib_load_error: "Error loading status", hamlib_no_ports: "No configured ports.", hamlib_active: "active", hamlib_client_singular: "client", hamlib_client_plural: "clients", hamlib_disabled: "— disabled —", hamlib_toggle_port_title: "Enable/disable this port", hamlib_saving: "saving…", // ── PROFILE tab ── profile_myprofile_hdr: "👤 MY PROFILE", profile_name_lbl: "NAME", profile_name_ph: "John", profile_callsign_lbl: "CALLSIGN", profile_locator_lbl: "QTH LOCATOR (e.g. JO82)", profile_save_btn: "💾 SAVE PROFILE", profile_logged_in_as: "Logged in as: {user} ({role})", profile_changepwd_hdr: "🔑 CHANGE PASSWORD", profile_old_pwd_lbl: "OLD PASSWORD", profile_new_pwd_lbl: "NEW PASSWORD (min. 8 characters)", profile_new_pwd2_lbl: "REPEAT NEW PASSWORD", profile_changepwd_btn: "🔑 CHANGE PASSWORD", profile_fill_fields: "Fill in the fields", profile_pwd_mismatch: "Passwords don't match", profile_pwd_min_len: "Min. 8 characters", profile_pwd_changed_short: "✓ Password changed", profile_pwd_changed_toast: "✓ Password changed (other devices logged out)", profile_saved_short: "✓ Profile saved", profile_error_fallback: "Error", profile_audio_hdr: "🔊 AUDIO — MY DEVICES (browser)", profile_audio_desc: "Choose where you hear the radio (RX) and where your voice is sent from (TX microphone). Settings are saved locally in your browser.", profile_audio_out_lbl: "RX SPEAKER (radio reception → your headphones)", profile_loading_option: "— loading… —", profile_audio_test_out_title: "Play a test tone", profile_audio_in_lbl: "TX MICROPHONE (your voice → radio)", profile_audio_test_in_title: "Measure microphone level (5s)", profile_mic_level_lbl: "MICROPHONE LEVEL:", profile_audio_footnote: "💡 Avoid \"CABLE\"/\"Virtual\" devices — those are virtual cables for programs (WSJT-X), not your actual hardware.", profile_no_devices: "no devices", profile_device_fallback: "Device", profile_mic_fallback: "Microphone", profile_toast_speaker_saved: "✓ RX speaker saved", profile_toast_mic_saved: "✓ TX microphone saved (active on next TX)", profile_toast_test_tone: "🔉 1 kHz test tone", profile_toast_speaker_test_err: "✕ Speaker test error: ", profile_mic_hint_speaking: "Speak into the microphone… ({s}s) — peak: {peak}%", profile_mic_hint_low: "⚠ Peak only {peak}% — microphone too quiet or wrong device selected", profile_mic_hint_high: "⚠ Peak {peak}% — too loud, may clip", profile_mic_hint_ok: "✓ Peak {peak}% — level OK", profile_mic_hint_error: "✕ Error: ", profile_toast_mic_open_err: "✕ Can't open the microphone: ", profile_theme_hdr: "🎨 APPEARANCE (color theme)", profile_theme_desc: "The theme changes the accent colors in the interface. Saved locally in the browser.", profile_theme_lbl: "THEME", profile_theme_opt_default: "Green + amber (classic IC-7300)", profile_theme_opt_blue: "Blue (modern)", profile_theme_opt_mono: "Monochrome (discreet)", profile_theme_opt_amber: "Classic amber (retro CRT)", profile_eq_hdr: "🎤 TX MICROPHONE — AUDIO EQ", profile_eq_desc: "Audio EQ shapes the microphone signal to the SSB passband (300-2700 Hz). Pick a preset matching your voice and check the monitor to hear how it'll sound on the air. Settings are saved locally in the browser.", profile_eq_preset_lbl: "EQ PRESET", profile_eq_opt_default: "Standard (balanced)", profile_eq_opt_dark: "Dark voice (male/deep) — more clarity", profile_eq_opt_bright: "Bright voice (female/child) — less treble", profile_eq_opt_dx: "DX/Contest — maximum punch", profile_eq_opt_ragchew: "Ragchew — natural, less aggressive", profile_eq_opt_flat: "Flat (no EQ, limiter only)", profile_eq_opt_custom: "Custom (manual)", profile_eq_custom_hdr: "CUSTOM EQ — manual adjustment", profile_eq_bass_lbl: "BASS (200 Hz):", profile_eq_mud_lbl: "MUD (700 Hz):", profile_eq_clarity_lbl: "CLARITY (1800 Hz):", profile_eq_punch_lbl: "PUNCH (2400 Hz):", profile_eq_air_lbl: "AIR (2700 Hz):", profile_eq_monitor_hdr: "🎧 MONITOR — hear how EQ changes your voice", profile_eq_monitor_start_btn: "▶ START MONITOR", profile_eq_monitor_stop_btn: "⏹ STOP MONITOR", profile_eq_monitor_desc: "The monitor runs your microphone through the EQ and plays it back locally in your headphones (not transmitted over the radio). Use headphones to avoid feedback!", profile_eq_monitor_vol_lbl: "Volume:", profile_toast_mic_unavailable: "Microphone unavailable in this browser", profile_toast_mic_no_access: "No microphone access: ", profile_toast_audioctx_unavailable: "AudioContext unavailable - click anywhere in the UI to activate audio, then try again", // ── Common (modals, buttons used in many places) ── common_confirm_title: "CONFIRM", common_cancel_btn: "CANCEL", common_delete_btn: "DELETE", // ── QSO LOG tab ── log_new_qso_btn: "+ NEW QSO", log_delete_selected_btn: "DELETE SELECTED", log_delete_all_btn: "⚠ DELETE ENTIRE LOG", log_import_title: "Import QSOs from an ADIF file (.adi/.adif)", log_filter_user_lbl: "USER", log_filter_all_users: "All", log_filter_from_lbl: "FROM", log_filter_to_lbl: "TO", log_filter_call_lbl: "CALLSIGN", log_filter_call_ph: "e.g. SP3GSK", log_pasmo_lbl: "BAND", log_filter_all: "All", log_mode_lbl: "MODE", log_search_btn: "SEARCH", log_clear_btn: "CLEAR", log_th_date: "DATE ↕", log_sort_date_title: "Sort by date", log_th_time_utc: "TIME UTC", log_sort_call_title: "Sort by callsign", log_th_locator: "LOCATOR", log_th_comment: "COMMENT", log_th_actions: "ACTIONS", log_select_all_title: "Select all (this page only)", log_select_all_page_warning: "Only the current page is selected ({shown} of {total} QSOs). To export the WHOLE log, click Export without checking any row.", log_prev_page_btn: "◀ PREVIOUS", log_next_page_btn: "NEXT ▶", log_per_page: "50 QSOs per page", log_field_call_lbl: "CALLSIGN *", log_lookup_title: "Fetch station data from QRZ.com/HamQTH", log_locator_ph: "KO02 or KO02ab", log_field_country_lbl: "COUNTRY (DXCC)", log_country_ph: "auto from callsign", log_field_date_lbl: "DATE *", log_field_time_lbl: "TIME UTC *", log_field_band_lbl: "BAND *", log_field_mode_lbl: "MODE *", log_sat_checkbox: "🛰 Satellite contact", log_sat_name_lbl: "SATELLITE", log_sat_name_ph: "e.g. SO-50, QO-100, ISS", log_sat_mode_lbl: "SATELLITE MODE", log_sat_mode_ph: "e.g. U/V, V/U, L/X", log_downlink_band_lbl: "DOWNLINK BAND", log_downlink_band_default: "— same as uplink —", log_save_qso_btn: "SAVE QSO", log_error_prefix: "Error: ", log_no_qso: "No QSOs", log_sat_tooltip_prefix: "Satellite: ", log_row_edit_btn: "EDIT", log_page_info_full: "Page {page} of {total} ({count} QSOs)", log_enter_callsign: "Enter a callsign", log_lookup_not_found: "✗ Not found or QRZ/HamQTH not configured (SETTINGS)", log_data_fetched_from: "✓ Data fetched from {source}", log_modal_new_title: "NEW QSO", log_modal_edit_title: "EDIT QSO", log_load_qso_error: "Error loading QSO: ", log_missing_callsign: "Missing callsign", log_qso_updated: "✓ QSO updated", log_qso_saved: "✓ QSO saved", log_unknown_fallback: "unknown", log_save_error_prefix: "✗ Save error: ", log_confirm_delete_one: "Delete this QSO?", log_qso_deleted: "✓ QSO deleted", log_exported_selected: "✓ Exported {n} selected QSOs", log_export_error_prefix: "✗ Export error: ", log_enter_callsign_excl: "Enter a callsign!", log_cant_read_file: "✗ Can't read file: ", log_no_qso_in_adif: "✗ No QSOs in the ADIF file", log_importing: "Importing {n} QSOs — please wait…", log_importing_progress: "Importing… {done}/{total} QSOs", log_import_done: "✓ ADIF import: {n} QSOs loaded", log_import_duplicates_skipped: ", {n} duplicates skipped", log_import_errors: ", {n} errors", log_select_qso_to_delete: "Select QSOs to delete", log_confirm_delete_selected: "Delete {n} selected QSOs?", log_deleted_count: "✓ Deleted {n} QSOs", log_delete_all_specific_user: "user {name}", log_delete_all_everyone: "ALL users", log_confirm_delete_all_1: "Delete ALL QSOs for {who}?\nThis action cannot be undone!", log_confirm_delete_all_2: "Are you SURE? The log for {who} will be deleted!", log_yes_delete_btn: "YES, DELETE", // ── CONFIGURATION tab ── cfg_loading_dash: "— loading —", cfg_default_dash: "— default —", cfg_yes: "YES", cfg_no: "NO", cfg_conn_error: "Connection error", cfg_refresh_btn: "🔍 REFRESH", cfg_none_paren: "(none)", cfg_name_lbl: "NAME", cfg_model_hamlib_lbl: "HAMLIB MODEL", cfg_port_com_lbl: "COM PORT", cfg_baud_lbl: "BAUD", // Transfer widget — shared by BANDS/MODES/RIG FEATURES tw_visible_lbl: "Visible", tw_available_lbl: "Available", tw_hint_remove: "Double-click to remove", tw_hint_add: "Double-click to add", tw_btn_all_left: "«« All", tw_btn_add: "« Add", tw_btn_remove: "Remove »", tw_btn_all_right: "All »»", tw_btn_up: "▲ Up", tw_btn_down: "▼ Down", tw_title_add_all: "Add all", tw_title_add_sel: "Add selected", tw_title_remove_sel: "Remove selected", tw_title_remove_all: "Remove all", tw_title_move_up: "Move up", tw_title_move_down: "Move down", // RADIO AUDIO — AUTO-DETECT cfg_audio_autodetect_hdr: "🎙 RADIO AUDIO — AUTO-DETECT", cfg_audio_autodetect_desc: "The server automatically detects the radio's audio card (IC-7300, IC-705, FT-991 etc.) by name.
Normally you don't need to configure anything — once the radio is connected via USB the card will be found.", cfg_rescan_btn: "🔍 RESCAN", cfg_expert_mode_btn: "⚙ EXPERT MODE (manual card selection)", cfg_detected_badge: "✓ DETECTED", cfg_not_detected_badge: "✕ NOT DETECTED", cfg_scanning_badge: "⏳ SCANNING…", cfg_card_detected_msg: "✓ Radio card detected (pattern: {pattern})", cfg_rx_arrow_lbl: "RX (radio → server):", cfg_tx_arrow_lbl: "TX (server → radio):", cfg_other_cards_active: "⚠ Different cards currently in use (manual configuration active):", cfg_not_detected_msg: "⚠ Radio card not detected automatically", cfg_not_detected_desc: "The server didn't find a card matching known patterns (IC-7300, IC-705, FT-991, SCU-17).
Check that the radio is connected via USB and powered on, then click RESCAN.
If you're using an unusual interface — switch to EXPERT MODE and pick the card manually.", cfg_avail_capture_lbl: "Available capture cards (RX):", cfg_avail_playback_lbl: "Available playback cards (TX):", cfg_toast_detected: "✓ Detected: {pattern}", cfg_toast_not_detected: "⚠ Radio card not detected", cfg_toast_scan_error_prefix: "✕ Scan error: ", cfg_toast_network_error_prefix: "✕ Network error: ", // MANUAL SOUND CARDS (expert mode) cfg_manual_cards_hdr: "⚙ SOUND CARDS — MANUAL (expert mode)", cfg_manual_cards_warn: "⚠ You usually don't need to touch this. Use it when auto-detect can't find the card or you have multiple radios.", cfg_card_rx_lbl: "RX card (radio → server)", cfg_card_tx_lbl: "TX card (server → radio)", cfg_bitrate_lbl: "Opus bitrate", cfg_txvol_ft8_lbl: "TX Volume FT8/FT4 (encoder tone multiplier → radio)", cfg_txvol_ssb_lbl: "TX Volume SSB (microphone multiplier → radio)", cfg_txvol_ssb_sublabel: "(separate from FT8/FT4 — different signal characteristics, easier to hit ALC)", cfg_gain_mode_ssb_lbl: "SSB microphone gain mode", cfg_gain_mode_ssb_sublabel: "(global, applies to every operator — pick one)", cfg_gain_mode_manual_opt: "Fixed multiplier (slider below)", cfg_gain_mode_agc_opt: "AGC (auto level normalization)", cfg_save_apply_btn: "💾 SAVE AND APPLY (cards + TX Volume)", cfg_refresh_cards_btn: "↺ REFRESH CARDS", cfg_loading_cards: "Loading cards...", cfg_empty_response_kept: "{source} — 0 RX, 0 TX (empty response, kept previous list)", cfg_cards_summary: "{source} — {rx} RX, {tx} TX", cfg_saving: "Saving...", cfg_saved_applied: "✓ Saved and applied", cfg_toast_audio_saved: "Audio card + TX Volume saved — Rust RX will restart", // RADIO CONFIGURATION cfg_rig_config_hdr: "RADIO CONFIGURATION", cfg_add_rig_btn: "+ ADD RADIO", cfg_radio_n_lbl: "RADIO {id}", cfg_default_paren: " (default)", cfg_civ_lbl: "CI-V (Icom)", cfg_card_rx_full_lbl: "RX SOUND CARD — receiving from radio", cfg_choose_rx_card: "-- choose RX card --", cfg_refresh_title: "Refresh", cfg_card_rx_desc: "For IC-7300/IC-7610/IC-9700/IC-705 etc. choose \"USB Audio CODEC\" (the radio's built-in USB audio codec — audio goes over USB, NOT through external ACC/AF Out/Phones jacks). An external card (e.g. a CAT/audio interface) is only needed for a radio without a built-in USB Audio Codec.", cfg_card_tx_full_lbl: "TX SOUND CARD — transmitting to radio", cfg_choose_tx_card: "-- choose TX card --", cfg_card_tx_desc: "Usually the same \"USB Audio CODEC\" as RX — one USB cable carries audio both ways (RX = card input/radio output, TX = card output/radio mic input). Choosing a different card (e.g. the PC's built-in microphone) will NOT reach the radio.", cfg_cw_keyer_hdr: "CW KEYER — DTR/RTS", cfg_dtr_desc: "Keys the transmitter via the COM port's DTR or RTS line - for newer radios (IC-7300+) HAMCTRL enables BK-IN and assigns the chosen line as the radio's key input over CI-V itself (no radio-menu digging needed). Empty PORT = same port as CI-V (typical for a single USB cable). If you have an external USB→COM adapter/transistor wired into the KEY jack (older radios without CI-V USB Keying), enter its port here.", cfg_dtr_port_lbl: "COM PORT (empty = same as CI-V)", cfg_dtr_port_ph: "(same as the CI-V port)", cfg_line_lbl: "LINE", cfg_save_dtr_btn: "SAVE AND ACTIVATE CW KEYER", cfg_connect_btn: "CONNECT", cfg_rig1_note: "Required: Hamlib rigctld.exe → the installer downloaded it to C:\\hamlib\\bin\\
If in SIM mode: check HAMLIB_PATH= in the .env file and click CONNECT", cfg_single_rig_notice: "The current version supports one radio. Multi-radio support is planned for the future.", cfg_choose_card_generic: "-- choose a card --", cfg_toast_rig_saved: "✓ Radio {id} ({name}) saved", cfg_connecting: "Connecting...", cfg_toast_connecting_radio: "Connecting to the radio...", cfg_sim_mode_msg: "⚠ Simulation mode — {msg}", cfg_connected_fallback: "Connected", cfg_sim_no_rigctld: "SIM — rigctld not found ({path})", cfg_not_found: "not found", cfg_connected_status: "✓ Connected", cfg_configuring: "Configuring...", // ROTATOR CONFIGURATION cfg_rotator_config_hdr: "ROTATOR CONFIGURATION", cfg_add_btn: "+ ADD", cfg_station_locator_lbl: "STATION LOCATOR", cfg_station_locator_ph: "e.g. JO72", cfg_station_locator_desc: "Antenna position — used to calculate the bearing to the other station (shown in the QSO log after typing a callsign). Shared by all operators, regardless of where they connect from.", cfg_save_restart_rotators_btn: "SAVE AND RESTART ROTATORS", cfg_rot_ph_lbl: "PH", cfg_rot_enabled_lbl: "ENABLED", cfg_no_rotators: "No rotators", cfg_toast_rotators_saved: "✓ Rotators saved", cfg_save_config_first: "Save the configuration first", cfg_testing: "Testing...", cfg_rot_test_ok: "✓ OK — az={az}° ({driver})", cfg_bad_locator_format: "✗ Bad format (e.g. JO72)", cfg_saved_capital: "✓ Saved", // CW DECODER — DEEPCW MODEL cfg_deepcw_hdr: "CW DECODER — DEEPCW MODEL", cfg_deepcw_desc: "Neural-network CW decoder (~15 MB). Downloaded once, saved in the data directory — survives program updates. The decoder only runs while its window is open.", cfg_deepcw_download_btn: "DOWNLOAD / UPDATE MODEL", cfg_deepcw_check_btn: "CHECK STATUS", cfg_deepcw_not_downloaded: "⚠ Model not downloaded", cfg_deepcw_loading: "⏳ Model on disk ({mb} MB), loading...", cfg_deepcw_ready: "✓ Ready ({mb} MB)", cfg_deepcw_starting_download: "Starting download...", cfg_deepcw_downloaded: "✓ Model downloaded ({mb} MB)", cfg_deepcw_download_error: "✗ Download error", // RADIO FEATURES — USER ACCESS cfg_rig_features_hdr: "RADIO FEATURES — USER ACCESS", cfg_rig_features_desc: "Check the features available to regular users (operator/viewer). Greyed-out features aren't supported by the currently connected radio.", cfg_save_rig_features_btn: "SAVE RADIO FEATURES", cfg_static_features_title: "STATIC FEATURES — user access", cfg_buttons_title: "FUNCTION BUTTONS — visible on the main screen", cfg_sliders_title: "SLIDERS — visible in the left column", cfg_toast_config_saved: "✓ Configuration saved", // Advanced CI-V settings (admin-only, approved 2026-10-15) adv_txdelay_hdr: "TX DELAY — PTT sequencing (amplifier / antenna relay)", adv_agc_hdr: "AGC", adv_agc_mode: "AGC mode", adv_tx_delay_hf: "TX Delay HF", adv_tx_delay_50m: "TX Delay 50MHz", adv_tx_delay_70m: "TX Delay 70MHz", // COM BRIDGE (CONFIGURATION) cfg_combridge_hdr: "🔌 COM BRIDGE — remote CAT access (CW Skimmer, HRD, Logger32)", cfg_combridge_no_client: "— no client connected —", cfg_combridge_desc: "The HAM RADIO CTRL app (Windows client) creates virtual COM ports on your computer and links them to the remote radio over the internet. Programs like CW Skimmer, Ham Radio Deluxe, or Logger32 see the radio as if it were connected locally.", cfg_combridge_port1_lbl: "PORT 1 — first application", cfg_combridge_port2_lbl: "PORT 2 — second application", cfg_combridge_note: "You get 2 independent ports — you can run two applications at once (e.g. CW Skimmer on COM11 and HRD on COM21). They don't conflict with each other or with the server.
Note: COM numbers on your computer may differ — check the HAM RADIO CTRL app window (the \"COM for app\" column).", cfg_combridge_howto_summary: "📖 How to set up (click)", cfg_combridge_steps: "1. Download and run the COM Bridge (button below)
2. Enter the server address, your login and password → CONNECT
3. The app will create COM ports (allow the UAC prompt the first time)
4. In CW Skimmer / HRD choose Icom IC-7300, CI-V address 94, 19200 baud
5. Enter the COM number from the app window (the \"COM for app\" column)
6. Connect — the radio works as if it were local!", cfg_combridge_download_btn: "⬇️ DOWNLOAD COM BRIDGE (Windows .exe)", cfg_combridge_winreq: "Requires Windows 10/11 · ~25 MB · includes the com0com driver", cfg_combridge_stats_hdr: "📊 COM BRIDGE — connected clients (ADMIN)", cfg_combridge_stats_hint: "Click REFRESH to see connected clients.", // BANDS / MODES cfg_bands_hdr: "BANDS — VISIBLE IN THE UI", cfg_bands_desc: "Choose which bands are visible to users in the BAND grid.
The change applies immediately to all connected clients.", cfg_bands_widget_title: "BANDS", cfg_save_bands_btn: "SAVE BANDS", cfg_modes_hdr: "OPERATING MODES — VISIBLE IN THE UI + FILTERS", cfg_modes_desc: "Choose the modes visible in the MODE grid. USB-D/LSB-D = digital (DATA) modes — a default filter can be assigned.", cfg_modes_widget_title: "MODES", cfg_mode_filter_hdr: "DEFAULT FILTER FOR MODE", cfg_mode_filter_desc: "When a user switches mode, the server automatically sets the chosen filter.
Empty = no automatic filter change.", cfg_none_filter: "-- none --", cfg_save_modes_btn: "SAVE MODES AND FILTERS", cfg_toast_bands_saved: "✓ Bands saved", cfg_toast_modes_saved: "✓ Modes saved", // COM BRIDGE stats (com_bridge.js) cfg_admin_only: "(admin only)", cfg_no_client_connected: "No connected clients.", cfg_client_online_1: "● {n} client online", cfg_client_online_n: "● {n} clients online", cfg_clients_connected_1: "● {n} client connected", cfg_clients_connected_n: "● {n} clients connected", // ── FT8/WSJT-X tab ── wj_decode_mode_title: "Decode mode", wj_band_placeholder: "-- band --", wj_band_select_title: "Choose a band and retune the radio to the standard frequency", wj_cq_only_title: "Show only CQ calls in Band Activity", wj_hound_title: "Fox/Hound DXpedition mode — type the Fox's callsign in the DX field", wj_hound_confirm_btn: "✓ CONFIRM", wj_period_title: "TX period (even/odd 15s window)", wj_tx_wait_title: "FT8/FT4 transmission must start exactly on the 15s/7.5s UTC window boundary — this is a normal wait, not a freeze. TX (PTT) itself starts once the counter reaches 0.", wj_ref_title: "Reference: shifts the 'typical noise' point on the palette. Higher = brighter waterfall for noise.", wj_zero_title: "Zero: cuts off the weakest signals (raises the palette 'floor'). Higher = more background noise fades to black.", wj_gain_title: "Gain: curve contrast. Higher = stronger distinction between noise and signal.", wj_reset_palette_title: "Restore defaults (REF=15, ZERO=0, GAIN=1.0)", wj_tune_title: "Transmit a steady tone for ATU tuning. Click again to stop.", wj_clear_btn: "CLEAR", wj_hide_worked_title: "Hide stations already logged", wj_own_rx_title: "Built-in FT8 decoder (audio from the radio, no external WSJT-X)", wj_own_rx_start: "▶ START (built-in RX)", wj_own_rx_stop: "⏹ STOP (built-in RX)", wj_rx_eq_tx_title: "Set the RX marker to the current TX frequency", wj_tx_eq_rx_title: "Set the TX marker to the current RX frequency", wj_halt_tx_title: "Stop TX after the current window", wj_tx_free_btn: "🔓 TX FREE", wj_tx_locked_btn: "📌 TX LOCKED", wj_tx_freeze_title: "Freeze the TX marker at the chosen spot — click the waterfall to set the TX position, then freeze it. RX still automatically follows calls addressed to you.", wj_fake_split_off: "⭕ Fake Split: OFF", wj_fake_split_on: "🎯 FAKE SPLIT: ON", wj_fake_split_title: "Fake Split: shifts the VFO during transmit so the audio sits near the center of the SSB filter (~1500Hz) — full power, no splatter at the filter edges. Controls the radio during TX, VFO returns after transmitting.", wj_band_activity_hdr: "BAND ACTIVITY", wj_country_col_title: "Country column: flag (click: switch to name)", wj_clear_band_activity_title: "Clear Band Activity", wj_th_message: "Message", wj_th_grid: "Grid", wj_th_country: "Country", wj_no_decodes: "No decodes — start WSJT-X and click ▶ START", wj_no_cq: "No CQ calls", wj_rxfreq_hdr: "RX FREQUENCY", wj_clear_rxfreq_title: "Clear Rx Frequency", wj_no_rxfreq_signal: "No signal on the RX frequency", wj_decode_count_default: "0 decodes", wj_decode_count_n: "{n} decodes", wj_cycle_utc_lbl: "UTC CYCLE:", wj_cycle_phase_tx: "TRANSMITTING (TX)", wj_cycle_phase_rx: "RECEIVING (RX)", wj_cycle_phase_decoding: "DECODING SIGNALS...", wj_cycle_phase_txprep: "PREPARING TO TRANSMIT (TX PREP)", wj_cycle_marker_dec: "DECODING", wj_autoqso_hdr: "QSO AUTOMATION", wj_auto_badge: "● AUTO", wj_auto_badge_title: "Automation is always active. Clicking a macro (F1-F6) in the TX panel manually forces that message to be sent.", wj_autoqso_ready: "Automation ready — click a CQ row to start an automatic QSO • clicking a macro forces manual send", wj_skip_btn: "⏭ Skip", wj_skip_title: "Abandon the current station (without waiting for the retry limit)", wj_tx_panel_hdr: "TRANSMIT TX", wj_timer_lbl: "TIMER:", wj_confirm_btn: "CONFIRM ▶", wj_dx_call_ph: "-- CLICK A ROW OR TYPE --", wj_quicklog_hdr: "QUICK QSO LOG", wj_antenna_hdr: "ANTENNA & ROTOR", wj_minilog_hdr: "RECENTLY LOGGED:", wj_antenna_lbl: "ANTENNA", wj_rotor_sp_title: "Point the rotator to the SP bearing (short path)", wj_rotor_lp_title: "Point the rotator to the LP bearing (long path)", wj_rotor_manual_title: "Manual: type a bearing in degrees or a locator", wj_call_dx_ph: "CALLSIGN DX *", wj_grid_dx_ph: "Grid DX", wj_comment_ph: "Comment (optional)", wj_addlog_btn: "+ LOG QSO", wj_adif_btn_title: "Export ADIF", wj_alltxt_btn_title: "Download ALL.TXT - everything received and sent", wj_no_logged_qso: "No logged QSOs", wj_color_legend_hdr: "COLOR LEGEND", wj_legend_cq: "CQ", wj_legend_dx: "Reply to DX", wj_legend_mycall: "My callsign", wj_legend_73: "73 / RR73", wj_legend_worked: "Already logged", wj_legend_owntx: "My TX", wj_rotor_modal_hdr: "ROTATOR — MANUAL POSITION", wj_rotor_azimuth_lbl: "BEARING (°) OR LOCATOR", wj_rotor_input_ph: "e.g. 272 or KO02", wj_set_btn: "SET", // Dynamic (wsjtx.js / wsjtx_scope.js) wj_toast_monitor_active: "✓ WSJT-X monitor active (UDP :{port})", wj_offline_autostart_failed: "○ OFFLINE (autostart failed?)", wj_decode_count_session: "{n} decodes received (session)", wj_toast_monitor_on_port: "✓ Monitor active on port {port}", wj_toast_monitor_stopped: "Monitor stopped", wj_toast_tx_stopped: "⛔ TX stopped", wj_toast_tx_paused: "TX paused", wj_toast_decode_mode: "Decode mode: {mode}", wj_toast_radio_busy: "⛔ Radio busy — held by {holder} — take over TRX", wj_toast_tx_period_prefix: "TX period: ", wj_toast_own_rx_on: "✓ Built-in FT8 RX decoder active", wj_toast_own_rx_off: "Built-in FT8 RX decoder stopped", wj_toast_station_not_visible: "{call} not currently visible on the band", wj_toast_no_mycall: "Set your callsign (MYCALL) in WSJT-X settings first", wj_toast_no_dxcall: "No DX Call — pick a station from the decode list", wj_toast_waiting_window: "Waiting for the 15s window ({s}s) — {text}", wj_toast_transmitting: "Transmitting: {text}", wj_toast_tx_error: "FT8 TX error: {error}", wj_tx_wait_countdown: "⏳ TX in {s}s — {text}", wj_tx_transmitting_status: "📡 TRANSMITTING — {text}", wj_toast_enter_callsign_dx: "✗ Enter a DX callsign", wj_toast_qso_logged: "✓ QSO logged: {call}", wj_log_error_fallback: "Logging error", wj_toast_log_error_generic: "✗ Logging error", wj_toast_export_success: "✓ Log exported (ADIF)", wj_toast_export_error: "✗ Export error", wj_toast_qso_logged_full: "✓ QSO logged: {call} {band} {mode}", wj_toast_timer_reset: "✓ Timer reset", wj_toast_timer_warning: "⚠️ FT8 Timer: 1 minute until TX stops!", wj_toast_timer_expired: "⛔ FT8 Timer: TX stopped — confirm presence!", wj_toast_no_foxcall: "Type the Fox's callsign in the DX field!", wj_toast_hound_searching: "🦊 Hound mode: searching for {call} — TX {freq} Hz", wj_toast_hound_off: "Hound mode off", wj_toast_hound_confirm: "⚠️ Hound: confirm presence (2 min) — TX paused", wj_toast_qso_complete_fox: "✓ QSO with {call} complete! RR73 received.", wj_toast_qso_complete: "✓ QSO with {call} complete — check and confirm in \"QUICK QSO LOG\"", wj_toast_no_rotor: "⚠ No rotator", wj_toast_rotor_moved: "↻ Rotator → {az}°{labelPart}", wj_toast_bad_format: "⚠ Invalid format (degrees or locator)", wj_toast_tx_frozen: "🧊 TX frozen — unfreeze to change frequency manually", wj_status_no_decoding: "Automation off — click a CQ row to answer manually", wj_status_waiting_call1st: "Auto-Sequencing active — waiting for a call", wj_status_qso_done: "✓ QSO with {partner} complete", wj_status_calling: "calling", wj_status_report_sent: "report sent, waiting for confirmation", wj_status_rrr_sent: "RRR sent, waiting for 73", wj_status_qso_with: "QSO with {partner}: {state}", wj_decoding_status: "⚡ DECODING", wj_hound_step_calling: "Calling the Fox", wj_hound_step_report: "Sending R+report", wj_hound_step_waiting: "Waiting for RR73", wj_hound_attempt: " | attempt {n}", // ── DX CLUSTER tab ── dx_hdr: "📡 DX CLUSTER", dx_disconnected: "○ DISCONNECTED", dx_connect_btn: "▶ CONNECT", dx_disconnect_btn: "✕ DISCONNECT", dx_config_btn: "⚙ CONFIG", dx_config_title: "Show/hide configuration", dx_send_spot_btn: "📤 SEND SPOT", dx_send_spot_title: "Send a spot to the cluster (fills in the current frequency and mode)", dx_band_lbl: "BAND:", dx_mode_lbl: "MODE:", dx_all_bands: "all", dx_search_ph: "search (call/spotter)", dx_spots_lbl: "Spots:", dx_clear_list_title: "Clear list", dx_config_desc: "Telnet server address (e.g. dxcluster.sq3mzm.ovh:7300). Login = callsign. Password optional.", dx_address_lbl: "ADDRESS", dx_port_lbl: "PORT", dx_login_lbl: "LOGIN", dx_password_lbl: "PASSWORD", dx_save_btn: "💾 SAVE", dx_optional_ph: "optional", dx_autoconnect_lbl: "Auto-connect on login", dx_th_call: "CALL (DX)", dx_th_mode: "MODE", dx_empty_initial: "No spots. Click ⚙ CONFIG to enter the server address, then ▶ CONNECT.", dx_cmd_lbl: "CMD:", dx_spot_modal_hdr: "📤 SEND SPOT TO CLUSTER", dx_spot_modal_desc: "The spot will be sent under your callsign (cluster login) and will be visible to operators worldwide.", dx_spot_call_lbl: "STATION CALLSIGN *", dx_spot_freq_lbl: "FREQUENCY (kHz) *", dx_spot_comment_lbl: "COMMENT (max 30 chars)", dx_spot_comment_ph: "e.g. FT8 CQ DX / 599 TNX QSO", dx_preview_lbl: "COMMAND PREVIEW:", dx_send_spot_modal_btn: "📤 SEND SPOT", // Dynamic (dxcluster.js) dx_saving: "⏳ Saving...", dx_saved: "✓ Saved", dx_cmd_send_fail: "Can't send the command (no connection?)", dx_empty_filtered: "No spots match the filters", dx_empty_connect: "No spots. Connect to DX Cluster to see live spots.", dx_spot_count_title: "Showing {shown} of {total} — use the filters to narrow it down", dx_invalid_freq: "✗ Invalid spot frequency", dx_trx_busy: "TRX is held by {holder}", dx_take_trx_first: "Take over TRX first (Radio tab → OPERATORS panel → TAKE OVER TRX)", dx_cant_qsy: "⛔ Can't QSY: {reason}", dx_connected: "● CONNECTED", dx_connecting: "⏳ CONNECTING...", dx_error_prefix: "✕ ERROR: ", dx_unknown: "Unknown", dx_spot_call_invalid: "Enter a valid station callsign (letters, digits, /)", dx_spot_freq_invalid: "Enter a frequency in kHz (e.g. 14074.0)", dx_sending: "SENDING...", dx_spot_sent: "✓ Spot sent: {call} on {freq} kHz", dx_spot_send_fail: "Failed to send the spot", dx_spot_conn_error: "Connection error: {msg}", dx_password_saved_ph: "•••• (saved)", // ── INTERNET tab ── in_status_hdr: "REMOTE ACCESS STATUS", in_public_addr_lbl: "ADDRESS TO SHARE", in_copy_btn: "📋 COPY", in_copy_title: "Copy to clipboard", in_cert_lbl: "🔒 CERT:", in_tunnel_lbl: "🌐 TUNNEL:", in_lan_lbl: "🏠 LAN:", in_mode_hdr: "ACCESS MODE", in_autosave_note: "changes save automatically", in_mode_home_title: "HOME ONLY", in_mode_home_desc: "LAN. No configuration needed.", in_mode_quick_title: "QUICK TUNNEL", in_mode_quick_desc: "Cloudflare, no signup.\n Address changes.", in_mode_duckdns_title: "DUCKDNS + HTTPS", in_mode_duckdns_desc: "Free subdomain.\n Fixed address.", in_mode_named_title: "CLOUDFLARE + DOMAIN", in_mode_named_desc: "Named subdomain of your own domain.", in_mode_staticip_title: "STATIC IP + HTTPS", in_mode_staticip_desc: "Public static IP + self-signed cert.", in_mode_customcert_title: "OWN DOMAIN + CERT", in_mode_customcert_desc: "You already have a domain and a purchased certificate.", in_named_instructions: "📋 dash.cloudflare.com → Zero Trust → Tunnels → Create tunnel → copy the token", in_your_domain_lbl: "YOUR DOMAIN", in_cf_token_lbl: "CLOUDFLARE TOKEN", in_duckdns_instructions: "📋 duckdns.org → log in → create a subdomain → copy the token", in_subdomain_lbl: "SUBDOMAIN", in_token_lbl: "TOKEN", in_your_address_lbl: "Your address:", in_gen_cert_btn: "🔒 GENERATE CERTIFICATE", in_letsencrypt_note: "Let's Encrypt cert (90 days, auto-renew). Requires port 80 or 443 open. Requires win-acme installed (no installer — extract to C:\\win-acme\\).", in_your_static_ip_lbl: "YOUR STATIC IP", in_port_lbl: "PORT", in_selfsigned_note: "Self-signed SSL cert generated automatically. The browser will warn once — click \"Advanced → Proceed\".", in_customcert_instructions: "📋 This mode generates NOTHING — you must already have a domain (DNS pointing to your static IP/router) and a ready SSL certificate (cert.pem + key.pem files, e.g. from an external CA).", in_certpath_lbl: "PATH TO cert.pem (optional)", in_leave_empty_ph: "leave empty = server's default data directory", in_keypath_lbl: "PATH TO key.pem (optional)", in_customcert_note: "Leaving the paths empty, you just need to replace the cert.pem and key.pem files in the server's data directory (next to the EXE or %APPDATA%\\HAMCTRL) — the rest happens automatically.", in_cf_checking: "cloudflared: checking...", in_cf_svc_active: "⚠ Windows service active", in_check_btn: "🔍 CHECK", in_start_btn: "▶ START", in_stop_btn: "■ STOP", // Dynamic (tunnel.js) in_toast_tunnel_active: "✓ Tunnel: {url}", in_no_cert: "no certificate", in_days_warn: "⚠ {n} days", in_days_ok: "✓ {n} days", in_active: "● ACTIVE", in_connecting_dots: "◌ CONNECTING…", in_error_badge: "✗ ERROR", in_stopped_tunnel: "○ STOPPED", in_stopped_badge: "○ OFF", in_waiting_url: "Waiting for the URL…", in_toast_addr_copied: "✓ Address copied", in_toast_copied_plain: "✓ Copied", in_toast_copied_prefix: "✓ Copied: ", in_toast_copy_failed: "✗ Can't copy: ", in_toast_no_addr: "⚠ No address to copy - start the tunnel first", in_toast_paste_token: "⚠ Paste the Cloudflare token", in_toast_duckdns_fields: "⚠ Enter the DuckDNS subdomain and token", in_toast_enter_ip: "⚠ Enter an IP address", in_toast_enter_domain: "⚠ Enter a domain", in_connecting_btn: "Connecting…", in_toast_tunnel_stopped: "■ Tunnel stopped", in_toast_config_saved: "✓ Tunnel configuration saved", in_toast_save_error: "✗ Save error", in_cf_available: "✓ cloudflared {ver}", in_cf_missing: "⚠ cloudflared missing — will be downloaded automatically", in_stale_procs: "⚠ Stale processes: {n}", in_cf_check_error: "Error checking cloudflared", in_toast_generating_cert: "Generating certificate — please wait ~2 min...", in_confirm_cleanup: "Clean up stale cloudflared processes and service?", in_toast_cleaned: "✓ Cleaned up: ", in_toast_cleanup_error: "✗ Error: ", // ── ADMIN tab ── adm_status_hdr: "📊 SERVER STATUS", adm_refresh_btn: "↻ REFRESH", adm_loading_status: "Loading status...", adm_test_civ_btn: "🔌 TEST CI-V", adm_backup_hdr: "💾 BACKUP / RESTORE CONFIGURATION", adm_backup_desc: "The backup contains config.json (radios, bands, relays, SMTP) and users.json (accounts, permissions, passwords).
Keep the file somewhere safe — it contains passwords. Restart the server after a restore.", adm_download_backup_btn: "⬇ DOWNLOAD BACKUP", adm_upload_backup_btn: "⬆ LOAD BACKUP", adm_users_hdr: "SYSTEM USERS", adm_new_user_btn: "+ NEW USER", adm_th_login_name: "LOGIN / NAME", adm_th_role: "ROLE", adm_th_callsign: "CALLSIGN", adm_th_perms: "PERMISSIONS", adm_th_status: "STATUS", adm_ft8timer_hdr: "FT8/HOUND SAFETY TIMER — GLOBAL", adm_ft8timer_desc: "Maximum continuous FT8/FT4/Hound transmit time without operator confirmation.
When time runs out — TX is stopped automatically. Applies to all users. Range: 1–60 min.", adm_timer_duration_lbl: "TIMER DURATION:", adm_minutes_word: "minutes", adm_myprofile_hdr: "MY PROFILE", adm_save_profile_btn: "SAVE PROFILE", adm_change_pwd_hdr: "CHANGE YOUR OWN PASSWORD", adm_change_pwd_btn: "CHANGE PASSWORD", adm_lock_settings_hdr: "RADIO LOCK — SETTINGS", adm_lock_timeout_lbl: "IDLE TIMEOUT (minutes)", adm_lock_timeout_desc: "If the active operator takes no action for this long, the radio is released automatically.", adm_save_timeout_btn: "SAVE TIMEOUT", adm_force_release_btn: "⚡ FORCE RELEASE RADIO", adm_timeout_saved: "✓ Timeout saved", adm_relay_hdr: "🔌 ARDUINO RELAYS (SP5IOU SDR220)", adm_relay_desc: "Relay control via Arduino running SP5IOU firmware (SDR220 protocol) — firmware bundled in arduino/relay_sp5iou/ in the HAMCTRL repo.
Each of the 8 relays can be set to \"manual\" (toggle on/off) or \"momentary\" (pulse with adjustable time 0.1-10s).
Access permissions (who sees the buttons) are assigned in the Users section with the \"Relay 0..7\" checkboxes.", adm_relay_enabled_lbl: "Enabled (connected to Arduino)", adm_serial_port_lbl: "SERIAL PORT", adm_choose_port: "(choose a port)", adm_baudrate_lbl: "BAUD RATE", adm_relay_config_hdr: "8 RELAYS CONFIGURATION", adm_save_relay_config_btn: "💾 SAVE CONFIGURATION", adm_smtp_hdr: "EMAIL — SMTP CONFIGURATION", adm_smtp_desc: "Required for the \"Forgot password\" feature to work. Supports STARTTLS (port 587) and SSL (port 465).
Example for Gmail: host=smtp.gmail.com, port=587, user=your@gmail.com, password = an App Password from Google.", adm_smtp_host_lbl: "SMTP SERVER (host)", adm_smtp_user_lbl: "USER (SMTP login)", adm_smtp_pass_lbl: "SMTP PASSWORD", adm_smtp_from_lbl: "SENDER ADDRESS (From)", adm_smtp_encryption_lbl: "ENCRYPTION", adm_smtp_starttls_opt: "STARTTLS (port 587, recommended)", adm_smtp_ssl_opt: "SSL/TLS (port 465)", adm_smtp_test_lbl: "TEST EMAIL — SEND TO", adm_send_test_btn: "SEND TEST", adm_save_smtp_btn: "SAVE SMTP", // user-modal adm_new_user_title: "NEW USER", adm_login_lbl: "LOGIN *", adm_role_lbl: "ROLE *", adm_pass_lbl: "PASSWORD *", adm_min_chars_ph: "min. 8 characters", adm_locator_grid_lbl: "LOCATOR (grid)", adm_perms_hdr: "GRANULAR PERMISSIONS", adm_group_radio: "RADIO", adm_group_hw: "HARDWARE", adm_group_system: "SYSTEM", adm_group_relay: "RELAYS", // Permissions (shared between the modal's static HTML and PERM_DEFS in admin.js) perm_ptt: "PTT — transmit", perm_rfpower: "RF Power — TX power", perm_rfgain: "RF/AF Gain, SQL", perm_mode: "Mode change (USB/CW…)", perm_band: "Band change", perm_freq: "Frequency tuning", perm_split: "Split VFO A/B", perm_cw: "CW Keyer — sending", perm_rotator: "Rotator — control", perm_log: "QSO Log — logging", perm_settings: "Server settings", perm_admin: "Admin panel", perm_relay_n: "Relay {n}", // Dynamic (admin.js — users, ft8 timer) adm_no_perms: "none", adm_edit_title: "Edit", adm_reset_pwd_title: "Reset password", adm_deactivate_title: "Deactivate", adm_activate_title: "Activate", adm_delete_title: "Delete", adm_edit_user_title: "Edit: {username}", adm_no_access: "✗ Access denied", adm_user_not_found: "✗ User not found", adm_enter_login: "Enter a login", adm_enter_password: "Enter a password", adm_network_error: "✗ Network error: ", adm_user_updated: "✓ User updated", adm_user_added: "✓ User added", adm_account_deactivated: "Account deactivated", adm_account_activated: "Account activated", adm_confirm_delete_user: "Delete user {username}?", adm_user_deleted: "✓ Deleted {username}", adm_new_pwd_for: "NEW PASSWORD FOR {username} (min. 8 characters)", adm_pwd_reset_done: "✓ Password for {username} reset", adm_timer_active: "✓ Active — {min} min for all users", adm_timer_saved_toast: "✓ FT8 Timer: {min} min for everyone", adm_timer_save_error: "✗ Error saving the timer", // Dynamic (admin_status.js) adm_stat_version: "Version", adm_stat_uptime: "Uptime", adm_stat_online: "Online", adm_stat_users_count: "{n} users", adm_stat_radio: "Radio", adm_stat_sim: "SIMULATION", adm_stat_disconnected: "DISCONNECTED", adm_stat_civ_connected: "CI-V connected", adm_stat_model_port: "Model / Port", adm_stat_freq: "Frequency", adm_stat_audio: "Audio", adm_stat_dxcluster: "DX Cluster", adm_stat_available: "available", adm_stat_unavailable: "unavailable", adm_stat_relays: "Relays", adm_stat_relays_connected: "connected", adm_stat_relays_configured: "configured (offline)", adm_stat_no_module: "— module missing", adm_stat_perf_link: "📊 Open detailed performance diagnostics (/perf) →", adm_testing: "⏳ Testing...", adm_generating: "⏳ Generating...", adm_downloaded: "✓ Downloaded", adm_confirm_restore: "Restore configuration from file \"{name}\"?\n\nThis will OVERWRITE the current configuration and all users. Restart the server after restoring.", adm_restore_btn: "RESTORE", adm_loading_ellipsis: "⏳ Loading...", adm_restored: "✓ Restored: {list}. Restart the server!", adm_toast_restored: "✓ Backup restored — restart the server", // Dynamic (relay_admin.js) adm_port_unavailable: " (unavailable)", adm_relay_connected_badge: "● CONNECTED", adm_relay_error_badge: "● CONNECTION ERROR", adm_relay_manual_opt: "Manual (toggle)", adm_relay_momentary_opt: "Momentary (pulse)", adm_relay_name_ph: "Name...", adm_visible_lbl: "Visible", // Dynamic (AdminSmtp — inline in index.html) adm_smtp_pass_saved_ph: "●●●●●● (set)", adm_smtp_saved: "✓ SMTP configuration saved", adm_smtp_enter_test_email: "✗ Enter an email address in the \"TEST EMAIL\" field", adm_smtp_sending_test: "⏳ Sending test email…", adm_smtp_sent: "✓ Sent", // Dynamic (AdminUpdate — inline in index.html) adm_update_hdr: "UPDATES", adm_update_desc: "Checks GitHub Releases once at server startup (or on demand below). Never downloads or installs anything — informational only.", adm_update_current_lbl: "YOUR VERSION", adm_update_status_lbl: "STATUS", adm_update_check_lbl: "Check automatically at startup", adm_update_email_lbl: "Notify admins by email (requires SMTP configured above)", adm_update_check_now_btn: "CHECK NOW", adm_update_no_repo: "Repository not configured", adm_update_available: "New version available: {v}", adm_update_open_release: "open release", adm_update_current_ok: "✓ You have the latest version", adm_update_checking: "⏳ Checking…", adm_update_checked: "✓ Checked", // ── MOBILE (mobile.html / mobile.js / mobile_audio.js) ── m_lang_toggle_title: "Zmień język / Change language", m_audio_btn_title: "Audio (RX monitor)", m_settings_title: "Phone settings", m_logout_btn: "Log out", m_lock_checking: "checking radio lock...", m_freq_hint: "hold the frequency to type it in manually", m_freq_strip_hint: "◄ drag to tune ►", m_functions_hdr: "Functions", m_sliders_hdr: "Radio sliders", m_autoqso_ready: "Automation ready — tap a CQ call in the list to start a QSO", m_confirm_presence: "Confirm presence", m_skip_qso_btn: "Skip QSO", m_dx_card_title: "DX (contact's callsign)", m_dx_call_ph: "-- tap a call in the list or type it in --", m_tx_macros_hdr: "TX macros", m_received_calls_hdr: "Received calls", m_tx_settings_hdr: "TX settings", m_filters_hdr: "Filters / view", m_hide_worked_lbl: "Hide worked (HIDE WORKED)", m_reset_palette_btn: "↺ Reset palette", m_cw_hint: "tap = send • hold = edit", m_cw_custom_ph: "custom CW text...", m_send_btn: "Send", m_quicklog_hdr: "Quick QSO log", m_rotor_none_badge: "● none", m_desktop_link: "Full version (desktop)", m_audio_toggle_lbl: "🔊 Audio (RX monitor)", m_mic_lbl: "MICROPHONE (TX)", m_mic_default_opt: "-- default --", m_close_btn: "Close", m_freq_edit_ph: "e.g. 14.074 (MHz)", m_freq_vfo_title: "Frequency VFO {vfo}", m_set_btn: "Set", m_cw_label_ph: "label (e.g. CQ)", m_cw_macro_text_ph: "macro text (e.g. CQ CQ DE {MYCALL} K)", m_save_btn: "Save", m_conn_connecting: "connecting...", m_conn_connected: "connected", m_radio_free_short: "Radio free", m_take_radio_btn: "Take radio", m_radio_busy_you: "Radio held by you", m_release_btn: "Release", m_radio_busy_by: "Radio held by: {name}", m_request_btn: "Request", m_request_sent_short: "Request sent", m_network_error: "Network error", m_need_radio_mode: "Take the radio to change mode", m_need_radio_band: "Take the radio to change band", m_need_radio_tune: "Take the radio to tune", m_need_radio_tx: "Take the radio to transmit", m_need_radio_cw: "Take the radio to send CW", m_need_radio_freq: "Take the radio to change frequency", m_invalid_freq: "Invalid frequency", m_log_empty: "no entries", m_load_error: "load error", m_mic_list_error: "Could not read the microphone list", m_audio_no_webcodecs: "This phone/browser doesn't support the audio decoder (WebCodecs)", m_no_webrtc: "WebRTC unavailable in this browser", m_mic_error_prefix: "Microphone error: ", m_clear_btn: "clear", m_log_hdr: "QSO Log", m_rotor_hdr: "Rotor", m_tab_radio: "Radio", m_tab_ft8: "FT8", m_tab_cw: "CW", m_tab_log: "Log", m_tab_rotor: "Rotor", m_radio_locked_generic: "Radio locked", cw_status_ready: "● READY", }, }; window.I18n = (function () { const SUPPORTED = ["pl", "en"]; let current = null; function detectInitial() { const saved = localStorage.getItem("ham_lang"); if (saved && SUPPORTED.includes(saved)) return saved; if (window.__HAM_INITIAL_LANG__ && SUPPORTED.includes(window.__HAM_INITIAL_LANG__)) { return window.__HAM_INITIAL_LANG__; } // No explicit choice (localStorage) or server-configured default yet - // fall back to the browser/OS language: Polish -> pl, anything else -> en. // Each device/browser has its own localStorage, so this matters most on // a brand new device (e.g. first time opening the phone) where nothing // has been chosen yet. const nav = (navigator.language || (navigator.languages && navigator.languages[0]) || "").toLowerCase(); return nav.startsWith("pl") ? "pl" : "en"; } function t(key) { const dict = window.I18N_STRINGS[current] || {}; if (key in dict) return dict[key]; // Fall back to Polish (the app's original/complete language) rather // than showing a raw key, then to the key itself as a last resort so a // missing translation is still visibly wrong instead of silently blank. if (key in window.I18N_STRINGS.pl) return window.I18N_STRINGS.pl[key]; return key; } function apply(root) { root = root || document; root.querySelectorAll("[data-i18n]").forEach((el) => { el.textContent = t(el.getAttribute("data-i18n")); }); root.querySelectorAll("[data-i18n-html]").forEach((el) => { el.innerHTML = t(el.getAttribute("data-i18n-html")); }); root.querySelectorAll("[data-i18n-title]").forEach((el) => { el.title = t(el.getAttribute("data-i18n-title")); }); root.querySelectorAll("[data-i18n-placeholder]").forEach((el) => { el.placeholder = t(el.getAttribute("data-i18n-placeholder")); }); const btn = document.getElementById("lang-switch"); if (btn) btn.textContent = current.toUpperCase(); document.documentElement.lang = current; } function setLang(lang) { if (!SUPPORTED.includes(lang)) return; current = lang; localStorage.setItem("ham_lang", lang); apply(); } function toggle() { setLang(current === "pl" ? "en" : "pl"); } function init() { current = detectInitial(); apply(); } document.addEventListener("DOMContentLoaded", init); return { t, apply, setLang, toggle, current: () => current }; })();