Merge branch 'cmp-rewrite' of https://github.com/NuvioMedia/NuvioMobile into cmp-rewrite

This commit is contained in:
tapframe 2026-06-20 19:15:50 +05:30
commit c3da1cf36f
16 changed files with 624 additions and 32 deletions

View file

@ -29,7 +29,7 @@ actual object ThemeSettingsStorage {
fun initialize(context: Context) {
preferences = context.getSharedPreferences(preferencesName, Context.MODE_PRIVATE)
applySelectedAppLanguage(loadSelectedAppLanguage() ?: AppLanguage.ENGLISH.code)
applySelectedAppLanguage(loadSelectedAppLanguage() ?: AppLanguage.DEVICE.code)
}
actual fun loadSelectedTheme(): String? =
@ -84,9 +84,13 @@ actual object ThemeSettingsStorage {
}
actual fun applySelectedAppLanguage(languageCode: String) {
AppCompatDelegate.setApplicationLocales(
LocaleListCompat.forLanguageTags(languageCode),
)
if (languageCode.equals("device", ignoreCase = true)) {
AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList())
} else {
AppCompatDelegate.setApplicationLocales(
LocaleListCompat.forLanguageTags(languageCode),
)
}
}
actual fun exportToSyncPayload(): JsonObject = buildJsonObject {
@ -103,6 +107,6 @@ actual object ThemeSettingsStorage {
payload.decodeSyncString(selectedThemeKey)?.let(::saveSelectedTheme)
payload.decodeSyncBoolean(amoledEnabledKey)?.let(::saveAmoledEnabled)
payload.decodeSyncBoolean(liquidGlassNativeTabBarEnabledKey)?.let(::saveLiquidGlassNativeTabBarEnabled)
applySelectedAppLanguage(loadSelectedAppLanguage() ?: AppLanguage.ENGLISH.code)
applySelectedAppLanguage(loadSelectedAppLanguage() ?: AppLanguage.DEVICE.code)
}
}

View file

@ -1459,4 +1459,415 @@
<string name="settings_stream_addon_logo_title">Logo dodatku</string>
<string name="settings_stream_addon_logo_description">Pokazuj logo i nazwę dodatku obok źródeł.</string>
<string name="settings_stream_display_section">Wyświetlanie</string>
<string name="settings_playback_option_original">Język oryginalny</string>
<string name="settings_playback_option_original_hint">Wymaga włączonego wzbogacania TMDB</string>
<string name="settings_appearance_app_language_device">Język urządzenia</string>
<string name="addons_manifest_missing_field">W manifeście brakuje \"%1$s\"</string>
<string name="cloud_library_playback_disabled">Biblioteka w chmurze jest wyłączona.</string>
<string name="cloud_library_provider_unavailable">Biblioteka w chmurze nie jest dostępna dla %1$s.</string>
<string name="collections_editor_media_movies_suffix">Filmy</string>
<string name="collections_editor_media_series_suffix">Seriale</string>
<string name="collections_editor_resolved_trakt_list">Rozpoznana lista Trakt</string>
<string name="collections_editor_tmdb_collection_title_format">Kolekcja TMDB %1$s</string>
<string name="collections_editor_tmdb_country_au">Australia</string>
<string name="collections_editor_tmdb_country_ca">Kanada</string>
<string name="collections_editor_tmdb_country_de">Niemcy</string>
<string name="collections_editor_tmdb_country_placeholder">US, KR, JP, IN</string>
<string name="collections_editor_tmdb_date_from_placeholder">2020-01-01</string>
<string name="collections_editor_tmdb_date_to_placeholder">2024-12-31</string>
<string name="collections_editor_tmdb_director_title_format">Reżyser TMDB %1$s</string>
<string name="collections_editor_tmdb_discover">TMDB Odkrywaj</string>
<string name="collections_editor_tmdb_discover_title">TMDB Odkrywaj</string>
<string name="collections_editor_tmdb_genres_movie_placeholder">28,12</string>
<string name="collections_editor_tmdb_genres_series_placeholder">18,35</string>
<string name="collections_editor_tmdb_invalid_id_error">Wprowadź prawidłowy ID lub URL TMDB.</string>
<string name="collections_editor_tmdb_invalid_id_or_url">Wprowadź prawidłowy ID lub URL TMDB.</string>
<string name="collections_editor_tmdb_language_placeholder">en, ko, ja, hi</string>
<string name="collections_editor_tmdb_list_title_format">Lista TMDB %1$s</string>
<string name="collections_editor_tmdb_load_error">Nie udało się załadować źródła TMDB</string>
<string name="collections_editor_tmdb_network_title_format">Sieć TMDB %1$s</string>
<string name="collections_editor_tmdb_person_title_format">Osoba TMDB %1$s</string>
<string name="collections_editor_tmdb_production_title_format">Produkcja TMDB %1$s</string>
<string name="collections_editor_tmdb_rating_max_placeholder">10</string>
<string name="collections_editor_tmdb_rating_min_placeholder">7.0</string>
<string name="collections_editor_tmdb_source_load_failed">Nie udało się załadować źródła TMDB</string>
<string name="collections_editor_tmdb_votes_min_placeholder">100</string>
<string name="collections_editor_tmdb_year_placeholder">2024</string>
<string name="collections_editor_trakt_enter_id_or_url">Wprowadź ID lub URL listy Trakt</string>
<string name="collections_editor_trakt_enter_name_url_or_id">Wprowadź nazwę, URL lub ID listy Trakt</string>
<string name="collections_editor_trakt_fallback_title">Lista Trakt %1$d</string>
<string name="collections_editor_trakt_id_url_required">Wprowadź ID lub URL listy Trakt</string>
<string name="collections_editor_trakt_input_required">Wprowadź nazwę, URL lub ID listy Trakt</string>
<string name="collections_editor_trakt_list_id_format">ID %1$s</string>
<string name="collections_editor_trakt_list_title_format">Lista Trakt %1$s</string>
<string name="collections_editor_trakt_load_error">Nie udało się załadować listy Trakt</string>
<string name="collections_editor_trakt_load_failed">Nie udało się załadować listy Trakt</string>
<string name="collections_editor_trakt_no_lists_found">Nie znaleziono list Trakt</string>
<string name="collections_editor_trakt_resolved_subtitle">Rozpoznana lista Trakt</string>
<string name="collections_editor_watch_region_placeholder">US, KR, JP, IN</string>
<string name="collections_folder_trakt_movie_list">Lista filmów Trakt</string>
<string name="collections_folder_trakt_series_list">Lista seriali Trakt</string>
<string name="collections_tmdb_api_key_required">Dodaj klucz API TMDB w Ustawieniach, aby korzystać ze źródeł TMDB.</string>
<string name="collections_tmdb_collection_not_found">Nie znaleziono kolekcji TMDB</string>
<string name="collections_tmdb_company_not_found">Nie znaleziono firmy TMDB</string>
<string name="collections_tmdb_discover_no_data">TMDB Odkrywaj nie zwrócił danych</string>
<string name="collections_tmdb_list_not_found">Nie znaleziono listy TMDB</string>
<string name="collections_tmdb_missing_collection_id">Brak ID kolekcji TMDB</string>
<string name="collections_tmdb_missing_list_id">Brak ID listy TMDB</string>
<string name="collections_tmdb_missing_person_id">Brak ID osoby TMDB</string>
<string name="collections_tmdb_network_not_found">Nie znaleziono sieci TMDB</string>
<string name="collections_tmdb_person_credits_not_found">Nie znaleziono filmografii osoby TMDB</string>
<string name="collections_tmdb_person_not_found">Nie znaleziono osoby TMDB</string>
<string name="collections_trakt_credentials_missing">Brak danych uwierzytelniających Trakt.</string>
<string name="collections_trakt_error_with_code">%1$s (%2$d)</string>
<string name="collections_trakt_invalid_list_id_or_url">Wprowadź prawidłowy ID lub URL listy Trakt</string>
<string name="collections_trakt_list_items_count">%1$d elementów</string>
<string name="collections_trakt_list_likes_count">%1$d polubień</string>
<string name="collections_trakt_list_not_found_or_private">Lista Trakt nie znaleziona lub niepubliczna</string>
<string name="collections_trakt_missing_list_id">Brak ID listy Trakt</string>
<string name="collections_trakt_missing_numeric_id">Lista Trakt nie zawiera numerycznego ID</string>
<string name="collections_trakt_public_list">Publiczna lista Trakt</string>
<string name="collections_trakt_rate_limit_reached">Osiągnięto limit zapytań Trakt</string>
<string name="collections_trakt_request_failed">Zapytanie Trakt nie powiodło się</string>
<string name="compose_player_no_subtitle_lines_found">Nie znaleziono linii napisów</string>
<string name="compose_player_subtitle_lines_load_error">Nie udało się załadować linii napisów</string>
<string name="compose_settings_page_advanced">Zaawansowane</string>
<string name="compose_settings_page_streams">Źródła</string>
<string name="compose_settings_root_advanced_description">Uruchamianie i zachowanie profilu.</string>
<string name="compose_settings_root_advanced_section">ZAAWANSOWANE</string>
<string name="compose_settings_root_streams_description">Wyświetlanie wyników źródeł i reguły badge URL.</string>
<string name="detail_more_like_this_powered_by_tmdb">Napędzane przez TMDB</string>
<string name="detail_more_like_this_powered_by_trakt">Napędzane przez Trakt</string>
<string name="details_actions_menu_label">Więcej akcji</string>
<string name="details_comments_trakt_load_failed_with_code">Nie udało się załadować komentarzy Trakt (%1$d)</string>
<string name="details_runtime_hours_minutes">%1$dh %2$dm</string>
<string name="details_runtime_hours_only">%1$dh</string>
<string name="details_runtime_minutes_only">%1$dm</string>
<string name="downloads_error_finalize_failed">Nie udało się sfinalizować pobieranego pliku</string>
<string name="downloads_error_finalize_file_failed">Nie udało się sfinalizować pobieranego pliku</string>
<string name="downloads_error_open_partial_failed">Nie udało się otworzyć częściowego pliku</string>
<string name="downloads_error_open_partial_file_failed">Nie udało się otworzyć częściowego pliku</string>
<string name="downloads_error_partial_file_not_open">Częściowy plik nie jest otwarty</string>
<string name="downloads_error_partial_not_open">Częściowy plik nie jest otwarty</string>
<string name="downloads_error_write_partial_failed">Nie udało się zapisać częściowego pliku</string>
<string name="downloads_error_write_partial_file_failed">Nie udało się zapisać częściowego pliku</string>
<string name="external_player_android_system">Systemowy odtwarzacz Android</string>
<string name="home_continue_watching_hours_minutes_left">Pozostało %1$dh %2$dm</string>
<string name="home_continue_watching_minutes_left">Pozostało %1$dm</string>
<string name="library_local_tab_title">Biblioteka Nuvio</string>
<string name="network_connection_issue">Problem z połączeniem</string>
<string name="network_empty_response_body">Pusta treść odpowiedzi</string>
<string name="network_error_empty_response_body">Pusta treść odpowiedzi</string>
<string name="network_error_request_failed_http">Zapytanie nie powiodło się z kodem HTTP %1$d</string>
<string name="network_please_check_connection">Sprawdź połączenie i spróbuj ponownie.</string>
<string name="network_request_failed_http">Zapytanie nie powiodło się z kodem HTTP %1$d</string>
<string name="p2p_consent_body">Ten strumień wykorzystuje technologię peer-to-peer (P2P). Włączając P2P, przyjmujesz do wiadomości i zgadzasz się, że:\n\n• Twój adres IP będzie widoczny dla innych użytkowników w sieci\n• Ponosisz wyłączną odpowiedzialność za treści, do których uzyskujesz dostęp\n• Potwierdzasz, że masz prawo do strumieniowania tych treści w swojej jurysdykcji\n• Nuvio nie hostuje, nie dystrybuuje ani nie kontroluje żadnych treści P2P\n• Nuvio nie ponosi odpowiedzialności za jakiekolwiek konsekwencje prawne wynikające z korzystania ze strumieniowania P2P\n\nKorzystasz z tej funkcji wyłącznie na własne ryzyko. P2P można wyłączyć w dowolnym momencie w Ustawieniach.</string>
<string name="p2p_consent_cancel">Anuluj</string>
<string name="p2p_consent_enable">Włącz P2P</string>
<string name="p2p_consent_title">Strumieniowanie P2P</string>
<string name="p2p_error_unknown">Nieznany błąd torrenta</string>
<string name="player_action_submit_intro">Zgłoś intro</string>
<string name="player_action_video_settings">Ustawienia wideo</string>
<string name="player_addon_subtitle_display_format">%1$s (%2$s)</string>
<string name="player_engine_unavailable_rebuild">Silnik odtwarzacza MPV niedostępny. Przebuduj aplikację.</string>
<string name="player_error_failed_start_torrent">Nie udało się uruchomić torrenta: %1$s</string>
<string name="player_error_mpv_unavailable">Silnik odtwarzacza MPV niedostępny. Przebuduj aplikację.</string>
<string name="player_error_torrent">Błąd torrenta: %1$s</string>
<string name="player_error_unable_to_play_stream">Nie można odtworzyć tego strumienia.</string>
<string name="player_ios_hardware_decoder_off">Wyłączony</string>
<string name="player_ios_preset_compatibility_desc">Najbliżej starszego zachowania MPV na iOS.</string>
<string name="player_ios_preset_compatibility_label">Kompatybilność</string>
<string name="player_ios_preset_custom_desc">Użyj własnych zaawansowanych wartości poniżej.</string>
<string name="player_ios_preset_custom_label">Własne</string>
<string name="player_ios_preset_native_edr_desc">Najlepsze dla iPhone\'ów i iPadów z obsługą HDR.</string>
<string name="player_ios_preset_native_edr_label">Natywne EDR</string>
<string name="player_ios_preset_sdr_tone_mapped_desc">Bardziej przewidywalne biele i czernie na wyjściu SDR.</string>
<string name="player_ios_preset_sdr_tone_mapped_label">Mapowanie tonalne SDR</string>
<string name="player_torrent_buffered_status">%1$s buforowane · %2$s · %3$s</string>
<string name="player_torrent_connecting_peers">Łączenie z peerami…</string>
<string name="player_torrent_peer_info">%1$d seedów · %2$d peerów</string>
<string name="player_torrent_starting_engine">Uruchamianie silnika P2P…</string>
<string name="player_torrent_stats">%1$d peerów · %2$d seedów · %3$d%%</string>
<string name="player_torrent_status">%1$s · %2$s</string>
<string name="player_video_settings_brightness">Jasność</string>
<string name="player_video_settings_contrast">Kontrast</string>
<string name="player_video_settings_deband">Deband</string>
<string name="player_video_settings_deband_desc">Zmniejsz pasmowanie kolorów kosztem wydajności.</string>
<string name="player_video_settings_gamma">Gamma</string>
<string name="player_video_settings_hdr_peak_detection">Wykrywanie szczytów HDR</string>
<string name="player_video_settings_hdr_peak_detection_desc">Szacuj szczytową jasność HDR gdy metadane są błędne lub brakujące.</string>
<string name="player_video_settings_interpolation">Interpolacja klatek</string>
<string name="player_video_settings_interpolation_desc">Płynny ruch gdy mpv może czysto zsynchronizować z wyświetlaczem.</string>
<string name="player_video_settings_output_preset">Preset wyjściowy</string>
<string name="player_video_settings_reset_tuning">Resetuj strojenie</string>
<string name="player_video_settings_saturation">Nasycenie</string>
<string name="player_video_settings_title">Wideo</string>
<string name="player_video_settings_tone_mapping">Mapowanie tonalne</string>
<string name="plugins_badge_disabled">Wtyczki wyłączone</string>
<string name="plugins_badge_enabled">Wtyczki włączone</string>
<string name="plugins_badge_providers">%1$d dostawców</string>
<string name="plugins_badge_refreshing">Odświeżanie</string>
<string name="plugins_badge_repos">%1$d repozytoriów</string>
<string name="plugins_badge_tmdb_key_missing">Brak klucza API TMDB</string>
<string name="plugins_badge_tmdb_key_set">Klucz API TMDB ustawiony</string>
<string name="plugins_button_installing">Instalowanie…</string>
<string name="plugins_button_install_repo">Zainstaluj repozytorium wtyczek</string>
<string name="plugins_button_testing">Testowanie…</string>
<string name="plugins_button_test_provider">Testuj dostawcę</string>
<string name="plugins_cd_delete_repo">Usuń repozytorium wtyczek</string>
<string name="plugins_cd_refresh_repo">Odśwież repozytorium wtyczek</string>
<string name="plugins_empty_providers">Brak dostępnych dostawców.</string>
<string name="plugins_empty_repos_subtitle">Dodaj URL repozytorium, aby zainstalować wtyczki do odkrywania źródeł.</string>
<string name="plugins_empty_repos_title">Nie zainstalowano jeszcze repozytoriów wtyczek.</string>
<string name="plugins_enable_globally_desc">Używaj dostawców wtyczek podczas wyszukiwania źródeł.</string>
<string name="plugins_enable_globally_title">Włącz dostawców wtyczek globalnie</string>
<string name="plugins_error_already_installed">To repozytorium wtyczek jest już zainstalowane.</string>
<string name="plugins_error_enter_repo_url">Wprowadź URL repozytorium wtyczek.</string>
<string name="plugins_error_enter_valid_url">Wprowadź prawidłowy URL wtyczki.</string>
<string name="plugins_error_install_failed">Nie udało się zainstalować repozytorium wtyczek</string>
<string name="plugins_error_provider_not_found">Nie znaleziono dostawcy</string>
<string name="plugins_error_refresh_failed">Nie udało się odświeżyć repozytorium</string>
<string name="plugins_error_unavailable_build">Wtyczki nie są dostępne w tej wersji.</string>
<string name="plugins_group_by_repo_desc">W Źródłach pokazuj jednego dostawcę na repozytorium zamiast na źródło.</string>
<string name="plugins_group_by_repo_title">Grupuj dostawców wtyczek wg repozytorium</string>
<string name="plugins_input_manifest_placeholder">URL manifestu wtyczki</string>
<string name="plugins_manifest_error_name_missing">Brak nazwy w manifeście.</string>
<string name="plugins_manifest_error_no_providers">Manifest nie ma dostawców.</string>
<string name="plugins_manifest_error_version_missing">Brak wersji w manifeście.</string>
<string name="plugins_manifest_name_missing">Brak nazwy w manifeście.</string>
<string name="plugins_manifest_no_providers">Manifest nie ma dostawców.</string>
<string name="plugins_manifest_version_missing">Brak wersji w manifeście.</string>
<string name="plugins_message_installed">Zainstalowano %1$s.</string>
<string name="plugins_provider_disabled_by_repo">Wyłączony przez repozytorium</string>
<string name="plugins_provider_no_description">Brak opisu</string>
<string name="plugins_provider_version">v%1$s</string>
<string name="plugins_repo_fallback_label">Repozytorium wtyczek</string>
<string name="plugins_repository_already_installed">To repozytorium wtyczek jest już zainstalowane.</string>
<string name="plugins_repository_install_failed">Nie udało się zainstalować repozytorium wtyczek</string>
<string name="plugins_repository_refresh_failed">Nie udało się odświeżyć repozytorium</string>
<string name="plugins_repo_version">Wersja %1$s</string>
<string name="plugins_section_add_repo">DODAJ REPOZYTORIUM</string>
<string name="plugins_section_installed_repos">ZAINSTALOWANE REPOZYTORIA</string>
<string name="plugins_section_overview">PRZEGLĄD</string>
<string name="plugins_section_providers">DOSTAWCY</string>
<string name="plugins_test_error_title">Błąd</string>
<string name="plugins_test_failed">Test dostawcy nie powiódł się</string>
<string name="plugins_test_results_count">Wyniki testu (%1$d)</string>
<string name="plugins_tmdb_required_message">Dostawcy wtyczek wymagają klucza API TMDB. Ustaw go na ekranie TMDB, w przeciwnym razie dostawcy nie będą działać poprawnie.</string>
<string name="search_error_no_results_for_catalog">Brak wyników wyszukiwania dla %1$s.</string>
<string name="settings_account_sync_backend">Backend synchronizacji</string>
<string name="settings_advanced_clear_cw_cache">Wyczyść pamięć Kontynuuj oglądanie</string>
<string name="settings_advanced_clear_cw_cache_done">Pamięć wyczyszczona</string>
<string name="settings_advanced_clear_cw_cache_subtitle">Usuń dane Kontynuuj oglądanie i odśwież postęp</string>
<string name="settings_advanced_remember_last_profile">Zapamiętaj ostatni profil</string>
<string name="settings_advanced_remember_last_profile_description">Zapamiętaj ostatnio wybrany profil przy uruchomieniu</string>
<string name="settings_advanced_section_cache">PAMIĘĆ PODRĘCZNA</string>
<string name="settings_advanced_section_startup">URUCHAMIANIE</string>
<string name="settings_continue_watching_style_card">Karta</string>
<string name="settings_continue_watching_style_card_description">Karta pozioma w stylu TV</string>
<string name="settings_debrid_learn_more">Dowiedz się więcej</string>
<string name="settings_debrid_max_results">Maks. wyników</string>
<string name="settings_debrid_max_results_desc">Ogranicz liczbę wyświetlanych wyników.</string>
<string name="settings_debrid_per_quality_limit">Limit na jakość</string>
<string name="settings_debrid_per_quality_limit_desc">Ogranicz powtarzające się wyniki BluRay, WEB-DL, REMUX po sortowaniu.</string>
<string name="settings_debrid_per_resolution_limit">Limit na rozdzielczość</string>
<string name="settings_debrid_per_resolution_limit_desc">Ogranicz powtarzające się wyniki 2160p, 1080p, 720p po sortowaniu.</string>
<string name="settings_debrid_release_groups_hint">Wprowadź jedną grupę na linię.</string>
<string name="settings_debrid_results_all">Wszystkie wyniki</string>
<string name="settings_debrid_results_count">%1$d wyników</string>
<string name="settings_debrid_rule_excluded_audio_tags">Wykluczone tagi audio</string>
<string name="settings_debrid_rule_excluded_audio_tags_desc">Ukryj wybrane tagi audio.</string>
<string name="settings_debrid_rule_excluded_channels">Wykluczone kanały</string>
<string name="settings_debrid_rule_excluded_channels_desc">Ukryj wybrane układy kanałów.</string>
<string name="settings_debrid_rule_excluded_encodes">Wykluczone kodowania</string>
<string name="settings_debrid_rule_excluded_encodes_desc">Ukryj wybrane kodowania.</string>
<string name="settings_debrid_rule_excluded_languages">Wykluczone języki</string>
<string name="settings_debrid_rule_excluded_languages_desc">Ukryj wyniki gdzie każdy język jest wykluczony.</string>
<string name="settings_debrid_rule_excluded_qualities">Wykluczone jakości</string>
<string name="settings_debrid_rule_excluded_qualities_desc">Ukryj wybrane jakości.</string>
<string name="settings_debrid_rule_excluded_release_groups">Wykluczone grupy release</string>
<string name="settings_debrid_rule_excluded_release_groups_desc">Ukryj wybrane grupy release.</string>
<string name="settings_debrid_rule_excluded_resolutions">Wykluczone rozdzielczości</string>
<string name="settings_debrid_rule_excluded_resolutions_desc">Ukryj wybrane rozdzielczości.</string>
<string name="settings_debrid_rule_excluded_visual_tags">Wykluczone tagi wizualne</string>
<string name="settings_debrid_rule_excluded_visual_tags_desc">Ukryj DV, HDR, 10bit, 3D i podobne tagi.</string>
<string name="settings_debrid_rule_preferred_audio_tags">Preferowane tagi audio</string>
<string name="settings_debrid_rule_preferred_audio_tags_desc">Sortuj Atmos, TrueHD, DTS, AAC i podobne tagi.</string>
<string name="settings_debrid_rule_preferred_channels">Preferowane kanały</string>
<string name="settings_debrid_rule_preferred_channels_desc">Sortuj preferowane układy kanałów jako pierwsze.</string>
<string name="settings_debrid_rule_preferred_encodes">Preferowane kodowania</string>
<string name="settings_debrid_rule_preferred_encodes_desc">Sortuj AV1, HEVC, AVC i podobne kodowania.</string>
<string name="settings_debrid_rule_preferred_languages">Preferowane języki</string>
<string name="settings_debrid_rule_preferred_languages_desc">Sortuj preferowane języki audio jako pierwsze.</string>
<string name="settings_debrid_rule_preferred_qualities">Preferowane jakości</string>
<string name="settings_debrid_rule_preferred_qualities_desc">Sortuj wybrane jakości jako pierwsze, w domyślnej kolejności.</string>
<string name="settings_debrid_rule_preferred_resolutions">Preferowane rozdzielczości</string>
<string name="settings_debrid_rule_preferred_resolutions_desc">Sortuj wybrane rozdzielczości jako pierwsze, w domyślnej kolejności.</string>
<string name="settings_debrid_rule_preferred_visual_tags">Preferowane tagi wizualne</string>
<string name="settings_debrid_rule_preferred_visual_tags_desc">Sortuj DV, HDR, 10bit, IMAX i podobne tagi.</string>
<string name="settings_debrid_rule_required_audio_tags">Wymagane tagi audio</string>
<string name="settings_debrid_rule_required_audio_tags_desc">Wymagaj Atmos, TrueHD, DTS, AAC i podobnych tagów.</string>
<string name="settings_debrid_rule_required_channels">Wymagane kanały</string>
<string name="settings_debrid_rule_required_channels_desc">Pokazuj tylko wybrane układy kanałów.</string>
<string name="settings_debrid_rule_required_encodes">Wymagane kodowania</string>
<string name="settings_debrid_rule_required_encodes_desc">Wymagaj AV1, HEVC, AVC i podobnych kodowań.</string>
<string name="settings_debrid_rule_required_languages">Wymagane języki</string>
<string name="settings_debrid_rule_required_languages_desc">Pokazuj tylko wyniki z wybranymi językami.</string>
<string name="settings_debrid_rule_required_qualities">Wymagane jakości</string>
<string name="settings_debrid_rule_required_qualities_desc">Pokazuj tylko wybrane jakości.</string>
<string name="settings_debrid_rule_required_release_groups">Wymagane grupy release</string>
<string name="settings_debrid_rule_required_release_groups_desc">Pokazuj tylko wybrane grupy release.</string>
<string name="settings_debrid_rule_required_resolutions">Wymagane rozdzielczości</string>
<string name="settings_debrid_rule_required_resolutions_desc">Pokazuj tylko wybrane rozdzielczości.</string>
<string name="settings_debrid_rule_required_visual_tags">Wymagane tagi wizualne</string>
<string name="settings_debrid_rule_required_visual_tags_desc">Wymagaj DV, HDR, 10bit, IMAX, SDR i podobnych tagów.</string>
<string name="settings_debrid_section_result_management">Zarządzanie wynikami</string>
<string name="settings_debrid_selection_any">Dowolne</string>
<string name="settings_debrid_selection_count">%1$d wybranych</string>
<string name="settings_debrid_size_min">%1$dGB+</string>
<string name="settings_debrid_size_range">Zakres rozmiaru</string>
<string name="settings_debrid_size_range_desc">Filtruj wyniki wg rozmiaru pliku.</string>
<string name="settings_debrid_size_range_value">%1$d-%2$dGB</string>
<string name="settings_debrid_size_up_to">Do %1$dGB</string>
<string name="settings_debrid_sort_best_audio">Najlepszy dźwięk najpierw</string>
<string name="settings_debrid_sort_best_quality">Najlepsza jakość najpierw</string>
<string name="settings_debrid_sort_language">Język najpierw</string>
<string name="settings_debrid_sort_largest">Największe najpierw</string>
<string name="settings_debrid_sort_original">Oryginalna kolejność</string>
<string name="settings_debrid_sort_results">Sortuj wyniki</string>
<string name="settings_debrid_sort_results_desc">Wybierz kolejność wyników.</string>
<string name="settings_debrid_sort_smallest">Najmniejsze najpierw</string>
<string name="settings_debrid_template_default_format">Format domyślny</string>
<string name="settings_debrid_template_original_format">Format oryginalny</string>
<string name="settings_fusion_badge_group_title">Grupa %1$d</string>
<string name="settings_fusion_badge_other_group_title">Inne badge Fusion</string>
<string name="settings_fusion_badge_preview_action">Podgląd</string>
<string name="settings_fusion_badge_preview_count">%1$d badge Fusion z tego URL</string>
<string name="settings_fusion_badge_preview_empty">Brak badge Fusion w tym URL.</string>
<string name="settings_fusion_badge_preview_title">Podgląd badge Fusion</string>
<string name="settings_fusion_badges_empty">Brak zaimportowanych URL badge Fusion.</string>
<string name="settings_fusion_badges_summary">%1$d/%2$d URL, %3$d aktywnych badge Fusion</string>
<string name="settings_fusion_badge_url_active">Aktywny</string>
<string name="settings_fusion_badge_url_inactive">Nieaktywny</string>
<string name="settings_fusion_badge_url_label">URL JSON badge Fusion</string>
<string name="settings_fusion_badge_urls_imported">%1$d/%2$d URL Fusion zaimportowanych</string>
<string name="settings_fusion_badge_url_status_summary">%1$s, %2$d włączonych badge, %3$d grup</string>
<string name="settings_meta_hero_trailer_playback">Odtwarzanie zwiastuna w tle</string>
<string name="settings_meta_hero_trailer_playback_description">Odtwarzaj podgląd zwiastuna w tle szczegółów gdy dostępny.</string>
<string name="settings_p2p_hide_stats_subtitle">Ukryj bufor, seedy, peery i prędkość pobierania podczas ładowania i odtwarzania</string>
<string name="settings_p2p_hide_stats_title">Ukryj statystyki torrenta</string>
<string name="settings_p2p_subtitle">Zezwalaj na strumienie peer-to-peer (torrent)</string>
<string name="settings_p2p_title">Strumieniowanie P2P</string>
<string name="settings_playback_introdb_invalid_api_key">Nieprawidłowy klucz API lub błąd połączenia</string>
<string name="settings_playback_introdb_invalid_key">Nieprawidłowy klucz API lub błąd połączenia</string>
<string name="settings_playback_ios_audio_output">Wyjście audio</string>
<string name="settings_playback_ios_audio_output_audiounit_desc">Użyj starszego wyjścia AudioUnit.</string>
<string name="settings_playback_ios_audio_output_auto_desc">Najpierw AVFoundation, potem AudioUnit jako zapasowe.</string>
<string name="settings_playback_ios_audio_output_avfoundation_desc">Eksperymentalna obsługa Spatial Audio i wielokanałowego wyjścia.</string>
<string name="settings_playback_ios_audio_output_dialog">Wyjście audio</string>
<string name="settings_playback_ios_audio_output_section">Wyjście audio iOS</string>
<string name="settings_playback_ios_display_color_hint">Wskazówka koloru wyświetlacza</string>
<string name="settings_playback_ios_display_color_hint_desc">Pozwól mpv celować w aktywną przestrzeń kolorów wyświetlacza.</string>
<string name="settings_playback_ios_extended_dynamic_range">Rozszerzony zakres dynamiczny</string>
<string name="settings_playback_ios_extended_dynamic_range_desc">Domyślny tryb wyjścia Metal dla nowych sesji odtwarzania.</string>
<string name="settings_playback_ios_hardware_decoder">Dekoder sprzętowy</string>
<string name="settings_playback_ios_hw_decoder_dialog">Dekoder sprzętowy</string>
<string name="settings_playback_ios_target_primaries">Docelowe kolory podstawowe</string>
<string name="settings_playback_ios_target_primaries_dialog">Docelowe kolory podstawowe</string>
<string name="settings_playback_ios_target_transfer">Docelowy transfer</string>
<string name="settings_playback_ios_target_transfer_dialog">Docelowy transfer</string>
<string name="settings_playback_ios_video_output">Wyjście wideo iOS</string>
<string name="settings_playback_touch_gestures">Gesty dotykowe</string>
<string name="settings_playback_touch_gestures_description">Pozwól na przesunięcia i podwójne dotknięcia na powierzchni odtwarzacza do przewijania, regulacji jasności lub głośności.</string>
<string name="settings_stream_badge_enter_url">Wprowadź URL JSON badge.</string>
<string name="settings_stream_badge_import_failed">Import badge nie powiódł się.</string>
<string name="settings_stream_badge_import_limit">Możesz zaimportować do %1$d URL badge.</string>
<string name="settings_stream_badge_position_bottom">Na dole</string>
<string name="settings_stream_badge_position_description">Wybierz czy badge Fusion i rozmiaru pojawiają się nad czy pod kartami źródeł.</string>
<string name="settings_stream_badge_position_dialog_description">Wybierz gdzie badge źródeł pojawiają się na kartach.</string>
<string name="settings_stream_badge_position_dialog_title">Pozycja badge</string>
<string name="settings_stream_badge_position_title">Pozycja badge</string>
<string name="settings_stream_badge_position_top">Na górze</string>
<string name="settings_stream_badges_section">Styl Fusion</string>
<string name="settings_stream_badge_url_scheme_invalid">URL badge musi zaczynać się od http:// lub https://.</string>
<string name="settings_stream_badge_urls_description">Importuj do %1$d URL JSON badge Fusion. Każdy URL można aktualizować lub usuwać osobno.</string>
<string name="settings_stream_badge_urls_search_description">Zarządzaj zaimportowanymi URL JSON badge Fusion.</string>
<string name="settings_stream_badge_urls_title">URL badge Fusion</string>
<string name="settings_stream_size_badges_description">Pokazuj badge rozmiaru pliku w wynikach źródeł i panelu źródeł odtwarzacza.</string>
<string name="settings_stream_size_badges_title">Badge rozmiaru</string>
<string name="streams_plugin_repository_fallback">Repozytorium wtyczek</string>
<string name="submit_intro_action">Zgłoś intro</string>
<string name="submit_intro_button_submit">Wyślij</string>
<string name="submit_intro_capture">Przechwyć</string>
<string name="submit_intro_capture_button">Przechwyć</string>
<string name="submit_intro_end_time">CZAS KOŃCA (MM:SS)</string>
<string name="submit_intro_end_time_label">CZAS KOŃCA (MM:SS)</string>
<string name="submit_intro_segment_intro">Intro</string>
<string name="submit_intro_segment_outro">Outro</string>
<string name="submit_intro_segment_recap">Podsumowanie</string>
<string name="submit_intro_segment_type">TYP SEGMENTU</string>
<string name="submit_intro_segment_type_label">TYP SEGMENTU</string>
<string name="submit_intro_start_time">CZAS STARTU (MM:SS)</string>
<string name="submit_intro_start_time_label">CZAS STARTU (MM:SS)</string>
<string name="submit_intro_submit">Wyślij</string>
<string name="submit_intro_title">Zgłoś znaczniki czasu</string>
<string name="tmdb_sources_api_key_required">Dodaj klucz API TMDB w Ustawieniach, aby korzystać ze źródeł TMDB.</string>
<string name="tmdb_sources_collection_fallback_title">Kolekcja TMDB %1$d</string>
<string name="tmdb_sources_collection_not_found">Nie znaleziono kolekcji TMDB</string>
<string name="tmdb_sources_company_fallback_title">Produkcja TMDB %1$d</string>
<string name="tmdb_sources_company_not_found">Nie znaleziono firmy TMDB</string>
<string name="tmdb_sources_director_fallback_title">Reżyser TMDB %1$d</string>
<string name="tmdb_sources_discover_no_data">TMDB Odkrywaj nie zwrócił danych</string>
<string name="tmdb_sources_discover_title">TMDB Odkrywaj</string>
<string name="tmdb_sources_invalid_id_or_url">Wprowadź prawidłowy ID lub URL TMDB.</string>
<string name="tmdb_sources_list_fallback_title">Lista TMDB %1$d</string>
<string name="tmdb_sources_list_not_found">Nie znaleziono listy TMDB</string>
<string name="tmdb_sources_load_failed">Nie udało się załadować źródła TMDB</string>
<string name="tmdb_sources_missing_collection_id">Brak ID kolekcji TMDB</string>
<string name="tmdb_sources_missing_list_id">Brak ID listy TMDB</string>
<string name="tmdb_sources_missing_person_id">Brak ID osoby TMDB</string>
<string name="tmdb_sources_network_fallback_title">Sieć TMDB %1$d</string>
<string name="tmdb_sources_network_not_found">Nie znaleziono sieci TMDB</string>
<string name="tmdb_sources_person_credits_not_found">Nie znaleziono filmografii osoby TMDB</string>
<string name="tmdb_sources_person_fallback_title">Osoba TMDB %1$d</string>
<string name="tmdb_sources_person_not_found">Nie znaleziono osoby TMDB</string>
<string name="trakt_connected">Połączono z Trakt</string>
<string name="trakt_connected_status">Połączono z Trakt</string>
<string name="trakt_disconnected">Rozłączono z Trakt</string>
<string name="trakt_disconnected_status">Rozłączono z Trakt</string>
<string name="trakt_error_add_list_failed">Nie udało się dodać do listy Trakt</string>
<string name="trakt_error_add_watchlist_failed">Nie udało się dodać do listy obserwowanych Trakt</string>
<string name="trakt_error_authorization_expired">Autoryzacja Trakt wygasła</string>
<string name="trakt_error_empty_response">Pusta treść odpowiedzi</string>
<string name="trakt_error_list_limit_reached">Osiągnięto limit list Trakt</string>
<string name="trakt_error_list_not_found">Nie znaleziono listy Trakt</string>
<string name="trakt_error_missing_ids">Brak kompatybilnych ID Trakt</string>
<string name="trakt_error_rate_limit_reached">Osiągnięto limit zapytań Trakt</string>
<string name="trakt_error_request_failed">Zapytanie Trakt nie powiodło się</string>
<string name="trakt_more_like_this_source_dialog_subtitle">Wybierz źródło rekomendacji na stronach szczegółów.</string>
<string name="trakt_more_like_this_source_dialog_title">Źródło Podobnych</string>
<string name="trakt_more_like_this_source_subtitle">Wybierz skąd pochodzą rekomendacje na stronach szczegółów</string>
<string name="trakt_more_like_this_source_title">Źródło Podobnych</string>
<string name="trakt_more_like_this_source_tmdb">TMDB</string>
<string name="trakt_more_like_this_source_trakt">Trakt</string>
<string name="trakt_public_list">Publiczna lista Trakt</string>
<string name="trakt_public_list_enter_valid_id_or_url">Wprowadź prawidłowy ID lub URL listy Trakt</string>
<string name="trakt_public_list_items_count">%1$d elementów</string>
<string name="trakt_public_list_likes_count">%1$d polubień</string>
<string name="trakt_public_list_missing_credentials">Brak danych uwierzytelniających Trakt w local.properties (TRAKT_CLIENT_ID).</string>
<string name="trakt_public_list_missing_id">Brak ID listy Trakt</string>
<string name="trakt_public_list_missing_numeric_id">Lista Trakt nie zawiera numerycznego ID</string>
<string name="trakt_public_list_not_found_or_not_public">Lista Trakt nie znaleziona lub niepubliczna</string>
<string name="trakt_public_list_rate_limit_reached">Osiągnięto limit zapytań Trakt</string>
<string name="trakt_public_list_request_failed">Zapytanie Trakt nie powiodło się</string>
<string name="unit_bytes_tb">TB</string>
<string name="updates_apk_asset_missing">Brak pliku APK w wydaniu</string>
<string name="updates_downloaded_file_missing">Pobrany plik aktualizacji nie istnieje.</string>
<string name="updates_download_empty_body">Pusta treść pobierania</string>
<string name="updates_download_failed_http">Pobieranie nie powiodło się z kodem HTTP %1$d</string>
<string name="updates_download_file_missing">Pobrany plik aktualizacji nie istnieje.</string>
<string name="updates_empty_download_body">Pusta treść pobierania</string>
<string name="updates_github_api_error">Błąd API GitHub releases: %1$d</string>
<string name="updates_no_channel_release">Nie opublikowano jeszcze żadnej aktualizacji.</string>
<string name="updates_release_missing_title">Wydanie nie ma tagu ani nazwy</string>
</resources>

View file

@ -536,6 +536,7 @@
<string name="settings_appearance_amoled_black">AMOLED Black</string>
<string name="settings_appearance_amoled_description">Use pure black backgrounds for OLED screens.</string>
<string name="settings_appearance_app_language">App Language</string>
<string name="settings_appearance_app_language_device">Device language</string>
<string name="settings_appearance_app_language_sheet_title">Choose Language</string>
<string name="settings_appearance_continue_watching_description">Settings for the Continue Watching section.</string>
<string name="settings_appearance_liquid_glass">Liquid Glass</string>
@ -864,6 +865,8 @@
<string name="settings_playback_not_set">Not set</string>
<string name="settings_playback_option_default">Default (media file)</string>
<string name="settings_playback_option_device_language">Device language</string>
<string name="settings_playback_option_original">Original language</string>
<string name="settings_playback_option_original_hint">Requires TMDB enrichment to be enabled</string>
<string name="settings_playback_option_forced">Forced</string>
<string name="settings_playback_option_none">None</string>
<string name="settings_playback_addon_subtitle_startup_all">All subtitles</string>

View file

@ -2004,6 +2004,7 @@ private fun MainAppContent(
parentMetaType = launch.parentMetaType ?: launch.type,
initialPositionMs = launch.resumePositionMs ?: 0L,
initialProgressFraction = launch.resumeProgressFraction,
contentLanguage = cached.contentLanguage,
)
if (playerSettings.externalPlayerEnabled) {
openExternalPlayback(playerLaunch)
@ -2430,6 +2431,7 @@ private fun MainAppContent(
torrentTrackers = launch.torrentTrackers,
initialPositionMs = launch.initialPositionMs,
initialProgressFraction = launch.initialProgressFraction,
contentLanguage = launch.contentLanguage,
onBack = {
ResumePromptRepository.markPlayerExitedNormally()
PlayerLaunchStore.remove(route.launchId)

View file

@ -84,6 +84,7 @@ import nuvio.composeapp.generated.resources.settings_playback_option_default
import nuvio.composeapp.generated.resources.settings_playback_option_device_language
import nuvio.composeapp.generated.resources.settings_playback_option_forced
import nuvio.composeapp.generated.resources.settings_playback_option_none
import nuvio.composeapp.generated.resources.settings_playback_option_original
import nuvio.composeapp.generated.resources.subtitle_language_unknown
import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.getString
@ -97,6 +98,7 @@ data class LanguagePreferenceOption(
object AudioLanguageOption {
const val DEFAULT = "default"
const val DEVICE = "device"
const val ORIGINAL = "original"
}
object SubtitleLanguageOption {
@ -463,6 +465,8 @@ fun languageLabelForCode(code: String?): String = when {
code.equals(AudioLanguageOption.DEVICE, ignoreCase = true) ||
code.equals(SubtitleLanguageOption.DEVICE, ignoreCase = true) ->
stringResource(Res.string.settings_playback_option_device_language)
code.equals(AudioLanguageOption.ORIGINAL, ignoreCase = true) ->
stringResource(Res.string.settings_playback_option_original)
else -> languageLabelResForCode(code)?.let { stringResource(it) }
?: stringResource(Res.string.subtitle_language_unknown)
}
@ -477,6 +481,8 @@ suspend fun getLanguageLabelForCode(code: String?): String = when {
code.equals(AudioLanguageOption.DEVICE, ignoreCase = true) ||
code.equals(SubtitleLanguageOption.DEVICE, ignoreCase = true) ->
getString(Res.string.settings_playback_option_device_language)
code.equals(AudioLanguageOption.ORIGINAL, ignoreCase = true) ->
getString(Res.string.settings_playback_option_original)
else -> languageLabelResForCode(code)?.let { getString(it) }
?: getString(Res.string.subtitle_language_unknown)
}
@ -485,6 +491,7 @@ fun resolvePreferredAudioLanguageTargets(
preferredAudioLanguage: String,
secondaryPreferredAudioLanguage: String?,
deviceLanguages: List<String>,
contentOriginalLanguage: String? = null,
): List<String> {
fun normalize(language: String?): String? {
val normalized = normalizeLanguageCode(language)
@ -495,6 +502,7 @@ fun resolvePreferredAudioLanguageTargets(
SubtitleLanguageOption.NONE,
SubtitleLanguageOption.FORCED,
-> null
AudioLanguageOption.ORIGINAL -> contentOriginalLanguage?.trim()?.lowercase()?.takeIf { it.isNotBlank() }
else -> normalized
}
}
@ -511,6 +519,21 @@ fun resolvePreferredAudioLanguageTargets(
+ listOfNotNull(normalize(secondaryPreferredAudioLanguage))
).distinct()
AudioLanguageOption.ORIGINAL -> {
val originalLang = contentOriginalLanguage?.trim()?.lowercase()?.takeIf { it.isNotBlank() }
if (originalLang != null) {
listOfNotNull(
originalLang,
normalize(secondaryPreferredAudioLanguage),
).distinct()
} else {
// Fallback to device languages when original language is unknown
(deviceLanguages.mapNotNull(::normalize)
+ listOfNotNull(normalize(secondaryPreferredAudioLanguage))
).distinct()
}
}
else -> listOfNotNull(
normalize(preferredAudioLanguage),
normalize(secondaryPreferredAudioLanguage),
@ -575,3 +598,51 @@ fun inferForcedSubtitleTrack(
if ("forced" in text) return true
return text.contains("songs") && text.contains("sign")
}
/**
* Best-effort mapping from country name/code to ISO 639-1 primary language.
* Used as a fallback when [resolveContentLanguage] has no explicit language field.
*/
fun countryToLanguageCode(country: String?): String? {
val normalized = country?.trim()?.lowercase()?.takeIf { it.isNotBlank() } ?: return null
return COUNTRY_TO_LANGUAGE_MAP[normalized]
}
/**
* Resolves the original content language as an ISO 639-1 code.
* Falls back to country-based inference when the explicit language field is absent.
*/
fun resolveContentLanguage(language: String?, country: String?): String? {
normalizeLanguageCode(language)?.let { return it }
countryToLanguageCode(country)?.let { return it }
return null
}
private val COUNTRY_TO_LANGUAGE_MAP = mapOf(
// ISO 3166-1 alpha-2
"jp" to "ja", "kr" to "ko", "cn" to "zh", "tw" to "zh",
"fr" to "fr", "de" to "de", "it" to "it", "es" to "es",
"pt" to "pt", "br" to "pt", "ru" to "ru", "in" to "hi",
"tr" to "tr", "pl" to "pl", "nl" to "nl", "se" to "sv",
"no" to "no", "dk" to "da", "fi" to "fi", "th" to "th",
"il" to "he", "cz" to "cs", "ro" to "ro", "hu" to "hu",
"ua" to "uk", "gr" to "el",
// ISO 3166-1 alpha-3
"jpn" to "ja", "kor" to "ko", "chn" to "zh", "twn" to "zh",
"fra" to "fr", "deu" to "de", "ita" to "it", "esp" to "es",
"prt" to "pt", "bra" to "pt", "rus" to "ru", "ind" to "hi",
"tur" to "tr", "pol" to "pl", "nld" to "nl", "swe" to "sv",
"nor" to "no", "dnk" to "da", "fin" to "fi", "tha" to "th",
"isr" to "he", "cze" to "cs", "rou" to "ro", "hun" to "hu",
"ukr" to "uk", "grc" to "el",
// Common full names
"japan" to "ja", "south korea" to "ko", "korea" to "ko",
"china" to "zh", "taiwan" to "zh", "france" to "fr",
"germany" to "de", "italy" to "it", "spain" to "es",
"portugal" to "pt", "brazil" to "pt", "russia" to "ru",
"india" to "hi", "turkey" to "tr", "poland" to "pl",
"netherlands" to "nl", "sweden" to "sv", "norway" to "no",
"denmark" to "da", "finland" to "fi", "thailand" to "th",
"israel" to "he", "romania" to "ro", "hungary" to "hu",
"ukraine" to "uk", "greece" to "el",
)

View file

@ -50,6 +50,7 @@ data class PlayerLaunch(
val torrentTrackers: List<String> = emptyList(),
val initialPositionMs: Long = 0L,
val initialProgressFraction: Float? = null,
val contentLanguage: String? = null,
)
object PlayerLaunchStore {

View file

@ -38,6 +38,7 @@ fun PlayerScreen(
torrentTrackers: List<String> = emptyList(),
initialPositionMs: Long = 0L,
initialProgressFraction: Float? = null,
contentLanguage: String? = null,
) {
PlayerScreenContent(
PlayerScreenArgs(
@ -74,6 +75,7 @@ fun PlayerScreen(
torrentTrackers = torrentTrackers,
initialPositionMs = initialPositionMs,
initialProgressFraction = initialProgressFraction,
contentLanguage = contentLanguage,
)
)
}

View file

@ -36,4 +36,5 @@ internal data class PlayerScreenArgs(
val torrentTrackers: List<String>,
val initialPositionMs: Long,
val initialProgressFraction: Float?,
val contentLanguage: String? = null,
)

View file

@ -482,5 +482,9 @@ private fun PlayerScreenRuntime.saveDirectStreamForReuse(
videoSize = stream.behaviorHints.videoSize,
bingeGroup = stream.behaviorHints.bingeGroup,
streamType = stream.streamType,
contentLanguage = resolveContentLanguage(
language = metaUiState.meta?.language,
country = metaUiState.meta?.country,
),
)
}

View file

@ -152,6 +152,10 @@ internal fun PlayerScreenRuntime.refreshTracks() {
preferredAudioLanguage = playerSettingsUiState.preferredAudioLanguage,
secondaryPreferredAudioLanguage = playerSettingsUiState.secondaryPreferredAudioLanguage,
deviceLanguages = DeviceLanguagePreferences.preferredLanguageCodes(),
contentOriginalLanguage = resolveContentLanguage(
language = metaUiState.meta?.language,
country = metaUiState.meta?.country,
) ?: args.contentLanguage,
)
if (preferredAudioTargets.isEmpty()) {
preferredAudioSelectionApplied = true

View file

@ -13,12 +13,14 @@ import nuvio.composeapp.generated.resources.lang_portuguese_portugal
import nuvio.composeapp.generated.resources.lang_spanish
import nuvio.composeapp.generated.resources.lang_turkish
import nuvio.composeapp.generated.resources.lang_norwegian
import nuvio.composeapp.generated.resources.settings_appearance_app_language_device
import org.jetbrains.compose.resources.StringResource
enum class AppLanguage(
val code: String,
val labelRes: StringResource,
) {
DEVICE("device", Res.string.settings_appearance_app_language_device),
CZECH("cs", Res.string.lang_czech),
ENGLISH("en", Res.string.lang_english),
FRENCH("fr", Res.string.lang_french),
@ -35,6 +37,6 @@ enum class AppLanguage(
companion object {
fun fromCode(code: String?): AppLanguage =
entries.firstOrNull { it.code.equals(code, ignoreCase = true) } ?: ENGLISH
entries.firstOrNull { it.code.equals(code, ignoreCase = true) } ?: DEVICE
}
}

View file

@ -404,6 +404,7 @@ private fun PlaybackSettingsSection(
description = when (preferredAudioLanguage) {
AudioLanguageOption.DEFAULT -> stringResource(Res.string.settings_playback_option_default)
AudioLanguageOption.DEVICE -> stringResource(Res.string.settings_playback_option_device_language)
AudioLanguageOption.ORIGINAL -> stringResource(Res.string.settings_playback_option_original)
else -> languageLabelForCode(preferredAudioLanguage)
},
enabled = audioLanguageEnabled,
@ -1090,11 +1091,13 @@ private fun PlaybackSettingsSection(
}
if (showPreferredAudioDialog) {
val originalHint = stringResource(Res.string.settings_playback_option_original_hint)
LanguageSelectionDialog(
title = stringResource(Res.string.settings_playback_preferred_audio_language),
options = listOf(
LanguageSelectionOption(AudioLanguageOption.DEFAULT, stringResource(Res.string.settings_playback_option_default)),
LanguageSelectionOption(AudioLanguageOption.DEVICE, stringResource(Res.string.settings_playback_option_device_language)),
LanguageSelectionOption(AudioLanguageOption.ORIGINAL, stringResource(Res.string.settings_playback_option_original), description = originalHint),
) + AvailableLanguageOptions.map { option ->
LanguageSelectionOption(option.code, stringResource(option.labelRes))
},
@ -1108,10 +1111,12 @@ private fun PlaybackSettingsSection(
}
if (showSecondaryAudioDialog) {
val originalHint = stringResource(Res.string.settings_playback_option_original_hint)
LanguageSelectionDialog(
title = stringResource(Res.string.settings_playback_secondary_audio_language),
options = listOf(
LanguageSelectionOption(null, stringResource(Res.string.settings_playback_option_none)),
LanguageSelectionOption(AudioLanguageOption.ORIGINAL, stringResource(Res.string.settings_playback_option_original), description = originalHint),
) + AvailableLanguageOptions.map { option ->
LanguageSelectionOption(option.code, stringResource(option.labelRes))
},
@ -1441,6 +1446,7 @@ private fun formatReuseCacheDuration(hours: Int): String = when {
private data class LanguageSelectionOption(
val value: String?,
val label: String,
val description: String? = null,
)
@Composable
@ -1720,12 +1726,20 @@ private fun LanguageSelectionDialog(
.padding(horizontal = 14.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = option.label,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.weight(1f),
)
Column(modifier = Modifier.weight(1f)) {
Text(
text = option.label,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
)
if (!option.description.isNullOrBlank()) {
Text(
text = option.description,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
Box(
modifier = Modifier.size(24.dp),
contentAlignment = Alignment.Center,

View file

@ -17,7 +17,7 @@ object ThemeSettingsRepository {
private val _liquidGlassNativeTabBarEnabled = MutableStateFlow(false)
val liquidGlassNativeTabBarEnabled: StateFlow<Boolean> = _liquidGlassNativeTabBarEnabled.asStateFlow()
private val _selectedAppLanguage = MutableStateFlow(AppLanguage.ENGLISH)
private val _selectedAppLanguage = MutableStateFlow(AppLanguage.DEVICE)
val selectedAppLanguage: StateFlow<AppLanguage> = _selectedAppLanguage.asStateFlow()
private var hasLoaded = false
@ -38,7 +38,7 @@ object ThemeSettingsRepository {
_liquidGlassNativeTabBarEnabled.value = false
NativeTabBridge.publishAccentColor(AppTheme.WHITE.nativeTabAccentHex())
NativeTabBridge.publishLiquidGlassEnabled(false)
_selectedAppLanguage.value = AppLanguage.ENGLISH
_selectedAppLanguage.value = AppLanguage.DEVICE
}
private fun loadFromDisk() {

View file

@ -19,6 +19,7 @@ data class CachedStreamLink(
val sources: List<String> = emptyList(),
val bingeGroup: String? = null,
val streamType: String? = null,
val contentLanguage: String? = null,
)
internal expect fun epochMs(): Long
@ -56,6 +57,7 @@ object StreamLinkCacheRepository {
sources: List<String> = emptyList(),
bingeGroup: String? = null,
streamType: String? = null,
contentLanguage: String? = null,
) {
if (url.isNotBlank() && url.hasLikelyExpiringPlaybackCredentials()) {
remove(contentKey)
@ -77,6 +79,7 @@ object StreamLinkCacheRepository {
sources = sources,
bingeGroup = bingeGroup,
streamType = streamType,
contentLanguage = contentLanguage,
)
val payload = json.encodeToString(CachedStreamLink.serializer(), entry)
StreamLinkCacheStorage.saveEntry(hashedKey(contentKey), payload)

View file

@ -85,6 +85,9 @@ import androidx.compose.material3.rememberModalBottomSheetState
import coil3.compose.AsyncImage
import com.nuvio.app.core.ui.nuvioSafeBottomPadding
import com.nuvio.app.features.debrid.DebridSettingsRepository
import com.nuvio.app.features.debrid.DirectDebridPlayableResult
import com.nuvio.app.features.debrid.DirectDebridPlaybackResolver
import com.nuvio.app.features.debrid.toastMessage
import com.nuvio.app.features.player.PlayerSettingsRepository
import com.nuvio.app.features.watchprogress.WatchProgressRepository
import kotlinx.coroutines.launch
@ -145,6 +148,7 @@ fun StreamsScreen(
val streamLinkCopiedText = stringResource(Res.string.streams_link_copied)
val noDirectStreamLinkText = stringResource(Res.string.streams_no_direct_link)
var streamActionsTarget by remember(videoId) { mutableStateOf<StreamItem?>(null) }
val downloadScope = rememberCoroutineScope()
var preferredFilterApplied by remember(videoId) { mutableStateOf(false) }
var autoPlayOverlayLogoLoadError by remember(logo) { mutableStateOf(false) }
val autoPlayOverlayLogoUrl = logo?.takeIf { it.isNotBlank() }
@ -356,27 +360,88 @@ fun StreamsScreen(
if (!directUrl.isNullOrBlank()) {
clipboardManager.setText(AnnotatedString(directUrl))
NuvioToastController.show(streamLinkCopiedText)
} else if (DirectDebridPlaybackResolver.shouldResolveToPlayableStream(stream)) {
downloadScope.launch {
val resolved = DirectDebridPlaybackResolver.resolveToPlayableStream(
stream = stream,
season = seasonNumber,
episode = episodeNumber,
)
when (resolved) {
is DirectDebridPlayableResult.Success -> {
val resolvedUrl = resolved.stream.playableDirectUrl
if (!resolvedUrl.isNullOrBlank()) {
clipboardManager.setText(AnnotatedString(resolvedUrl))
NuvioToastController.show(streamLinkCopiedText)
} else {
NuvioToastController.show(noDirectStreamLinkText)
}
}
else -> {
val message = resolved.toastMessage()
if (message != null) {
NuvioToastController.show(message)
}
}
}
}
} else {
NuvioToastController.show(noDirectStreamLinkText)
}
},
onDownload = { stream ->
val result = DownloadsRepository.enqueueFromStream(
contentType = type,
videoId = videoId,
parentMetaId = parentMetaId,
parentMetaType = parentMetaType,
title = title,
logo = logo,
poster = poster,
background = background,
seasonNumber = seasonNumber,
episodeNumber = episodeNumber,
episodeTitle = episodeTitle,
episodeThumbnail = episodeThumbnail,
stream = stream,
)
NuvioToastController.show(result.toastMessage())
if (DirectDebridPlaybackResolver.shouldResolveToPlayableStream(stream)) {
downloadScope.launch {
val resolved = DirectDebridPlaybackResolver.resolveToPlayableStream(
stream = stream,
season = seasonNumber,
episode = episodeNumber,
)
when (resolved) {
is DirectDebridPlayableResult.Success -> {
val result = DownloadsRepository.enqueueFromStream(
contentType = type,
videoId = videoId,
parentMetaId = parentMetaId,
parentMetaType = parentMetaType,
title = title,
logo = logo,
poster = poster,
background = background,
seasonNumber = seasonNumber,
episodeNumber = episodeNumber,
episodeTitle = episodeTitle,
episodeThumbnail = episodeThumbnail,
stream = resolved.stream,
)
NuvioToastController.show(result.toastMessage())
}
else -> {
val message = resolved.toastMessage()
if (message != null) {
NuvioToastController.show(message)
}
}
}
}
} else {
val result = DownloadsRepository.enqueueFromStream(
contentType = type,
videoId = videoId,
parentMetaId = parentMetaId,
parentMetaType = parentMetaType,
title = title,
logo = logo,
poster = poster,
background = background,
seasonNumber = seasonNumber,
episodeNumber = episodeNumber,
episodeTitle = episodeTitle,
episodeThumbnail = episodeThumbnail,
stream = stream,
)
NuvioToastController.show(result.toastMessage())
}
},
onOpen = { stream, openExternally ->
onStreamActionOpen(
@ -922,7 +987,7 @@ private fun LazyListScope.streamSection(
}
},
onLongClick = {
if (stream.playableDirectUrl != null) {
if (stream.playableDirectUrl != null || stream.isAddonDebridCandidate) {
onStreamLongPress(stream)
}
},

View file

@ -72,6 +72,11 @@ actual object ThemeSettingsStorage {
}
actual fun applySelectedAppLanguage(languageCode: String) {
if (languageCode.equals("device", ignoreCase = true)) {
NSUserDefaults.standardUserDefaults.removeObjectForKey("AppleLanguages")
NSUserDefaults.standardUserDefaults.synchronize()
return
}
val normalizedCode = languageCode
.trim()
.takeIf { it.isNotBlank() }
@ -97,6 +102,6 @@ actual object ThemeSettingsStorage {
payload.decodeSyncString(selectedThemeKey)?.let(::saveSelectedTheme)
payload.decodeSyncBoolean(amoledEnabledKey)?.let(::saveAmoledEnabled)
payload.decodeSyncBoolean(liquidGlassNativeTabBarEnabledKey)?.let(::saveLiquidGlassNativeTabBarEnabled)
applySelectedAppLanguage(loadSelectedAppLanguage() ?: AppLanguage.ENGLISH.code)
applySelectedAppLanguage(loadSelectedAppLanguage() ?: AppLanguage.DEVICE.code)
}
}