From 59de540b3dffb8a97831e48bd47494a1fbe739df Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Sun, 7 Jun 2026 13:48:52 -0700 Subject: [PATCH] =?UTF-8?q?Release=20v0.51.320=20=E2=80=94=20Release=20KJ?= =?UTF-8?q?=20(Phase=202:=20Polish=20(pl)=20language=20support,=20#3781)?= =?UTF-8?q?=20(#3796)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete Polish locale. #3781 (@leszek3737). Full suite green, CI 11/11, 180 locale/parity tests pass. Co-authored-by: leszek3737 --- CHANGELOG.md | 5 + api/routes.py | 9 + static/i18n.js | 1330 ++++++++++++++++- tests/test_2735_open_in_vscode.py | 4 +- tests/test_auxiliary_models_settings.py | 2 +- .../test_issue1488_composer_voice_buttons.py | 4 +- tests/test_issue2419_cache_usage_display.py | 4 +- tests/test_issue2679_hide_suggestions.py | 4 +- tests/test_issue3242_3214_i18n_tooltips.py | 2 +- tests/test_login_locale_parity.py | 11 +- tests/test_polish_locale.py | 201 +++ tests/test_pr1721_rtl_salvage.py | 4 +- tests/test_quota_chip_settings_toggle.py | 4 +- tests/test_verdigris_skin.py | 2 +- tests/test_zeus_skin.py | 2 +- 15 files changed, 1567 insertions(+), 21 deletions(-) create mode 100644 tests/test_polish_locale.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 41ef8a536..0bc37c484 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ ## [Unreleased] +## [v0.51.320] — 2026-06-07 — Release KJ (Phase 2 — Polish (pl) language support) + +### Added +- **Polish (pl) language support.** Adds a complete Polish translation set to the in-app localization, a Polish login-page locale, and resolver coverage for `pl` / `pl-PL` / `pl_PL`, with the locale registered consistently across every key group (appearance skins, approval/clarify prompts, tooltips, cache-usage labels, profile skill counts) and parity tests asserting the new locale is complete and actually translated. (#3781, @leszek3737) + ## [v0.51.319] — 2026-06-07 — Release KI (Phase 3 light — refresh stale continuation metadata) ### Fixed diff --git a/api/routes.py b/api/routes.py index db4d91eac..3fdee07d6 100644 --- a/api/routes.py +++ b/api/routes.py @@ -3561,6 +3561,15 @@ _LOGIN_LOCALE = { "invalid_pw": "Ge\u00e7ersiz \u015fifre", "conn_failed": "Ba\u011flant\u0131 ba\u015far\u0131s\u0131z", }, + "pl": { + "lang": "pl-PL", + "title": "Zaloguj si\u0119", + "subtitle": "Wpisz has\u0142o, aby kontynuowa\u0107", + "placeholder": "Has\u0142o", + "btn": "Zaloguj si\u0119", + "invalid_pw": "Nieprawid\u0142owe has\u0142o", + "conn_failed": "Po\u0142\u0105czenie nie powiod\u0142o si\u0119", + }, } diff --git a/static/i18n.js b/static/i18n.js index 259d32aa4..4a3b6d0f9 100644 --- a/static/i18n.js +++ b/static/i18n.js @@ -15496,7 +15496,1335 @@ const LOCALES = { - } + }, + pl: { + offline_title: 'Połączenie utracone', + offline_browser_detail: 'Twoja przeglądarka zgłasza, że to urządzenie jest offline.', + offline_network_detail: 'Hermes jest obecnie nieosiągalny z tej przeglądarki.', + offline_autorefresh: 'Odświeżę tę stronę automatycznie, gdy Hermes będzie ponownie osiągalny.', + offline_check_now: 'Sprawdź teraz', + offline_checking: 'Sprawdzanie…', + offline_stream_waiting: 'Połączenie utracone. Oczekiwanie na odświeżenie…', + _lang: 'pl', + _label: 'Polski', + _speech: 'pl-PL', + // boot.js + cancelling: 'Anulowanie…', + cancel_failed: 'Anulowanie nie powiodło się: ', + mic_denied: 'Odmowa dostępu do mikrofonu. Sprawdź uprawnienia przeglądarki.', + mic_no_speech: 'Nie wykryto mowy. Spróbuj ponownie.', + mic_network: 'Rozpoznawanie mowy jest niedostępne.', + mic_error: 'Błąd wejścia głosowego: ', + // Composer voice buttons (#1488 — distinct labels for dictation vs voice mode) + voice_dictate: 'Dyktuj', + voice_dictate_active: 'Zatrzymaj dyktowanie', + voice_recording_active: 'Zatrzymaj nagrywanie', + voice_mode_toggle: 'Tryb głosowy', + voice_mode_toggle_active: 'Wyjdź z trybu głosowego', + // Turn-based voice mode (#1333) + voice_listening: 'Słucham…', + voice_speaking: 'Mówię…', + voice_thinking: 'Myślę…', + voice_error: 'Głos nie jest obsługiwany w tej przeglądarce', + voice_mode_active: 'Tryb głosowy włączony', + voice_mode_off: 'Tryb głosowy wyłączony', + session_imported: 'Zaimportowano sesję', + import_failed: 'Import nie powiódł się: ', + import_invalid_json: 'Nieprawidłowy JSON', + image_pasted: 'Wklejono obraz: ', + // messages.js + edit_message: 'Edytuj wiadomość', + regenerate: 'Wygeneruj ponownie odpowiedź', + copy: 'Kopiuj', + copied: 'Skopiowano!', + copy_failed: 'Kopiowanie nie powiodło się', + selected_text_reply: 'Odpowiedz z zaznaczeniem', + selected_text_reply_title: 'Dodaj zaznaczony tekst czatu jako cytowany kontekst', + selected_text_reply_appended: 'Dodano zaznaczony tekst do pola wprowadzania', + + diff_loading: 'Ładowanie zmian (diff)', + diff_error: 'Nie można załadować pliku poprawki', + diff_too_large: 'Plik poprawki jest zbyt duży, aby wyświetlić jego podgląd', + tree_view: 'Drzewo', + raw_view: 'Źródło', + parse_failed_note: 'błąd parsowania', + you: 'Ty', + mcp_servers_title: 'Serwery MCP', + mcp_servers_desc: 'Wyświetl serwery MCP skonfigurowane w config.yaml.', + mcp_no_servers: 'Brak skonfigurowanych serwerów MCP.', + mcp_add_server: '+ Dodaj serwer', + mcp_field_name: 'Nazwa serwera', + mcp_transport_label: 'Typ transportu', + mcp_field_command: 'Polecenie', + mcp_field_args: 'Argumenty (rozdzielone przecinkami)', + mcp_field_url: 'URL', + mcp_field_timeout: 'Limit czasu (sekundy)', + mcp_save: 'Zapisz', + mcp_cancel: 'Anuluj', + mcp_name_required: 'Nazwa serwera jest wymagana.', + mcp_url_required: 'URL jest wymagany dla transportu HTTP.', + mcp_command_required: 'Polecenie jest wymagane dla transportu stdio.', + mcp_saved: 'Zapisano serwer MCP.', + mcp_save_failed: 'Nie udało się zapisać serwera MCP.', + mcp_delete_confirm_title: 'Usuń serwer MCP', + mcp_delete_confirm_message: 'Usunąć serwer MCP "{0}"? Tej operacji nie można cofnąć.', + mcp_deleted: 'Usunięto serwer MCP.', + mcp_delete_failed: 'Nie udało się usunąć serwera MCP.', + mcp_load_failed: 'Nie udało się załadować serwerów MCP.', + mcp_restart_hint: 'Zmiany serwerów są tutaj obecnie tylko do odczytu. Edytuj config.yaml i zrestartuj Hermes, aby zmiany odniosły skutek.', + mcp_toggle_followup: 'Kontrolki włączania/wyłączania są celowo odłożone, dopóki semantyka przeładowania MCP nie stanie się jasna.', + mcp_status_active: 'Aktywny', + mcp_status_configured: 'Skonfigurowany', + mcp_status_disabled: 'Wyłączony', + mcp_status_invalid_config: 'Nieprawidłowa konfiguracja', + mcp_status_unknown: 'Nieznany', + mcp_tool_count: '{0} narzędzi', + mcp_enabled_yes: 'Włączone', + mcp_enabled_no: 'Wyłączone', + mcp_enable_server: 'Włącz ten serwer MCP', + mcp_disable_server: 'Wyłącz ten serwer MCP', + mcp_enabled_toast: (name) => `Serwer MCP "${name}" został włączony.`, + mcp_disabled_toast: (name) => `Serwer MCP "${name}" został wyłączony.`, + mcp_toggle_failed: 'Nie udało się zaktualizować serwera MCP.', + mcp_tools_title: 'Narzędzia MCP', + mcp_tools_desc: 'Przeszukaj znane narzędzia we wszystkich aktywnych serwerach MCP.', + mcp_tools_search_placeholder: 'Szukaj narzędzi po nazwie, serwerze lub opisie…', + mcp_tools_no_tools: 'Brak dostępnych narzędzi MCP w aktywnym inwentarzu środowiska uruchomieniowego.', + mcp_tools_no_matches: 'Brak narzędzi MCP pasujących do wyszukiwania.', + mcp_tools_load_failed: 'Nie udało się załadować narzędzi MCP.', + mcp_tools_schema_empty: 'Brak parametrów schematu.', + mcp_tools_runtime_note: 'Inwentarz narzędzi korzysta wyłącznie z już znanych aktywnych danych uruchomieniowych MCP; WebUI nie uruchamia ani nie sonduje serwerów.', + mcp_tools_summary_no_matches: (query,total) => `Brak narzędzi MCP pasujących do „${query}” (łącznie ${total}).`, + mcp_tools_summary_none: 'Brak narzędzi MCP do wyświetlenia.', + mcp_tools_summary_matching: (query) => ` pasujących do „${query}”`, + mcp_tools_summary_total_note: (total) => ` (łącznie ${total})`, + mcp_tools_summary_showing: (start,end,filtered,searchNote,totalNote,page,pages) => `Wyświetlanie ${start}-${end} z ${filtered} narzędzi MCP${searchNote}${totalNote}. Strona ${page} z ${pages}.`, + mcp_tools_page_size_prefix: 'Pokaż', + mcp_tools_page_size_suffix: 'na stronę', + mcp_tools_per_page_aria: 'Liczba narzędzi MCP na stronę', + mcp_tools_inactive_configured_servers: (servers) => `Skonfigurowane, ale nieaktywne w tym środowisku WebUI: ${servers}.`, + mcp_tools_pagination_label: 'Stronicowanie narzędzi MCP', + mcp_tools_previous_page: '‹ Poprzednia', + mcp_tools_previous_page_aria: 'Poprzednia strona narzędzi MCP', + mcp_tools_next_page: 'Następna ›', + mcp_tools_next_page_aria: 'Następna strona narzędzi MCP', + // PDF preview (#480) + pdf_loading: 'Ładowanie PDF {0}…', + pdf_too_large: 'Plik PDF jest zbyt duży, aby wyświetlić jego podgląd', + pdf_no_pages: 'Plik PDF nie ma stron', + pdf_error: 'Nie udało się wyrenderować podglądu PDF', + pdf_download: 'Pobierz PDF', + // HTML sandbox preview (#482) + html_loading: 'Ładowanie podglądu HTML…', + html_too_large: 'Plik HTML jest zbyt duży, aby wyświetlić jego podgląd', + html_error: 'Nie udało się wyrenderować podglądu HTML', + html_open_full: 'Otwórz pełną stronę', + html_sandbox_label: 'Podgląd HTML (izolowany)', + thinking: 'Myślenie', + expand_all: 'Rozwiń wszystko', + collapse_all: 'Zwiń wszystko', + edit_failed: 'Edycja nie powiodła się: ', + regen_failed: 'Ponowne generowanie nie powiodło się: ', + reconnect_active: 'Odpowiedź jest nadal generowana. Odświeżyć, gdy będzie gotowa?', + reconnect_finished: 'Podczas Twojej ostatniej wizyty odpowiedź była w toku. Wiadomości mogły zostać zaktualizowane.', + // approval card + approval_heading: 'Wymagana aprobata', + approval_desc_prefix: 'Wykryto niebezpieczne polecenie', + approval_btn_once: 'Zezwól raz', + approval_btn_once_title: 'Zezwól na to jedno polecenie (Enter)', + approval_btn_session: 'Zezwól na sesję', + approval_btn_session_title: 'Zezwól na tę sesję konwersacji', + approval_btn_always: 'Zawsze zezwalaj', + approval_btn_always_title: 'Zawsze zezwalaj na ten wzorzec poleceń', + approval_btn_deny: 'Odmów', + approval_btn_deny_title: 'Odmów — nie uruchamiaj tego polecenia', + approval_responding: 'Odpowiadanie…', + clarify_heading: 'Wymagane wyjaśnienie', + clarify_hint: 'Wybierz opcję lub wpisz własną odpowiedź poniżej.', + clarify_other: 'Inne', + clarify_send: 'Wyślij', + clarify_input_placeholder: 'Wpisz swoją odpowiedź…', + clarify_responding: 'Odpowiadanie…', + session_attention_approval: (n) => n === 1 ? 'Aprobata' : `Aprobaty (${n})`, + session_attention_clarify: (n) => n === 1 ? 'Pytanie' : `Pytania (${n})`, + session_attention_generic: (n) => n === 1 ? 'Wymaga uwagi' : `Wymaga uwagi (${n})`, + session_attention_approval_title: 'Oczekiwanie na decyzję o zezwoleniu', + session_attention_clarify_title: 'Oczekiwanie na Twoją odpowiedź', + session_attention_generic_title: 'Oczekiwanie na działanie użytkownika', + untitled: 'Bez tytułu', + n_messages: (n) => n === 1 ? '1 wiadomość' : `${n} wiadomości`, + load_older_messages: '↑ Przewiń w górę lub kliknij, aby załadować starsze wiadomości', + session_jump_start: 'Początek', + session_jump_start_label: 'Przejdź do początku sesji', + session_jump_end: 'Koniec', + session_jump_end_label: 'Przejdź do końca sesji', + jump_to_question: 'do pytania', + jump_to_question_label: 'Przejdź do pytania dotyczącego tej odpowiedzi', + queued_label: 'Zostanie wysłane po odpowiedzi', + queued_count: (n) => n === 1 ? '1 w kolejce' : `${n} w kolejce`, + queued_cancel: 'Anuluj wiadomości w kolejce', + model_unavailable: ' (niedostępny)', + model_unavailable_title: 'Ten model nie znajduje się już na liście Twoich aktywnych dostawców', + provider_mismatch_warning: (m,p)=>`"${m}" może nie działać z Twoim skonfigurowanym dostawcą (${p}). Wyślij mimo to lub uruchom \`hermes model\` w terminalu, aby przełączyć.`, + provider_mismatch_label: 'Niezgodność dostawcy', + gateway_auth_label: 'Uwierzytelnianie bramy nie powiodło się', + model_not_found_label: 'Model nie został znaleziony', + model_custom_label: 'Niestandardowy ID modelu', + model_custom_placeholder: 'np. openai/gpt-5.4', + model_search_placeholder: 'Szukaj modeli…', + model_search_no_results: 'Nie znaleziono żadnych modeli', + model_group_configured: 'Skonfigurowane', + ws_search_placeholder: 'Szukaj obszarów roboczych…', + ws_no_results: 'Nie znaleziono żadnych obszarów roboczych', + workspace_new_worktree_conversation: 'Nowa konwersacja w worktree', + workspace_new_worktree_conversation_meta: 'Utwórz izolowane git worktree dla tego obszaru roboczego.', + workspace_worktree_created: 'Utworzono konwersację w worktree', + workspace_worktree_failed: 'Tworzenie worktree nie powiodło się: ', + session_worktree_badge: 'Worktree', + model_scope_advisory: 'Dotyczy tej konwersacji od Twojej następnej wiadomości.', + model_scope_toast: 'Dotyczy tej konwersacji od Twojej następnej wiadomości.', + // commands.js + cmd_clear: 'Wyczyść wiadomości konwersacji', + cmd_help: 'Pokaż dostępne polecenia ukośnika', + cmd_compress: 'Ręcznie skompresuj kontekst konwersacji (użycie: /compress [temat])', + ctx_compress_hint: 'Skompresuj kontekst, aby zwolnić miejsce →', + ctx_compress_action: '⚠ Skompresuj teraz, aby zwolnić kontekst', + cmd_compact_alias: 'Starszy alias dla /compress', + cmd_model: 'Przełącz model (np. /model gpt-4o)', + cmd_workspace: 'Przełącz obszar roboczy według nazwy', + cmd_terminal: 'Otwórz terminal obszaru roboczego', + cmd_new: 'Rozpocznij nową sesję czatu', + cmd_usage: 'Włącz/wyłącz wyświetlanie zużycia tokenów', + cmd_theme: 'Przełącz wygląd (motyw: system/dark/light, skórka: default/ares/mono/graphite/slate/poseidon/sisyphus/charizard/sienna/catppuccin/nous/geist-contrast/zeus/verdigris)', + cmd_personality: 'Przełącz osobowość agenta', + cmd_skills: 'Lista dostępnych umiejętności Hermes', + cmd_use: 'Wymuś umiejętność dla następnej wiadomości', + available_commands: 'Dostępne polecenia:', + type_slash: 'Wpisz /, aby zobaczyć polecenia', + conversation_cleared: 'Wyczyszczono konwersację', + command_label: 'Polecenie', + context_compaction_label: 'Kompresja kontekstu', + retrieval_context_label: 'Zindeksowany kontekst', + retrieval_context_preview: 'Wcześniejsze wiadomości są przechowywane i mogą być pobierane za pomocą narzędzi kontekstowych', + preserved_task_list_label: 'Zachowana lista zadań', + reference_only_label: 'Tylko jako odniesienie', + model_usage: 'Użycie: /model ', + no_model_match: 'Brak modelu pasującego do "', + model_did_you_mean: (m) => ` — czy chodziło Ci o "${m}"?`, + switched_to: 'Przełączono na ', + workspace_usage: 'Użycie: /workspace ', + no_workspace_match: 'Brak obszaru roboczego pasującego do "', + switched_workspace: 'Przełączono na obszar roboczy: ', + workspace_switch_failed: 'Przełączanie obszaru roboczego nie powiodło się: ', + new_session: 'Utworzono nową sesję', + new_session_creating: 'Tworzenie nowej konwersacji…', + compressing: 'Żądanie kompresji kontekstu...', + compress_running_label: 'Kompresowanie', + compress_complete_label: 'Kompresja zakończona', + auto_compress_label: 'Automatyczna kompresja', + compress_failed_label: 'Kompresja nie powiodła się', + focus_label: 'Skupienie', + token_usage_on: 'Zużycie tokenów włączone', + token_usage_off: 'Zużycie tokenów wyłączone', + usage_cache_hit_detail: 'Pamięć podręczna: {0}% trafień ({1} odczyt / {2} zapis)', + usage_cached_percent: '{0}% w pamięci podręcznej', + theme_usage: 'Użycie: /theme ', + theme_set: 'Motyw: ', + no_active_session: 'Brak aktywnej sesji', + cmd_queue: 'Umieść wiadomość w kolejce na następną turę', + cmd_goal: 'Ustaw lub sprawdź trwały cel', + goal_evaluating_progress: 'Ocena postępu celu…', + goal_working_toward: 'Praca nad realizacją celu…', + goal_continuing_toast: 'Kontynuowanie dążenia do celu…', + goal_status_none: 'Brak aktywnego celu. Ustaw go za pomocą /goal .', + goal_status_active: (turns, max_turns, goal) => `⊙ Cel (aktywny, ${turns}/${max_turns} tur): ${goal}`, + goal_status_paused: (turns, max_turns, reason, goal) => `⏸ Cel (wstrzymany, ${turns}/${max_turns}${reason ? `, ${reason}` : ''}): ${goal}`, + goal_status_done: (turns, max_turns, goal) => `✓ Cel ukończony (${turns}/${max_turns}): ${goal}`, + goal_set: (turns, goal) => `⊙ Cel ustawiony (budżet ${turns} tur): ${goal}`, + goal_paused: (goal) => `⏸ Cel wstrzymany: ${goal}`, + goal_resumed: (goal) => `▶ Cel wznowiony: ${goal}`, + goal_cleared: 'Cel został wyczyszczony.', + goal_no_goal: 'Brak aktywnego celu.', + goal_achieved: (reason) => `✓ Cel osiągnięty: ${reason}`, + goal_paused_budget_exhausted: (turns, max_turns) => `⏸ Cel wstrzymany — wykorzystano ${turns}/${max_turns} tur. Użyj /goal resume, aby kontynuować, lub /goal clear, aby zatrzymać.`, + goal_continuing: (turns, max_turns, reason) => `↻ Kontynuowanie dążenia do celu (${turns}/${max_turns}): ${reason}`, + cmd_interrupt: 'Anuluj bieżącą turę i wyślij nową wiadomość', + cmd_steer: 'Wprowadź korektę w trakcie tury bez przerywania pracy agenta', + cmd_queue_no_msg: 'Użycie: /queue ', + cmd_queue_not_busy: 'Brak aktywnego zadania — po prostu wyślij normalnie', + cmd_queue_confirm: 'Wiadomość dodana do kolejki', + cmd_interrupt_no_msg: 'Użycie: /interrupt ', + cmd_interrupt_confirm: 'Przerwano — wysyłanie nowej wiadomości', + cmd_steer_no_msg: 'Użycie: /steer ', + cmd_steer_fallback: 'Korekta niedostępna — dodano do kolejki na następną turę', + cmd_steer_delivered: 'Korekta dostarczona — agent zobaczy ją przy następnym wyniku narzędzia', + steer_leftover_queued: 'Korekta dodana do kolejki na następną turę', + busy_steer_fallback: 'Korekta niedostępna — dodano do kolejki na następną turę', + busy_interrupt_confirm: 'Przerwano — wysyłanie nowej wiadomości', + settings_label_busy_input_mode: 'Tryb wprowadzania przy zajętości', + settings_desc_busy_input_mode: 'Kontroluje, co dzieje się, gdy wysyłasz wiadomość, podczas gdy agent pracuje. Kolejkowanie czeka; Przerwanie anuluje i zaczyna od nowa; Kierowanie (Steer) wprowadza korektę w środku tury bez przerywania (cofa się do kolejkowania, gdy agent lub strumień jest niedostępny).', + settings_label_fade_text_effect: 'Efekt pojawiania się tekstu', + settings_desc_fade_text_effect: 'Płynne pojawianie się nowo przesyłanych słów, podczas gdy asystent odpowiada. Podobnie jak w OpenWebUI; domyślnie wyłączone dla maksymalnej wydajności.', + settings_busy_input_mode_queue: 'Kolejkuj tury', + settings_busy_input_mode_interrupt: 'Przerwij bieżącą turę', + settings_busy_input_mode_steer: 'Korekta w trakcie tury (steer)', + + slash_skill_badge: 'Umiejętność', + slash_skill_desc: 'Wywołaj tę umiejętność', + cmd_stop: 'Zatrzymaj bieżącą odpowiedź', + cmd_title: 'Pobierz lub ustaw tytuł sesji', + cmd_retry: 'Wyślij ponownie ostatnią wiadomość', + cmd_undo: 'Usuń ostatnią wymianę', + cmd_btw: 'Zadaj pytanie poboczne (na marginesie)', + cmd_btw_usage: '/btw — zadaj pytanie poboczne przy użyciu kontekstu konwersacji', + cmd_background: 'Uruchom zapytanie w tle', + cmd_background_usage: '/background — uruchom w tle bez blokowania', + cmd_branch: 'Rozgałęź tę konwersację na nową sesję', + cmd_branch_usage: '/branch [nazwa] — rozgałęź konwersację (opcjonalnie z nazwą)', + branch_forked: 'Rozgałęziono na nową sesję', + branch_failed: 'Rozgałęzienie nie powiodło się: ', + fork_from_here: 'Rozgałęź stąd', + forked_from: 'Rozgałęzione z', + subagent_children: 'Sesje subagentów', + btw_asking: 'Zadawanie pytania pobocznego…', + btw_label: 'Pytanie poboczne — poza historią', + btw_done: 'Odpowiedziano na pytanie poboczne', + btw_no_answer: 'Nie otrzymano odpowiedzi.', + btw_failed: 'Pytanie poboczne nie powiodło się: ', + bg_running: 'Uruchamianie w tle…', + bg_complete: 'Zadanie w tle ukończone', + bg_label: 'Wynik z tła:', + bg_no_answer: '(brak odpowiedzi)', + bg_failed: 'Zadanie w tle nie powiodło się: ', + undo_exchange: 'Cofnij ostatnią wymianę', + cmd_status: 'Pokaż informacje o sesji', + cmd_voice: 'Przełącz wejście mikrofonu', + stream_stopped: 'Generowanie odpowiedzi zatrzymane.', + no_active_task: 'Brak aktywnego zadania do zatrzymania.', + cancel_unavailable: 'Anulowanie niedostępne.', + retry_failed: 'Ponowna próba nie powiodła się: ', + undo_failed: 'Cofanie nie powiodło się: ', + undid_n_messages: 'Usunięto', + undid_messages_suffix: 'wiadomość/wiadomości.', + status_heading: 'Status sesji', + status_session_id: 'ID sesji', + status_title: 'Tytuł', + status_model: 'Model', + status_provider: 'Dostawca', + status_workspace: 'Obszar roboczy', + status_personality: 'Osobowość', + status_messages: 'Wiadomości', + status_agent_running: 'Agent jest uruchomiony', + status_profile: 'Profil', + status_hermes_home: 'Katalog domowy Hermes', + status_started: 'Uruchomiono', + status_updated: 'Zaktualizowano', + status_tokens: 'Tokeny', + status_ephemeral: 'Snapshot ulotny — niezapisywany w historii transkrypcji.', + status_no_tokens: 'Nie użyto żadnych tokenów', + status_unknown: 'Nieznany', + status_yes: 'Tak', + status_no: 'Nie', + status_load_failed: 'Nie udało się załadować statusu: ', + title_current: 'Bieżący tytuł', + title_change_hint: 'Użyj `/title `, aby zmienić nazwę.', + title_set: 'Tytuł ustawiony na', + cmd_webui_only_session: 'To polecenie nie jest dostępne dla sesji zaimportowanych z CLI.', + cmd_voice_use_mic: 'Kliknij przycisk mikrofonu w kompozytorze.', + usage_heading: 'Zużycie tokenów', + usage_default_model: 'domyślny', + usage_unknown: 'nieznane', + usage_input_tokens: 'Tokeny wejściowe', + usage_output_tokens: 'Tokeny wyjściowe', + usage_total: 'Tokeny łącznie', + usage_estimated_cost: 'Szacowany koszt', + usage_settings_tip: 'Uwaga: szacunki kosztów są przybliżone.', + usage_load_failed: 'Nie udało się załadować zużycia: ', + usage_personality_none: 'brak', + // Session toolsets (#493) + session_toolsets: 'Zestawy narzędzi sesji', + session_toolsets_desc: 'Ogranicz dostępne narzędzia dla tej sesji (puste = użyj konfiguracji globalnej)', + session_toolsets_global: 'Globalne (domyślne)', + session_toolsets_custom: 'Niestandardowe', + session_toolsets_placeholder: 'narzędzie1, narzędzie2, …', + session_toolsets_apply: 'Zastosuj', + session_toolsets_clear: 'Wyczyść (użyj globalnych)', + session_toolsets_applied: 'Zaktualizowano zestawy narzędzi', + session_toolsets_cleared: 'Wyczyszczono zestawy narzędzi — używanie konfiguracji globalnej', + session_toolsets_failed: 'Nie udało się zaktualizować zestawów narzędzi: ', + no_personalities: 'Nie znaleziono żadnych osobowości (dodaj je do ~/.hermes/personalities/)', + available_personalities: 'Dostępne osobowości:', + personality_switch_hint: '\n\nUżyj `/personality `, aby przełączyć, lub `/personality none`, aby wyczyścić.', + personalities_load_failed: 'Nie udało się załadować osobowości', + personality_cleared: 'Wyczyszczono osobowość', + personality_set: 'Osobowość: ', + failed_colon: 'Nie powiodło się: ', + // ui.js + no_workspace: 'Brak obszaru roboczego', + terminal_open_title: 'Otwórz terminal obszaru roboczego', + terminal_no_workspace_title: 'Wybierz obszar roboczy, aby otworzyć terminal', + terminal_title: 'Terminal', + terminal_clear: 'Wyczyść', + terminal_copy_output: 'Kopiuj wyjście', + terminal_restart: 'Uruchom ponownie', + terminal_collapse: 'Zwiń', + terminal_expand: 'Rozwiń', + terminal_close: 'Zamknij', + terminal_input_placeholder: 'Uruchom polecenie...', + terminal_start_failed: 'Uruchomienie terminala nie powiodło się: ', + terminal_input_failed: 'Wprowadzanie w terminalu nie powiodło się: ', + terminal_copy_failed: 'Kopiowanie nie powiodło się: ', + terminal_error: 'Błąd terminala', + terminal_remote_backend_unsupported: 'Wbudowany terminal jest obsługiwany wyłącznie dla lokalnych terminali jako backend.', + workspace_empty_no_path: 'Nie wybrano obszaru roboczego. Ustaw obszar roboczy w Ustawienia → Obszar roboczy, aby przeglądać pliki.', + workspace_empty_dir: 'Ten obszar roboczy jest pusty.', + workspace_show_hidden_files: 'Pokaż ukryte pliki', + workspace_show_hidden_files_desc: 'Dołącz .DS_Store, .git, node_modules i inne ukryte/systemowe pliki w drzewie plików.', + workspace_hidden_files_visible: 'ukryte widoczne', + workspace_hidden_files_visible_title: 'Ukryte pliki są widoczne — kliknij, aby przejść do opcji', + workspace_options: 'Opcje obszaru roboczego', + dialog_confirm_title: 'Potwierdź działanie', + dialog_prompt_title: 'Wpisz wartość', + dialog_confirm_btn: 'Potwierdź', + // workspace.js + unsaved_confirm: 'Masz niezapisane zmiany w podglądzie. Odrzucić i nawigować dalej?', + discard: 'Odrzuć', + save: 'Zapisz', + edit: 'Edytuj', + clear: 'Wyczyść', + create: 'Utwórz', + remove: 'Usuń', + save_title: 'Zapisz zmiany', + edit_title: 'Edytuj ten plik', + saved: 'Zapisano', + save_failed: 'Zapisywanie nie powiodło się: ', + image_load_failed: 'Nie można załadować obrazu', + file_open_failed: 'Nie można otworzyć pliku', + downloading: (name) => `Pobieranie ${name}…`, + double_click_rename: 'Kliknij dwukrotnie, aby zmienić nazwę', + session_rename_failed_no_row: 'Nie można rozpocząć zmiany nazwy — nie znaleziono wiersza.', + renamed_to: 'Nazwa zmieniona na ', + rename_failed: 'Zmiana nazwy nie powiodła się: ', + moved_to: 'Przeniesiono do ', + move_failed: 'Przeniesienie nie powiodło się: ', + delete_title: 'Usuń', + delete_confirm: (name) => `Usunąć ${name}?`, + delete_dir_confirm: (name) => `Usunąć folder "${name}" wraz z całą jego zawartością?`, + rename_title: 'Zmień nazwę', + rename_prompt: 'Nowa nazwa:', + deleted: 'Usunięto ', + delete_failed: 'Usuwanie nie powiodło się: ', + reveal_in_finder: 'Pokaż w Menedżerze Plików', + reveal_failed: 'Nie udało się pokazać: ', + copy_file_path: 'Kopiuj ścieżkę pliku', + open_in_vscode: 'Otwórz w VS Code', + open_in_vscode_failed: 'Nie udało się otworzyć w VS Code: ', + download_folder: 'Pobierz folder', + path_copied: 'Ścieżka pliku została skopiowana do schowka', + path_copy_failed: 'Nie udało się skopiować ścieżki: ', + session_rename: 'Zmień nazwę konwersacji', + session_rename_desc: 'Edytuj tytuł tej konwersacji', + session_title_regenerate: 'Wygeneruj tytuł ponownie', + session_title_regenerate_desc: 'Poproś Hermes o wygenerowanie nowego tytułu z konwersacji', + session_title_regenerating: 'Ponowne generowanie tytułu…', + session_title_regenerated: (title) => `Tytuł wygenerowany ponownie: ${title}`, + session_title_regenerate_failed: 'Ponowne generowanie tytułu nie powiodło się: ', + session_copy_link: 'Kopiuj link do konwersacji', + session_copy_link_desc: 'Kopiuj bezpośredni link do tej konwersacji', + session_link_copied: 'Link do konwersacji skopiowany do schowka', + session_link_copy_failed: 'Nie udało się skopiować linku do konwersacji: ', + new_file_prompt: 'Nazwa nowego pliku (np. notes.md):', + project_name_prompt: 'Nazwa projektu:', + created: 'Utworzono ', + create_failed: 'Tworzenie nie powiodło się: ', + new_folder_prompt: 'Nazwa nowego folderu:', + folder_created: 'Utworzono folder ', + folder_create_failed: 'Tworzenie folderu nie powiodło się: ', + workspace_auto_create_folder: 'Utwórz folder, jeśli nie istnieje', + folder_add_as_space_btn: 'Dodaj jako przestrzeń', + folder_add_as_space_msg: 'Dodać ten folder jako nową przestrzeń w liście Twoich obszarów roboczych?', + folder_add_as_space_title: 'Dodać jako przestrzeń?', + remove_title: 'Usuń', + empty_dir: '(pusty)', + upload_failed: 'Przesyłanie nie powiodło się: ', + upload_too_large: (maxMb, fileMb) => `Plik jest za duży (${fileMb} MB). Maksymalny rozmiar przesyłania to ${maxMb} MB.`, + uploading: 'Przesyłanie…', + uploaded: 'Przesłano', + all_uploads_failed: (n) => `Wszystkie ${n} przesłane pliki nie powiodły się`, + archive_extracted: (n, c) => `Wyodrębniono ${n} plików z ${c} archiwów`, + session_pin: 'Przypnij konwersację', + session_unpin: 'Odepnij konwersację', + session_pin_desc: 'Zachowaj tę konwersację na samej górze', + session_unpin_desc: 'Usuń z przypiętych', + session_pin_failed: 'Przypinanie nie powiodło się: ', + session_move_project: 'Przenieś do projektu', + session_move_project_desc_has: 'Zmień projekt dla tej konwersacji', + session_move_project_desc_none: 'Przypisz projekt do tej konwersacji', + session_archive: 'Zarchiwizuj konwersację', + session_hide_external: 'Ukryj z listy', + session_restore: 'Przywróć konwersację', + session_archive_desc: 'Ukryj tę konwersację, dopóki nie zostanie użyty filtr "Zarchiwizowane"', + session_archive_worktree_desc: 'Ukryj tę konwersację; zachowaj jej worktree na dysku', + session_hide_external_desc: 'Ukryj tę zaimportowaną sesję z WebUI bez usuwania oryginalnej historii źródłowej.', + session_restore_desc: 'Przywróć tę konwersację z powrotem do głównej listy', + session_archived: 'Konwersacja zarchiwizowana', + session_archived_worktree: 'Konwersacja zarchiwizowana. Worktree pozostaje na dysku.', + session_hidden: 'Konwersacja ukryta z listy', + session_restored: 'Konwersacja przywrócona', + session_archive_failed: 'Archiwizacja nie powiodła się: ', + session_duplicate: 'Duplikuj konwersację', + session_duplicate_desc: 'Utwórz kopię konwersacji z tym samym obszarem roboczym i modelem', + session_duplicated: 'Zduplikowano konwersację', + session_duplicate_failed: 'Duplikowanie nie powiodło się: ', + session_stop_response: 'Zatrzymaj odpowiedź', + session_stop_response_desc: 'Anuluj uruchomioną generację odpowiedzi dla tej konwersacji', + session_delete: 'Usuń konwersację', + session_delete_desc: 'Trwale usuń tę konwersację', + session_delete_confirm: 'Usunąć tę konwersację?', + session_delete_worktree_desc: 'Usuń tylko konwersację z WebUI; zachowaj worktree na dysku', + session_delete_worktree_confirm: (path) => `Usunąć tę konwersację? Worktree pod ścieżką ${path} pozostanie na dysku.`, + session_deleted: 'Konwersacja usunięta', + session_deleted_worktree: 'Konwersacja usunięta. Worktree pozostaje na dysku.', + session_worktree_remove: 'Usuń worktree', + session_worktree_remove_desc: (path) => `Usuń git worktree pod ścieżką ${path} z dysku`, + session_worktree_remove_confirm: (path) => `Usunąć git worktree z dysku?\n\nŚcieżka: ${path}\n\nTo usunie cały katalog worktree. Dane sesji pozostaną w WebUI.`, + session_worktree_remove_not_exists: (path) => `Worktree pod ścieżką ${path} już nie istnieje na dysku.`, + session_worktree_remove_confirm_label: 'Usuń', + session_worktree_removed: 'Usunięto worktree.', + session_worktree_remove_failed: 'Nie udało się usunąć worktree: ', + session_worktree_remove_status_failed: 'Nie udało się odczytać statusu worktree: ', + session_worktree_remove_locked_by_stream: 'Nie można usunąć — aktywna sesja strumieniowania korzysta z tego worktree.', + session_worktree_remove_locked_by_terminal: 'Nie można usunąć — aktywna sesja terminala korzysta z tego worktree.', + session_worktree_remove_unsafe_blocked: 'Rozwiąż lokalne zmiany lub niewypchnięte commity przed usunięciem tego worktree.', + session_worktree_remove_dirty_warning: 'OSTRZEŻENIE: To worktree zawiera niezatwierdzone zmiany, które zostaną utracone.', + session_worktree_remove_untracked_warning: (count) => `${count} nieśledzonych plików zostanie trwale usuniętych.`, + session_worktree_remove_ahead_warning: (ahead) => `${ahead} niewypchniętych commitów zostanie utraconych.`, + session_select_mode: 'Wybierz', + session_select_mode_desc: 'Zaznacz konwersacje, aby zarządzać nimi zbiorczo', + session_select_all: 'Zaznacz wszystko', + session_deselect_all: 'Odznacz wszystko', + session_selected_count: '{0} zaznaczonych', + session_batch_archive: 'Zarchiwizuj', + session_batch_delete: 'Usuń', + session_batch_move: 'Przenieś do projektu', + session_batch_delete_confirm: 'Usunąć {0} konwersacji?', + session_batch_archive_confirm: 'Zarchiwizować {0} konwersacji?', + session_batch_delete_worktree_confirm: 'Usunąć {0} konwersacji? {1} konwersacji opartych o worktree pozostawi swoje katalogi worktree na dysku.', + session_batch_archive_worktree_confirm: 'Zarchiwizować {0} konwersacji? {1} konwersacji opartych o worktree zachowa swoje katalogi worktree na dysku.', + session_no_selection: 'Brak zaznaczonych konwersacji', + // settings panel + settings_heading_title: 'Centrum sterowania', + settings_heading_subtitle: 'Preferencje, narzędzia konwersacji i sterowanie systemem.', + settings_section_conversation_title: 'Konwersacja', + settings_section_appearance_title: 'Wygląd', + settings_section_appearance_meta: 'Motyw, akcenty kolorystyczne i styl wizualny.', + settings_section_preferences_title: 'Preferencje', + settings_section_preferences_meta: 'Domyślne ustawienia i zachowanie UI dla Hermes Web UI.', + settings_section_system_title: 'System', + settings_section_system_meta: 'Wersja instancji i kontrola dostępu.', + settings_check_now: 'Sprawdź teraz', + settings_checking: 'Sprawdzanie…', + settings_up_to_date: 'Aktualny ✓', + settings_updates_available: 'Dostępne aktualizacje ({count})', + settings_updates_disabled: 'Sprawdzanie aktualizacji wyłączone', + settings_update_check_failed: 'Sprawdzanie aktualizacji nie powiodło się', + settings_label_workspace_panel_open: 'Zachowaj panel obszaru roboczego domyślnie otwarty', + settings_desc_workspace_panel_open: 'Gdy ta opcja jest włączona, panel obszaru roboczego / przeglądarki plików otwiera się automatycznie przy każdej nowej sesji. Nadal możesz go zamknąć ręcznie w dowolnym momencie.', + settings_label_session_jump_buttons: 'Pokaż przyciski skoku sesji', + settings_desc_session_jump_buttons: 'Pokaż pływające przyciski Początek i Koniec podczas czytania długich historii sesji.', + + settings_label_session_endless_scroll: 'Ładuj starsze wiadomości podczas przewijania w górę', + + settings_desc_session_endless_scroll: 'Gdy ta opcja jest włączona, starsze wiadomości ładują się automatycznie przy przewijaniu w górę. Gdy jest wyłączona, użyj przycisku wczytywania starszych wiadomości.', + settings_label_activity_feed_expanded_default: 'Rozwiń kanał aktywności domyślnie', + settings_desc_activity_feed_expanded_default: 'Rozwijaj nowe szczegóły Aktywności automatycznie, aby postęp narzędzi i modeli był widoczny bez dodatkowego kliknięcia. Ręczne decyzje o zwinięciu/rozwinięciu w danej turze mają pierwszeństwo.', + + settings_label_tab_visibility: 'Karty paska bocznego', + settings_desc_tab_visibility: 'Wybierz, które karty pojawiają się na pasku bocznym i szynie. Przeciągnij elementy, aby zmienić ich kolejność. Czat i Ustawienia są zawsze widoczne.', + open_in_browser: 'Otwórz w przeglądarce', + settings_dropdown_conversation: 'Konwersacja', + settings_dropdown_appearance: 'Wygląd', + settings_dropdown_preferences: 'Preferencje', + settings_dropdown_providers: 'Dostawcy', + settings_dropdown_system: 'System', + settings_tab_conversation: 'Konwersacja', + settings_tab_appearance: 'Wygląd', + settings_tab_preferences: 'Preferencje', + settings_tab_plugins: 'Wtyczki', + settings_plugins_title: 'Wtyczki', + plugins_enable_toggle: 'Włącz', + settings_plugins_meta: 'Zobacz zainstalowane wtyczki Hermes oraz rejestrowane przez nie haki cyklu życia. Ten panel jest tylko do odczytu.', + settings_plugins_empty: 'Żadne wtyczki Hermes nie są obecnie widoczne. Zainstaluj lub włącz wtyczki z poziomu CLI/konfiguracji Hermes, aby zobaczyć je tutaj.', + plugins_unnamed: 'Nienazwana wtyczka', + plugins_no_description: 'Brak opisu.', + plugins_no_hooks: 'Brak zarejestrowanych haków cyklu życia', + plugins_registered_hooks: 'Zarejestrowane haki', + plugins_enabled: 'Włączone', + plugins_disabled: 'Wyłączone', + plugins_active_provider: 'Aktywny (dostawca)', + plugins_provider_no_hooks: 'Wtyczka dostawcy — brak haków widocznych dla agenta', + plugins_load_failed: 'Nie udało się załadować wtyczek: ', + settings_tab_system: 'System', + settings_title: 'Ustawienia', + settings_save_btn: 'Zapisz ustawienia', + settings_label_model: 'Domyślny model', + settings_desc_model: 'Używany dla nowych konwersacji. Istniejące konwersacje zachowują swój wybrany model.', + settings_label_auxiliary_models: 'Modele pomocnicze', + settings_desc_auxiliary_models: 'Pomocnicze trasowanie zadań dla wizji, kompresji, generowania tytułów itp. „Auto” używa Twojego głównego modelu czatu.', + settings_btn_reset_aux_models: 'Resetuj wszystko do auto', + settings_btn_apply_aux_models: 'Zastosuj zmiany', + settings_aux_provider_auto: 'używaj głównego modelu', + settings_aux_model_auto: 'auto (użyj domyślnego dla dostawcy)', + settings_aux_model_custom: 'Niestandardowy model…', + settings_aux_model_custom_prompt: 'Wpisz ID modelu:', + settings_aux_loading: 'Ładowanie modeli pomocniczych…', + settings_aux_load_failed: 'Nie można załadować ustawień modeli pomocniczych. Upewnij się, że API agenta jest dostępne.', + settings_aux_reset_confirm_title: 'Resetować modele pomocnicze?', + settings_aux_reset_confirm_msg: 'To ustawi wszystkie zadania pomocnicze na auto (użycie głównego modelu).', + settings_aux_reset_done: 'Modele pomocnicze zresetowane do auto', + settings_aux_save_failed: 'Nie udało się zapisać modelu pomocniczego', + settings_aux_saved: 'Zaktualizowano modele pomocnicze', + settings_aux_no_changes: 'Brak zmian do zastosowania', + settings_label_send_key: 'Klawisz wysyłania', + settings_label_theme: 'Motyw', + settings_label_skin: 'Skórka', + settings_label_font_size: 'Rozmiar czcionki', + font_size_small: 'Mała', + font_size_default: 'Domyślna', + font_size_large: 'Duża', + font_size_xlarge: 'Bardzo duża', + settings_autosave_saving: 'Zapisywanie…', + settings_autosave_saved: 'Zapisano', + settings_autosave_failed: 'Zapisywanie nie powiodło się', + settings_autosave_retry: 'Ponów próbę', + settings_label_language: 'Język', + settings_label_quota_chip: 'Pokaż wskaźnik limitu dostawcy w polu wprowadzania', + settings_desc_quota_chip: 'Wyświetla subtelny wskaźnik pozostałego limitu (np. saldo kredytów OpenRouter) w stopce pola wprowadzania. Domyślnie wyłączone. Widoczne tylko na szerokich ekranach (≥1400px), aby utrzymać pole wprowadzania czytelnym na laptopach i standardowych monitorach.', + settings_label_hide_suggestions: 'Ukryj sugestie nowej konwersacji', + settings_desc_hide_suggestions: 'Ukryj trzy domyślne przyciski sugestii na pustym ekranie nowej konwersacji, aby zapobiec przypadkowym kliknięciom.', + settings_label_token_usage: 'Pokaż zużycie tokenów', + settings_label_sidebar_density: 'Gęstość paska bocznego', + cmd_reasoning: 'Przełącz widoczność myślenia (pokaż/ukryj), ustaw poziom wysiłku lub sprawdź bieżący status', + settings_label_external_sessions: 'Pokaż sesje inne niż WebUI', + settings_label_cron_sessions: 'Pokaż sesje cron', + settings_label_previous_messaging_sessions: 'Pokaż poprzednie sesje komunikatorów', + settings_label_sync_insights: 'Synchronizuj z Insights', + settings_label_check_updates: 'Sprawdzaj aktualizacje', + settings_label_ignore_agent_updates: 'Ignoruj aktualizacje agenta', + settings_label_whats_new_summary: 'Podsumuj Co Nowego za pomocą AI', + settings_label_bot_name: 'Domyślna nazwa asystenta', + settings_label_password: 'Hasło dostępu', + settings_saved: 'Zapisano ustawienia', + settings_save_failed: 'Zapisywanie nie powiodło się: ', + settings_load_failed: 'Nie udało się załadować ustawień: ', + settings_saved_pw: 'Zapisano ustawienia — ochrona hasłem włączona, ta przeglądarka pozostanie zalogowana', + settings_saved_pw_updated: 'Zapisano ustawienia — hasło zaktualizowane', + // login page (used server-side via /api/i18n/login endpoint) + login_title: 'Zaloguj się', + login_subtitle: 'Wpisz hasło, aby kontynuować', + login_placeholder: 'Hasło', + login_btn: 'Zaloguj się', + login_invalid_pw: 'Nieprawidłowe hasło', + login_conn_failed: 'Połączenie nie powiodło się', + // Sidebar & Tabs + tab_chat: 'Czat', + tab_tasks: 'Zadania', + tab_skills: 'Umiejętności', + tab_memory: 'Pamięć', + tab_workspaces: 'Przestrzenie', + tab_profiles: 'Profile', + tab_kanban: 'Kanban', + kanban_board: 'Tablica', + kanban_visible_tasks: '{0} widocznych zadań', + kanban_search_tasks: 'Szukaj zadań', + kanban_all_assignees: 'Wszyscy przypisani', + kanban_all_tenants: 'Wszyscy dzierżawcy', + kanban_include_archived: 'Uwzględnij zarchiwizowane', + kanban_no_matching_tasks: 'Brak pasujących zadań', + kanban_no_data: 'Brak danych Kanban', + kanban_work_queue_hint: 'To jest kolejka pracy Agenta Hermes. Utwórz zadanie lub dokonaj triażu, przypisz je, przenieś do statusu "Gotowe", a następnie pozwól dyspozytorowi je pobrać.', + kanban_unavailable: 'Kanban niedostępny', + kanban_read_only: 'Widok tylko do odczytu', + kanban_empty: 'Puste', + kanban_task: 'Zadanie', + kanban_no_description: 'Brak opisu', + kanban_refresh: 'Odśwież', + kanban_status_triage: 'Do weryfikacji (Triage)', + kanban_status_todo: 'Do zrobienia', + kanban_status_ready: 'Gotowe', + kanban_status_running: 'Uruchomione', + kanban_status_blocked: 'Zablokowane', + kanban_status_done: 'Zrobione', + kanban_comments_count: 'Komentarze ({0})', + kanban_events_count: 'Zdarzenia ({0})', + kanban_links: 'Linki', + kanban_parents: 'Nadrzędne (rodzice)', + kanban_children: 'Podrzędne (dzieci)', + kanban_runs_count: 'Uruchomienia ({0})', + kanban_no_comments: 'Brak komentarzy', + kanban_no_events: 'Brak zdarzeń', + kanban_no_runs: 'Brak uruchomień', + kanban_title: 'Tytuł', + kanban_description: 'Opis', + kanban_description_placeholder: 'Opcjonalnie — co musi się wydarzyć, kryteria akceptacji, linki', + kanban_status: 'Status', + kanban_assignee: 'Przypisana osoba', + kanban_assignee_placeholder: 'Opcjonalnie — pozostaw puste dla dowolnego wykonawcy', + kanban_tenant: 'Dzierżawca (projekt/zespół)', + kanban_tenant_placeholder: 'Opcjonalnie — identyfikator projektu lub zespołu', + kanban_priority: 'Priorytet', + kanban_priority_hint: 'Wyższe liczby są uruchamiane jako pierwsze. Domyślnie 0.', + kanban_title_required: 'Tytuł jest wymagany.', + kanban_new_task: 'Nowe zadanie', + kanban_edit_task: 'Edytuj zadanie', + kanban_status_original_hint: 'Rzeczywisty status: {0}. To okno dialogowe obsługuje wyłącznie edycję dla "Do weryfikacji" / "Do zrobienia" / "Gotowe".', + kanban_run_dispatcher: 'Uruchom dyspozytora', + kanban_run_dispatcher_confirm: 'Spowoduje to przypisanie zadań o statusie "Gotowe" na tej tablicy i uruchomienie procesów pomocniczych (jeden na zadanie, do 8 na kliknięcie). Kontynuować?', + kanban_assignee_profiles_label: 'Profile Hermes', + kanban_assignee_other_label: 'Inne (ścieżki CLI / usunięte profile)', + kanban_assignee_unassigned: '— Nieprzypisane (nie uruchomi się automatycznie) —', + kanban_ready_needs_assignee: 'Wybrano "Nieprzypisane" + "Gotowe". Dyspozytor pominie to zadanie. Wyślij ponownie, aby potwierdzić, lub wybierz profil.', + kanban_dispatch_preview_prefix: 'Podgląd:', + kanban_dispatch_run_prefix: 'Wysłano:', + kanban_dispatch_spawned: 'uruchomiono', + kanban_dispatch_promoted: 'awansowano', + kanban_dispatch_reclaimed: 'odzyskane', + kanban_dispatch_skipped_unassigned: 'pominięto (brak przypisanego)', + kanban_dispatch_skipped_nonspawnable: 'pominięto (nieznany profil)', + kanban_dispatch_auto_blocked: 'automatycznie zablokowane', + kanban_dispatch_timed_out: 'przekroczono limit czasu', + kanban_dispatch_crashed: 'awaria', + kanban_add_comment: 'Dodaj komentarz', + kanban_only_mine: 'Tylko moje', + kanban_bulk_action: 'Działanie zbiorcze', + kanban_nudge_dispatcher: 'Podgląd dyspozytora', + kanban_stats: 'Statystyki', + kanban_worker_log: 'Log wykonawcy', + kanban_block: 'Zablokuj', + kanban_unblock: 'Odblokuj', + kanban_back_to_board: 'Powrót do tablicy', + kanban_lanes_by_profile: 'Tory według profili', + kanban_new_board: 'Nowa tablica…', + kanban_rename_board: 'Zmień nazwę bieżącej tablicy…', + kanban_archive_board: 'Zarchiwizuj bieżącą tablicę…', + kanban_archive_board_confirm: 'Zarchiwizować tablicę "{name}"? Zadania pozostaną na dysku, a tablica może zostać przywrócona z kanban/boards/_archived/.', + kanban_board_archived: 'Tablica zarchiwizowana', + kanban_board_name: 'Nazwa', + kanban_board_slug: 'Identyfikator (małe litery, myślniki)', + kanban_board_description: 'Opis (opcjonalnie)', + kanban_board_icon: 'Ikona (emoji, opcjonalnie)', + kanban_board_color: 'Kolor (opcjonalnie)', + kanban_board_name_required: 'Nazwa jest wymagana', + kanban_board_slug_required: 'Identyfikator (slug) jest wymagany', + kanban_card_complete: 'ukończone', + kanban_card_archive: 'archiwizuj', + kanban_unassigned: 'nieprzypisane', + kanban_status_archived: 'Zarchiwizowane', + tab_todos: 'Lista zadań', + tab_insights: 'Statystyki', + tab_dashboard: 'Pulpit nawigacyjny Hermes', + dashboard_loopback_warning: 'Pulpit nawigacyjny na serwerze jest dostępny wyłącznie lokalnie (loopback). Przeglądaj bezpośrednio z serwera lub uruchom go ponownie z flagą --host 0.0.0.0 (niebezpieczne).', + tab_logs: 'Logi', + tab_settings: 'Ustawienia', + new_conversation: 'Nowa konwersacja', + filter_conversations: 'Filtruj konwersacje...', + session_time_unknown: 'Nieznany', + session_time_minutes_ago: (n) => `${n} min temu`, + session_time_hours_ago: (n) => `${n} godz. temu`, + session_time_days_ago: (n) => `${n} dni temu`, + session_time_last_week: '1 tydz. temu', + session_time_bucket_today: 'Dzisiaj', + session_time_bucket_yesterday: 'Wczoraj', + session_time_bucket_this_week: 'W tym tygodniu', + session_time_bucket_last_week: 'W zeszłym tygodniu', + session_time_bucket_older: 'Starsze', + scheduled_jobs: 'Zaplanowane zadania', + new_job: 'Nowe zadanie', + loading: 'Ładowanie...', + search_skills: 'Szukaj umiejętności...', + new_skill: 'Nowa umiejętność', + personal_memory: 'Pamięć osobista', + current_task_list: 'Bieżąca lista zadań', + // Logs + logs_title: 'Logi', + logs_file: 'Plik', + logs_tail: 'Śledzenie końcówki', + logs_auto_refresh: 'Automatyczne odświeżanie (5s)', + logs_wrap: 'Zawijaj wiersze', + logs_copy_all: 'Kopiuj wszystko', + logs_empty: 'Brak wpisów w logach.', + logs_loading: 'Ładowanie logów…', + logs_load_failed: 'Nie udało się załadować logów', + logs_status_idle: 'Wybierz plik logów, aby wyświetlić najnowsze wiersze.', + logs_no_mtime: 'jeszcze nie zapisano', + logs_truncated_hint: 'Wyświetlanie końcówki dużego pliku logów; starsze bajty zostały pominięte, aby ograniczyć zużycie pamięci.', + logs_copied: 'Logi skopiowane', + logs_severity: 'Poziom ważności', + logs_severity_all: 'Wszystkie', + logs_severity_errors: 'Błędy', + logs_severity_warnings: 'Ostrzeżenia+', + logs_filter_active: 'wyświetlane (filtr aktywny)', + + // Insights + insights_title: 'Analityka zużycia', + insights_sessions: 'Sesje', + insights_messages: 'Wiadomości', + insights_tokens: 'Tokeny', + insights_cost: 'Szacowany koszt', + insights_no_cost: 'Niedostępne', + insights_models: 'Modele', + insights_activity_by_day: 'Aktywność według dni', + insights_activity_by_hour: 'Aktywność według godzin', + insights_peak_hour: 'Szczyt: {hour}', + insights_token_breakdown: 'Podział tokenów', + insights_input_tokens: 'Wejście', + insights_output_tokens: 'Wyjście', + insights_total: 'Łącznie', + insights_daily_tokens: 'Tokeny dziennie', + insights_model_name: 'Model', + insights_model_sessions: 'Sesje', + insights_model_tokens: 'Tokeny', + insights_model_cost: 'Koszt', + insights_model_share: 'Udział', + insights_no_usage_data: 'Brak danych o zużyciu', + insights_footer: 'Wyświetlanie danych z ostatnich {days} dni', + insights_skill_usage_title: 'Użycie umiejętności', + insights_skill_usage_sub: 'Częstotliwość wywołań narzędzi', + insights_skill_usage_total: 'Wszystkie wywołania', + insights_skill_usage_skills_used: 'Użyte umiejętności', + insights_skill_usage_no_data: 'Brak danych o użyciu umiejętności', + insights_skill_usage_no_data_hint: 'Umiejętności pojawią się tutaj, gdy zostaną użyte w konwersacjach.', + insights_skill_usage_footer: 'Liczba wywołań z katalogu ~/.hermes/skills/', + insights_skill_usage_col_skill: 'Umiejętność', + insights_skill_usage_col_uses: 'Wywołania', + insights_skill_usage_col_views: 'Wyświetlenia', + insights_skill_usage_col_share: 'Użycie %', + insights_skill_usage_col_patches: 'Poprawki', + workspace_desc: 'Dodawaj i przełączaj obszary robocze dla swoich sesji.', + session_meta_messages: (n) => n === 1 ? '1 wiadomość' : `${n} wiadomości`, + session_meta_children: (n) => n === 1 ? '1 podrzędna' : `${n} podrzędne`, + session_meta_segments: (n) => n === 1 ? '1 wcześniejsza tura' : `${n} wcześniejszych tur`, + session_lineage_toggle_hint: '{0} — wcześniejsze tury kontekstu są tutaj zwinięte. Kliknij, aby je pokazać lub ukryć.', + session_lineage_static_hint: '{0} — wcześniejsze tury kontekstu są tutaj zwinięte.', + session_child_toggle_hint: '{0} — konwersacje podrzędne wygenerowane z tej sesji. Kliknij, aby je pokazać lub ukryć.', + session_readonly_title_hint: 'Zaimportowana sesja tylko do odczytu — {0}', + session_lineage_segment_untitled: 'Nienazwany segment', + session_lineage_segment_open: 'Otwórz segment genealogii', + new_profile: 'Nowy profil', + transcript: 'Transkrypcja', + download_transcript: 'Pobierz jako Markdown', + import: 'Importuj', + export_session_json: 'JSON', + export_session_json_tooltip: 'Eksportuj pełną sesję jako JSON', + import_session_json_tooltip: 'Importuj sesję z pliku JSON', + clear_conversation_btn_tooltip: 'Wyczyść wszystkie wiadomości w tej konwersacji', + // Settings detail + settings_label_rtl: 'Układ czatu od prawej do lewej', + settings_desc_rtl: 'Odwraca wyrównanie wiadomości czatu i pola wprowadzania dla języków takich jak arabski czy hebrajski. Wpływa tylko na obszar czatu — pasek boczny i inne panele pozostają wyrównane od lewej do prawej.', + settings_label_sound: 'Dźwięk powiadomienia', + settings_desc_sound: 'Odtwórz dźwięk, gdy asystent zakończy generowanie odpowiedzi.', + // TTS (#499) + tts_listen: 'Słuchaj', + tts_not_supported: 'Synteza mowy nie jest obsługiwana w tej przeglądarce.', + settings_label_tts: 'Zamiana tekstu na mowę (TTS) dla odpowiedzi', + settings_desc_tts: 'Pokaż przycisk głośnika przy każdej wiadomości asystenta, aby przeczytać ją na głos przy użyciu syntezy mowy w przeglądarce.', + settings_label_tts_auto_read: 'Czytaj automatycznie odpowiedzi na głos', + settings_desc_tts_auto_read: 'Automatycznie odczytuj każdą nową odpowiedź asystenta po jej zakończeniu. Pauzuje podczas pisania.', + // Composer voice-mode pref (#1488) + settings_label_voice_mode: 'Przycisk trybu głosowego głośnomówiącego', + settings_desc_voice_mode: 'Pokaż przycisk trybu głosowego (fala dźwiękowa) obok mikrofonu dyktowania. Pozwala mówić naturalnie — Hermes automatycznie wysyła wiadomość po pauzie i czyta odpowiedzi na głos. Wymaga przeglądarki obsługującej zarówno rozpoznawanie mowy, jak i TTS.', + settings_label_raw_audio: 'Wyślij surowy dźwięk zamiast transkrypcji', + settings_desc_raw_audio: 'Nagraj i wyślij oryginalny plik audio do agenta, zamiast najpierw konwertować go na tekst. Agent może go wtedy transkrybować lub przetwarzać surowe audio (emocje, szum tła, własny STT). Podobnie jak wiadomości głosowe w Telegramie.', + voice_send_raw: 'Wyślij surowe audio', + voice_raw_attached: 'Dołączono dźwięk. Naciśnij Wyślij lub wpisz więcej tekstu.', + settings_label_tts_voice: 'Głos', + settings_desc_tts_voice: 'Preferowany głos. Pobierany z dostępnych głosów w Twojej przeglądarce.', + settings_label_tts_rate: 'Tempo mowy', + settings_label_tts_pitch: 'Ton mowy', + settings_label_tts_engine: 'Silnik TTS', + settings_desc_tts_engine: 'Wybierz silnik mowy. Edge TTS korzysta z głosów neuronowych Microsoft za pośrednictwem serwera.', + settings_label_notifications: 'Powiadomienia przeglądarki', + settings_desc_notifications: 'Pokaż powiadomienie systemowe, gdy odpowiedź zostanie ukończona, podczas gdy aplikacja działa w tle.', + settings_desc_token_usage: 'Wyświetla liczbę tokenów wejściowych/wyjściowych pod każdą odpowiedzią asystenta. Można też przełączyć za pomocą /usage.', + settings_label_api_redact: 'Ukrywaj poufne dane w odpowiedziach API', + settings_desc_api_redact: 'Użytkownicy korzystający z hostingu własnego mogą wyłączyć to dla przejrzystości (niezalecane dla współdzielonych instancji).', + settings_sidebar_density_compact: 'Kompaktowa', + settings_sidebar_density_detailed: 'Szczegółowa', + settings_desc_sidebar_density: 'Kontroluje, ile metadanych wyświetla lista sesji na lewym pasku bocznym.', + settings_label_auto_title_refresh: 'Adaptacyjne odświeżanie tytułu', + settings_auto_title_refresh_off: 'Wyłączone', + settings_auto_title_refresh_5: 'Co 5 wymian', + settings_auto_title_refresh_10: 'Co 10 wymian', + settings_auto_title_refresh_20: 'Co 20 wymian', + settings_desc_auto_title_refresh: 'Automatycznie generuje na nowo tytuł konwersacji na podstawie najnowszej wymiany, utrzymując go adekwatnym w miarę rozwoju rozmowy. Wymaga skonfigurowanego modelu LLM do generowania tytułów.', + settings_desc_external_sessions: 'Pokaż konwersacje z CLI, Telegrama, Discorda, Slacka i innych kanałów na liście sesji. Kliknij, aby zaimportować i kontynuować.', + settings_desc_cron_sessions: 'Wyświetlaj wyjście zadań cron jako konwersacje na pasku bocznym. Aktywne tylko wtedy, gdy włączone są sesje spoza WebUI. Domyślnie wyłączone; zadania o wysokiej częstotliwości mogą zalać pasek boczny.', + settings_desc_previous_messaging_sessions: 'Pokaż starsze sesje z Discorda, Telegrama, Slacka i Weixin, które zostały zastąpione przez reset lub kompresję.', + settings_desc_sync_insights: 'Odzwierciedla zużycie tokenów WebUI w state.db, dzięki czemu hermes /insights uwzględnia dane sesji przeglądarki. Domyślnie wyłączone.', + settings_desc_check_updates: 'Pokaż baner, gdy dostępne są nowsze wersje WebUI lub Agenta. Okresowo uruchamia pobieranie git fetch w tle.', + settings_desc_ignore_agent_updates: 'Zachowaj sprawdzanie aktualizacji WebUI, ale ukrywaj powiadomienia o aktualizacjach Agenta i pomijaj pobieranie aktualizacji Agenta.', + settings_desc_whats_new_summary: "Zmienia działanie „Co nowego” z otwierania najpierw surowego diffa na generowanie krótkiego, czytelnego dla człowieka podsumowania przez AI. Zwykłe porównanie diff pozostaje dostępne po podsumowaniu.", + settings_desc_bot_name: 'Używane tylko dla profilu domyślnego. Inne profile używają własnych nazw profilu.', + settings_desc_password: 'Wpisz nowe hasło, aby je ustawić lub zmienić. Pozostaw puste, aby zachować obecne ustawienie.', + password_placeholder: 'Wpisz nowe hasło…', + password_env_var_locked: 'Zmienna środowiskowa HERMES_WEBUI_PASSWORD jest obecnie ustawiona i ma pierwszeństwo. Usuń ją i zrestartuj serwer, aby zarządzać hasłem stąd.', + password_env_var_locked_placeholder: 'Zablokowane: zmienna HERMES_WEBUI_PASSWORD jest ustawiona', + disable_auth: 'Wyłącz uwierzytelnianie', + sign_out: 'Wyloguj się', + settings_label_shutdown: 'Zatrzymaj serwer Hermes WebUI', + settings_desc_shutdown: 'Bezpiecznie zatrzymuje lokalny serwer WebUI. Przydatne, jeśli uruchomiłeś go przez ./ctl.sh start i chcesz zatrzymać bez przełączania się do terminala. Będziesz musiał ponownie uruchomić serwer (./ctl.sh start lub aplikację natywną), zanim WebUI będzie ponownie osiągalny.', + settings_btn_shutdown: 'Zatrzymaj serwer', + settings_shutdown_confirm_title: 'Zatrzymaj Hermes WebUI', + settings_shutdown_confirm_message: 'Zatrzymać serwer Hermes WebUI?', + settings_shutdown_confirm_btn: 'Zatrzymaj', + settings_shutdown_stopped_message: 'Serwer został zatrzymany. Możesz zamknąć tę kartę.', + // Providers panel + providers_tab_title: 'Dostawcy', + providers_section_title: 'Dostawcy', + providers_section_meta: 'Zarządzaj kluczami API dla dostawców AI. Zmiany wchodzą w życie natychmiast.', + providers_status_configured: 'Klucz API skonfigurowany', + providers_status_not_configured: 'Brak klucza API', + providers_status_oauth: 'OAuth', + providers_status_api_key: 'Klucz API', + providers_status_not_configured_label: 'Nieskonfigurowany', + providers_oauth_hint: 'Uwierzytelniono przez OAuth. Klucz API nie jest wymagany.', + providers_oauth_config_yaml_hint: 'Token skonfigurowany w config.yaml. Aby zaktualizować, edytuj sekcję providers w config.yaml lub uruchom hermes auth.', + providers_oauth_not_configured_hint: 'Brak uwierzytelnienia. Uruchom hermes auth w terminalu, aby skonfigurować tego dostawcę.', + providers_save: 'Zapisz', + providers_refresh_models: 'Odśwież modele', + providers_refreshing: 'Odświeżanie…', + providers_models_refreshed: 'Modele odświeżone', + providers_remove: 'Usuń', + providers_saving: 'Zapisywanie…', + providers_removing: 'Usuwanie…', + providers_enter_key: 'Wprowadź klucz API', + providers_empty: 'Nie znaleziono konfigurowalnych dostawców.', + providers_key_updated: 'Klucz API został zapisany', + providers_key_removed: 'Klucz API został usunięty', + providers_key_placeholder_new: 'sk-...', + providers_key_placeholder_replace: 'Wprowadź nowy klucz, aby zastąpić…', + provider_quota_title: 'Aktywna kwota dostawcy', + provider_quota_active_provider: 'Aktywny dostawca', + provider_quota_last_checked_after_refresh: 'Ostatnio sprawdzono po odświeżeniu', + provider_quota_last_checked: 'Ostatnio sprawdzono {0}', + provider_quota_refresh_usage: 'Odśwież zużycie', + provider_quota_refreshing: 'Odświeżanie...', + provider_quota_refresh_title: 'Odśwież limity zużycia dostawcy teraz', + provider_quota_refresh_succeeded: 'Zużycie dostawcy odświeżone', + provider_quota_refresh_failed: 'Odświeżenie zużycia dostawcy nie powiodło się', + provider_quota_session_limit: 'Limit 5-godzinny', + provider_quota_weekly_limit: 'Limit tygodniowy', + provider_quota_window_fallback: 'Okno', + provider_quota_metric_remaining: 'Pozostało', + provider_quota_metric_used: 'Zużyte', + provider_quota_metric_limit: 'Limit', + provider_quota_status_available: 'dostępne', + provider_quota_status_exhausted: 'wyczerpane', + provider_quota_status_unavailable: 'niedostępne', + provider_quota_status_failed: 'nie powiodło się', + provider_quota_status_checked: 'sprawdzone', + provider_quota_status_no_key: 'brak klucza', + provider_quota_status_invalid_key: 'nieprawidłowy klucz', + provider_quota_status_unsupported: 'nieobsługiwane', + provider_quota_used_meta: 'zużyto {0}', + provider_quota_resets_meta: 'resetuje się o {0}', + provider_quota_credential_pool: 'Pula poświadczeń', + provider_quota_credential_label: 'Poświadczenie {0}', + provider_quota_pool_plans: 'Plany: {0}', + provider_quota_pool_no_windows: 'Nie zgłoszono okien limitów konta.', + provider_quota_account_limits_loaded: 'Załadowano limity konta.', + provider_quota_unavailable: 'Status kwoty niedostępny', + provider_quota_retry_after: 'Spróbuj ponownie po {0}', + provider_quota_pool_summary_available: 'dostępne {0}/{1}', + provider_quota_pool_summary_exhausted: '{0} wyczerpanych', + provider_quota_pool_summary_failed: '{0} nie powiodło się', + provider_quota_pool_summary_checked: '{0} sprawdzonych', + cancel: 'Anuluj', + create_job: 'Utwórz zadanie', + save_skill: 'Zapisz umiejętność', + editing: 'Edytowanie', + // Empty state + empty_title: 'W czym mogę pomóc?', + empty_subtitle: 'Zadaj dowolne pytanie, uruchamiaj polecenia, przeglądaj pliki lub zarządzaj zaplanowanymi zadaniami.', + suggest_files: 'Jakie pliki znajdują się w tym obszarze roboczym?', + suggest_schedule: 'Co mam dziś w harmonogramie?', + suggest_plan: 'Pomóż mi zaplanować mały projekt.', + // onboarding + onboarding_badge: 'PIERWSZE URUCHOMIENIE', + onboarding_title: 'Witaj w Hermes Web UI', + onboarding_lead: 'Szybki, prowadzony krok po kroku proces konfiguracji zweryfikuje Hermes, zapisze rzeczywistą konfigurację dostawcy, wybierze obszar roboczy oraz model i opcjonalnie zabezpieczy aplikację hasłem.', + onboarding_back: 'Wstecz', + onboarding_continue: 'Dalej', + onboarding_skip: 'Pomiń konfigurację', + onboarding_skipped: 'Pominięto konfigurację — używanie istniejącej konfiguracji.', + onboarding_open: 'Otwórz Hermes', + onboarding_step_system_title: 'Sprawdzenie systemu', + onboarding_step_system_desc: 'Zweryfikuj dostępność Agenta Hermes i widoczność konfiguracji.', + onboarding_step_setup_title: 'Konfiguracja dostawcy', + onboarding_step_setup_desc: 'Zapisz minimalną konfigurację dostawcy Hermes.', + onboarding_step_workspace_title: 'Obszar roboczy + model', + onboarding_step_workspace_desc: 'Wybierz domyślne ustawienia dla nowych sesji i czatu.', + onboarding_step_password_title: 'Opcjonalne hasło', + onboarding_step_password_desc: 'Opcjonalnie zabezpiecz Web UI przed udostępnieniem.', + onboarding_step_finish_title: 'Koniec', + onboarding_step_finish_desc: 'Przejrzyj i wejdź do aplikacji.', + onboarding_notice_system_ready: 'Agent Hermes wydaje się osiągalny z poziomu Web UI.', + onboarding_notice_system_unavailable: 'Agent Hermes nie jest jeszcze w pełni dostępny. Bootstrap może go zainstalować, ale konfiguracja dostawcy może nadal wymagać terminala.', + onboarding_check_agent: 'Agent Hermes', + onboarding_check_agent_ready: 'Wykryty i możliwy do zaimportowania', + onboarding_check_agent_missing: 'Brakujący lub tylko częściowo importowalny', + onboarding_check_password: 'Hasło', + onboarding_check_password_enabled: 'Już włączone', + onboarding_check_password_disabled: 'Jeszcze niewłączone', + onboarding_check_provider: 'Konfiguracja dostawcy', + onboarding_check_provider_ready: 'Gotowy do czatu', + onboarding_check_provider_partial: 'Zapisana, lecz niekompletna', + onboarding_check_provider_pending: 'Wymaga weryfikacji', + onboarding_config_file: 'Plik konfiguracyjny:', + onboarding_env_file: 'Plik .env:', + onboarding_unknown: 'Nieznany', + onboarding_current_provider: 'Bieżąca konfiguracja:', + onboarding_missing_imports: 'Brakujące importy:', + onboarding_notice_setup_required: 'Wybierz tutaj prostą ścieżkę dostawcy. Zaawansowane przepływy OAuth na razie pozostają w CLI Hermes.', + onboarding_notice_setup_already_ready: 'Działająca konfiguracja dostawcy Hermes została już wykryta. Możesz ją zachować lub zastąpić tutaj.', + onboarding_oauth_provider_ready_title: 'Dostawca został już uwierzytelniony', + onboarding_oauth_provider_ready_body: 'Ta instancja jest skonfigurowana do korzystania z dostawcy OAuth ({provider}), który został skonfigurowany za pomocą CLI Hermes. Klucz API nie jest tutaj wymagany — kliknij Dalej, aby ukończyć konfigurację.', + onboarding_oauth_provider_not_ready_title: 'Dostawca OAuth nie został jeszcze uwierzytelniony', + onboarding_oauth_provider_not_ready_body: 'Ta instancja jest skonfigurowana do korzystania z {provider}, który korzysta z OAuth zamiast klucza API. Uruchom hermes auth lub hermes model w terminalu, aby uwierzytelnić, a następnie odśwież Web UI.', + onboarding_oauth_switch_hint: 'Lub wybierz innego dostawcę poniżej, aby przełączyć się na konfigurację z kluczem API:', + oauth_login_codex: 'Zaloguj się przez Codex (ChatGPT)', + oauth_codex_step1: 'Krok 1: Odwiedź ten URL i wpisz kod', + oauth_codex_step2: 'Krok 2: Wpisz ten kod na stronie', + oauth_codex_polling: 'Oczekiwanie na autoryzację...', + oauth_codex_success: 'Logowanie Codex OAuth powiodło się!', + oauth_codex_error: 'Logowanie OAuth nie powiodło się', + oauth_codex_expired: 'Kod wygasł, spróbuj ponownie', + onboarding_notice_workspace: 'Te wartości używają tych samych API ustawień, co zwykła aplikacja.', + onboarding_workspace_label: 'Obszar roboczy', + onboarding_workspace_or_path: 'Lub wprowadź ścieżkę obszaru roboczego', + onboarding_workspace_placeholder: '/home/uzytkownik/workspace', + onboarding_provider_label: 'Tryb konfiguracji', + onboarding_quick_setup_badge: 'szybka konfiguracja', + provider_category_easy_start: 'Łatwy start', + provider_category_self_hosted: 'Lokalne / hosting własny', + provider_category_specialized: 'Specjalistyczne', + onboarding_api_key_label: 'Klucz API', + onboarding_api_key_placeholder: 'Pozostaw puste, aby zachować istniejący zapisany klucz', + onboarding_api_key_label_optional: 'Klucz API (opcjonalny)', + onboarding_api_key_placeholder_optional: 'Pozostaw puste dla serwerów bez klucza', + onboarding_api_key_help_keyless: 'Większość instalacji LM Studio / Ollama / vLLM działa bez klucza — pozostaw to pole puste, jeśli Twój serwer nie wymaga uwierzytelniania. Użyj przycisku "Testuj połączenie", aby zweryfikować.', + onboarding_api_key_help_prefix: 'Zapisano jako klucz tajny w pliku .env aplikacji Hermes przy użyciu', + onboarding_base_url_label: 'Adres bazowy URL', + onboarding_base_url_placeholder: 'https://twoj-punkt-koncowy.example/v1', + onboarding_base_url_help: 'Użyj tego dla routerów zgodnych z OpenAI, serwerów hostowanych samodzielnie, LiteLLM, Ollama, LM Studio, vLLM lub podobnych punktów końcowych.', + onboarding_model_label: 'Domyślny model', + onboarding_workspace_help: 'Wybierz model, którego Hermes powinien używać dla nowych czatów po zakończeniu konfiguracji.', + onboarding_custom_model_placeholder: 'nazwa-twojego-modelu', + onboarding_custom_model_help: 'Dla niestandardowych punktów końcowych wprowadź dokładny identyfikator modelu oczekiwany przez Twój serwer.', + onboarding_notice_password_enabled: 'Hasło zostało już skonfigurowane. Wprowadź nowe tylko wtedy, gdy chcesz je zastąpić.', + onboarding_notice_password_recommended: 'Opcjonalne, lecz zalecane, jeśli udostępniasz interfejs poza localhost.', + onboarding_password_label: 'Hasło (opcjonalne)', + onboarding_password_placeholder: 'Pozostaw puste, aby pominąć', + onboarding_password_help: 'Hasła są przechowywane za pośrednictwem istniejącego API ustawień i haszowane po stronie serwera.', + onboarding_notice_finish: 'Możesz otworzyć Ustawienia ponownie później, aby to zmienić.', + onboarding_not_set: 'Nieustawione', + onboarding_password_will_enable: 'Zostanie włączone', + onboarding_password_will_replace: 'Zostanie zastąpione', + onboarding_password_keep_existing: 'Zachowaj obecne hasło', + onboarding_password_remains_disabled: 'Pozostanie wyłączone', + onboarding_password_skipped: 'Na razie pominięte', + onboarding_finish_help: 'Zakończenie zapisuje wartość onboarding_completed w ustawieniach i przenosi Cię do właściwej aplikacji.', + onboarding_error_choose_workspace: 'Wybierz obszar roboczy przed kontynuowaniem.', + onboarding_error_choose_model: 'Wybierz model przed kontynuowaniem.', + onboarding_error_provider_required: 'Wybierz tryb konfiguracji przed kontynuowaniem.', + onboarding_error_base_url_required: 'URL bazowy jest wymagany dla niestandardowych punktów końcowych.', + onboarding_probe_test_button: 'Testuj połączenie', + onboarding_probe_probing: 'Testowanie połączenia…', + onboarding_probe_ok: 'Połączono. Dostępnych modeli: {n}.', + onboarding_probe_error_generic: 'Nie można nawiązać połączenia ze skonfigurowanym adresem URL bazy.', + onboarding_probe_error_invalid_url: 'Adres URL bazy musi zaczynać się od http:// lub https://.', + onboarding_probe_error_dns: 'Nie można rozpoznać nazwy hosta. Sprawdź adres URL lub użyj adresu IP hosta.', + onboarding_probe_error_connect_refused: 'Połączenie odrzucone — serwer może nie być uruchomiony pod tym adresem. Przy korzystaniu z Docker spróbuj podać IP hosta zamiast localhost.', + onboarding_probe_error_timeout: 'Punkt końcowy nie odpowiedział na czas. Sprawdź, czy serwer działa i czy adres URL jest prawidłowy.', + onboarding_probe_error_http_4xx: 'Punkt końcowy zwrócił błąd klienta. Sprawdź uwierzytelnienie i ścieżkę URL (zazwyczaj kończy się na /v1).', + onboarding_probe_error_http_5xx: 'Punkt końcowy zwrócił błąd serwera. Sprawdź logi serwera LM Studio / Ollama.', + onboarding_probe_error_parse: 'Punkt końcowy nie zwrócił listy modeli w oczekiwanym formacie. Zweryfikuj, czy URL wskazuje na główny katalog API zgodnego z OpenAI.', + onboarding_probe_error_unreachable: 'Nie można nawiązać połączenia ze skonfigurowanym adresem URL bazy.', + onboarding_error_probe_failed: 'Nie udało się zweryfikować skonfigurowanego adresu URL bazy.', + onboarding_error_workspace_required: 'Obszar roboczy jest wymagany.', + onboarding_error_model_required: 'Model jest wymagany.', + onboarding_complete: 'Konfiguracja wstępna zakończona', + + // panel/runtime i18n + error_prefix: 'Błąd: ', + default: 'domyślny', + search: 'Szukaj', + not_available: 'Niedostępne', + never: 'nigdy', + add: 'Dodaj', + add_failed: 'Dodawanie nie powiodło się: ', + remove_failed: 'Usuwanie nie powiodło się: ', + switch_failed: 'Przełączanie nie powiodło się: ', + name_required: 'Nazwa jest wymagana', + content_required: 'Zawartość jest wymagana', + view: 'Wyświetl', + dismiss: 'Odrzuć', + disable: 'Wyłącz', + cron_no_jobs: 'Nie znaleziono zaplanowanych zadań.', + cron_status_off: 'wyłączone', + cron_status_paused: 'wstrzymane', + cron_status_error: 'błąd', + cron_status_active: 'aktywne', + cron_status_running: 'uruchomione…', + cron_status_needs_attention: 'wymaga uwagi', + cron_attention_desc: 'To powtarzające się zadanie nie ma zaplanowanego czasu następnego uruchomienia. Harmonogram mógł nie obliczyć prawidłowo kolejnego wywołania.', + cron_attention_croniter_hint: 'Środowisko uruchomieniowe bramy może nie posiadać pakietu croniter. Zrestartuj bramę z obsługą cron, a następnie wznów to zadanie.', + cron_attention_resume: 'Wznów i przelicz ponownie', + cron_jobs_project: 'Zadania Cron', + cron_attention_run_once: 'Uruchom raz teraz', + cron_attention_copy_diagnostics: 'Kopiuj diagnostykę', + cron_diagnostics_copied: 'Skopiowano diagnostykę zadań cron', + cron_next: 'Następny', + cron_last: 'Ostatni', + cron_run_now: 'Uruchom teraz', + cron_pause: 'Wstrzymaj', + cron_resume: 'Wznów', + cron_job_name_placeholder: 'Nazwa zadania', + cron_schedule_placeholder: 'Harmonogram', + cron_prompt_placeholder: 'Zapytanie (prompt)', + cron_last_output: 'Ostatnie wyjście', + cron_expand_prompt: 'Rozwiń zapytanie', + cron_collapse_prompt: 'Zwiń zapytanie', + cron_expand_output: 'Rozwiń wyjście', + cron_collapse_output: 'Zwiń wyjście', + cron_view_full_output: 'Wyświetl pełne wyjście', + cron_all_runs: 'Wszystkie uruchomienia', + cron_hide_runs: 'Ukryj uruchomienia', + cron_no_runs_yet: '(brak uruchomień)', + cron_schedule_required_example: 'Harmonogram jest wymagany (np. "0 9 * * *" lub "every 1h")', + cron_schedule_required: 'Harmonogram jest wymagany', + cron_prompt_required: 'Zapytanie (prompt) jest wymagane', + cron_job_created: 'Utworzono zadanie', + cron_duplicate: 'Zduplikuj', + cron_duplicated: 'Zadanie zduplikowane (wstrzymane)', + cron_job_triggered: 'Zadanie wywołane', + cron_job_paused: 'Zadanie wstrzymane', + cron_job_resumed: 'Zadanie wznowione', + cron_job_updated: 'Zadanie zaktualizowane', + cron_delete_confirm_title: 'Usuń zadanie cron', + cron_delete_confirm_message: 'Tej operacji nie można cofnąć.', + cron_job_deleted: 'Zadanie usunięte', + cron_completion_status: (name, status) => `Cron "${name}" — ${status}`, + status_failed: 'nie powiodło się', + status_completed: 'ukończone', + todos_no_active: 'Brak aktywnej listy zadań w tej sesji.', + clear_conversation_title: 'Wyczyść konwersację', + clear_conversation_message: 'Wyczyścić wszystkie wiadomości? Tej operacji nie można cofnąć.', + clear_failed: 'Czyszczenie nie powiodło się: ', + skills_no_match: 'Brak pasujących umiejętności.', + skill_enabled: 'Włączone', + skill_disabled: 'Wyłączone', + skill_toggle_failed: 'Nie udało się przełączyć umiejętności: ', + linked_files: 'Połączone pliki', + skill_load_failed: 'Nie można załadować umiejętności: ', + skill_file_load_failed: 'Nie można załadować pliku: ', + skill_name_required: 'Nazwa umiejętności jest wymagana', + skill_updated: 'Umiejętność zaktualizowana', + skill_created: 'Utworzono umiejętność', + skill_deleted: 'Umiejętność usunięta', + skill_delete_confirm: 'Usunąć umiejętność "{0}"?', + skills_empty_title: 'Wybierz umiejętność', + skills_empty_sub: 'Wybierz umiejętność z paska bocznego, aby wyświetlić jej zawartość, lub utwórz nową.', + skills_edit: 'Edytuj', + skills_delete: 'Usuń', + skills_back_to: 'Powrót do {0}', + tasks_empty_title: 'Wybierz zaplanowane zadanie', + tasks_empty_sub: 'Wybierz zadanie z paska bocznego, aby wyświetlić szczegóły i uruchomienia, lub utwórz nowe.', + workspaces_empty_title: 'Wybierz przestrzeń', + workspaces_empty_sub: 'Wybierz przestrzeń z paska bocznego, aby wyświetlić jej pliki i ustawienia, lub dodaj nową.', + profiles_empty_title: 'Wybierz profil', + profiles_empty_sub: 'Wybierz profil agenta z paska bocznego, aby przeglądać i edytować jego ustawienia, lub utwórz nowy.', + memory_notes_label: 'pamięć (notatki)', + memory_saved: 'Zapisano pamięć', + my_notes: 'Moje notatki', + user_profile: 'Profil użytkownika', + external_notes_sources: 'Notatki zewnętrzne (firm trzecich)', + external_notes_empty: 'Nie są jeszcze widoczne żadne zewnętrzne źródła notatek ani wiedzy MCP. Skonfiguruj Joplin, Obsidian, Notion, llm-wiki lub inny serwer notatek, aby wyświetlić go na tej liście.', + external_notes_auto_recall_hint: 'Automatyczne wywoływanie sesji pozostaje bez zmian; ta szuflada wyświetla tylko skonfigurowane źródła oraz dostępne narzędzia odczytu/wyszukiwania.', + external_notes_no_tools: 'Obecnie brak widocznych narzędzi odczytu/wyszukiwania dla tego źródła.', + external_notes_tool_count: (count) => count === 1 ? '1 narzędzie do notatek dostępne' : `${count} narzędzi do notatek dostępnych`, + external_notes_configured_hint: 'Oczekiwane są nazwy narzędzi z tego skonfigurowanego źródła; schematy live pojawią się, gdy środowisko uruchomieniowe WebUI je udostępni.', + external_notes_recent_ai: 'Ostatnio używane przez AI', + external_notes_auto: 'auto', + external_notes_recent_ai_reason: 'Automatyczne przywoływanie', + external_notes_search_placeholder: 'Szukaj notatek…', + external_notes_search_empty: 'Przeszukaj skonfigurowane źródło notatek, aby wyświetlić ich podgląd tutaj.', + source_active: 'aktywne', + source_configured: 'skonfigurowane', + no_notes_yet: 'Brak notatek.', + no_profile_yet: 'Brak profilu.', + agent_soul: 'Dusza agenta', + no_soul_yet: 'Dusza nie została jeszcze zdefiniowana.', + workspace_choose_path: 'Wybierz ścieżkę obszaru roboczego', + workspace_choose_path_meta: 'Dodaj zweryfikowaną ścieżkę i przełącz tę konwersację', + workspace_manage: 'Zarządzaj obszarami roboczymi', + workspace_manage_meta: 'Otwórz panel Przestrzenie', + workspace_use_title: 'Użyj w bieżącej sesji', + workspace_use: 'Użyj', + workspace_add_path_placeholder: 'Dodaj ścieżkę obszaru roboczego (np. /home/uzytkownik/moj-projekt)', + workspace_paths_validated_hint: 'Ścieżki są weryfikowane jako istniejące katalogi przed zapisaniem.', + + workspace_drag_hint: 'Przeciągnij, aby zmienić kolejność', + workspace_reorder_failed: 'Zmiana kolejności nie powiodła się', + workspace_added: 'Dodano obszar roboczy', + workspace_renamed: 'Zmieniono nazwę obszaru roboczego', + workspace_remove_confirm_title: 'Usuń obszar roboczy', + workspace_remove_confirm_message: (path) => `Usunąć „${path}”?`, + workspace_removed: 'Usunięto obszar roboczy', + workspace_switch_prompt_title: 'Przełącz obszar roboczy', + workspace_switch_prompt_message: 'Wprowadź absolutną ścieżkę obszaru roboczego, aby dodać i przełączyć tę konwersację.', + workspace_switch_prompt_confirm: 'Przełącz', + workspace_switch_prompt_placeholder: '/Users/uzytkownik/projekt', + workspace_not_added: 'Obszar roboczy nie został dodany', + workspace_already_saved: 'Obszar roboczy został już zapisany — wybierz go z listy', + workspace_busy_switch: 'Nie można przełączyć obszaru roboczego, gdy agent jest uruchomiony', + discard_file_edits_title: 'Odrzucić edycję plików?', + discard_file_edits_message: 'Przełączenie obszaru roboczego odrzuci niezapisane edycje plików w podglądzie.', + workspace_switched_to: (name) => `Przełączono na ${name}`, + profiles_no_profiles: 'Nie znaleziono profili.', + profile_api_keys_configured: 'Klucze API skonfigurowane', + profile_gateway_running: 'Brama jest uruchomiona', + profile_gateway_stopped: 'Brama została zatrzymana', + profile_active: 'AKTYWNY', + profile_no_configuration: 'Brak konfiguracji', + profile_skill_count: (count) => `${count} umiejętność/umiejętności`, + profile_use: 'Użyj', + profile_switch_title: 'Przełącz na ten profil', + profile_delete_title: 'Usuń ten profil', + profile_default_label: '(domyślny)', + profile_name_placeholder: 'Nazwa profilu (małe litery, a-z 0-9, myślniki)', + profile_clone_label: 'Klonuj konfigurację z aktywnego profilu', + profile_model_label: 'Model / dostawca', + profile_model_hint: 'Wybierz spośród skonfigurowanych dostawców i modeli dla tego nowego profilu.', + profile_model_use_default: 'Użyj domyślnego dla aktywnego profilu', + profile_base_url_placeholder: 'Adres bazowy URL (opcjonalnie, np. http://localhost:11434)', + profile_api_key_placeholder: 'Klucz API (opcjonalnie)', + manage_profiles: 'Zarządzaj profilami', + profiles_load_failed: 'Nie udało się załadować profili', + profile_switched_new_conversation: (name) => `Przełączono na profil: ${name} — rozpoczęto nową konwersację`, + profile_switched: (name) => `Przełączono na profil: ${name}`, + profile_name_rule: 'Tylko małe litery, cyfry, myślniki i podkreślenia', + profile_base_url_rule: 'URL bazowy musi zaczynać się od http:// lub https://', + profile_created: (name) => `Profil utworzony: ${name}`, + profile_delete_confirm_title: (name) => `Usunąć profil „${name}”?`, + profile_delete_confirm_message: 'Wszystkie sesje, konfiguracja, umiejętności i pamięć dla tego profilu zostaną trwale usunięte. Tej operacji nie można cofnąć.', + profile_deleted: (name) => `Profil usunięty: ${name}`, + active_conversation_none: 'Nie wybrano aktywnej konwersacji.', + active_conversation_meta: (title, count) => count === 1 ? `${title} · 1 wiadomość` : `${title} · ${count} wiadomości`, + settings_unsaved_changes: 'Masz niezapisane zmiany.', + sign_out_failed: 'Wylogowanie nie powiodło się: ', + disable_auth_confirm_title: 'Wyłącz ochronę hasłem', + disable_auth_confirm_message: 'Każdy będzie mógł uzyskać dostęp do tej instancji.', + auth_disabled: 'Uwierzytelnianie wyłączone — usunięto ochronę hasłem', + disable_auth_failed: 'Wyłączenie uwierzytelniania nie powiodło się: ', + bg_error_single: (title) => `W „${title}” wystąpił błąd`, + bg_error_multi: (count) => `W ${count} sesjach wystąpił błąd`, + // skill form + skill_name: 'Nazwa', + skill_category: 'Kategoria', + skill_category_placeholder: 'Opcjonalnie, np. devops', + skill_content: 'Zawartość SKILL.md', + skill_content_placeholder: 'Frontmatter YAML + zawartość Markdown', + skill_rename_not_supported: 'Zmiana nazwy umiejętności nie jest obsługiwana. Utwórz nową umiejętność i usuń starą, aby zmienić nazwę.', + skill_metadata: 'Metadane', + // cron form + cron_name_label: 'Nazwa', + cron_name_placeholder: 'Opcjonalnie', + cron_schedule_label: 'Harmonogram', + cron_schedule_hint: "Użyj 'every 1h' lub wyrażenia cron dla zadań cyklicznych. Same formy czasu trwania, jak '30m', zostaną uruchomione tylko raz.", + cron_schedule_once_warning: 'Formy czasu trwania, takie jak \'30m\', są uruchamiane raz i usuwane po uruchomieniu. Użyj \'every 30m\', aby utrzymać zadanie cykliczne.', + cron_prompt_label: 'Monit', + cron_deliver_label: 'Dostarcz wyjście do', + cron_deliver_local: 'Lokalnie (tylko zapisz wyjście)', + cron_deliver_custom: 'Niestandardowy cel dostarczenia', + cron_profile_label: 'Profil', + cron_profile_server_default: 'domyślny serwera', + cron_profile_server_default_hint: 'Używa domyślnego profilu serwera WebUI w czasie uruchomienia. Istniejące zadania bez przypisanego profilu zachowują to dotychczasowe działanie.', + cron_toast_notifications_label: 'Powiadomienia o zakończeniu', + cron_toast_notifications_hint: 'Pokaż powiadomienie (toast), gdy to zadanie cron się zakończy. Plakietka Zadań oraz znacznik nowego uruchomienia będą nadal aktualizowane, nawet gdy ta opcja jest wyłączona.', + cron_toast_notifications_enabled: 'Włączone', + cron_toast_notifications_disabled: 'Wyłączone', + cron_skills_label: 'Umiejętności', + cron_skills_placeholder: 'Dodaj umiejętności (opcjonalnie)…', + cron_skills_edit_hint: 'Lista umiejętności nie jest edytowalna po utworzeniu.', + // workspace form + workspace_name_label: 'Nazwa', + workspace_name_placeholder: 'Opcjonalna przyjazna nazwa', + workspace_path_label: 'Ścieżka', + workspace_path_required: 'Ścieżka jest wymagana', + workspace_path_readonly: 'Ścieżka nie może być zmieniona. Możliwa tylko zmiana nazwy.', + workspace_new_title: 'Nowa przestrzeń', + // profile form + profile_name_label: 'Nazwa', + profile_base_url_label: 'Adres bazowy URL', + profile_api_key_label: 'Klucz API', + cmd_yolo: 'Przełącz tryb YOLO (pomiń aprobaty)', + yolo_no_session: 'Brak aktywnej sesji', + yolo_enabled: '⚡ Tryb YOLO WŁĄCZONY — pomijanie aprobat w tej sesji', + yolo_disabled: 'Tryb YOLO WYŁĄCZONY', + yolo_pill_label: 'YOLO', + yolo_pill_title_active: 'Tryb YOLO aktywny — kliknij, aby wyłączyć', + approval_skip_all: '⚡ Pomiń wszystkie w tej sesji', + approval_skip_all_title: 'Pomiń wszystkie monity o aprobatę w tej sesji', + // composer action tooltips + composer_send: 'Wyślij wiadomość', + composer_queue: 'Kolejkuj wiadomość', + composer_interrupt: 'Przerwij i wyślij', + composer_steer: 'Steruj bieżącą odpowiedzią', + composer_stop: 'Zatrzymaj generowanie', + composer_disabled_clarify: 'Odpowiedz na prośbę o wyjaśnienie', + composer_disabled_compression: 'Oczekiwanie na zakończenie kompresji', + composer_compression_will_queue: 'Wpisz wiadomość — zostanie zakolejkowana i wysłana po zakończeniu kompresji', + composer_disabled_empty: 'Wpisz wiadomość, aby wysłać', + composer_mobile_workspace: 'Obszar roboczy', + composer_mobile_model: 'Model', + composer_mobile_reasoning: 'Myślenie', + composer_mobile_context: 'Kontekst', + media_audio_label: 'Dźwięk', + media_svg_label: 'Diagram', + media_video_label: 'Wideo', + media_download: 'Pobierz', + csv_loading: 'Ładowanie CSV', + csv_too_large: 'Plik CSV jest zbyt duży dla renderowania w linii', + csv_no_data: 'Plik CSV nie zawiera wystarczającej liczby danych, aby wyrenderować go jako tabelę', + csv_error: 'Nie udało się załadować pliku CSV', + csv_header_note: 'Pierwszy wiersz wyświetlany jako nagłówek tabeli', + excalidraw_loading: 'Ładowanie diagramu', + excalidraw_too_large: 'Plik Excalidraw jest zbyt duży dla renderowania w linii', + excalidraw_invalid: 'Nieprawidłowy format pliku Excalidraw', + excalidraw_error: 'Nie udało się załadować pliku Excalidraw', + excalidraw_label: 'Diagram', + excalidraw_download: 'Pobierz', + excalidraw_empty: 'Pusty diagram', + excalidraw_render_error: 'Nie udało się wyrenderować diagramu', + excalidraw_simplified: 'Uproszczony podgląd SVG — nie jest identyczny piksel w piksel z obszarem roboczym Excalidraw', + // ── Checkpoints / Rollback ── + checkpoint_title: 'Checkpoints (punkty kontrolne)', + checkpoint_empty: 'Nie znaleziono punktów kontrolnych dla tego obszaru roboczego.', + checkpoint_loading: 'Ładowanie punktów kontrolnych…', + checkpoint_error: 'Nie udało się załadować punktów kontrolnych', + checkpoint_date: 'Data', + checkpoint_message: 'Wiadomość', + checkpoint_files: 'Pliki', + checkpoint_view_diff: 'Wyświetl diff', + checkpoint_restore: 'Przywróć', + checkpoint_restore_confirm_title: 'Przywrócić punkt kontrolny?', + checkpoint_restore_confirm_message: (ckpt) => `Przywrócić obszar roboczy do punktu kontrolnego "${ckpt}"? To nadpisze pliki wersjami zapisanymi. Pliki dodane po tym punkcie kontrolnym nie zostaną usunięte.`, + checkpoint_restored: 'Punkt kontrolny przywrócony', + checkpoint_diff_title: 'Zmiany w punkcie kontrolnym', + checkpoint_diff_no_changes: 'Nie znaleziono różnic między tym punktem kontrolnym a obecnym obszarem roboczym.', + checkpoint_diff_files_changed: (n) => n === 1 ? '1 plik zmieniony' : `${n} zmienionych plików`, + }, }; // Active locale — defaults to English; overridden by loadLocale() at boot. diff --git a/tests/test_2735_open_in_vscode.py b/tests/test_2735_open_in_vscode.py index 207d358da..79184ed1d 100644 --- a/tests/test_2735_open_in_vscode.py +++ b/tests/test_2735_open_in_vscode.py @@ -229,7 +229,7 @@ class TestOpenInVsCodeI18n: """open_in_vscode key must appear exactly once per locale (11 total).""" src = I18N.read_text(encoding="utf-8") count = src.count("open_in_vscode:") - assert count == 11, ( + assert count == 12, ( f"Expected 11 open_in_vscode: entries (one per locale), found {count}" ) @@ -237,7 +237,7 @@ class TestOpenInVsCodeI18n: """open_in_vscode_failed key must appear exactly once per locale (11 total).""" src = I18N.read_text(encoding="utf-8") count = src.count("open_in_vscode_failed:") - assert count == 11, ( + assert count == 12, ( f"Expected 11 open_in_vscode_failed: entries (one per locale), found {count}" ) diff --git a/tests/test_auxiliary_models_settings.py b/tests/test_auxiliary_models_settings.py index 012eecb5b..5ec6684e0 100644 --- a/tests/test_auxiliary_models_settings.py +++ b/tests/test_auxiliary_models_settings.py @@ -162,7 +162,7 @@ class TestAuxiliaryModelsI18n: """Count of each key should equal the number of locales (12 with Turkish).""" for key in self.REQUIRED_KEYS: count = I18N_JS.count(f"{key}:") - assert count == 12, ( + assert count == 13, ( f"i18n key '{key}' found {count} times — expected 12 (one per locale)" ) diff --git a/tests/test_issue1488_composer_voice_buttons.py b/tests/test_issue1488_composer_voice_buttons.py index 72870e196..0f08eb251 100644 --- a/tests/test_issue1488_composer_voice_buttons.py +++ b/tests/test_issue1488_composer_voice_buttons.py @@ -123,7 +123,7 @@ class TestComposerVoiceButtonI18n: "voice_mode_toggle_active", ) - LOCALES = ("en", "fr", "it", "ja", "ru", "es", "de", "zh", "zh-Hant", "pt", "ko", "tr") + LOCALES = ("en", "fr", "it", "ja", "ru", "es", "de", "zh", "zh-Hant", "pt", "ko", "tr", "pl") def test_legacy_voice_toggle_key_removed(self): """The old key whose string was 'Voice input' caused the duplicate- @@ -171,7 +171,7 @@ class TestComposerVoiceButtonI18n: class TestVoiceModePreferenceGate: """boot.js must hide btnVoiceMode by default, surface it via Preferences.""" - LOCALES = ("en", "fr", "it", "ja", "ru", "es", "de", "zh", "zh-Hant", "pt", "ko", "tr") + LOCALES = ("en", "fr", "it", "ja", "ru", "es", "de", "zh", "zh-Hant", "pt", "ko", "tr", "pl") def test_voice_mode_pref_is_localstorage_backed(self): """The pref reads from localStorage key 'hermes-voice-mode-button'.""" diff --git a/tests/test_issue2419_cache_usage_display.py b/tests/test_issue2419_cache_usage_display.py index e5cd88488..e70258893 100644 --- a/tests/test_issue2419_cache_usage_display.py +++ b/tests/test_issue2419_cache_usage_display.py @@ -63,8 +63,8 @@ def test_context_indicator_surfaces_cache_hit_rate(): def test_cache_usage_labels_are_localized(): src = (ROOT / "static" / "i18n.js").read_text() - assert src.count("usage_cache_hit_detail:") == 12 - assert src.count("usage_cached_percent:") == 12 + assert src.count("usage_cache_hit_detail:") == 13 + assert src.count("usage_cached_percent:") == 13 assert "usage_cache_hit_detail: 'Cache: {0}% hit ({1} read / {2} write)'" in src assert "usage_cached_percent: '{0}% cached'" in src diff --git a/tests/test_issue2679_hide_suggestions.py b/tests/test_issue2679_hide_suggestions.py index 1b4f24e8a..893d02c1b 100644 --- a/tests/test_issue2679_hide_suggestions.py +++ b/tests/test_issue2679_hide_suggestions.py @@ -50,8 +50,8 @@ def test_panels_round_trip_and_hot_apply_hide_suggestions(): def test_hide_suggestions_i18n_all_locales_and_changelog(): js = I18N.read_text(encoding="utf-8") - assert js.count("settings_label_hide_suggestions:") == 12 - assert js.count("settings_desc_hide_suggestions:") == 12 + assert js.count("settings_label_hide_suggestions:") == 13 + assert js.count("settings_desc_hide_suggestions:") == 13 changelog = CHANGELOG.read_text(encoding="utf-8") assert "#2679" in changelog assert "hide_empty_state_suggestions" in changelog diff --git a/tests/test_issue3242_3214_i18n_tooltips.py b/tests/test_issue3242_3214_i18n_tooltips.py index 01c946b44..ad5d88141 100644 --- a/tests/test_issue3242_3214_i18n_tooltips.py +++ b/tests/test_issue3242_3214_i18n_tooltips.py @@ -5,7 +5,7 @@ BOOT_JS = (ROOT / "static" / "boot.js").read_text(encoding="utf-8") I18N_JS = (ROOT / "static" / "i18n.js").read_text(encoding="utf-8") SESSIONS_JS = (ROOT / "static" / "sessions.js").read_text(encoding="utf-8") -LOCALE_COUNT = 12 +LOCALE_COUNT = 13 def test_raw_audio_active_recording_uses_dedicated_i18n_key(): diff --git a/tests/test_login_locale_parity.py b/tests/test_login_locale_parity.py index 313e26328..0d37f6063 100644 --- a/tests/test_login_locale_parity.py +++ b/tests/test_login_locale_parity.py @@ -267,7 +267,7 @@ def test_login_locale_count_matches_or_exceeds_floor(): assert k in login, f"_LOGIN_LOCALE missing core locale {k!r}" -@pytest.mark.parametrize("loc_key", ["en", "es", "de", "ru", "zh", "zh-Hant", "ja", "pt", "ko"]) +@pytest.mark.parametrize("loc_key", ["en", "es", "de", "ru", "zh", "zh-Hant", "ja", "pt", "ko", "pl"]) def test_login_locale_entry_well_formed(loc_key: str): """Each _LOGIN_LOCALE entry must have all required sub-keys and non-empty string values.""" login = _load_login_locale() @@ -281,7 +281,7 @@ def test_login_locale_entry_well_formed(loc_key: str): def test_login_locale_resolver_handles_new_locales(): - """_resolve_login_locale_key() must map ja/pt/ko (and common BCP-47 variants) to their entries.""" + """_resolve_login_locale_key() must map ja/pt/ko/pl (and common BCP-47 variants) to their entries.""" sys.path.insert(0, str(REPO)) from api.routes import _resolve_login_locale_key @@ -293,6 +293,9 @@ def test_login_locale_resolver_handles_new_locales(): assert _resolve_login_locale_key("pt-PT") == "pt" assert _resolve_login_locale_key("ko") == "ko" assert _resolve_login_locale_key("ko-KR") == "ko" + assert _resolve_login_locale_key("pl") == "pl" + assert _resolve_login_locale_key("pl-PL") == "pl" + assert _resolve_login_locale_key("pl_PL") == "pl" assert _resolve_login_locale_key("fr") == "fr" assert _resolve_login_locale_key("fr-FR") == "fr" assert _resolve_login_locale_key("fr-CA") == "fr" @@ -310,7 +313,7 @@ def _value_of(seg: str, key: str) -> str | None: return None -@pytest.mark.parametrize("loc_key", ["es", "de", "ru", "zh", "zh-Hant", "ja", "pt", "ko"]) +@pytest.mark.parametrize("loc_key", ["es", "de", "ru", "zh", "zh-Hant", "ja", "pt", "ko", "pl"]) def test_login_flow_keys_are_translated(loc_key: str): """Login/sign-out/password keys in static/i18n.js must NOT equal the English value. @@ -351,7 +354,7 @@ SESSION_MANAGEMENT_KEYS = ( ) -@pytest.mark.parametrize("loc_key", ["en", "es", "de", "ru", "zh", "zh-Hant", "ja", "pt", "ko"]) +@pytest.mark.parametrize("loc_key", ["en", "es", "de", "ru", "zh", "zh-Hant", "ja", "pt", "ko", "pl"]) def test_session_management_keys_present(loc_key: str): """Every locale block must define all session-management keys (no fallback to English).""" seg = _i18n_locale_block(loc_key) diff --git a/tests/test_polish_locale.py b/tests/test_polish_locale.py new file mode 100644 index 000000000..40b78cb2c --- /dev/null +++ b/tests/test_polish_locale.py @@ -0,0 +1,201 @@ +from collections import Counter +from pathlib import Path +import re + + +REPO = Path(__file__).resolve().parent.parent + + +def read(path: Path) -> str: + return path.read_text(encoding="utf-8") + + +def extract_locale_block(src: str, locale_key: str) -> str: + start_match = re.search(rf"\b{re.escape(locale_key)}\s*:\s*\{{", src) + assert start_match, f"{locale_key} locale block not found" + + start = start_match.end() - 1 + depth = 0 + in_single = False + in_double = False + in_backtick = False + escape = False + + for i in range(start, len(src)): + ch = src[i] + + if escape: + escape = False + continue + + if in_single: + if ch == "\\": + escape = True + elif ch == "'": + in_single = False + continue + + if in_double: + if ch == "\\": + escape = True + elif ch == '"': + in_double = False + continue + + if in_backtick: + if ch == "\\": + escape = True + elif ch == "`": + in_backtick = False + continue + + if ch == "'": + in_single = True + continue + if ch == '"': + in_double = True + continue + if ch == "`": + in_backtick = True + continue + + if ch == "{": + depth += 1 + continue + if ch == "}": + depth -= 1 + if depth == 0: + return src[start + 1 : i] + + raise AssertionError(f"{locale_key} locale block braces are not balanced") + + +def locale_keys(src: str, locale_key: str) -> list[str]: + key_pattern = re.compile(r"^\s*([a-zA-Z0-9_]+)\s*:", re.MULTILINE) + return key_pattern.findall(extract_locale_block(src, locale_key)) + + +def test_polish_locale_block_exists(): + src = read(REPO / "static" / "i18n.js") + pl_block = extract_locale_block(src, "pl") + assert pl_block + assert "_lang: 'pl'" in pl_block + assert "_label: 'Polski'" in pl_block + assert "_speech: 'pl-PL'" in pl_block + + +def test_polish_locale_includes_representative_translations(): + src = read(REPO / "static" / "i18n.js") + pl_block = extract_locale_block(src, "pl") + expected = [ + "settings_title: 'Ustawienia'", + "settings_label_language: 'Język'", + "login_title: 'Zaloguj się'", + "approval_heading: 'Wymagana aprobata'", + "tab_chat: 'Czat'", + "tab_tasks: 'Zadania'", + "tab_profiles: 'Profile'", + "empty_title: 'W czym mogę pomóc?'", + "onboarding_title: 'Witaj w Hermes Web UI'", + ] + for entry in expected: + assert entry in pl_block + + +def test_polish_settings_detail_descriptions_are_translated(): + src = read(REPO / "static" / "i18n.js") + pl_block = extract_locale_block(src, "pl") + expected = [ + "settings_desc_workspace_panel_open: 'Gdy ta opcja jest włączona, panel obszaru roboczego / przeglądarki plików otwiera się automatycznie przy każdej nowej sesji. Nadal możesz go zamknąć ręcznie w dowolnym momencie.'", + "settings_desc_notifications: 'Pokaż powiadomienie systemowe, gdy odpowiedź zostanie ukończona, podczas gdy aplikacja działa w tle.'", + "settings_desc_token_usage: 'Wyświetla liczbę tokenów wejściowych/wyjściowych pod każdą odpowiedzią asystenta. Można też przełączyć za pomocą /usage.'", + "settings_desc_sidebar_density: 'Kontroluje, ile metadanych wyświetla lista sesji na lewym pasku bocznym.'", + "settings_desc_auto_title_refresh: 'Automatycznie generuje na nowo tytuł konwersacji na podstawie najnowszej wymiany, utrzymując go adekwatnym w miarę rozwoju rozmowy. Wymaga skonfigurowanego modelu LLM do generowania tytułów.'", + "settings_desc_external_sessions: 'Pokaż konwersacje z CLI, Telegrama, Discorda, Slacka i innych kanałów na liście sesji. Kliknij, aby zaimportować i kontynuować.'", + "settings_desc_cron_sessions: 'Wyświetlaj wyjście zadań cron jako konwersacje na pasku bocznym. Aktywne tylko wtedy, gdy włączone są sesje spoza WebUI. Domyślnie wyłączone; zadania o wysokiej częstotliwości mogą zalać pasek boczny.'", + "settings_desc_sync_insights: 'Odzwierciedla zużycie tokenów WebUI w state.db, dzięki czemu hermes /insights uwzględnia dane sesji przeglądarki. Domyślnie wyłączone.'", + "settings_desc_check_updates: 'Pokaż baner, gdy dostępne są nowsze wersje WebUI lub Agenta. Okresowo uruchamia pobieranie git fetch w tle.'", + "settings_desc_bot_name: 'Używane tylko dla profilu domyślnego. Inne profile używają własnych nazw profilu.'", + "settings_desc_password: 'Wpisz nowe hasło, aby je ustawić lub zmienić. Pozostaw puste, aby zachować obecne ustawienie.'", + ] + for entry in expected: + assert entry in pl_block + + +def test_polish_locale_matches_english_key_coverage(): + src = read(REPO / "static" / "i18n.js") + en_keys = set(locale_keys(src, "en")) + pl_keys = set(locale_keys(src, "pl")) + assert sorted(en_keys - pl_keys) == [] + assert sorted(pl_keys - en_keys) == [] + + +def test_polish_locale_has_no_duplicate_keys(): + src = read(REPO / "static" / "i18n.js") + keys = locale_keys(src, "pl") + + duplicates = sorted(k for k, count in Counter(keys).items() if count > 1) + assert not duplicates, f"Polish locale has duplicate keys: {duplicates}" + + +def test_polish_locale_keys_use_standard_indentation(): + src = read(REPO / "static" / "i18n.js") + pl_block = extract_locale_block(src, "pl") + + # Enforce strict 4-space indentation for keys. + badly_indented = [] + for line in pl_block.splitlines(): + m = re.match(r"^(\s*)[a-zA-Z0-9_]+\s*:", line) + if m and len(m.group(1)) != 4: + badly_indented.append(f"{len(m.group(1))} spaces: {line.strip()}") + assert badly_indented == [] + + +def test_polish_locale_arrow_function_values_mirror_english(): + src = read(REPO / "static" / "i18n.js") + en_block = extract_locale_block(src, "en") + pl_block = extract_locale_block(src, "pl") + + value_re = re.compile(r"^\s+([a-zA-Z0-9_]+):\s*(.+?)(?:,\s*$|\s*$)", re.MULTILINE) + arrow_re = re.compile(r"^\s*\(?[a-zA-Z_,\s]*\)?\s*=>") + + def arrows(block): + return {k for k, v in value_re.findall(block) if arrow_re.match(v)} + + assert arrows(pl_block) == arrows(en_block) + + +def test_polish_locale_preserves_placeholder_patterns(): + src = read(REPO / "static" / "i18n.js") + en_block = extract_locale_block(src, "en") + pl_block = extract_locale_block(src, "pl") + + value_re = re.compile(r"^\s+([a-zA-Z0-9_]+):\s*(.+?)(?:,\s*$|\s*$)", re.MULTILINE) + placeholder_re = re.compile(r"\{[0-9]+\}|\$\{[a-zA-Z_][a-zA-Z0-9_]*\}") + + def kv(block): + out = {} + for k, v in value_re.findall(block): + out[k] = v + return out + + en_kv = kv(en_block) + pl_kv = kv(pl_block) + + for key, en_val in en_kv.items(): + if key not in pl_kv: + continue + en_vars = sorted(placeholder_re.findall(en_val)) + pl_vars = sorted(placeholder_re.findall(pl_kv[key])) + # Skip arrow functions which might contain duplicate conditional logic and thus more template vars + if "=>" not in pl_kv[key]: + if en_vars or pl_vars: + assert pl_vars == en_vars, f"Key '{key}' missing placeholders in pl locale" + + +def test_polish_locale_has_no_double_escaped_unicode_sequences(): + """JSON-style double escapes (\\\\u2026) render literal backslash-u in the UI.""" + src = read(REPO / "static" / "i18n.js") + pl_block = extract_locale_block(src, "pl") + for bad in ("\\\\u2026", "\\\\u2192", "\\\\u2713"): + assert bad not in pl_block, f"Polish locale must not contain {bad!r}" diff --git a/tests/test_pr1721_rtl_salvage.py b/tests/test_pr1721_rtl_salvage.py index bc7c84459..4d365578a 100644 --- a/tests/test_pr1721_rtl_salvage.py +++ b/tests/test_pr1721_rtl_salvage.py @@ -112,5 +112,5 @@ def test_rtl_in_config_defaults_and_writable_keys(): def test_rtl_localized_in_all_locales(): js = I18N.read_text(encoding="utf-8") # Count occurrences — should match the 11 locale blocks - assert js.count("settings_label_rtl:") == 12 - assert js.count("settings_desc_rtl:") == 12 + assert js.count("settings_label_rtl:") == 13 + assert js.count("settings_desc_rtl:") == 13 diff --git a/tests/test_quota_chip_settings_toggle.py b/tests/test_quota_chip_settings_toggle.py index 91c90de77..1908fb50f 100644 --- a/tests/test_quota_chip_settings_toggle.py +++ b/tests/test_quota_chip_settings_toggle.py @@ -88,5 +88,5 @@ def test_quota_chip_panels_round_trip(): def test_quota_chip_localized_in_all_locales(): js = I18N.read_text(encoding="utf-8") - assert js.count("settings_label_quota_chip:") == 12, "12 locales expected" - assert js.count("settings_desc_quota_chip:") == 12, "12 locales expected" + assert js.count("settings_label_quota_chip:") == 13, "12 locales expected" + assert js.count("settings_desc_quota_chip:") == 13, "12 locales expected" diff --git a/tests/test_verdigris_skin.py b/tests/test_verdigris_skin.py index 81a68673c..fa2736a04 100644 --- a/tests/test_verdigris_skin.py +++ b/tests/test_verdigris_skin.py @@ -38,4 +38,4 @@ def test_verdigris_has_no_light_variant(): def test_verdigris_i18n_lists_skin_in_all_locales(): # There are 12 locales; each should now include verdigris as the trailing skin. # 10 locales use ASCII closing paren, 2 Chinese locales use full-width paren. - assert I18N_JS.count("verdigris)") + I18N_JS.count("verdigris)") == 12 + assert I18N_JS.count("verdigris)") + I18N_JS.count("verdigris)") == 13 diff --git a/tests/test_zeus_skin.py b/tests/test_zeus_skin.py index 10080d37c..34b4e8770 100644 --- a/tests/test_zeus_skin.py +++ b/tests/test_zeus_skin.py @@ -45,4 +45,4 @@ def test_zeus_modals_are_not_navy(): def test_zeus_i18n_lists_skin_in_all_locales(): # There are 12 locales; each should include zeus in the skin list. - assert I18N_JS.count("/zeus/") == 12 + assert I18N_JS.count("/zeus/") == 13