From a2b993bb9b9d4e8c089ccdfe434b8ebd55b2c8fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrew=20Po=C5=BEenel?= Date: Sun, 18 Jan 2026 22:36:30 +0100 Subject: [PATCH 1/8] Added Slovenian translation --- src/locales/sl/translation.json | 844 ++++++++++++++++++++++++++++++++ 1 file changed, 844 insertions(+) create mode 100644 src/locales/sl/translation.json diff --git a/src/locales/sl/translation.json b/src/locales/sl/translation.json new file mode 100644 index 00000000..05a39151 --- /dev/null +++ b/src/locales/sl/translation.json @@ -0,0 +1,844 @@ +{ + "language_name": "Slovenščina", + "app": { + "successfully_signed_in": "Uspešno ste se prijavili" + }, + "home": { + "surprise_me": "Preseneti me", + "no_results": "Ni najdenih rezultatov", + "start_typing": "Začnite tipkati za iskanje...", + "hot": "Trenutno vroče", + "weekly": "📅 Najboljše igre tedna", + "achievements": "🏆 Igre za premagati" + }, + "sidebar": { + "catalogue": "Katalog", + "library": "Knjižnica", + "downloads": "Prenosi", + "settings": "Nastavitve", + "my_library": "Moja knjižnica", + "downloading_metadata": "{{title}} (Prenos metapodatkov…)", + "paused": "{{title}} (V premoru)", + "downloading": "{{title}} ({{percentage}} - Prenos…)", + "filter": "Filtriraj knjižnico", + "home": "Domov", + "queued": "{{title}} (V čakalni vrsti)", + "game_has_no_executable": "Igra nima izbrane izvršljive datoteke", + "sign_in": "Prijavite se", + "friends": "Prijatelji", + "notifications": "Obvestila", + "need_help": "Potrebujete pomoč?", + "favorites": "Priljubljene", + "playable_button_title": "Pokaži le igre, ki jih lahko igrate zdaj", + "add_custom_game_tooltip": "Dodaj igro po meri", + "show_playable_only_tooltip": "Pokaži samo igrljive", + "custom_game_modal": "Dodaj igro po meri", + "custom_game_modal_description": "Dodajte igro po meri v vašo knjižnico z izbiro izvršljive datoteke", + "custom_game_modal_executable_path": "Pot do izvršljive datoteke", + "custom_game_modal_select_executable": "Izberite izvršljivo datoteko", + "custom_game_modal_title": "Naslov", + "custom_game_modal_enter_title": "Vnesite naslov", + "custom_game_modal_browse": "Brskaj", + "custom_game_modal_cancel": "Prekliči", + "custom_game_modal_add": "Dodaj igro", + "custom_game_modal_adding": "Dodajanje igre...", + "custom_game_modal_success": "Igra po meri je bila uspešno dodana", + "custom_game_modal_failed": "Dodajanje igre po meri ni uspelo", + "custom_game_modal_executable": "Izvršljiva datoteka", + "edit_game_modal": "Prilagodi sredstva", + "edit_game_modal_description": "Prilagodite sredstva in podrobnosti igre", + "edit_game_modal_title": "Naslov", + "edit_game_modal_enter_title": "Vnesite naslov", + "edit_game_modal_image": "Slika", + "edit_game_modal_select_image": "Izberite sliko", + "edit_game_modal_browse": "Brskaj", + "edit_game_modal_image_preview": "Predogled slike", + "edit_game_modal_icon": "Ikona", + "edit_game_modal_select_icon": "Izberite ikono", + "edit_game_modal_icon_preview": "Predogled ikone", + "edit_game_modal_logo": "Logotip", + "edit_game_modal_select_logo": "Izberite logotip", + "edit_game_modal_logo_preview": "Predogled logotipa", + "edit_game_modal_hero": "Hero knjižnice", + "edit_game_modal_select_hero": "Izberite sliko hero knjižnice", + "edit_game_modal_hero_preview": "Predogled hero slike knjižnice", + "edit_game_modal_cancel": "Prekliči", + "edit_game_modal_update": "Posodobi", + "edit_game_modal_updating": "Posodabljanje...", + "edit_game_modal_fill_required": "Prosimo, izpolnite vsa obvezna polja", + "edit_game_modal_success": "Sredstva so bila uspešno posodobljena", + "edit_game_modal_failed": "Posodabljanje sredstev ni uspelo", + "edit_game_modal_image_filter": "Slika", + "edit_game_modal_icon_resolution": "Priporočena resolucija: 256x256px", + "edit_game_modal_logo_resolution": "Priporočena resolucija: 640x360px", + "edit_game_modal_hero_resolution": "Priporočena resolucija: 1920x620px", + "edit_game_modal_assets": "Sredstva", + "edit_game_modal_drop_icon_image_here": "Spustite ikono tukaj", + "edit_game_modal_drop_logo_image_here": "Spustite logotip tukaj", + "edit_game_modal_drop_hero_image_here": "Spustite hero sliko tukaj", + "edit_game_modal_drop_to_replace_icon": "Spustite za zamenjavo ikone", + "edit_game_modal_drop_to_replace_logo": "Spustite za zamenjavo logotipa", + "edit_game_modal_drop_to_replace_hero": "Spustite za zamenjavo hero slike", + "install_decky_plugin": "Namesti Decky vtičnik", + "update_decky_plugin": "Posodobi Decky vtičnik", + "decky_plugin_installed_version": "Decky vtičnik (v{{version}})", + "install_decky_plugin_title": "Namesti Hydra Decky vtičnik", + "install_decky_plugin_message": "To bo preneslo in namestilo Hydra vtičnik za Decky Loader. To lahko zahteva povišane pravice. Nadaljujem?", + "update_decky_plugin_title": "Posodobi Hydra Decky vtičnik", + "update_decky_plugin_message": "Na voljo je nova različica Hydra Decky vtičnika. Ali želite posodobiti zdaj?", + "decky_plugin_installed": "Decky vtičnik v{{version}} je bil uspešno nameščen", + "decky_plugin_installation_failed": "Namestitev Decky vtičnika ni uspela: {{error}}", + "decky_plugin_installation_error": "Napaka pri nameščanju Decky vtičnika: {{error}}", + "confirm": "Potrdi", + "cancel": "Prekliči" + }, + "header": { + "search": "Išči igre", + "search_library": "Išči v knjižnici", + "recent_searches": "Nedavna iskanja", + "suggestions": "Predlogi", + "clear_history": "Počisti", + "remove_from_history": "Odstrani iz zgodovine", + "loading": "Nalaganje...", + "no_results": "Ni rezultatov", + "home": "Domov", + "catalogue": "Katalog", + "library": "Knjižnica", + "downloads": "Prenosi", + "search_results": "Rezultati iskanja", + "settings": "Nastavitve", + "version_available_install": "Različica {{version}} je na voljo. Kliknite tukaj za ponovni zagon in namestitev.", + "version_available_download": "Različica {{version}} je na voljo. Kliknite tukaj za prenos." + }, + "bottom_panel": { + "no_downloads_in_progress": "Ni prenosa v teku", + "downloading_metadata": "Prenos metapodatkov {{title}}…", + "downloading": "Prenos {{title}}… ({{percentage}} končano) - Čas {{eta}} - {{speed}}", + "calculating_eta": "Prenos {{title}}… ({{percentage}} končano) - Izračun preostalega časa…", + "checking_files": "Preverjanje datotek {{title}}… ({{percentage}} končano)", + "extracting": "Razpakiranje {{title}}… ({{percentage}} končano)", + "installing_common_redist": "{{log}}…", + "installation_complete": "Namestitev zaključena", + "installation_complete_message": "Skupni redistributables so bili uspešno nameščeni" + }, + "catalogue": { + "search": "Filtriraj…", + "developers": "Razvijalci", + "genres": "Žanri", + "tags": "Oznake", + "publishers": "Izdajatelji", + "download_sources": "Viri prenosa", + "result_count": "{{resultCount}} rezultatov", + "filter_count": "{{filterCount}} na voljo", + "clear_filters": "Počisti {{filterCount}} izbranih" + }, + "game_details": { + "open_download_options": "Odpri možnosti prenosa", + "download_options_zero": "Ni možnosti prenosa", + "download_options_one": "{{count}} možnost prenosa", + "download_options_other": "{{count}} možnosti prenosa", + "updated_at": "Posodobljeno {{updated_at}}", + "install": "Namesti", + "resume": "Nadaljuj", + "pause": "Premor", + "cancel": "Prekliči", + "remove": "Odstrani", + "space_left_on_disk": "{{space}} prosto na disku", + "eta": "Zaključek {{eta}}", + "calculating_eta": "Izračun preostalega časa…", + "downloading_metadata": "Prenos metapodatkov…", + "filter": "Filtriraj repake", + "requirements": "Sistemske zahteve", + "minimum": "Minimum", + "recommended": "Priporočeno", + "paused": "V premoru", + "release_date": "Izid dne {{date}}", + "publisher": "Objavljeno s strani {{publisher}}", + "hours": "ur", + "minutes": "minut", + "amount_hours": "{{amount}} ur", + "amount_minutes": "{{amount}} minut", + "accuracy": "{{accuracy}}% natančnost", + "add_to_library": "Dodaj v knjižnico", + "already_in_library": "Že v knjižnici", + "remove_from_library": "Odstrani iz knjižnice", + "no_downloads": "Ni razpoložljivih prenosov", + "play_time": "Odigrano {{amount}}", + "last_time_played": "Nazadnje igrano {{period}}", + "not_played_yet": "Še niste igrali {{title}}", + "next_suggestion": "Naslednji predlog", + "play": "Igraj", + "deleting": "Brisanje namestitvenega programa…", + "close": "Zapri", + "playing_now": "Trenutno igranje", + "change": "Spremeni", + "repacks_modal_description": "Izberite repak, ki ga želite prenesti", + "select_folder_hint": "Za spremembo privzete mape pojdite v <0>Nastavitve", + "download_now": "Prenesi zdaj", + "loading": "Nalaganje...", + "no_shop_details": "Podatkov o trgovini ni bilo mogoče pridobiti.", + "download_options": "Možnosti prenosa", + "download_path": "Pot prenosa", + "previous_screenshot": "Prejšnji posnetek zaslona", + "next_screenshot": "Naslednji posnetek zaslona", + "screenshot": "Posnetek zaslona {{number}}", + "open_screenshot": "Odpri posnetek zaslona {{number}}", + "download_settings": "Nastavitve prenosa", + "downloader": "Prenosnik", + "downloader_online": "Spletno", + "downloader_not_configured": "Na voljo, vendar ni nastavljeno", + "downloader_offline": "Povezava je brez povezave", + "downloader_not_available": "Ni na voljo", + "recommended": "Priporočeno", + "go_to_settings": "Pojdi v nastavitve", + "select_executable": "Izberi", + "no_executable_selected": "Ni izbrane izvršljive datoteke", + "open_folder": "Odpri mapo", + "open_download_location": "Poglej prenesene datoteke", + "create_shortcut": "Ustvari bližnjico na namizju", + "create_shortcut_simple": "Ustvari bližnjico", + "clear": "Počisti", + "remove_files": "Odstrani datoteke", + "remove_from_library_title": "Ali ste prepričani?", + "remove_from_library_description": "To bo odstranilo {{game}} iz vaše knjižnice", + "options": "Možnosti", + "properties": "Lastnosti", + "executable_section_title": "Izvršljiva datoteka", + "executable_section_description": "Pot do datoteke, ki se bo izvedla ob kliku na \"Igraj\"", + "downloads_section_title": "Prenosi", + "downloads_section_description": "Preverite posodobitve ali druge različice te igre", + "danger_zone_section_title": "Nevarno območje", + "danger_zone_section_description": "Odstranite to igro iz knjižnice ali datoteke, ki jih je prenesel Hydra", + "download_in_progress": "Prenos v teku", + "download_paused": "Prenos v premoru", + "extracting": "Razpakiranje", + "last_downloaded_option": "Zadnja prenesena možnost", + "new_download_option": "Novo", + "create_steam_shortcut": "Ustvari Steam bližnjico", + "create_shortcut_success": "Bližnjica je bila uspešno ustvarjena", + "you_might_need_to_restart_steam": "Morda boste morali ponovno zagnati Steam, da vidite spremembe", + "create_shortcut_error": "Napaka pri ustvarjanju bližnjice", + "add_to_favorites": "Dodaj med priljubljene", + "remove_from_favorites": "Odstrani iz priljubljenih", + "failed_update_favorites": "Posodabljanje priljubljenih ni uspelo", + "game_removed_from_library": "Igra odstranjena iz knjižnice", + "failed_remove_from_library": "Odstranjevanje iz knjižnice ni uspelo", + "files_removed_success": "Datoteke so bile uspešno odstranjene", + "failed_remove_files": "Odstranjevanje datotek ni uspelo", + "nsfw_content_title": "Ta igra vsebuje neprimerno vsebino", + "nsfw_content_description": "{{title}} vsebuje vsebino, ki morda ni primerna za vse starosti. Ali ste prepričani, da želite nadaljevati?", + "allow_nsfw_content": "Nadaljuj", + "refuse_nsfw_content": "Nazaj", + "stats": "Statistika", + "download_count": "Prenosi", + "player_count": "Aktivni igralci", + "rating_count": "Ocena", + "download_error": "Ta možnost prenosa ni na voljo", + "download": "Prenesi", + "executable_path_in_use": "Izvršljiva datoteka že uporablja \"{{game}}\"", + "warning": "Opozorilo:", + "hydra_needs_to_remain_open": "Za ta prenos mora Hydra ostati odprta, dokler ni končana. Če se Hydra zapre pred končanim prenosom, boste izgubili napredek.", + "achievements": "Dosežki", + "achievements_count": "Dosežki {{unlockedCount}}/{{achievementsCount}}", + "show_more": "Pokaži več", + "show_less": "Pokaži manj", + "reviews": "Mnenja", + "review_played_for": "Odigrano za", + "leave_a_review": "Oddajte mnenje", + "write_review_placeholder": "Delite svoje misli o tej igri...", + "sort_newest": "Najnovejše", + "no_reviews_yet": "Ni še mnenj", + "be_first_to_review": "Bodite prvi, ki delite svoje misli o tej igri!", + "sort_oldest": "Najstarejše", + "sort_highest_score": "Najvišja ocena", + "sort_lowest_score": "Najnižja ocena", + "sort_most_voted": "Največ glasov", + "rating": "Ocena", + "rating_stats": "Ocena", + "rating_very_negative": "Zelo negativno", + "rating_negative": "Negativno", + "rating_neutral": "Nevtralno", + "rating_positive": "Pozitivno", + "rating_very_positive": "Zelo pozitivno", + "submit_review": "Pošlji", + "submitting": "Pošiljanje...", + "review_submitted_successfully": "Mnenje je bilo uspešno poslano!", + "review_submission_failed": "Pošiljanje mnenja ni uspelo. Prosimo, poskusite znova.", + "review_cannot_be_empty": "Polje mnenja ne sme biti prazno.", + "review_deleted_successfully": "Mnenje je bilo uspešno izbrisano.", + "review_deletion_failed": "Brisanje mnenja ni uspelo. Prosimo, poskusite znova.", + "loading_reviews": "Nalagam mnenja...", + "loading_more_reviews": "Nalagam več mnenj...", + "load_more_reviews": "Naloži več mnenj", + "you_seemed_to_enjoy_this_game": "Zdi se, da uživate v tej igri", + "would_you_recommend_this_game": "Bi radi oddali mnenje o tej igri?", + "yes": "Da", + "maybe_later": "Mogoče kasneje", + "cloud_save": "Shranjevanje v oblaku", + "cloud_save_description": "Shranjujte napredek v oblak in nadaljujte igranje na katerikoli napravi", + "backups": "Varnostne kopije", + "install_backup": "Namesti", + "delete_backup": "Izbriši", + "create_backup": "Nova varnostna kopija", + "last_backup_date": "Zadnja varnostna kopija {{date}}", + "no_backup_preview": "Ni shranjenih iger za ta naslov", + "restoring_backup": "Obnavljanje varnostne kopije ({{progress}} končano)…", + "uploading_backup": "Nalaganje varnostne kopije…", + "no_backups": "Za to igro še niste ustvarili varnostnih kopij", + "backup_uploaded": "Varnostna kopija naložena", + "backup_failed": "Varnostna kopija ni uspela", + "backup_deleted": "Varnostna kopija izbrisana", + "backup_restored": "Varnostna kopija obnovljena", + "see_all_achievements": "Poglej vse dosežke", + "sign_in_to_see_achievements": "Prijavite se za ogled dosežkov", + "mapping_method_automatic": "Samodejno", + "mapping_method_manual": "Ročno", + "mapping_method_label": "Način mapiranja", + "files_automatically_mapped": "Datoteke so samodejno preslikane", + "no_backups_created": "Za to igro ni ustvarjenih varnostnih kopij", + "manage_files": "Upravljaj datoteke", + "loading_save_preview": "Iskanje shranjenih iger…", + "wine_prefix": "Wine predpona", + "wine_prefix_description": "Wine predpona, uporabljena za zagon te igre", + "launch_options": "Možnosti zagona", + "launch_options_description": "Napredni uporabniki lahko vpišejo spremembe v možnosti zagona (eksperimentalna funkcija)", + "launch_options_placeholder": "Ni določenega parametra", + "no_download_option_info": "Ni razpoložljivih informacij", + "backup_deletion_failed": "Brisanje varnostne kopije ni uspelo", + "max_number_of_artifacts_reached": "Doseženo je največje število varnostnih kopij za to igro", + "achievements_not_sync": "Oglejte si, kako sinhronizirati svoje dosežke", + "manage_files_description": "Upravljajte, katere datoteke bodo varnostno kopirane in obnovljene", + "select_folder": "Izberite mapo", + "backup_from": "Varnostna kopija od {{date}}", + "automatic_backup_from": "Samodejna varnostna kopija od {{date}}", + "enable_automatic_cloud_sync": "Omogoči samodejno sinhronizacijo v oblaku", + "custom_backup_location_set": "Nastavljena je po meri lokacija varnostne kopije", + "no_directory_selected": "Ni izbrane mape", + "no_write_permission": "V to mapo ni mogoče prenesti. Kliknite tukaj za več informacij.", + "reset_achievements": "Ponastavi dosežke", + "reset_achievements_description": "To bo ponastavilo vse dosežke za {{game}}", + "reset_achievements_title": "Ali ste prepričani?", + "reset_achievements_success": "Dosežki so bili uspešno ponastavljeni", + "reset_achievements_error": "Ponastavitev dosežkov ni uspela", + "download_error_gofile_quota_exceeded": "Presegli ste mesečno kvoto Gofile. Prosimo, počakajte, da se kvota ponastavi.", + "download_error_real_debrid_account_not_authorized": "Vaš račun Real-Debrid ni pooblaščen za nove prenose. Preverite nastavitve računa in poskusite znova.", + "download_error_not_cached_on_real_debrid": "Ta prenos ni na voljo v Real-Debrid in preverjanje statusa prenosa iz Real-Debrid še ni na voljo.", + "update_playtime_title": "Posodobi čas igranja", + "update_playtime_description": "Ročno posodobite čas igranja za {{game}}", + "update_playtime": "Posodobi čas igranja", + "update_playtime_success": "Čas igranja je bil uspešno posodobljen", + "update_playtime_error": "Posodabljanje časa igranja ni uspelo", + "update_game_playtime": "Posodobi čas igranja igre", + "manual_playtime_warning": "Vaše ure bodo označene kot ročno posodobljene, tega ni mogoče razveljaviti.", + "manual_playtime_tooltip": "Ta čas igranja je bil ročno posodobljen", + "download_error_not_cached_on_torbox": "Ta prenos ni na voljo v TorBox in preverjanje statusa prenosa iz TorBox še ni na voljo.", + "download_error_not_cached_on_hydra": "Ta prenos ni na voljo v Nimbus.", + "game_removed_from_favorites": "Igra odstranjena iz priljubljenih", + "game_added_to_favorites": "Igra dodana med priljubljene", + "game_removed_from_pinned": "Igra odstranjena iz pripetih", + "game_added_to_pinned": "Igra pripeta", + "automatically_extract_downloaded_files": "Samodejno razpakiraj prenesene datoteke", + "create_start_menu_shortcut": "Ustvari bližnjico v Start meniju", + "invalid_wine_prefix_path": "Neveljavna pot Wine predpone", + "invalid_wine_prefix_path_description": "Pot do Wine predpone je neveljavna. Preverite pot in poskusite znova.", + "missing_wine_prefix": "Wine predpona je potrebna za ustvarjanje varnostne kopije na Linuxu", + "artifact_renamed": "Varnostna kopija je bila uspešno preimenovana", + "rename_artifact": "Preimenuj varnostno kopijo", + "rename_artifact_description": "Preimenujte varnostno kopijo v bolj opisno ime", + "artifact_name_label": "Ime varnostne kopije", + "artifact_name_placeholder": "Vnesite ime varnostne kopije", + "save_changes": "Shrani spremembe", + "required_field": "To polje je obvezno", + "max_length_field": "To polje mora biti krajše od {{length}} znakov", + "freeze_backup": "Pripni, da jo samodejne varnostne kopije ne prepišejo", + "unfreeze_backup": "Odpni", + "backup_frozen": "Varnostna kopija je pripeta", + "backup_unfrozen": "Varnostna kopija je odprijeta", + "backup_freeze_failed": "Pripenjanje varnostne kopije ni uspelo", + "backup_freeze_failed_description": "Morate pustiti vsaj en prost prostor za samodejne varnostne kopije", + "edit_game_modal_button": "Prilagodi sredstva igre", + "game_details": "Podrobnosti igre", + "currency_symbol": "$", + "currency_country": "us", + "prices": "Cene", + "no_prices_found": "Ni najdenih cen", + "view_all_prices": "Kliknite za ogled vseh cen", + "retail_price": "Maloprodajna cena", + "keyshop_price": "Cena v trgovini s ključki", + "historical_retail": "Zgodovinska maloprodajna cena", + "historical_keyshop": "Zgodovinska cena v trgovini s ključki", + "language": "Jezik", + "caption": "Naslov", + "audio": "Zvok", + "filter_by_source": "Filtriraj po viru", + "no_repacks_found": "Za to igro ni najdenih virov", + "delete_review": "Izbriši mnenje", + "remove_review": "Odstrani mnenje", + "delete_review_modal_title": "Ali ste prepričani, da želite izbrisati svoje mnenje?", + "delete_review_modal_description": "Tega dejanja ni mogoče razveljaviti.", + "delete_review_modal_delete_button": "Izbriši", + "delete_review_modal_cancel_button": "Prekliči", + "vote_failed": "Glasovanja ni uspelo. Prosimo, poskusite znova.", + "show_original": "Pokaži original", + "show_translation": "Pokaži prevod", + "show_original_translated_from": "Pokaži original (prevedeno iz {{language}})", + "hide_original": "Skrij original", + "review_from_blocked_user": "Mnenje blokiranega uporabnika", + "show": "Pokaži", + "hide": "Skrij" + }, + "activation": { + "title": "Aktiviraj Hydra", + "installation_id": "ID namestitve:", + "enter_activation_code": "Vnesite aktivacijsko kodo", + "message": "Če ne veste, kje naj to pridobite, potem tega ne bi smeli imeti.", + "activate": "Aktiviraj", + "loading": "Nalaganje…" + }, + "downloads": { + "resume": "Nadaljuj", + "pause": "Premor", + "eta": "Zaključek {{eta}}", + "paused": "V premoru", + "verifying": "Preverjanje…", + "completed": "Dokončano", + "removed": "Ni preneseno", + "cancel": "Prekliči", + "cancel_download": "Prekliči prenos?", + "cancel_download_description": "Ali ste prepričani, da želite prekiniti ta prenos? Vse prenesene datoteke bodo izbrisane.", + "keep_downloading": "Ne, nadaljuj prenos", + "yes_cancel": "Da, prekliči", + "filter": "Filtriraj prenesene igre", + "remove": "Odstrani", + "downloading_metadata": "Prenos metapodatkov…", + "deleting": "Brisanje namestitvenega programa…", + "delete": "Odstrani namestitveni program", + "delete_modal_title": "Ali ste prepričani?", + "delete_modal_description": "To bo odstranilo vse namestitvene datoteke z računalnika", + "install": "Namesti", + "download_in_progress": "V teku", + "queued_downloads": "Prenosi v čakalni vrsti", + "downloads_completed": "Dokončano", + "queued": "V čakalni vrsti", + "no_downloads_title": "Tako prazno", + "no_downloads_description": "Še niste prenesli ničesar z Hydra, a nikoli ni prepozno začeti.", + "checking_files": "Preverjanje datotek…", + "seeding": "Sejanje", + "stop_seeding": "Ustavi sejanje", + "resume_seeding": "Nadaljuj sejanje", + "options": "Upravljaj", + "extract": "Razpakiraj datoteke", + "extracting": "Razpakiranje datotek…", + "delete_archive_title": "Ali želite izbrisati {{fileName}}?", + "delete_archive_description": "Datoteka je bila uspešno razpakirana in ni več potrebna.", + "yes": "Da", + "no": "Ne", + "network": "OMREŽJE", + "peak": "VRH" + }, + "settings": { + "downloads_path": "Pot prenosa", + "change": "Posodobi", + "notifications": "Obvestila", + "enable_download_notifications": "Ko je prenos končan", + "enable_repack_list_notifications": "Ko je dodan nov repack", + "real_debrid_api_token_label": "Real-Debrid API žeton", + "quit_app_instead_hiding": "Ne skrij Hydre pri zapiranju", + "launch_with_system": "Zaženi Hydra ob zagonu sistema", + "general": "Splošno", + "behavior": "Obnašanje", + "download_sources": "Viri prenosa", + "language": "Jezik", + "api_token": "API žeton", + "enable_real_debrid": "Omogoči Real-Debrid", + "real_debrid_description": "Real-Debrid je neomejen prenašalnik, ki vam omogoča hitro prenašanje datotek, omejeno le s hitrostjo vašega interneta.", + "debrid_invalid_token": "Neveljaven API žeton", + "debrid_api_token_hint": "Žeton API lahko dobite <0>tukaj", + "real_debrid_free_account_error": "Račun \"{{username}}\" je brezplačen. Prosimo, naročite se na Real-Debrid", + "debrid_linked_message": "Račun \"{{username}}\" povezan", + "save_changes": "Shrani spremembe", + "changes_saved": "Spremembe uspešno shranjene", + "download_sources_description": "Hydra bo pridobila povezave za prenos iz teh virov. URL vira mora biti neposredna povezava do .json datoteke, ki vsebuje povezave za prenos.", + "validate_download_source": "Preveri", + "remove_download_source": "Odstrani", + "add_download_source": "Dodaj vir", + "adding": "Dodajanje…", + "failed_add_download_source": "Dodajanje vira za prenos ni uspelo. Poskusite znova.", + "download_source_already_exists": "Ta URL vira za prenos že obstaja.", + "download_count_zero": "Ni možnosti prenosa", + "download_count_one": "{{countFormatted}} možnost prenosa", + "download_count_other": "{{countFormatted}} možnosti prenosa", + "download_source_url": "URL vira za prenos", + "add_download_source_description": "Vstavite URL .json datoteke", + "download_source_up_to_date": "Posodobljeno", + "download_source_errored": "Napaka", + "download_source_pending_matching": "Kmalu posodobljeno", + "download_source_matched": "Posodobljeno", + "download_source_matching": "Posodabljanje", + "download_source_failed": "Napaka", + "download_source_no_information": "Ni podatkov na voljo", + "sync_download_sources": "Sinhroniziraj vire", + "removed_download_source": "Vir prenosa odstranjen", + "removed_download_sources": "Viri prenosa odstranjeni", + "removed_all_download_sources": "Vsi viri prenosa odstranjeni", + "download_sources_synced_successfully": "Vsi viri prenosa so sinhronizirani", + "cancel_button_confirmation_delete_all_sources": "Ne", + "confirm_button_confirmation_delete_all_sources": "Da, izbriši vse", + "title_confirmation_delete_all_sources": "Izbriši vse vire prenosa", + "description_confirmation_delete_all_sources": "Izbrišete vse vire prenosa", + "button_delete_all_sources": "Odstrani vse", + "added_download_source": "Vir prenosa dodan", + "download_sources_synced": "Vsi viri prenosa so sinhronizirani", + "insert_valid_json_url": "Vnesite veljaven JSON URL", + "found_download_option_zero": "Ni možnosti prenosa", + "found_download_option_one": "Najdena {{countFormatted}} možnost prenosa", + "found_download_option_other": "Najdenih {{countFormatted}} možnosti prenosa", + "import": "Uvozi", + "importing": "Uvažanje...", + "public": "Javno", + "private": "Zasebno", + "friends_only": "Samo prijatelji", + "privacy": "Zasebnost", + "profile_visibility": "Vidnost profila", + "profile_visibility_description": "Izberite, kdo lahko vidi vaš profil in knjižnico", + "required_field": "To polje je obvezno", + "source_already_exists": "Ta vir je že bil dodan", + "must_be_valid_url": "Vir mora biti veljaven URL", + "blocked_users": "Blokirani uporabniki", + "user_unblocked": "Uporabnik je odblokiran", + "enable_achievement_notifications": "Ko je dosežek odklenjen", + "launch_minimized": "Zaženi Hydra minimizirano", + "disable_nsfw_alert": "Onemogoči opozorilo NSFW", + "seed_after_download_complete": "Sejanje po končanem prenosu", + "show_hidden_achievement_description": "Pokaži opis skritih dosežkov pred njihovim odklepanjem", + "account": "Račun", + "hydra_cloud": "Hydra Cloud", + "no_users_blocked": "Nimate blokiranih uporabnikov", + "subscription_active_until": "Vaš Hydra Cloud je aktiven do {{date}}", + "manage_subscription": "Upravljaj naročnino", + "update_email": "Posodobi e-pošto", + "update_password": "Posodobi geslo", + "current_email": "Trenutna e-pošta:", + "no_email_account": "Še niste nastavili e-pošte", + "account_data_updated_successfully": "Podatki računa so bili uspešno posodobljeni", + "renew_subscription": "Obnovi Hydra Cloud", + "subscription_expired_at": "Vaša naročnina je potekla {{date}}", + "no_subscription": "Uživajte v Hydri na najboljši način", + "become_subscriber": "Postanite Hydra Cloud uporabnik", + "subscription_renew_cancelled": "Samodejno podaljševanje je onemogočeno", + "subscription_renews_on": "Vaša naročnina se podaljša {{date}}", + "bill_sent_until": "Naslednji račun bo poslan do tega dne", + "no_themes": "Zdi se, da še nimate tem, vendar brez skrbi, kliknite tukaj, da ustvarite svojo prvo mojstrovino.", + "editor_tab_code": "Koda", + "editor_tab_info": "Info", + "editor_tab_save": "Shrani", + "web_store": "Spletna trgovina", + "clear_themes": "Počisti", + "create_theme": "Ustvari", + "create_theme_modal_title": "Ustvari prilagojeno temo", + "create_theme_modal_description": "Ustvarite novo temo za prilagajanje videza Hydre", + "theme_name": "Ime", + "insert_theme_name": "Vstavite ime teme", + "set_theme": "Nastavi temo", + "unset_theme": "Odstrani temo", + "delete_theme": "Izbriši temo", + "edit_theme": "Uredi temo", + "delete_all_themes": "Izbriši vse teme", + "delete_all_themes_description": "To bo izbrisalo vse vaše prilagojene teme", + "delete_theme_description": "To bo izbrisalo temo {{theme}}", + "cancel": "Prekliči", + "appearance": "Videz", + "debrid": "Debrid", + "debrid_description": "Debrid storitve so premium neomejeni prenašalniki, ki vam omogočajo hitro prenašanje datotek, gostovanih na različnih storitvah za gostovanje datotek, omejeno le s hitrostjo vašega interneta.", + "enable_torbox": "Omogoči TorBox", + "torbox_description": "TorBox je vaša premium seedbox storitev, ki se lahko kosuje tudi najboljšim strežnikom na trgu.", + "torbox_account_linked": "TorBox račun povezan", + "create_real_debrid_account": "Kliknite tukaj, če še nimate Real-Debrid računa", + "create_torbox_account": "Kliknite tukaj, če še nimate TorBox računa", + "real_debrid_account_linked": "Real-Debrid račun povezan", + "name_min_length": "Ime teme mora imeti vsaj 3 znake", + "import_theme": "Uvozi temo", + "import_theme_description": "Uvožili boste {{theme}} iz trgovine tem", + "error_importing_theme": "Napaka pri uvozu teme", + "theme_imported": "Tema uspešno uvožena", + "enable_friend_request_notifications": "Ko je prejet prijateljski zahtevek", + "enable_auto_install": "Samodejno prenesi posodobitve", + "common_redist": "Skupni redistributable-ji", + "common_redist_description": "Skupni redistributable-ji so potrebni za zagon nekaterih iger. Priporočamo njihovo namestitev, da se izognete težavam.", + "install_common_redist": "Namesti", + "installing_common_redist": "Nameščanje…", + "show_download_speed_in_megabytes": "Pokaži hitrost prenosa v megabajtih na sekundo", + "extract_files_by_default": "Privzeto razpakiraj datoteke po prenosu", + "enable_steam_achievements": "Omogoči iskanje po Steam dosežkih", + "enable_new_download_options_badges": "Pokaži značke novih možnosti prenosa", + "achievement_custom_notification_position": "Lastna pozicija obvestil o dosežkih", + "top-left": "Zgoraj levo", + "top-center": "Zgoraj na sredini", + "top-right": "Zgoraj desno", + "bottom-left": "Spodaj levo", + "bottom-center": "Spodaj na sredini", + "bottom-right": "Spodaj desno", + "enable_achievement_custom_notifications": "Omogoči lastna obvestila o dosežkih", + "alignment": "Poravnava", + "variation": "Variacija", + "default": "Privzeto", + "rare": "Redko", + "platinum": "Platinasto", + "hidden": "Skrito", + "test_notification": "Preizkusno obvestilo", + "achievement_sound_volume": "Glasnost zvoka dosežka", + "select_achievement_sound": "Izberite zvok dosežka", + "change_achievement_sound": "Spremeni zvok dosežka", + "remove_achievement_sound": "Odstrani zvok dosežka", + "preview_sound": "Predogled zvoka", + "select": "Izberi", + "preview": "Predogled", + "remove": "Odstrani", + "no_sound_file_selected": "Nobena zvočna datoteka ni izbrana", + "notification_preview": "Predogled obvestila o dosežku", + "enable_friend_start_game_notifications": "Ko prijatelj začne igrati igro", + "autoplay_trailers_on_game_page": "Samodejno predvajaj napovednike na strani igre", + "hide_to_tray_on_game_start": "Skrij Hydreo v sistemsko vrstico ob zagonu igre", + "downloads": "Prenosi", + "use_native_http_downloader": "Uporabi izvorni HTTP prenašalnik (eksperimentalno)", + "cannot_change_downloader_while_downloading": "Nastavitve ni mogoče spremeniti med prenosom", + "notifications": { + "download_complete": "Prenos končan", + "game_ready_to_install": "{{title}} je pripravljen za namestitev", + "repack_list_updated": "Seznam repackov posodobljen", + "repack_count_one": "{{count}} repack dodan", + "repack_count_other": "{{count}} repackov dodanih", + "new_update_available": "Različica {{version}} na voljo", + "restart_to_install_update": "Znova zaženite Hydreo za namestitev posodobitve", + "notification_achievement_unlocked_title": "Dosežek odklenjen za {{game}}", + "notification_achievement_unlocked_body": "{{achievement}} in drugi {{count}} so bili odklenjeni", + "new_friend_request_description": "{{displayName}} vam je poslal prijateljsko zahtevo", + "new_friend_request_title": "Nova prijateljska zahteva", + "extraction_complete": "Razpakiranje končano", + "game_extracted": "{{title}} je bil uspešno razpakiran", + "friend_started_playing_game": "{{displayName}} je začel igrati igro", + "test_achievement_notification_title": "To je preizkusno obvestilo", + "test_achievement_notification_description": "Kar kul, kajne?" + }, + "system_tray": { + "open": "Odpri Hydreo", + "quit": "Izhod" + }, + "game_card": { + "available_one": "Na voljo", + "available_other": "Na voljo", + "no_downloads": "Ni razpoložljivih prenosov", + "calculating": "Računam" + }, + "binary_not_found_modal": { + "title": "Programi niso nameščeni", + "description": "Izvajalniki Wine ali Lutris niso bili najdeni na vašem sistemu", + "instructions": "Preverite pravi način za namestitev katerega od njih na vašo Linux distribucijo, da bi igra lahko normalno tekla" + }, + "modal": { + "close": "Zapri gumb" + }, + "forms": { + "toggle_password_visibility": "Preklopi vidnost gesla" + }, + "user_profile": { + "amount_hours": "{{amount}} ur", + "amount_minutes": "{{amount}} minut", + "amount_hours_short": "{{amount}}h", + "amount_minutes_short": "{{amount}}m", + "last_time_played": "Nazadnje igrano {{period}}", + "activity": "Nedavna dejavnost", + "library": "Knjižnica", + "pinned": "Pripeto", + "sort_by": "Razvrsti po:", + "achievements_earned": "Odklenjeni dosežki", + "played_recently": "Nazadnje igrano", + "playtime": "Čas igranja", + "total_play_time": "Skupni čas igranja", + "manual_playtime_tooltip": "Ta čas igranja je bil ročno posodobljen", + "no_recent_activity_title": "Hmmm… nič tukaj", + "no_recent_activity_description": "Niste igrali nobene igre v zadnjem času. Čas je, da to spremenite!", + "display_name": "Prikazno ime", + "saving": "Shranjevanje", + "save": "Shrani", + "edit_profile": "Uredi profil", + "saved_successfully": "Uspešno shranjeno", + "try_again": "Prosimo, poskusite znova", + "sign_out_modal_title": "Ste prepričani?", + "cancel": "Prekliči", + "successfully_signed_out": "Uspešno odjavljeni", + "sign_out": "Odjavi se", + "playing_for": "Igra za {{amount}}", + "sign_out_modal_text": "Vaša knjižnica je povezana s trenutnim računom. Ob odjavi knjižnica ne bo več vidna, napredek pa se ne bo shranil. Nadaljujete z odjavo?", + "add_friends": "Dodaj prijatelje", + "add": "Dodaj", + "friend_code": "Koda prijatelja", + "see_profile": "Poglej profil", + "sending": "Pošiljanje", + "friend_request_sent": "Zahteva za prijateljstvo poslana", + "friends": "Prijatelji", + "badges": "Značke", + "friends_list": "Seznam prijateljev", + "user_not_found": "Uporabnik ni najden", + "block_user": "Blokiraj uporabnika", + "add_friend": "Dodaj prijatelja", + "request_sent": "Zahteva poslana", + "request_received": "Zahteva prejeta", + "accept_request": "Sprejmi zahtevo", + "ignore_request": "Ignoriraj zahtevo", + "cancel_request": "Prekliči zahtevo", + "undo_friendship": "Razveljavi prijateljstvo", + "friendship_removed": "Prijatelj odstranjen", + "request_accepted": "Zahteva sprejeta", + "user_blocked_successfully": "Uporabnik uspešno blokiran", + "user_block_modal_text": "To bo blokiralo {{displayName}}", + "blocked_users": "Blokirani uporabniki", + "unblock": "Odblokiraj", + "no_friends_added": "Nimate dodanih prijateljev", + "no_friends_yet": "Še niste dodali prijateljev", + "view_all": "Poglej vse", + "load_more": "Naloži več", + "loading": "Nalaganje", + "pending": "V teku", + "no_pending_invites": "Nimate čakajočih povabil", + "no_blocked_users": "Nimate blokiranih uporabnikov", + "friend_code_copied": "Koda prijatelja kopirana", + "undo_friendship_modal_text": "To bo razveljavilo vaše prijateljstvo z {{displayName}}", + "privacy_hint": "Za prilagoditev, kdo to vidi, pojdite na <0>Nastavitve", + "locked_profile": "Ta profil je zaseben", + "image_process_failure": "Napaka pri obdelavi slike", + "required_field": "To polje je obvezno", + "displayname_min_length": "Prikazno ime mora biti dolgo vsaj 3 znake", + "displayname_max_length": "Prikazno ime mora imeti največ 50 znakov", + "report_profile": "Prijavi ta profil", + "report_reason": "Zakaj prijavljate ta profil?", + "report_description": "Dodatne informacije", + "report_description_placeholder": "Dodatne informacije", + "report": "Prijavi", + "report_reason_hate": "Sovražni govor", + "report_reason_sexual_content": "Seksualna vsebina", + "report_reason_violence": "Nasilje", + "report_reason_spam": "Spam", + "report_reason_other": "Drugo", + "profile_reported": "Profil prijavljen", + "your_friend_code": "Vaša koda prijatelja:", + "copy_friend_code": "Kopiraj kodo prijatelja", + "copied": "Kopirano!", + "upload_banner": "Naloži banner", + "uploading_banner": "Nalaganje bannerja…", + "change_banner": "Spremeni banner", + "replace_banner": "Zamenjaj banner", + "remove_banner": "Odstrani banner", + "remove_banner_modal_title": "Odstrani banner?", + "remove_banner_confirmation": "Ali ste prepričani, da želite odstraniti banner? Kadarkoli lahko izberete novega.", + "remove": "Odstrani", + "background_image_updated": "Pozadinska slika posodobljena", + "stats": "Statistika", + "achievements": "dosežki", + "games": "Igre", + "top_percentile": "Top {{percentile}}%", + "ranking_updated_weekly": "Uvrstitev se posodablja tedensko", + "playing": "Igra {{game}}", + "achievements_unlocked": "Dosežki odklenjeni", + "earned_points": "Zaslužene točke", + "show_achievements_on_profile": "Pokaži vaše dosežke na profilu", + "show_points_on_profile": "Pokaži vaše zaslužene točke na profilu", + "error_adding_friend": "Zahteve za prijatelja ni bilo mogoče poslati. Preverite kodo prijatelja", + "friend_code_length_error": "Koda prijatelja mora vsebovati 8 znakov", + "game_removed_from_pinned": "Igra odstranjena iz pripetih", + "game_added_to_pinned": "Igra dodana med pripete", + "karma": "Karma", + "karma_count": "karma", + "user_reviews": "Mnenja", + "delete_review": "Izbriši mnenje", + "loading_reviews": "Nalaganje mnenj...", + "wrapped_2025": "Wrapped 2025" + }, + "library": { + "library": "Knjižnica", + "play": "Igraj", + "download": "Prenesi", + "downloading": "Prenašanje", + "game": "igra", + "games": "igre", + "grid_view": "Mrežni pogled", + "compact_view": "Kompaktni pogled", + "large_view": "Velik pogled", + "no_games_title": "Vaša knjižnica je prazna", + "no_games_description": "Dodajte igre iz kataloga ali jih prenesite, da začnete", + "amount_hours": "{{amount}} ur", + "amount_minutes": "{{amount}} minut", + "amount_hours_short": "{{amount}}h", + "amount_minutes_short": "{{amount}}m", + "manual_playtime_tooltip": "Ta čas igranja je bil ročno posodobljen", + "all_games": "Vse igre", + "recently_played": "Nedavno igrane", + "favorites": "Priljubljene" + }, + "achievement": { + "achievement_unlocked": "Dosežek odklenjen", + "user_achievements": "Dosežki uporabnika {{displayName}}", + "your_achievements": "Vaši dosežki", + "unlocked_at": "Odklenjeno: {{date}}", + "subscription_needed": "Naročnina na Hydra Cloud je potrebna za ogled te vsebine", + "new_achievements_unlocked": "Odklenili ste {{achievementCount}} novih dosežkov iz {{gameCount}} iger", + "achievement_progress": "{{unlockedCount}}/{{totalCount}} dosežkov", + "achievements_unlocked_for_game": "Odklenili ste {{achievementCount}} novih dosežkov za {{gameTitle}}", + "hidden_achievement_tooltip": "To je skriti dosežek", + "achievement_earn_points": "Z zaslužite {{points}} točk s tem dosežkom", + "earned_points": "Zaslužene točke:", + "available_points": "Razpoložljive točke:", + "how_to_earn_achievements_points": "Kako zaslužiti točke za dosežke?" + }, + "hydra_cloud": { + "subscription_tour_title": "Naročnina Hydra Cloud", + "subscribe_now": "Naroči se zdaj", + "cloud_saving": "Shranjevanje v oblak", + "cloud_achievements": "Shrani svoje dosežke v oblak", + "animated_profile_picture": "Animirane profilne slike", + "premium_support": "Premium podpora", + "show_and_compare_achievements": "Pokaži in primerjaj svoje dosežke z drugimi uporabniki", + "animated_profile_banner": "Animirani profilni banner", + "hydra_cloud": "Hydra Cloud", + "hydra_cloud_feature_found": "Pravkar ste odkrili funkcijo Hydra Cloud!", + "learn_more": "Več informacij", + "debrid_description": "Prenesite do 4x hitreje z Nimbusom" + }, + "notifications_page": { + "title": "Obvestila", + "mark_all_as_read": "Označi vse kot prebrano", + "clear_all": "Počisti vse", + "loading": "Nalagam...", + "empty_title": "Ni obvestil", + "empty_description": "Ste na tekočem! Preverite kasneje za nove posodobitve.", + "empty_filter_description": "Nobeno obvestilo ne ustreza tem filtram.", + "filter_all": "Vse", + "filter_unread": "Neprebrano", + "filter_friends": "Prijatelji", + "filter_badges": "Značke", + "filter_upvotes": "Glasovi za všečkanje", + "filter_local": "Lokalno", + "load_more": "Naloži več", + "dismiss": "Opusti", + "accept": "Sprejmi", + "refuse": "Zavrni", + "notification": "Obvestilo", + "friend_request_received_title": "Nova prijateljska zahteva!", + "friend_request_received_description": "{{displayName}} želi biti vaš prijatelj", + "friend_request_accepted_title": "Zahteva za prijateljstvo sprejeta!", + "friend_request_accepted_description": "{{displayName}} je sprejel vašo zahtevo", + "badge_received_title": "Prejeli ste novo značko!", + "badge_received_description": "{{badgeName}}", + "review_upvote_title": "Vaša recenzija za {{gameTitle}} je dobila glasove!", + "review_upvote_description": "Vaša recenzija je dobila {{count}} novih glasov", + "marked_all_as_read": "Vsa obvestila označena kot prebrana", + "failed_to_mark_as_read": "Neuspešno označevanje obvestil kot prebranih", + "cleared_all": "Vsa obvestila izbrisana", + "failed_to_clear": "Neuspešno brisanje obvestil", + "failed_to_load": "Neuspešno nalaganje obvestil", + "failed_to_dismiss": "Neuspešno opustitev obvestila", + "friend_request_accepted": "Zahteva za prijateljstvo sprejeta", + "friend_request_refused": "Zahteva za prijateljstvo zavrnjena" + } + } + } \ No newline at end of file From 128f864ca7f686e0a41621a0b10201254789cc02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrew=20Po=C5=BEenel?= Date: Sun, 18 Jan 2026 23:02:46 +0100 Subject: [PATCH 2/8] Reformat the translation.json file --- src/locales/sl/translation.json | 800 ++++++++++++++++---------------- 1 file changed, 400 insertions(+), 400 deletions(-) diff --git a/src/locales/sl/translation.json b/src/locales/sl/translation.json index 05a39151..8b024033 100644 --- a/src/locales/sl/translation.json +++ b/src/locales/sl/translation.json @@ -439,406 +439,406 @@ "settings": { "downloads_path": "Pot prenosa", "change": "Posodobi", - "notifications": "Obvestila", - "enable_download_notifications": "Ko je prenos končan", - "enable_repack_list_notifications": "Ko je dodan nov repack", - "real_debrid_api_token_label": "Real-Debrid API žeton", - "quit_app_instead_hiding": "Ne skrij Hydre pri zapiranju", - "launch_with_system": "Zaženi Hydra ob zagonu sistema", - "general": "Splošno", - "behavior": "Obnašanje", - "download_sources": "Viri prenosa", - "language": "Jezik", - "api_token": "API žeton", - "enable_real_debrid": "Omogoči Real-Debrid", - "real_debrid_description": "Real-Debrid je neomejen prenašalnik, ki vam omogoča hitro prenašanje datotek, omejeno le s hitrostjo vašega interneta.", - "debrid_invalid_token": "Neveljaven API žeton", - "debrid_api_token_hint": "Žeton API lahko dobite <0>tukaj", - "real_debrid_free_account_error": "Račun \"{{username}}\" je brezplačen. Prosimo, naročite se na Real-Debrid", - "debrid_linked_message": "Račun \"{{username}}\" povezan", - "save_changes": "Shrani spremembe", - "changes_saved": "Spremembe uspešno shranjene", - "download_sources_description": "Hydra bo pridobila povezave za prenos iz teh virov. URL vira mora biti neposredna povezava do .json datoteke, ki vsebuje povezave za prenos.", - "validate_download_source": "Preveri", - "remove_download_source": "Odstrani", - "add_download_source": "Dodaj vir", - "adding": "Dodajanje…", - "failed_add_download_source": "Dodajanje vira za prenos ni uspelo. Poskusite znova.", - "download_source_already_exists": "Ta URL vira za prenos že obstaja.", - "download_count_zero": "Ni možnosti prenosa", - "download_count_one": "{{countFormatted}} možnost prenosa", - "download_count_other": "{{countFormatted}} možnosti prenosa", - "download_source_url": "URL vira za prenos", - "add_download_source_description": "Vstavite URL .json datoteke", - "download_source_up_to_date": "Posodobljeno", - "download_source_errored": "Napaka", - "download_source_pending_matching": "Kmalu posodobljeno", - "download_source_matched": "Posodobljeno", - "download_source_matching": "Posodabljanje", - "download_source_failed": "Napaka", - "download_source_no_information": "Ni podatkov na voljo", - "sync_download_sources": "Sinhroniziraj vire", - "removed_download_source": "Vir prenosa odstranjen", - "removed_download_sources": "Viri prenosa odstranjeni", - "removed_all_download_sources": "Vsi viri prenosa odstranjeni", - "download_sources_synced_successfully": "Vsi viri prenosa so sinhronizirani", - "cancel_button_confirmation_delete_all_sources": "Ne", - "confirm_button_confirmation_delete_all_sources": "Da, izbriši vse", - "title_confirmation_delete_all_sources": "Izbriši vse vire prenosa", - "description_confirmation_delete_all_sources": "Izbrišete vse vire prenosa", - "button_delete_all_sources": "Odstrani vse", - "added_download_source": "Vir prenosa dodan", - "download_sources_synced": "Vsi viri prenosa so sinhronizirani", - "insert_valid_json_url": "Vnesite veljaven JSON URL", - "found_download_option_zero": "Ni možnosti prenosa", - "found_download_option_one": "Najdena {{countFormatted}} možnost prenosa", - "found_download_option_other": "Najdenih {{countFormatted}} možnosti prenosa", - "import": "Uvozi", - "importing": "Uvažanje...", - "public": "Javno", - "private": "Zasebno", - "friends_only": "Samo prijatelji", - "privacy": "Zasebnost", - "profile_visibility": "Vidnost profila", - "profile_visibility_description": "Izberite, kdo lahko vidi vaš profil in knjižnico", - "required_field": "To polje je obvezno", - "source_already_exists": "Ta vir je že bil dodan", - "must_be_valid_url": "Vir mora biti veljaven URL", - "blocked_users": "Blokirani uporabniki", - "user_unblocked": "Uporabnik je odblokiran", - "enable_achievement_notifications": "Ko je dosežek odklenjen", - "launch_minimized": "Zaženi Hydra minimizirano", - "disable_nsfw_alert": "Onemogoči opozorilo NSFW", - "seed_after_download_complete": "Sejanje po končanem prenosu", - "show_hidden_achievement_description": "Pokaži opis skritih dosežkov pred njihovim odklepanjem", - "account": "Račun", - "hydra_cloud": "Hydra Cloud", - "no_users_blocked": "Nimate blokiranih uporabnikov", - "subscription_active_until": "Vaš Hydra Cloud je aktiven do {{date}}", - "manage_subscription": "Upravljaj naročnino", - "update_email": "Posodobi e-pošto", - "update_password": "Posodobi geslo", - "current_email": "Trenutna e-pošta:", - "no_email_account": "Še niste nastavili e-pošte", - "account_data_updated_successfully": "Podatki računa so bili uspešno posodobljeni", - "renew_subscription": "Obnovi Hydra Cloud", - "subscription_expired_at": "Vaša naročnina je potekla {{date}}", - "no_subscription": "Uživajte v Hydri na najboljši način", - "become_subscriber": "Postanite Hydra Cloud uporabnik", - "subscription_renew_cancelled": "Samodejno podaljševanje je onemogočeno", - "subscription_renews_on": "Vaša naročnina se podaljša {{date}}", - "bill_sent_until": "Naslednji račun bo poslan do tega dne", - "no_themes": "Zdi se, da še nimate tem, vendar brez skrbi, kliknite tukaj, da ustvarite svojo prvo mojstrovino.", - "editor_tab_code": "Koda", - "editor_tab_info": "Info", - "editor_tab_save": "Shrani", - "web_store": "Spletna trgovina", - "clear_themes": "Počisti", - "create_theme": "Ustvari", - "create_theme_modal_title": "Ustvari prilagojeno temo", - "create_theme_modal_description": "Ustvarite novo temo za prilagajanje videza Hydre", - "theme_name": "Ime", - "insert_theme_name": "Vstavite ime teme", - "set_theme": "Nastavi temo", - "unset_theme": "Odstrani temo", - "delete_theme": "Izbriši temo", - "edit_theme": "Uredi temo", - "delete_all_themes": "Izbriši vse teme", - "delete_all_themes_description": "To bo izbrisalo vse vaše prilagojene teme", - "delete_theme_description": "To bo izbrisalo temo {{theme}}", - "cancel": "Prekliči", - "appearance": "Videz", - "debrid": "Debrid", - "debrid_description": "Debrid storitve so premium neomejeni prenašalniki, ki vam omogočajo hitro prenašanje datotek, gostovanih na različnih storitvah za gostovanje datotek, omejeno le s hitrostjo vašega interneta.", - "enable_torbox": "Omogoči TorBox", - "torbox_description": "TorBox je vaša premium seedbox storitev, ki se lahko kosuje tudi najboljšim strežnikom na trgu.", - "torbox_account_linked": "TorBox račun povezan", - "create_real_debrid_account": "Kliknite tukaj, če še nimate Real-Debrid računa", - "create_torbox_account": "Kliknite tukaj, če še nimate TorBox računa", - "real_debrid_account_linked": "Real-Debrid račun povezan", - "name_min_length": "Ime teme mora imeti vsaj 3 znake", - "import_theme": "Uvozi temo", - "import_theme_description": "Uvožili boste {{theme}} iz trgovine tem", - "error_importing_theme": "Napaka pri uvozu teme", - "theme_imported": "Tema uspešno uvožena", - "enable_friend_request_notifications": "Ko je prejet prijateljski zahtevek", - "enable_auto_install": "Samodejno prenesi posodobitve", - "common_redist": "Skupni redistributable-ji", - "common_redist_description": "Skupni redistributable-ji so potrebni za zagon nekaterih iger. Priporočamo njihovo namestitev, da se izognete težavam.", - "install_common_redist": "Namesti", - "installing_common_redist": "Nameščanje…", - "show_download_speed_in_megabytes": "Pokaži hitrost prenosa v megabajtih na sekundo", - "extract_files_by_default": "Privzeto razpakiraj datoteke po prenosu", - "enable_steam_achievements": "Omogoči iskanje po Steam dosežkih", - "enable_new_download_options_badges": "Pokaži značke novih možnosti prenosa", - "achievement_custom_notification_position": "Lastna pozicija obvestil o dosežkih", - "top-left": "Zgoraj levo", - "top-center": "Zgoraj na sredini", - "top-right": "Zgoraj desno", - "bottom-left": "Spodaj levo", - "bottom-center": "Spodaj na sredini", - "bottom-right": "Spodaj desno", - "enable_achievement_custom_notifications": "Omogoči lastna obvestila o dosežkih", - "alignment": "Poravnava", - "variation": "Variacija", - "default": "Privzeto", - "rare": "Redko", - "platinum": "Platinasto", - "hidden": "Skrito", - "test_notification": "Preizkusno obvestilo", - "achievement_sound_volume": "Glasnost zvoka dosežka", - "select_achievement_sound": "Izberite zvok dosežka", - "change_achievement_sound": "Spremeni zvok dosežka", - "remove_achievement_sound": "Odstrani zvok dosežka", - "preview_sound": "Predogled zvoka", - "select": "Izberi", - "preview": "Predogled", - "remove": "Odstrani", - "no_sound_file_selected": "Nobena zvočna datoteka ni izbrana", - "notification_preview": "Predogled obvestila o dosežku", - "enable_friend_start_game_notifications": "Ko prijatelj začne igrati igro", - "autoplay_trailers_on_game_page": "Samodejno predvajaj napovednike na strani igre", - "hide_to_tray_on_game_start": "Skrij Hydreo v sistemsko vrstico ob zagonu igre", - "downloads": "Prenosi", - "use_native_http_downloader": "Uporabi izvorni HTTP prenašalnik (eksperimentalno)", - "cannot_change_downloader_while_downloading": "Nastavitve ni mogoče spremeniti med prenosom", - "notifications": { - "download_complete": "Prenos končan", - "game_ready_to_install": "{{title}} je pripravljen za namestitev", - "repack_list_updated": "Seznam repackov posodobljen", - "repack_count_one": "{{count}} repack dodan", - "repack_count_other": "{{count}} repackov dodanih", - "new_update_available": "Različica {{version}} na voljo", - "restart_to_install_update": "Znova zaženite Hydreo za namestitev posodobitve", - "notification_achievement_unlocked_title": "Dosežek odklenjen za {{game}}", - "notification_achievement_unlocked_body": "{{achievement}} in drugi {{count}} so bili odklenjeni", - "new_friend_request_description": "{{displayName}} vam je poslal prijateljsko zahtevo", - "new_friend_request_title": "Nova prijateljska zahteva", - "extraction_complete": "Razpakiranje končano", - "game_extracted": "{{title}} je bil uspešno razpakiran", - "friend_started_playing_game": "{{displayName}} je začel igrati igro", - "test_achievement_notification_title": "To je preizkusno obvestilo", - "test_achievement_notification_description": "Kar kul, kajne?" - }, - "system_tray": { - "open": "Odpri Hydreo", - "quit": "Izhod" - }, - "game_card": { - "available_one": "Na voljo", - "available_other": "Na voljo", - "no_downloads": "Ni razpoložljivih prenosov", - "calculating": "Računam" - }, - "binary_not_found_modal": { - "title": "Programi niso nameščeni", - "description": "Izvajalniki Wine ali Lutris niso bili najdeni na vašem sistemu", - "instructions": "Preverite pravi način za namestitev katerega od njih na vašo Linux distribucijo, da bi igra lahko normalno tekla" - }, - "modal": { - "close": "Zapri gumb" - }, - "forms": { - "toggle_password_visibility": "Preklopi vidnost gesla" - }, - "user_profile": { - "amount_hours": "{{amount}} ur", - "amount_minutes": "{{amount}} minut", - "amount_hours_short": "{{amount}}h", - "amount_minutes_short": "{{amount}}m", - "last_time_played": "Nazadnje igrano {{period}}", - "activity": "Nedavna dejavnost", - "library": "Knjižnica", - "pinned": "Pripeto", - "sort_by": "Razvrsti po:", - "achievements_earned": "Odklenjeni dosežki", - "played_recently": "Nazadnje igrano", - "playtime": "Čas igranja", - "total_play_time": "Skupni čas igranja", - "manual_playtime_tooltip": "Ta čas igranja je bil ročno posodobljen", - "no_recent_activity_title": "Hmmm… nič tukaj", - "no_recent_activity_description": "Niste igrali nobene igre v zadnjem času. Čas je, da to spremenite!", - "display_name": "Prikazno ime", - "saving": "Shranjevanje", - "save": "Shrani", - "edit_profile": "Uredi profil", - "saved_successfully": "Uspešno shranjeno", - "try_again": "Prosimo, poskusite znova", - "sign_out_modal_title": "Ste prepričani?", - "cancel": "Prekliči", - "successfully_signed_out": "Uspešno odjavljeni", - "sign_out": "Odjavi se", - "playing_for": "Igra za {{amount}}", - "sign_out_modal_text": "Vaša knjižnica je povezana s trenutnim računom. Ob odjavi knjižnica ne bo več vidna, napredek pa se ne bo shranil. Nadaljujete z odjavo?", - "add_friends": "Dodaj prijatelje", - "add": "Dodaj", - "friend_code": "Koda prijatelja", - "see_profile": "Poglej profil", - "sending": "Pošiljanje", - "friend_request_sent": "Zahteva za prijateljstvo poslana", - "friends": "Prijatelji", - "badges": "Značke", - "friends_list": "Seznam prijateljev", - "user_not_found": "Uporabnik ni najden", - "block_user": "Blokiraj uporabnika", - "add_friend": "Dodaj prijatelja", - "request_sent": "Zahteva poslana", - "request_received": "Zahteva prejeta", - "accept_request": "Sprejmi zahtevo", - "ignore_request": "Ignoriraj zahtevo", - "cancel_request": "Prekliči zahtevo", - "undo_friendship": "Razveljavi prijateljstvo", - "friendship_removed": "Prijatelj odstranjen", - "request_accepted": "Zahteva sprejeta", - "user_blocked_successfully": "Uporabnik uspešno blokiran", - "user_block_modal_text": "To bo blokiralo {{displayName}}", - "blocked_users": "Blokirani uporabniki", - "unblock": "Odblokiraj", - "no_friends_added": "Nimate dodanih prijateljev", - "no_friends_yet": "Še niste dodali prijateljev", - "view_all": "Poglej vse", - "load_more": "Naloži več", - "loading": "Nalaganje", - "pending": "V teku", - "no_pending_invites": "Nimate čakajočih povabil", - "no_blocked_users": "Nimate blokiranih uporabnikov", - "friend_code_copied": "Koda prijatelja kopirana", - "undo_friendship_modal_text": "To bo razveljavilo vaše prijateljstvo z {{displayName}}", - "privacy_hint": "Za prilagoditev, kdo to vidi, pojdite na <0>Nastavitve", - "locked_profile": "Ta profil je zaseben", - "image_process_failure": "Napaka pri obdelavi slike", + "notifications": "Obvestila", + "enable_download_notifications": "Ko je prenos končan", + "enable_repack_list_notifications": "Ko je dodan nov repack", + "real_debrid_api_token_label": "Real-Debrid API žeton", + "quit_app_instead_hiding": "Ne skrij Hydre pri zapiranju", + "launch_with_system": "Zaženi Hydra ob zagonu sistema", + "general": "Splošno", + "behavior": "Obnašanje", + "download_sources": "Viri prenosa", + "language": "Jezik", + "api_token": "API žeton", + "enable_real_debrid": "Omogoči Real-Debrid", + "real_debrid_description": "Real-Debrid je neomejen prenašalnik, ki vam omogoča hitro prenašanje datotek, omejeno le s hitrostjo vašega interneta.", + "debrid_invalid_token": "Neveljaven API žeton", + "debrid_api_token_hint": "Žeton API lahko dobite <0>tukaj", + "real_debrid_free_account_error": "Račun \"{{username}}\" je brezplačen. Prosimo, naročite se na Real-Debrid", + "debrid_linked_message": "Račun \"{{username}}\" povezan", + "save_changes": "Shrani spremembe", + "changes_saved": "Spremembe uspešno shranjene", + "download_sources_description": "Hydra bo pridobila povezave za prenos iz teh virov. URL vira mora biti neposredna povezava do .json datoteke, ki vsebuje povezave za prenos.", + "validate_download_source": "Preveri", + "remove_download_source": "Odstrani", + "add_download_source": "Dodaj vir", + "adding": "Dodajanje…", + "failed_add_download_source": "Dodajanje vira za prenos ni uspelo. Poskusite znova.", + "download_source_already_exists": "Ta URL vira za prenos že obstaja.", + "download_count_zero": "Ni možnosti prenosa", + "download_count_one": "{{countFormatted}} možnost prenosa", + "download_count_other": "{{countFormatted}} možnosti prenosa", + "download_source_url": "URL vira za prenos", + "add_download_source_description": "Vstavite URL .json datoteke", + "download_source_up_to_date": "Posodobljeno", + "download_source_errored": "Napaka", + "download_source_pending_matching": "Kmalu posodobljeno", + "download_source_matched": "Posodobljeno", + "download_source_matching": "Posodabljanje", + "download_source_failed": "Napaka", + "download_source_no_information": "Ni podatkov na voljo", + "sync_download_sources": "Sinhroniziraj vire", + "removed_download_source": "Vir prenosa odstranjen", + "removed_download_sources": "Viri prenosa odstranjeni", + "removed_all_download_sources": "Vsi viri prenosa odstranjeni", + "download_sources_synced_successfully": "Vsi viri prenosa so sinhronizirani", + "cancel_button_confirmation_delete_all_sources": "Ne", + "confirm_button_confirmation_delete_all_sources": "Da, izbriši vse", + "title_confirmation_delete_all_sources": "Izbriši vse vire prenosa", + "description_confirmation_delete_all_sources": "Izbrišete vse vire prenosa", + "button_delete_all_sources": "Odstrani vse", + "added_download_source": "Vir prenosa dodan", + "download_sources_synced": "Vsi viri prenosa so sinhronizirani", + "insert_valid_json_url": "Vnesite veljaven JSON URL", + "found_download_option_zero": "Ni možnosti prenosa", + "found_download_option_one": "Najdena {{countFormatted}} možnost prenosa", + "found_download_option_other": "Najdenih {{countFormatted}} možnosti prenosa", + "import": "Uvozi", + "importing": "Uvažanje...", + "public": "Javno", + "private": "Zasebno", + "friends_only": "Samo prijatelji", + "privacy": "Zasebnost", + "profile_visibility": "Vidnost profila", + "profile_visibility_description": "Izberite, kdo lahko vidi vaš profil in knjižnico", "required_field": "To polje je obvezno", - "displayname_min_length": "Prikazno ime mora biti dolgo vsaj 3 znake", - "displayname_max_length": "Prikazno ime mora imeti največ 50 znakov", - "report_profile": "Prijavi ta profil", - "report_reason": "Zakaj prijavljate ta profil?", - "report_description": "Dodatne informacije", - "report_description_placeholder": "Dodatne informacije", - "report": "Prijavi", - "report_reason_hate": "Sovražni govor", - "report_reason_sexual_content": "Seksualna vsebina", - "report_reason_violence": "Nasilje", - "report_reason_spam": "Spam", - "report_reason_other": "Drugo", - "profile_reported": "Profil prijavljen", - "your_friend_code": "Vaša koda prijatelja:", - "copy_friend_code": "Kopiraj kodo prijatelja", - "copied": "Kopirano!", - "upload_banner": "Naloži banner", - "uploading_banner": "Nalaganje bannerja…", - "change_banner": "Spremeni banner", - "replace_banner": "Zamenjaj banner", - "remove_banner": "Odstrani banner", - "remove_banner_modal_title": "Odstrani banner?", - "remove_banner_confirmation": "Ali ste prepričani, da želite odstraniti banner? Kadarkoli lahko izberete novega.", - "remove": "Odstrani", - "background_image_updated": "Pozadinska slika posodobljena", - "stats": "Statistika", - "achievements": "dosežki", - "games": "Igre", - "top_percentile": "Top {{percentile}}%", - "ranking_updated_weekly": "Uvrstitev se posodablja tedensko", - "playing": "Igra {{game}}", - "achievements_unlocked": "Dosežki odklenjeni", - "earned_points": "Zaslužene točke", - "show_achievements_on_profile": "Pokaži vaše dosežke na profilu", - "show_points_on_profile": "Pokaži vaše zaslužene točke na profilu", - "error_adding_friend": "Zahteve za prijatelja ni bilo mogoče poslati. Preverite kodo prijatelja", - "friend_code_length_error": "Koda prijatelja mora vsebovati 8 znakov", - "game_removed_from_pinned": "Igra odstranjena iz pripetih", - "game_added_to_pinned": "Igra dodana med pripete", - "karma": "Karma", - "karma_count": "karma", - "user_reviews": "Mnenja", - "delete_review": "Izbriši mnenje", - "loading_reviews": "Nalaganje mnenj...", - "wrapped_2025": "Wrapped 2025" - }, - "library": { - "library": "Knjižnica", - "play": "Igraj", - "download": "Prenesi", - "downloading": "Prenašanje", - "game": "igra", - "games": "igre", - "grid_view": "Mrežni pogled", - "compact_view": "Kompaktni pogled", - "large_view": "Velik pogled", - "no_games_title": "Vaša knjižnica je prazna", - "no_games_description": "Dodajte igre iz kataloga ali jih prenesite, da začnete", - "amount_hours": "{{amount}} ur", - "amount_minutes": "{{amount}} minut", - "amount_hours_short": "{{amount}}h", - "amount_minutes_short": "{{amount}}m", - "manual_playtime_tooltip": "Ta čas igranja je bil ročno posodobljen", - "all_games": "Vse igre", - "recently_played": "Nedavno igrane", - "favorites": "Priljubljene" - }, - "achievement": { - "achievement_unlocked": "Dosežek odklenjen", - "user_achievements": "Dosežki uporabnika {{displayName}}", - "your_achievements": "Vaši dosežki", - "unlocked_at": "Odklenjeno: {{date}}", - "subscription_needed": "Naročnina na Hydra Cloud je potrebna za ogled te vsebine", - "new_achievements_unlocked": "Odklenili ste {{achievementCount}} novih dosežkov iz {{gameCount}} iger", - "achievement_progress": "{{unlockedCount}}/{{totalCount}} dosežkov", - "achievements_unlocked_for_game": "Odklenili ste {{achievementCount}} novih dosežkov za {{gameTitle}}", - "hidden_achievement_tooltip": "To je skriti dosežek", - "achievement_earn_points": "Z zaslužite {{points}} točk s tem dosežkom", - "earned_points": "Zaslužene točke:", - "available_points": "Razpoložljive točke:", - "how_to_earn_achievements_points": "Kako zaslužiti točke za dosežke?" - }, - "hydra_cloud": { - "subscription_tour_title": "Naročnina Hydra Cloud", - "subscribe_now": "Naroči se zdaj", - "cloud_saving": "Shranjevanje v oblak", - "cloud_achievements": "Shrani svoje dosežke v oblak", - "animated_profile_picture": "Animirane profilne slike", - "premium_support": "Premium podpora", - "show_and_compare_achievements": "Pokaži in primerjaj svoje dosežke z drugimi uporabniki", - "animated_profile_banner": "Animirani profilni banner", + "source_already_exists": "Ta vir je že bil dodan", + "must_be_valid_url": "Vir mora biti veljaven URL", + "blocked_users": "Blokirani uporabniki", + "user_unblocked": "Uporabnik je odblokiran", + "enable_achievement_notifications": "Ko je dosežek odklenjen", + "launch_minimized": "Zaženi Hydra minimizirano", + "disable_nsfw_alert": "Onemogoči opozorilo NSFW", + "seed_after_download_complete": "Sejanje po končanem prenosu", + "show_hidden_achievement_description": "Pokaži opis skritih dosežkov pred njihovim odklepanjem", + "account": "Račun", "hydra_cloud": "Hydra Cloud", - "hydra_cloud_feature_found": "Pravkar ste odkrili funkcijo Hydra Cloud!", - "learn_more": "Več informacij", - "debrid_description": "Prenesite do 4x hitreje z Nimbusom" - }, - "notifications_page": { - "title": "Obvestila", - "mark_all_as_read": "Označi vse kot prebrano", - "clear_all": "Počisti vse", - "loading": "Nalagam...", - "empty_title": "Ni obvestil", - "empty_description": "Ste na tekočem! Preverite kasneje za nove posodobitve.", - "empty_filter_description": "Nobeno obvestilo ne ustreza tem filtram.", - "filter_all": "Vse", - "filter_unread": "Neprebrano", - "filter_friends": "Prijatelji", - "filter_badges": "Značke", - "filter_upvotes": "Glasovi za všečkanje", - "filter_local": "Lokalno", - "load_more": "Naloži več", - "dismiss": "Opusti", - "accept": "Sprejmi", - "refuse": "Zavrni", - "notification": "Obvestilo", - "friend_request_received_title": "Nova prijateljska zahteva!", - "friend_request_received_description": "{{displayName}} želi biti vaš prijatelj", - "friend_request_accepted_title": "Zahteva za prijateljstvo sprejeta!", - "friend_request_accepted_description": "{{displayName}} je sprejel vašo zahtevo", - "badge_received_title": "Prejeli ste novo značko!", - "badge_received_description": "{{badgeName}}", - "review_upvote_title": "Vaša recenzija za {{gameTitle}} je dobila glasove!", - "review_upvote_description": "Vaša recenzija je dobila {{count}} novih glasov", - "marked_all_as_read": "Vsa obvestila označena kot prebrana", - "failed_to_mark_as_read": "Neuspešno označevanje obvestil kot prebranih", - "cleared_all": "Vsa obvestila izbrisana", - "failed_to_clear": "Neuspešno brisanje obvestil", - "failed_to_load": "Neuspešno nalaganje obvestil", - "failed_to_dismiss": "Neuspešno opustitev obvestila", - "friend_request_accepted": "Zahteva za prijateljstvo sprejeta", - "friend_request_refused": "Zahteva za prijateljstvo zavrnjena" - } + "no_users_blocked": "Nimate blokiranih uporabnikov", + "subscription_active_until": "Vaš Hydra Cloud je aktiven do {{date}}", + "manage_subscription": "Upravljaj naročnino", + "update_email": "Posodobi e-pošto", + "update_password": "Posodobi geslo", + "current_email": "Trenutna e-pošta:", + "no_email_account": "Še niste nastavili e-pošte", + "account_data_updated_successfully": "Podatki računa so bili uspešno posodobljeni", + "renew_subscription": "Obnovi Hydra Cloud", + "subscription_expired_at": "Vaša naročnina je potekla {{date}}", + "no_subscription": "Uživajte v Hydri na najboljši način", + "become_subscriber": "Postanite Hydra Cloud uporabnik", + "subscription_renew_cancelled": "Samodejno podaljševanje je onemogočeno", + "subscription_renews_on": "Vaša naročnina se podaljša {{date}}", + "bill_sent_until": "Naslednji račun bo poslan do tega dne", + "no_themes": "Zdi se, da še nimate tem, vendar brez skrbi, kliknite tukaj, da ustvarite svojo prvo mojstrovino.", + "editor_tab_code": "Koda", + "editor_tab_info": "Info", + "editor_tab_save": "Shrani", + "web_store": "Spletna trgovina", + "clear_themes": "Počisti", + "create_theme": "Ustvari", + "create_theme_modal_title": "Ustvari prilagojeno temo", + "create_theme_modal_description": "Ustvarite novo temo za prilagajanje videza Hydre", + "theme_name": "Ime", + "insert_theme_name": "Vstavite ime teme", + "set_theme": "Nastavi temo", + "unset_theme": "Odstrani temo", + "delete_theme": "Izbriši temo", + "edit_theme": "Uredi temo", + "delete_all_themes": "Izbriši vse teme", + "delete_all_themes_description": "To bo izbrisalo vse vaše prilagojene teme", + "delete_theme_description": "To bo izbrisalo temo {{theme}}", + "cancel": "Prekliči", + "appearance": "Videz", + "debrid": "Debrid", + "debrid_description": "Debrid storitve so premium neomejeni prenašalniki, ki vam omogočajo hitro prenašanje datotek, gostovanih na različnih storitvah za gostovanje datotek, omejeno le s hitrostjo vašega interneta.", + "enable_torbox": "Omogoči TorBox", + "torbox_description": "TorBox je vaša premium seedbox storitev, ki se lahko kosuje tudi najboljšim strežnikom na trgu.", + "torbox_account_linked": "TorBox račun povezan", + "create_real_debrid_account": "Kliknite tukaj, če še nimate Real-Debrid računa", + "create_torbox_account": "Kliknite tukaj, če še nimate TorBox računa", + "real_debrid_account_linked": "Real-Debrid račun povezan", + "name_min_length": "Ime teme mora imeti vsaj 3 znake", + "import_theme": "Uvozi temo", + "import_theme_description": "Uvožili boste {{theme}} iz trgovine tem", + "error_importing_theme": "Napaka pri uvozu teme", + "theme_imported": "Tema uspešno uvožena", + "enable_friend_request_notifications": "Ko je prejet prijateljski zahtevek", + "enable_auto_install": "Samodejno prenesi posodobitve", + "common_redist": "Skupni redistributable-ji", + "common_redist_description": "Skupni redistributable-ji so potrebni za zagon nekaterih iger. Priporočamo njihovo namestitev, da se izognete težavam.", + "install_common_redist": "Namesti", + "installing_common_redist": "Nameščanje…", + "show_download_speed_in_megabytes": "Pokaži hitrost prenosa v megabajtih na sekundo", + "extract_files_by_default": "Privzeto razpakiraj datoteke po prenosu", + "enable_steam_achievements": "Omogoči iskanje po Steam dosežkih", + "enable_new_download_options_badges": "Pokaži značke novih možnosti prenosa", + "achievement_custom_notification_position": "Lastna pozicija obvestil o dosežkih", + "top-left": "Zgoraj levo", + "top-center": "Zgoraj na sredini", + "top-right": "Zgoraj desno", + "bottom-left": "Spodaj levo", + "bottom-center": "Spodaj na sredini", + "bottom-right": "Spodaj desno", + "enable_achievement_custom_notifications": "Omogoči lastna obvestila o dosežkih", + "alignment": "Poravnava", + "variation": "Variacija", + "default": "Privzeto", + "rare": "Redko", + "platinum": "Platinasto", + "hidden": "Skrito", + "test_notification": "Preizkusno obvestilo", + "achievement_sound_volume": "Glasnost zvoka dosežka", + "select_achievement_sound": "Izberite zvok dosežka", + "change_achievement_sound": "Spremeni zvok dosežka", + "remove_achievement_sound": "Odstrani zvok dosežka", + "preview_sound": "Predogled zvoka", + "select": "Izberi", + "preview": "Predogled", + "remove": "Odstrani", + "no_sound_file_selected": "Nobena zvočna datoteka ni izbrana", + "notification_preview": "Predogled obvestila o dosežku", + "enable_friend_start_game_notifications": "Ko prijatelj začne igrati igro", + "autoplay_trailers_on_game_page": "Samodejno predvajaj napovednike na strani igre", + "hide_to_tray_on_game_start": "Skrij Hydreo v sistemsko vrstico ob zagonu igre", + "downloads": "Prenosi", + "use_native_http_downloader": "Uporabi izvorni HTTP prenašalnik (eksperimentalno)", + "cannot_change_downloader_while_downloading": "Nastavitve ni mogoče spremeniti med prenosom", + "notifications": { + "download_complete": "Prenos končan", + "game_ready_to_install": "{{title}} je pripravljen za namestitev", + "repack_list_updated": "Seznam repackov posodobljen", + "repack_count_one": "{{count}} repack dodan", + "repack_count_other": "{{count}} repackov dodanih", + "new_update_available": "Različica {{version}} na voljo", + "restart_to_install_update": "Znova zaženite Hydreo za namestitev posodobitve", + "notification_achievement_unlocked_title": "Dosežek odklenjen za {{game}}", + "notification_achievement_unlocked_body": "{{achievement}} in drugi {{count}} so bili odklenjeni", + "new_friend_request_description": "{{displayName}} vam je poslal prijateljsko zahtevo", + "new_friend_request_title": "Nova prijateljska zahteva", + "extraction_complete": "Razpakiranje končano", + "game_extracted": "{{title}} je bil uspešno razpakiran", + "friend_started_playing_game": "{{displayName}} je začel igrati igro", + "test_achievement_notification_title": "To je preizkusno obvestilo", + "test_achievement_notification_description": "Kar kul, kajne?" + }, + "system_tray": { + "open": "Odpri Hydreo", + "quit": "Izhod" + }, + "game_card": { + "available_one": "Na voljo", + "available_other": "Na voljo", + "no_downloads": "Ni razpoložljivih prenosov", + "calculating": "Računam" + }, + "binary_not_found_modal": { + "title": "Programi niso nameščeni", + "description": "Izvajalniki Wine ali Lutris niso bili najdeni na vašem sistemu", + "instructions": "Preverite pravi način za namestitev katerega od njih na vašo Linux distribucijo, da bi igra lahko normalno tekla" + }, + "modal": { + "close": "Zapri gumb" + }, + "forms": { + "toggle_password_visibility": "Preklopi vidnost gesla" + }, + "user_profile": { + "amount_hours": "{{amount}} ur", + "amount_minutes": "{{amount}} minut", + "amount_hours_short": "{{amount}}h", + "amount_minutes_short": "{{amount}}m", + "last_time_played": "Nazadnje igrano {{period}}", + "activity": "Nedavna dejavnost", + "library": "Knjižnica", + "pinned": "Pripeto", + "sort_by": "Razvrsti po:", + "achievements_earned": "Odklenjeni dosežki", + "played_recently": "Nazadnje igrano", + "playtime": "Čas igranja", + "total_play_time": "Skupni čas igranja", + "manual_playtime_tooltip": "Ta čas igranja je bil ročno posodobljen", + "no_recent_activity_title": "Hmmm… nič tukaj", + "no_recent_activity_description": "Niste igrali nobene igre v zadnjem času. Čas je, da to spremenite!", + "display_name": "Prikazno ime", + "saving": "Shranjevanje", + "save": "Shrani", + "edit_profile": "Uredi profil", + "saved_successfully": "Uspešno shranjeno", + "try_again": "Prosimo, poskusite znova", + "sign_out_modal_title": "Ste prepričani?", + "cancel": "Prekliči", + "successfully_signed_out": "Uspešno odjavljeni", + "sign_out": "Odjavi se", + "playing_for": "Igra za {{amount}}", + "sign_out_modal_text": "Vaša knjižnica je povezana s trenutnim računom. Ob odjavi knjižnica ne bo več vidna, napredek pa se ne bo shranil. Nadaljujete z odjavo?", + "add_friends": "Dodaj prijatelje", + "add": "Dodaj", + "friend_code": "Koda prijatelja", + "see_profile": "Poglej profil", + "sending": "Pošiljanje", + "friend_request_sent": "Zahteva za prijateljstvo poslana", + "friends": "Prijatelji", + "badges": "Značke", + "friends_list": "Seznam prijateljev", + "user_not_found": "Uporabnik ni najden", + "block_user": "Blokiraj uporabnika", + "add_friend": "Dodaj prijatelja", + "request_sent": "Zahteva poslana", + "request_received": "Zahteva prejeta", + "accept_request": "Sprejmi zahtevo", + "ignore_request": "Ignoriraj zahtevo", + "cancel_request": "Prekliči zahtevo", + "undo_friendship": "Razveljavi prijateljstvo", + "friendship_removed": "Prijatelj odstranjen", + "request_accepted": "Zahteva sprejeta", + "user_blocked_successfully": "Uporabnik uspešno blokiran", + "user_block_modal_text": "To bo blokiralo {{displayName}}", + "blocked_users": "Blokirani uporabniki", + "unblock": "Odblokiraj", + "no_friends_added": "Nimate dodanih prijateljev", + "no_friends_yet": "Še niste dodali prijateljev", + "view_all": "Poglej vse", + "load_more": "Naloži več", + "loading": "Nalaganje", + "pending": "V teku", + "no_pending_invites": "Nimate čakajočih povabil", + "no_blocked_users": "Nimate blokiranih uporabnikov", + "friend_code_copied": "Koda prijatelja kopirana", + "undo_friendship_modal_text": "To bo razveljavilo vaše prijateljstvo z {{displayName}}", + "privacy_hint": "Za prilagoditev, kdo to vidi, pojdite na <0>Nastavitve", + "locked_profile": "Ta profil je zaseben", + "image_process_failure": "Napaka pri obdelavi slike", + "required_field": "To polje je obvezno", + "displayname_min_length": "Prikazno ime mora biti dolgo vsaj 3 znake", + "displayname_max_length": "Prikazno ime mora imeti največ 50 znakov", + "report_profile": "Prijavi ta profil", + "report_reason": "Zakaj prijavljate ta profil?", + "report_description": "Dodatne informacije", + "report_description_placeholder": "Dodatne informacije", + "report": "Prijavi", + "report_reason_hate": "Sovražni govor", + "report_reason_sexual_content": "Seksualna vsebina", + "report_reason_violence": "Nasilje", + "report_reason_spam": "Spam", + "report_reason_other": "Drugo", + "profile_reported": "Profil prijavljen", + "your_friend_code": "Vaša koda prijatelja:", + "copy_friend_code": "Kopiraj kodo prijatelja", + "copied": "Kopirano!", + "upload_banner": "Naloži banner", + "uploading_banner": "Nalaganje bannerja…", + "change_banner": "Spremeni banner", + "replace_banner": "Zamenjaj banner", + "remove_banner": "Odstrani banner", + "remove_banner_modal_title": "Odstrani banner?", + "remove_banner_confirmation": "Ali ste prepričani, da želite odstraniti banner? Kadarkoli lahko izberete novega.", + "remove": "Odstrani", + "background_image_updated": "Pozadinska slika posodobljena", + "stats": "Statistika", + "achievements": "dosežki", + "games": "Igre", + "top_percentile": "Top {{percentile}}%", + "ranking_updated_weekly": "Uvrstitev se posodablja tedensko", + "playing": "Igra {{game}}", + "achievements_unlocked": "Dosežki odklenjeni", + "earned_points": "Zaslužene točke", + "show_achievements_on_profile": "Pokaži vaše dosežke na profilu", + "show_points_on_profile": "Pokaži vaše zaslužene točke na profilu", + "error_adding_friend": "Zahteve za prijatelja ni bilo mogoče poslati. Preverite kodo prijatelja", + "friend_code_length_error": "Koda prijatelja mora vsebovati 8 znakov", + "game_removed_from_pinned": "Igra odstranjena iz pripetih", + "game_added_to_pinned": "Igra dodana med pripete", + "karma": "Karma", + "karma_count": "karma", + "user_reviews": "Mnenja", + "delete_review": "Izbriši mnenje", + "loading_reviews": "Nalaganje mnenj...", + "wrapped_2025": "Wrapped 2025" + }, + "library": { + "library": "Knjižnica", + "play": "Igraj", + "download": "Prenesi", + "downloading": "Prenašanje", + "game": "igra", + "games": "igre", + "grid_view": "Mrežni pogled", + "compact_view": "Kompaktni pogled", + "large_view": "Velik pogled", + "no_games_title": "Vaša knjižnica je prazna", + "no_games_description": "Dodajte igre iz kataloga ali jih prenesite, da začnete", + "amount_hours": "{{amount}} ur", + "amount_minutes": "{{amount}} minut", + "amount_hours_short": "{{amount}}h", + "amount_minutes_short": "{{amount}}m", + "manual_playtime_tooltip": "Ta čas igranja je bil ročno posodobljen", + "all_games": "Vse igre", + "recently_played": "Nedavno igrane", + "favorites": "Priljubljene" + }, + "achievement": { + "achievement_unlocked": "Dosežek odklenjen", + "user_achievements": "Dosežki uporabnika {{displayName}}", + "your_achievements": "Vaši dosežki", + "unlocked_at": "Odklenjeno: {{date}}", + "subscription_needed": "Naročnina na Hydra Cloud je potrebna za ogled te vsebine", + "new_achievements_unlocked": "Odklenili ste {{achievementCount}} novih dosežkov iz {{gameCount}} iger", + "achievement_progress": "{{unlockedCount}}/{{totalCount}} dosežkov", + "achievements_unlocked_for_game": "Odklenili ste {{achievementCount}} novih dosežkov za {{gameTitle}}", + "hidden_achievement_tooltip": "To je skriti dosežek", + "achievement_earn_points": "Z zaslužite {{points}} točk s tem dosežkom", + "earned_points": "Zaslužene točke:", + "available_points": "Razpoložljive točke:", + "how_to_earn_achievements_points": "Kako zaslužiti točke za dosežke?" + }, + "hydra_cloud": { + "subscription_tour_title": "Naročnina Hydra Cloud", + "subscribe_now": "Naroči se zdaj", + "cloud_saving": "Shranjevanje v oblak", + "cloud_achievements": "Shrani svoje dosežke v oblak", + "animated_profile_picture": "Animirane profilne slike", + "premium_support": "Premium podpora", + "show_and_compare_achievements": "Pokaži in primerjaj svoje dosežke z drugimi uporabniki", + "animated_profile_banner": "Animirani profilni banner", + "hydra_cloud": "Hydra Cloud", + "hydra_cloud_feature_found": "Pravkar ste odkrili funkcijo Hydra Cloud!", + "learn_more": "Več informacij", + "debrid_description": "Prenesite do 4x hitreje z Nimbusom" + }, + "notifications_page": { + "title": "Obvestila", + "mark_all_as_read": "Označi vse kot prebrano", + "clear_all": "Počisti vse", + "loading": "Nalagam...", + "empty_title": "Ni obvestil", + "empty_description": "Ste na tekočem! Preverite kasneje za nove posodobitve.", + "empty_filter_description": "Nobeno obvestilo ne ustreza tem filtram.", + "filter_all": "Vse", + "filter_unread": "Neprebrano", + "filter_friends": "Prijatelji", + "filter_badges": "Značke", + "filter_upvotes": "Glasovi za všečkanje", + "filter_local": "Lokalno", + "load_more": "Naloži več", + "dismiss": "Opusti", + "accept": "Sprejmi", + "refuse": "Zavrni", + "notification": "Obvestilo", + "friend_request_received_title": "Nova prijateljska zahteva!", + "friend_request_received_description": "{{displayName}} želi biti vaš prijatelj", + "friend_request_accepted_title": "Zahteva za prijateljstvo sprejeta!", + "friend_request_accepted_description": "{{displayName}} je sprejel vašo zahtevo", + "badge_received_title": "Prejeli ste novo značko!", + "badge_received_description": "{{badgeName}}", + "review_upvote_title": "Vaša recenzija za {{gameTitle}} je dobila glasove!", + "review_upvote_description": "Vaša recenzija je dobila {{count}} novih glasov", + "marked_all_as_read": "Vsa obvestila označena kot prebrana", + "failed_to_mark_as_read": "Neuspešno označevanje obvestil kot prebranih", + "cleared_all": "Vsa obvestila izbrisana", + "failed_to_clear": "Neuspešno brisanje obvestil", + "failed_to_load": "Neuspešno nalaganje obvestil", + "failed_to_dismiss": "Neuspešno opustitev obvestila", + "friend_request_accepted": "Zahteva za prijateljstvo sprejeta", + "friend_request_refused": "Zahteva za prijateljstvo zavrnjena" } - } \ No newline at end of file + } +} From fb1380356e0fb0f4089764b2b35fafbeed1747ab Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sat, 24 Jan 2026 18:46:07 +0200 Subject: [PATCH 3/8] feat: add functionality to manage download queue with new actions and translations --- src/locales/en/translation.json | 5 +- .../events/torrenting/add-game-to-queue.ts | 96 +++++++++++++++++++ src/main/events/torrenting/index.ts | 2 + .../update-download-queue-position.ts | 67 +++++++++++++ src/preload/index.ts | 13 +++ src/renderer/src/declaration.d.ts | 8 ++ src/renderer/src/hooks/use-download.ts | 9 ++ .../src/pages/downloads/download-group.tsx | 81 +++++++++++++++- .../src/pages/downloads/downloads.tsx | 11 ++- .../src/pages/game-details/game-details.tsx | 33 ++++--- .../modals/download-settings-modal.tsx | 22 ++++- .../game-details/modals/repacks-modal.tsx | 3 +- 12 files changed, 331 insertions(+), 19 deletions(-) create mode 100644 src/main/events/torrenting/add-game-to-queue.ts create mode 100644 src/main/events/torrenting/update-download-queue-position.ts diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 3ba3f0b7..022696be 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -185,6 +185,7 @@ "repacks_modal_description": "Choose the repack you want to download", "select_folder_hint": "To change the default folder, go to the <0>Settings", "download_now": "Download now", + "add_to_queue": "Add to queue", "loading": "Loading...", "no_shop_details": "Could not retrieve shop details.", "download_options": "Download options", @@ -447,7 +448,9 @@ "yes": "Yes", "no": "No", "network": "NETWORK", - "peak": "PEAK" + "peak": "PEAK", + "move_up": "Move up", + "move_down": "Move down" }, "settings": { "downloads_path": "Downloads path", diff --git a/src/main/events/torrenting/add-game-to-queue.ts b/src/main/events/torrenting/add-game-to-queue.ts new file mode 100644 index 00000000..85f321b1 --- /dev/null +++ b/src/main/events/torrenting/add-game-to-queue.ts @@ -0,0 +1,96 @@ +import { registerEvent } from "../register-event"; +import type { Download, StartGameDownloadPayload } from "@types"; +import { HydraApi, logger } from "@main/services"; +import { createGame } from "@main/services/library-sync"; +import { + downloadsSublevel, + gamesShopAssetsSublevel, + gamesSublevel, + levelKeys, +} from "@main/level"; + +const addGameToQueue = async ( + _event: Electron.IpcMainInvokeEvent, + payload: StartGameDownloadPayload +) => { + const { + objectId, + title, + shop, + downloadPath, + downloader, + uri, + automaticallyExtract, + } = payload; + + const gameKey = levelKeys.game(shop, objectId); + + const game = await gamesSublevel.get(gameKey); + const gameAssets = await gamesShopAssetsSublevel.get(gameKey); + + await downloadsSublevel.del(gameKey); + + if (game) { + await gamesSublevel.put(gameKey, { + ...game, + isDeleted: false, + }); + } else { + await gamesSublevel.put(gameKey, { + title, + iconUrl: gameAssets?.iconUrl ?? null, + libraryHeroImageUrl: gameAssets?.libraryHeroImageUrl ?? null, + logoImageUrl: gameAssets?.logoImageUrl ?? null, + objectId, + shop, + remoteId: null, + playTimeInMilliseconds: 0, + lastTimePlayed: null, + isDeleted: false, + }); + } + + const download: Download = { + shop, + objectId, + status: "paused", + progress: 0, + bytesDownloaded: 0, + downloadPath, + downloader, + uri, + folderName: null, + fileSize: null, + shouldSeed: false, + timestamp: Date.now(), + queued: true, + extracting: false, + automaticallyExtract, + extractionProgress: 0, + }; + + try { + await downloadsSublevel.put(gameKey, download); + + const updatedGame = await gamesSublevel.get(gameKey); + + await Promise.all([ + createGame(updatedGame!).catch(() => {}), + HydraApi.post(`/games/${shop}/${objectId}/download`, null, { + needsAuth: false, + }).catch(() => {}), + ]); + + return { ok: true }; + } catch (err: unknown) { + logger.error("Failed to add game to queue", err); + + if (err instanceof Error) { + return { ok: false, error: err.message }; + } + + return { ok: false }; + } +}; + +registerEvent("addGameToQueue", addGameToQueue); diff --git a/src/main/events/torrenting/index.ts b/src/main/events/torrenting/index.ts index 408ecf17..143135da 100644 --- a/src/main/events/torrenting/index.ts +++ b/src/main/events/torrenting/index.ts @@ -1,3 +1,4 @@ +import "./add-game-to-queue"; import "./cancel-game-download"; import "./check-debrid-availability"; import "./pause-game-download"; @@ -5,3 +6,4 @@ import "./pause-game-seed"; import "./resume-game-download"; import "./resume-game-seed"; import "./start-game-download"; +import "./update-download-queue-position"; diff --git a/src/main/events/torrenting/update-download-queue-position.ts b/src/main/events/torrenting/update-download-queue-position.ts new file mode 100644 index 00000000..5672d63d --- /dev/null +++ b/src/main/events/torrenting/update-download-queue-position.ts @@ -0,0 +1,67 @@ +import { registerEvent } from "../register-event"; +import { downloadsSublevel, levelKeys } from "@main/level"; +import { GameShop } from "@types"; +import { orderBy } from "lodash-es"; + +const updateDownloadQueuePosition = async ( + _event: Electron.IpcMainInvokeEvent, + shop: GameShop, + objectId: string, + direction: "up" | "down" +) => { + const gameKey = levelKeys.game(shop, objectId); + + const download = await downloadsSublevel.get(gameKey); + + if (!download || !download.queued || download.status !== "paused") { + return false; + } + + const allDownloads = await downloadsSublevel.values().all(); + + const queuedDownloads = orderBy( + allDownloads.filter((d) => d.status === "paused" && d.queued), + "timestamp", + "desc" + ); + + const currentIndex = queuedDownloads.findIndex( + (d) => d.shop === shop && d.objectId === objectId + ); + + if (currentIndex === -1) { + return false; + } + + const targetIndex = direction === "up" ? currentIndex - 1 : currentIndex + 1; + + if (targetIndex < 0 || targetIndex >= queuedDownloads.length) { + return false; + } + + const currentDownload = queuedDownloads[currentIndex]; + const adjacentDownload = queuedDownloads[targetIndex]; + + const currentKey = levelKeys.game( + currentDownload.shop, + currentDownload.objectId + ); + const adjacentKey = levelKeys.game( + adjacentDownload.shop, + adjacentDownload.objectId + ); + + const tempTimestamp = currentDownload.timestamp; + await downloadsSublevel.put(currentKey, { + ...currentDownload, + timestamp: adjacentDownload.timestamp, + }); + await downloadsSublevel.put(adjacentKey, { + ...adjacentDownload, + timestamp: tempTimestamp, + }); + + return true; +}; + +registerEvent("updateDownloadQueuePosition", updateDownloadQueuePosition); diff --git a/src/preload/index.ts b/src/preload/index.ts index 6d929d99..412f1422 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -27,6 +27,8 @@ contextBridge.exposeInMainWorld("electron", { /* Torrenting */ startGameDownload: (payload: StartGameDownloadPayload) => ipcRenderer.invoke("startGameDownload", payload), + addGameToQueue: (payload: StartGameDownloadPayload) => + ipcRenderer.invoke("addGameToQueue", payload), cancelGameDownload: (shop: GameShop, objectId: string) => ipcRenderer.invoke("cancelGameDownload", shop, objectId), pauseGameDownload: (shop: GameShop, objectId: string) => @@ -37,6 +39,17 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("pauseGameSeed", shop, objectId), resumeGameSeed: (shop: GameShop, objectId: string) => ipcRenderer.invoke("resumeGameSeed", shop, objectId), + updateDownloadQueuePosition: ( + shop: GameShop, + objectId: string, + direction: "up" | "down" + ) => + ipcRenderer.invoke( + "updateDownloadQueuePosition", + shop, + objectId, + direction + ), onDownloadProgress: (cb: (value: DownloadProgress | null) => void) => { const listener = ( _event: Electron.IpcRendererEvent, diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index e86c207b..68beb95d 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -47,11 +47,19 @@ declare global { startGameDownload: ( payload: StartGameDownloadPayload ) => Promise<{ ok: boolean; error?: string }>; + addGameToQueue: ( + payload: StartGameDownloadPayload + ) => Promise<{ ok: boolean; error?: string }>; cancelGameDownload: (shop: GameShop, objectId: string) => Promise; pauseGameDownload: (shop: GameShop, objectId: string) => Promise; resumeGameDownload: (shop: GameShop, objectId: string) => Promise; pauseGameSeed: (shop: GameShop, objectId: string) => Promise; resumeGameSeed: (shop: GameShop, objectId: string) => Promise; + updateDownloadQueuePosition: ( + shop: GameShop, + objectId: string, + direction: "up" | "down" + ) => Promise; onDownloadProgress: ( cb: (value: DownloadProgress | null) => void ) => () => Electron.IpcRenderer; diff --git a/src/renderer/src/hooks/use-download.ts b/src/renderer/src/hooks/use-download.ts index f6cc071f..8adb9574 100644 --- a/src/renderer/src/hooks/use-download.ts +++ b/src/renderer/src/hooks/use-download.ts @@ -38,6 +38,14 @@ export function useDownload() { return response; }; + const addGameToQueue = async (payload: StartGameDownloadPayload) => { + const response = await window.electron.addGameToQueue(payload); + + if (response.ok) updateLibrary(); + + return response; + }; + const pauseDownload = async (shop: GameShop, objectId: string) => { await window.electron.pauseGameDownload(shop, objectId); await updateLibrary(); @@ -113,6 +121,7 @@ export function useDownload() { lastPacket, eta: calculateETA(), startDownload, + addGameToQueue, pauseDownload, resumeDownload, cancelDownload, diff --git a/src/renderer/src/pages/downloads/download-group.tsx b/src/renderer/src/pages/downloads/download-group.tsx index 5b26f685..6f969a2a 100644 --- a/src/renderer/src/pages/downloads/download-group.tsx +++ b/src/renderer/src/pages/downloads/download-group.tsx @@ -26,6 +26,8 @@ import { DropdownMenuItem, } from "@renderer/components/dropdown-menu/dropdown-menu"; import { + ArrowDownIcon, + ArrowUpIcon, ClockIcon, ColumnsIcon, DownloadIcon, @@ -40,6 +42,44 @@ import { import { MoreVertical, Folder } from "lucide-react"; import { average } from "color.js"; +function hexToRgb(hex: string): [number, number, number] { + let h = hex.replace("#", ""); + if (h.length === 3) { + h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2]; + } + const r = parseInt(h.substring(0, 2), 16) || 0; + const g = parseInt(h.substring(2, 4), 16) || 0; + const b = parseInt(h.substring(4, 6), 16) || 0; + return [r, g, b]; +} + +function isTooCloseRGB(a: string, b: string, threshold: number): boolean { + const [r1, g1, b1] = hexToRgb(a); + const [r2, g2, b2] = hexToRgb(b); + const distance = Math.sqrt( + Math.pow(r1 - r2, 2) + Math.pow(g1 - g2, 2) + Math.pow(b1 - b2, 2) + ); + return distance < threshold; +} + +const CHART_BACKGROUND_COLOR = "#1a1a1a"; +const COLOR_DISTANCE_THRESHOLD = 28; +const FALLBACK_CHART_COLOR = "#fff"; + +function pickChartColor(dominant?: string): string { + if (!dominant || typeof dominant !== "string" || !dominant.startsWith("#")) { + return FALLBACK_CHART_COLOR; + } + + if ( + isTooCloseRGB(dominant, CHART_BACKGROUND_COLOR, COLOR_DISTANCE_THRESHOLD) + ) { + return FALLBACK_CHART_COLOR; + } + + return dominant; +} + interface AnimatedPercentageProps { value: number; } @@ -442,6 +482,7 @@ export interface DownloadGroupProps { openDeleteGameModal: (shop: GameShop, objectId: string) => void; openGameInstaller: (shop: GameShop, objectId: string) => void; seedingStatus: SeedingStatus[]; + queuedGameIds?: string[]; } export function DownloadGroup({ @@ -450,6 +491,7 @@ export function DownloadGroup({ openDeleteGameModal, openGameInstaller, seedingStatus, + queuedGameIds = [], }: Readonly) { const { t } = useTranslation("downloads"); const { t: tGameDetails } = useTranslation("game_details"); @@ -690,6 +732,18 @@ export function DownloadGroup({ setGameToCancelObjectId(null); }, []); + const handleMoveInQueue = useCallback( + async (shop: GameShop, objectId: string, direction: "up" | "down") => { + await window.electron.updateDownloadQueuePosition( + shop, + objectId, + direction + ); + updateLibrary(); + }, + [updateLibrary] + ); + const getGameActions = (game: LibraryGame): DropdownMenuItem[] => { const download = lastPacket?.download; const isGameDownloading = isGameDownloadingMap[game.id]; @@ -765,7 +819,12 @@ export function DownloadGroup({ (download?.downloader === Downloader.TorBox && !userPreferences?.torBoxApiToken); - return [ + const queueIndex = queuedGameIds.indexOf(game.id); + const isFirstInQueue = queueIndex === 0; + const isLastInQueue = queueIndex === queuedGameIds.length - 1; + const isInQueue = queueIndex !== -1; + + const actions = [ { label: t("resume"), disabled: isResumeDisabled, @@ -774,6 +833,22 @@ export function DownloadGroup({ }, icon: , }, + { + label: t("move_up"), + show: isInQueue && !isFirstInQueue, + onClick: () => { + handleMoveInQueue(game.shop, game.objectId, "up"); + }, + icon: , + }, + { + label: t("move_down"), + show: isInQueue && !isLastInQueue, + onClick: () => { + handleMoveInQueue(game.shop, game.objectId, "down"); + }, + icon: , + }, { label: t("cancel"), onClick: () => { @@ -782,6 +857,8 @@ export function DownloadGroup({ icon: , }, ]; + + return actions.filter((action) => action.show !== false); }; const downloadInfo = useMemo( @@ -863,7 +940,7 @@ export function DownloadGroup({ currentProgress = lastPacket.progress; } - const dominantColor = dominantColors[game.id] || "#fff"; + const dominantColor = pickChartColor(dominantColors[game.id]); return ( <> diff --git a/src/renderer/src/pages/downloads/downloads.tsx b/src/renderer/src/pages/downloads/downloads.tsx index 10d817f1..620a71d2 100644 --- a/src/renderer/src/pages/downloads/downloads.tsx +++ b/src/renderer/src/pages/downloads/downloads.tsx @@ -103,18 +103,26 @@ export default function Downloads() { }; }, [library, lastPacket?.gameId, extraction?.visibleId]); + const queuedGameIds = useMemo( + () => libraryGroup.queued.map((game) => game.id), + [libraryGroup.queued] + ); + const downloadGroups = [ { title: t("download_in_progress"), library: libraryGroup.downloading, + queuedGameIds: [] as string[], }, { title: t("queued_downloads"), library: libraryGroup.queued, + queuedGameIds, }, { title: t("downloads_completed"), library: libraryGroup.complete, + queuedGameIds: [] as string[], }, ]; @@ -142,10 +150,11 @@ export default function Downloads() { ))} diff --git a/src/renderer/src/pages/game-details/game-details.tsx b/src/renderer/src/pages/game-details/game-details.tsx index 6bc28c10..f885596c 100644 --- a/src/renderer/src/pages/game-details/game-details.tsx +++ b/src/renderer/src/pages/game-details/game-details.tsx @@ -37,7 +37,7 @@ export default function GameDetails() { const fromRandomizer = searchParams.get("fromRandomizer"); const gameTitle = searchParams.get("title"); - const { startDownload } = useDownload(); + const { startDownload, addGameToQueue } = useDownload(); const { t } = useTranslation("game_details"); @@ -100,17 +100,28 @@ export default function GameDetails() { repack: GameRepack, downloader: Downloader, downloadPath: string, - automaticallyExtract: boolean + automaticallyExtract: boolean, + addToQueueOnly = false ) => { - const response = await startDownload({ - objectId: objectId!, - title: gameTitle, - downloader, - shop, - downloadPath, - uri: selectRepackUri(repack, downloader), - automaticallyExtract: automaticallyExtract, - }); + const response = addToQueueOnly + ? await addGameToQueue({ + objectId: objectId!, + title: gameTitle, + downloader, + shop, + downloadPath, + uri: selectRepackUri(repack, downloader), + automaticallyExtract: automaticallyExtract, + }) + : await startDownload({ + objectId: objectId!, + title: gameTitle, + downloader, + shop, + downloadPath, + uri: selectRepackUri(repack, downloader), + automaticallyExtract: automaticallyExtract, + }); if (response.ok) { await updateGame(); diff --git a/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx b/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx index 0a2c6721..6f20e439 100644 --- a/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx @@ -12,11 +12,17 @@ import { DownloadIcon, SyncIcon, CheckCircleFillIcon, + PlusIcon, } from "@primer/octicons-react"; import { Downloader, formatBytes, getDownloadersForUri } from "@shared"; import type { GameRepack } from "@types"; import { DOWNLOADER_NAME } from "@renderer/constants"; -import { useAppSelector, useFeature, useToast } from "@renderer/hooks"; +import { + useAppSelector, + useDownload, + useFeature, + useToast, +} from "@renderer/hooks"; import { motion } from "framer-motion"; import { Tooltip } from "react-tooltip"; import { RealDebridInfoModal } from "./real-debrid-info-modal"; @@ -29,7 +35,8 @@ export interface DownloadSettingsModalProps { repack: GameRepack, downloader: Downloader, downloadPath: string, - automaticallyExtract: boolean + automaticallyExtract: boolean, + addToQueueOnly?: boolean ) => Promise<{ ok: boolean; error?: string }>; repack: GameRepack | null; } @@ -46,8 +53,11 @@ export function DownloadSettingsModal({ (state) => state.userPreferences.value ); + const { lastPacket } = useDownload(); const { showErrorToast } = useToast(); + const hasActiveDownload = lastPacket !== null; + const [diskFreeSpace, setDiskFreeSpace] = useState(null); const [selectedPath, setSelectedPath] = useState(""); const [downloadStarting, setDownloadStarting] = useState(false); @@ -220,7 +230,8 @@ export function DownloadSettingsModal({ repack, selectedDownloader!, selectedPath, - automaticExtractionEnabled + automaticExtractionEnabled, + hasActiveDownload ); if (response.ok) { @@ -456,6 +467,11 @@ export function DownloadSettingsModal({ {t("loading")} + ) : hasActiveDownload ? ( + <> + + {t("add_to_queue")} + ) : ( <> diff --git a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx index 1a1132f1..a805c2ad 100644 --- a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx @@ -39,7 +39,8 @@ export interface RepacksModalProps { repack: GameRepack, downloader: Downloader, downloadPath: string, - automaticallyExtract: boolean + automaticallyExtract: boolean, + addToQueueOnly?: boolean ) => Promise<{ ok: boolean; error?: string }>; onClose: () => void; } From eea714810808ae3cb3c1bfca82cc129bae846da7 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sat, 24 Jan 2026 19:38:00 +0200 Subject: [PATCH 4/8] refactor: enhance download management by validating URLs and adding file size handling --- src/main/events/library/delete-game-folder.ts | 23 +++-- .../events/torrenting/add-game-to-queue.ts | 99 +++++++++++++++---- .../events/torrenting/start-game-download.ts | 5 +- .../services/download/download-manager.ts | 38 ++++--- src/renderer/src/hooks/use-download.ts | 12 ++- .../src/pages/game-details/game-details.tsx | 2 + src/shared/index.ts | 19 ++++ src/types/index.ts | 1 + 8 files changed, 153 insertions(+), 46 deletions(-) diff --git a/src/main/events/library/delete-game-folder.ts b/src/main/events/library/delete-game-folder.ts index b9cef25b..1bfa36e0 100644 --- a/src/main/events/library/delete-game-folder.ts +++ b/src/main/events/library/delete-game-folder.ts @@ -15,14 +15,7 @@ const deleteGameFolder = async ( const downloadKey = levelKeys.game(shop, objectId); const download = await downloadsSublevel.get(downloadKey); - if (!download?.folderName) return; - - const folderPath = path.join( - download.downloadPath ?? (await getDownloadsPath()), - download.folderName - ); - - const metaPath = `${folderPath}.meta`; + if (!download) return; const deleteFile = async (filePath: string, isDirectory = false) => { if (fs.existsSync(filePath)) { @@ -47,8 +40,18 @@ const deleteGameFolder = async ( } }; - await deleteFile(folderPath, true); - await deleteFile(metaPath); + if (download.folderName) { + const folderPath = path.join( + download.downloadPath ?? (await getDownloadsPath()), + download.folderName + ); + + const metaPath = `${folderPath}.meta`; + + await deleteFile(folderPath, true); + await deleteFile(metaPath); + } + await downloadsSublevel.del(downloadKey); }; diff --git a/src/main/events/torrenting/add-game-to-queue.ts b/src/main/events/torrenting/add-game-to-queue.ts index 85f321b1..b0cd7962 100644 --- a/src/main/events/torrenting/add-game-to-queue.ts +++ b/src/main/events/torrenting/add-game-to-queue.ts @@ -1,6 +1,6 @@ import { registerEvent } from "../register-event"; import type { Download, StartGameDownloadPayload } from "@types"; -import { HydraApi, logger } from "@main/services"; +import { DownloadManager, HydraApi, logger } from "@main/services"; import { createGame } from "@main/services/library-sync"; import { downloadsSublevel, @@ -8,6 +8,8 @@ import { gamesSublevel, levelKeys, } from "@main/level"; +import { Downloader, DownloadError, parseBytes } from "@shared"; +import { AxiosError } from "axios"; const addGameToQueue = async ( _event: Electron.IpcMainInvokeEvent, @@ -21,10 +23,86 @@ const addGameToQueue = async ( downloader, uri, automaticallyExtract, + fileSize, } = payload; const gameKey = levelKeys.game(shop, objectId); + const download: Download = { + shop, + objectId, + status: "paused", + progress: 0, + bytesDownloaded: 0, + downloadPath, + downloader, + uri, + folderName: null, + fileSize: parseBytes(fileSize ?? null), + shouldSeed: false, + timestamp: Date.now(), + queued: true, + extracting: false, + automaticallyExtract, + extractionProgress: 0, + }; + + try { + await DownloadManager.validateDownloadUrl(download); + } catch (err: unknown) { + logger.error("Failed to validate download URL for queue", err); + + if (err instanceof AxiosError) { + if (err.response?.status === 429 && downloader === Downloader.Gofile) { + return { ok: false, error: DownloadError.GofileQuotaExceeded }; + } + + if ( + err.response?.status === 403 && + downloader === Downloader.RealDebrid + ) { + return { + ok: false, + error: DownloadError.RealDebridAccountNotAuthorized, + }; + } + + if (downloader === Downloader.TorBox) { + return { ok: false, error: err.response?.data?.detail }; + } + } + + if (err instanceof Error) { + if (downloader === Downloader.Buzzheavier) { + if (err.message.includes("Rate limit")) { + return { ok: false, error: "Buzzheavier: Rate limit exceeded" }; + } + if ( + err.message.includes("not found") || + err.message.includes("deleted") + ) { + return { ok: false, error: "Buzzheavier: File not found" }; + } + } + + if (downloader === Downloader.FuckingFast) { + if (err.message.includes("Rate limit")) { + return { ok: false, error: "FuckingFast: Rate limit exceeded" }; + } + if ( + err.message.includes("not found") || + err.message.includes("deleted") + ) { + return { ok: false, error: "FuckingFast: File not found" }; + } + } + + return { ok: false, error: err.message }; + } + + return { ok: false }; + } + const game = await gamesSublevel.get(gameKey); const gameAssets = await gamesShopAssetsSublevel.get(gameKey); @@ -50,25 +128,6 @@ const addGameToQueue = async ( }); } - const download: Download = { - shop, - objectId, - status: "paused", - progress: 0, - bytesDownloaded: 0, - downloadPath, - downloader, - uri, - folderName: null, - fileSize: null, - shouldSeed: false, - timestamp: Date.now(), - queued: true, - extracting: false, - automaticallyExtract, - extractionProgress: 0, - }; - try { await downloadsSublevel.put(gameKey, download); diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index e44ba936..f178029f 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -2,7 +2,7 @@ import { registerEvent } from "../register-event"; import type { Download, StartGameDownloadPayload } from "@types"; import { DownloadManager, HydraApi, logger } from "@main/services"; import { createGame } from "@main/services/library-sync"; -import { Downloader, DownloadError } from "@shared"; +import { Downloader, DownloadError, parseBytes } from "@shared"; import { downloadsSublevel, gamesShopAssetsSublevel, @@ -23,6 +23,7 @@ const startGameDownload = async ( downloader, uri, automaticallyExtract, + fileSize, } = payload; const gameKey = levelKeys.game(shop, objectId); @@ -75,7 +76,7 @@ const startGameDownload = async ( downloader, uri, folderName: null, - fileSize: null, + fileSize: parseBytes(fileSize ?? null), shouldSeed: false, timestamp: Date.now(), queued: true, diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index e66d04b6..7688ae66 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -499,18 +499,20 @@ export class DownloadManager { } static async cancelDownload(downloadKey = this.downloadingGameId) { - if (this.usingJsDownloader && this.jsDownloader) { - logger.log("[DownloadManager] Cancelling JS download"); - this.jsDownloader.cancelDownload(); - this.jsDownloader = null; - this.usingJsDownloader = false; - } else if (!this.isPreparingDownload) { - await PythonRPC.rpc - .post("/action", { action: "cancel", game_id: downloadKey }) - .catch((err) => logger.error("Failed to cancel game download", err)); - } + const isActiveDownload = downloadKey === this.downloadingGameId; + + if (isActiveDownload) { + if (this.usingJsDownloader && this.jsDownloader) { + logger.log("[DownloadManager] Cancelling JS download"); + this.jsDownloader.cancelDownload(); + this.jsDownloader = null; + this.usingJsDownloader = false; + } else if (!this.isPreparingDownload) { + await PythonRPC.rpc + .post("/action", { action: "cancel", game_id: downloadKey }) + .catch((err) => logger.error("Failed to cancel game download", err)); + } - if (downloadKey === this.downloadingGameId) { WindowManager.mainWindow?.setProgressBar(-1); WindowManager.mainWindow?.webContents.send("on-download-progress", null); this.downloadingGameId = null; @@ -932,6 +934,20 @@ export class DownloadManager { } } + static async validateDownloadUrl(download: Download): Promise { + const useJsDownloader = await this.shouldUseJsDownloader(); + const isHttp = this.isHttpDownloader(download.downloader); + + if (useJsDownloader && isHttp) { + const options = await this.getJsDownloadOptions(download); + if (!options) { + throw new Error("Failed to validate download URL"); + } + } else if (isHttp) { + await this.getDownloadPayload(download); + } + } + static async startDownload(download: Download) { const useJsDownloader = await this.shouldUseJsDownloader(); const isHttp = this.isHttpDownloader(download.downloader); diff --git a/src/renderer/src/hooks/use-download.ts b/src/renderer/src/hooks/use-download.ts index 8adb9574..6405a85c 100644 --- a/src/renderer/src/hooks/use-download.ts +++ b/src/renderer/src/hooks/use-download.ts @@ -69,10 +69,16 @@ export function useDownload() { }; const cancelDownload = async (shop: GameShop, objectId: string) => { - await window.electron.cancelGameDownload(shop, objectId); - dispatch(clearDownload()); - updateLibrary(); + const gameId = `${shop}:${objectId}`; + const isActiveDownload = lastPacket?.gameId === gameId; + await window.electron.cancelGameDownload(shop, objectId); + + if (isActiveDownload) { + dispatch(clearDownload()); + } + + updateLibrary(); removeGameInstaller(shop, objectId); }; diff --git a/src/renderer/src/pages/game-details/game-details.tsx b/src/renderer/src/pages/game-details/game-details.tsx index f885596c..af12ef0f 100644 --- a/src/renderer/src/pages/game-details/game-details.tsx +++ b/src/renderer/src/pages/game-details/game-details.tsx @@ -112,6 +112,7 @@ export default function GameDetails() { downloadPath, uri: selectRepackUri(repack, downloader), automaticallyExtract: automaticallyExtract, + fileSize: repack.fileSize, }) : await startDownload({ objectId: objectId!, @@ -121,6 +122,7 @@ export default function GameDetails() { downloadPath, uri: selectRepackUri(repack, downloader), automaticallyExtract: automaticallyExtract, + fileSize: repack.fileSize, }); if (response.ok) { diff --git a/src/shared/index.ts b/src/shared/index.ts index 5da36bd9..4ab56405 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -51,6 +51,25 @@ export const formatBytes = (bytes: number): string => { return `${Math.trunc(formatedByte * 10) / 10} ${FORMAT[base]}`; }; +export const parseBytes = (sizeString: string | null): number | null => { + if (!sizeString) return null; + + const regex = /^([\d.,]+)\s*([A-Za-z]+)$/; + const match = regex.exec(sizeString.trim()); + if (!match) return null; + + const value = Number.parseFloat(match[1].replaceAll(",", ".")); + const unit = match[2].toUpperCase(); + + if (Number.isNaN(value)) return null; + + const unitIndex = FORMAT.indexOf(unit); + if (unitIndex === -1) return null; + + const byteKBase = 1024; + return Math.round(value * Math.pow(byteKBase, unitIndex)); +}; + export const formatBytesToMbps = (bytesPerSecond: number): string => { const bitsPerSecond = bytesPerSecond * 8; const mbps = bitsPerSecond / (1024 * 1024); diff --git a/src/types/index.ts b/src/types/index.ts index 39fd0791..e2bb9fb8 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -118,6 +118,7 @@ export interface StartGameDownloadPayload { downloadPath: string; downloader: Downloader; automaticallyExtract: boolean; + fileSize?: string | null; } export interface UserFriend { From d448a699da2de8f0a857b6f858c90153098f41b5 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sat, 24 Jan 2026 19:50:25 +0200 Subject: [PATCH 5/8] refactor: streamline error handling in download processes by utilizing a dedicated error handler --- .../events/torrenting/add-game-to-queue.ts | 55 +-------------- .../events/torrenting/start-game-download.ts | 67 +------------------ src/main/helpers/download-error-handler.ts | 51 ++++++++++++++ src/main/helpers/index.ts | 1 + 4 files changed, 58 insertions(+), 116 deletions(-) create mode 100644 src/main/helpers/download-error-handler.ts diff --git a/src/main/events/torrenting/add-game-to-queue.ts b/src/main/events/torrenting/add-game-to-queue.ts index b0cd7962..4ebb447c 100644 --- a/src/main/events/torrenting/add-game-to-queue.ts +++ b/src/main/events/torrenting/add-game-to-queue.ts @@ -8,8 +8,8 @@ import { gamesSublevel, levelKeys, } from "@main/level"; -import { Downloader, DownloadError, parseBytes } from "@shared"; -import { AxiosError } from "axios"; +import { parseBytes } from "@shared"; +import { handleDownloadError } from "@main/helpers"; const addGameToQueue = async ( _event: Electron.IpcMainInvokeEvent, @@ -51,56 +51,7 @@ const addGameToQueue = async ( await DownloadManager.validateDownloadUrl(download); } catch (err: unknown) { logger.error("Failed to validate download URL for queue", err); - - if (err instanceof AxiosError) { - if (err.response?.status === 429 && downloader === Downloader.Gofile) { - return { ok: false, error: DownloadError.GofileQuotaExceeded }; - } - - if ( - err.response?.status === 403 && - downloader === Downloader.RealDebrid - ) { - return { - ok: false, - error: DownloadError.RealDebridAccountNotAuthorized, - }; - } - - if (downloader === Downloader.TorBox) { - return { ok: false, error: err.response?.data?.detail }; - } - } - - if (err instanceof Error) { - if (downloader === Downloader.Buzzheavier) { - if (err.message.includes("Rate limit")) { - return { ok: false, error: "Buzzheavier: Rate limit exceeded" }; - } - if ( - err.message.includes("not found") || - err.message.includes("deleted") - ) { - return { ok: false, error: "Buzzheavier: File not found" }; - } - } - - if (downloader === Downloader.FuckingFast) { - if (err.message.includes("Rate limit")) { - return { ok: false, error: "FuckingFast: Rate limit exceeded" }; - } - if ( - err.message.includes("not found") || - err.message.includes("deleted") - ) { - return { ok: false, error: "FuckingFast: File not found" }; - } - } - - return { ok: false, error: err.message }; - } - - return { ok: false }; + return handleDownloadError(err, downloader); } const game = await gamesSublevel.get(gameKey); diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index f178029f..852a6d86 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -2,14 +2,14 @@ import { registerEvent } from "../register-event"; import type { Download, StartGameDownloadPayload } from "@types"; import { DownloadManager, HydraApi, logger } from "@main/services"; import { createGame } from "@main/services/library-sync"; -import { Downloader, DownloadError, parseBytes } from "@shared"; +import { parseBytes } from "@shared"; import { downloadsSublevel, gamesShopAssetsSublevel, gamesSublevel, levelKeys, } from "@main/level"; -import { AxiosError } from "axios"; +import { handleDownloadError } from "@main/helpers"; const startGameDownload = async ( _event: Electron.IpcMainInvokeEvent, @@ -102,68 +102,7 @@ const startGameDownload = async ( return { ok: true }; } catch (err: unknown) { logger.error("Failed to start download", err); - - if (err instanceof AxiosError) { - if (err.response?.status === 429 && downloader === Downloader.Gofile) { - return { ok: false, error: DownloadError.GofileQuotaExceeded }; - } - - if ( - err.response?.status === 403 && - downloader === Downloader.RealDebrid - ) { - return { - ok: false, - error: DownloadError.RealDebridAccountNotAuthorized, - }; - } - - if (downloader === Downloader.TorBox) { - return { ok: false, error: err.response?.data?.detail }; - } - } - - if (err instanceof Error) { - if (downloader === Downloader.Buzzheavier) { - if (err.message.includes("Rate limit")) { - return { - ok: false, - error: "Buzzheavier: Rate limit exceeded", - }; - } - if ( - err.message.includes("not found") || - err.message.includes("deleted") - ) { - return { - ok: false, - error: "Buzzheavier: File not found", - }; - } - } - - if (downloader === Downloader.FuckingFast) { - if (err.message.includes("Rate limit")) { - return { - ok: false, - error: "FuckingFast: Rate limit exceeded", - }; - } - if ( - err.message.includes("not found") || - err.message.includes("deleted") - ) { - return { - ok: false, - error: "FuckingFast: File not found", - }; - } - } - - return { ok: false, error: err.message }; - } - - return { ok: false }; + return handleDownloadError(err, downloader); } }; diff --git a/src/main/helpers/download-error-handler.ts b/src/main/helpers/download-error-handler.ts new file mode 100644 index 00000000..ad39d263 --- /dev/null +++ b/src/main/helpers/download-error-handler.ts @@ -0,0 +1,51 @@ +import { AxiosError } from "axios"; +import { Downloader, DownloadError } from "@shared"; + +export const handleDownloadError = ( + err: unknown, + downloader: Downloader +): { ok: false; error?: string } => { + if (err instanceof AxiosError) { + if (err.response?.status === 429 && downloader === Downloader.Gofile) { + return { ok: false, error: DownloadError.GofileQuotaExceeded }; + } + + if (err.response?.status === 403 && downloader === Downloader.RealDebrid) { + return { ok: false, error: DownloadError.RealDebridAccountNotAuthorized }; + } + + if (downloader === Downloader.TorBox) { + return { ok: false, error: err.response?.data?.detail }; + } + } + + if (err instanceof Error) { + if (downloader === Downloader.Buzzheavier) { + if (err.message.includes("Rate limit")) { + return { ok: false, error: "Buzzheavier: Rate limit exceeded" }; + } + if ( + err.message.includes("not found") || + err.message.includes("deleted") + ) { + return { ok: false, error: "Buzzheavier: File not found" }; + } + } + + if (downloader === Downloader.FuckingFast) { + if (err.message.includes("Rate limit")) { + return { ok: false, error: "FuckingFast: Rate limit exceeded" }; + } + if ( + err.message.includes("not found") || + err.message.includes("deleted") + ) { + return { ok: false, error: "FuckingFast: File not found" }; + } + } + + return { ok: false, error: err.message }; + } + + return { ok: false }; +}; diff --git a/src/main/helpers/index.ts b/src/main/helpers/index.ts index 664dbd78..f75bd469 100644 --- a/src/main/helpers/index.ts +++ b/src/main/helpers/index.ts @@ -94,3 +94,4 @@ export const getThemeSoundPath = ( }; export * from "./reg-parser"; +export * from "./download-error-handler"; From c9afd6553655ec29a5f4c907753dc6558302dd66 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sat, 24 Jan 2026 19:56:55 +0200 Subject: [PATCH 6/8] refactor: simplify game entry preparation in download processes by consolidating logic into a helper function --- .../events/torrenting/add-game-to-queue.ts | 34 ++------------ .../events/torrenting/start-game-download.ts | 34 ++------------ src/main/helpers/download-game-helper.ts | 45 +++++++++++++++++++ src/main/helpers/index.ts | 1 + 4 files changed, 52 insertions(+), 62 deletions(-) create mode 100644 src/main/helpers/download-game-helper.ts diff --git a/src/main/events/torrenting/add-game-to-queue.ts b/src/main/events/torrenting/add-game-to-queue.ts index 4ebb447c..f57cfaf6 100644 --- a/src/main/events/torrenting/add-game-to-queue.ts +++ b/src/main/events/torrenting/add-game-to-queue.ts @@ -2,14 +2,9 @@ import { registerEvent } from "../register-event"; import type { Download, StartGameDownloadPayload } from "@types"; import { DownloadManager, HydraApi, logger } from "@main/services"; import { createGame } from "@main/services/library-sync"; -import { - downloadsSublevel, - gamesShopAssetsSublevel, - gamesSublevel, - levelKeys, -} from "@main/level"; +import { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level"; import { parseBytes } from "@shared"; -import { handleDownloadError } from "@main/helpers"; +import { handleDownloadError, prepareGameEntry } from "@main/helpers"; const addGameToQueue = async ( _event: Electron.IpcMainInvokeEvent, @@ -54,30 +49,7 @@ const addGameToQueue = async ( return handleDownloadError(err, downloader); } - const game = await gamesSublevel.get(gameKey); - const gameAssets = await gamesShopAssetsSublevel.get(gameKey); - - await downloadsSublevel.del(gameKey); - - if (game) { - await gamesSublevel.put(gameKey, { - ...game, - isDeleted: false, - }); - } else { - await gamesSublevel.put(gameKey, { - title, - iconUrl: gameAssets?.iconUrl ?? null, - libraryHeroImageUrl: gameAssets?.libraryHeroImageUrl ?? null, - logoImageUrl: gameAssets?.logoImageUrl ?? null, - objectId, - shop, - remoteId: null, - playTimeInMilliseconds: 0, - lastTimePlayed: null, - isDeleted: false, - }); - } + await prepareGameEntry({ gameKey, title, objectId, shop }); try { await downloadsSublevel.put(gameKey, download); diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index 852a6d86..71a0cc00 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -3,13 +3,8 @@ import type { Download, StartGameDownloadPayload } from "@types"; import { DownloadManager, HydraApi, logger } from "@main/services"; import { createGame } from "@main/services/library-sync"; import { parseBytes } from "@shared"; -import { - downloadsSublevel, - gamesShopAssetsSublevel, - gamesSublevel, - levelKeys, -} from "@main/level"; -import { handleDownloadError } from "@main/helpers"; +import { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level"; +import { handleDownloadError, prepareGameEntry } from "@main/helpers"; const startGameDownload = async ( _event: Electron.IpcMainInvokeEvent, @@ -39,30 +34,7 @@ const startGameDownload = async ( } } - const game = await gamesSublevel.get(gameKey); - const gameAssets = await gamesShopAssetsSublevel.get(gameKey); - - await downloadsSublevel.del(gameKey); - - if (game) { - await gamesSublevel.put(gameKey, { - ...game, - isDeleted: false, - }); - } else { - await gamesSublevel.put(gameKey, { - title, - iconUrl: gameAssets?.iconUrl ?? null, - libraryHeroImageUrl: gameAssets?.libraryHeroImageUrl ?? null, - logoImageUrl: gameAssets?.logoImageUrl ?? null, - objectId, - shop, - remoteId: null, - playTimeInMilliseconds: 0, - lastTimePlayed: null, - isDeleted: false, - }); - } + await prepareGameEntry({ gameKey, title, objectId, shop }); await DownloadManager.cancelDownload(gameKey); diff --git a/src/main/helpers/download-game-helper.ts b/src/main/helpers/download-game-helper.ts new file mode 100644 index 00000000..fc779351 --- /dev/null +++ b/src/main/helpers/download-game-helper.ts @@ -0,0 +1,45 @@ +import { + downloadsSublevel, + gamesShopAssetsSublevel, + gamesSublevel, +} from "@main/level"; +import type { GameShop } from "@types"; + +interface PrepareGameEntryParams { + gameKey: string; + title: string; + objectId: string; + shop: GameShop; +} + +export const prepareGameEntry = async ({ + gameKey, + title, + objectId, + shop, +}: PrepareGameEntryParams): Promise => { + const game = await gamesSublevel.get(gameKey); + const gameAssets = await gamesShopAssetsSublevel.get(gameKey); + + await downloadsSublevel.del(gameKey); + + if (game) { + await gamesSublevel.put(gameKey, { + ...game, + isDeleted: false, + }); + } else { + await gamesSublevel.put(gameKey, { + title, + iconUrl: gameAssets?.iconUrl ?? null, + libraryHeroImageUrl: gameAssets?.libraryHeroImageUrl ?? null, + logoImageUrl: gameAssets?.logoImageUrl ?? null, + objectId, + shop, + remoteId: null, + playTimeInMilliseconds: 0, + lastTimePlayed: null, + isDeleted: false, + }); + } +}; diff --git a/src/main/helpers/index.ts b/src/main/helpers/index.ts index f75bd469..c942cb5b 100644 --- a/src/main/helpers/index.ts +++ b/src/main/helpers/index.ts @@ -95,3 +95,4 @@ export const getThemeSoundPath = ( export * from "./reg-parser"; export * from "./download-error-handler"; +export * from "./download-game-helper"; From 57c2e740133f8f97b29dba6ba3d643c470141dc6 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 24 Jan 2026 20:54:54 -0300 Subject: [PATCH 7/8] chore: update ww sdk --- package.json | 2 +- src/renderer/src/app.tsx | 12 ++++++------ yarn.lock | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 066982fd..bbd585b3 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "user-agents": "^1.1.387", "uuid": "^13.0.0", "winreg": "^1.2.5", - "workwonders-sdk": "0.0.14", + "workwonders-sdk": "0.1.1", "ws": "^8.18.1", "yaml": "^2.6.1", "yup": "^1.5.0" diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index eb5f7e49..4a442a26 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { Sidebar, BottomPanel, Header, Toast } from "@renderer/components"; -import { WorkWondersSdk } from "workwonders-sdk"; +import { WorkWonders } from "workwonders-sdk"; import { useAppDispatch, useAppSelector, @@ -52,7 +52,7 @@ export function App() { const { clearDownload, setLastPacket } = useDownload(); - const workwondersRef = useRef(null); + const workwondersRef = useRef(null); const { hasActiveSubscription, @@ -125,18 +125,18 @@ export function App() { const parsedLocale = possibleLocales.find((l) => l === locale?.slice(0, 2)) ?? "en"; - workwondersRef.current = new WorkWondersSdk(); + workwondersRef.current = new WorkWonders(); await workwondersRef.current.init({ organization: "hydra", token, locale: parsedLocale, }); - await workwondersRef.current.initChangelogWidget(); - workwondersRef.current.initChangelogWidgetMini(); + await workwondersRef.current.changelog.initChangelogWidget(); + workwondersRef.current.changelog.initChangelogWidgetMini(); if (token) { - workwondersRef.current.initFeedbackWidget(); + workwondersRef.current.feedback.initFeedbackWidget(); } }, [workwondersRef] diff --git a/yarn.lock b/yarn.lock index 26f557c3..2ec6d647 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9238,10 +9238,10 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -workwonders-sdk@0.0.14: - version "0.0.14" - resolved "https://registry.yarnpkg.com/workwonders-sdk/-/workwonders-sdk-0.0.14.tgz#a14333a8fe2c9f2d671ebb6e76b0cb80250800a4" - integrity sha512-52pjvY6xry4Zn3iM9Vtwkv07+0XAXBUZ8IVA9+Czf9kskYcDRdepjJW5p8f44T3HV12kBnUUXrx4oX9JAoRmZA== +workwonders-sdk@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/workwonders-sdk/-/workwonders-sdk-0.1.1.tgz#7ac0eb3d9ef0a5a8cc5ca4e6f5e387e29875faa9" + integrity sha512-PEsl33QCeiBlYed/MmnX1unnd4Kn7vzVIza00HQ/5Zsan89nqnwWx9vqgJnNipXkkmIWl8oDL9bGRNjtL4XZ4Q== dependencies: ky "^1.14.2" From d4bd8f7bf1b956b62cfdea4fae1edd6af80eaaf7 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Sun, 25 Jan 2026 10:40:38 +0200 Subject: [PATCH 8/8] refactor: remove unnecessary download key deletion in game installer actions and simplify file size handling in download manager --- .../events/library/get-game-installer-action-type.ts | 1 - src/main/events/library/open-game-installer.ts | 1 - src/main/events/torrenting/start-game-download.ts | 4 +--- src/main/services/download/download-manager.ts | 11 ++++++----- src/main/services/download/js-http-downloader.ts | 9 ++++++++- src/renderer/src/pages/downloads/download-group.tsx | 6 +++--- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/main/events/library/get-game-installer-action-type.ts b/src/main/events/library/get-game-installer-action-type.ts index 2e58968a..7d49c642 100644 --- a/src/main/events/library/get-game-installer-action-type.ts +++ b/src/main/events/library/get-game-installer-action-type.ts @@ -22,7 +22,6 @@ const getGameInstallerActionType = async ( ); if (!fs.existsSync(gamePath)) { - await downloadsSublevel.del(downloadKey); return "open-folder"; } diff --git a/src/main/events/library/open-game-installer.ts b/src/main/events/library/open-game-installer.ts index 9cf1d978..57feeb87 100644 --- a/src/main/events/library/open-game-installer.ts +++ b/src/main/events/library/open-game-installer.ts @@ -38,7 +38,6 @@ const openGameInstaller = async ( ); if (!fs.existsSync(gamePath)) { - await downloadsSublevel.del(downloadKey); return true; } diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index 71a0cc00..181dd532 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -2,7 +2,6 @@ import { registerEvent } from "../register-event"; import type { Download, StartGameDownloadPayload } from "@types"; import { DownloadManager, HydraApi, logger } from "@main/services"; import { createGame } from "@main/services/library-sync"; -import { parseBytes } from "@shared"; import { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level"; import { handleDownloadError, prepareGameEntry } from "@main/helpers"; @@ -18,7 +17,6 @@ const startGameDownload = async ( downloader, uri, automaticallyExtract, - fileSize, } = payload; const gameKey = levelKeys.game(shop, objectId); @@ -48,7 +46,7 @@ const startGameDownload = async ( downloader, uri, folderName: null, - fileSize: parseBytes(fileSize ?? null), + fileSize: null, shouldSeed: false, timestamp: Date.now(), queued: true, diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index 7688ae66..71daf2d6 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -21,7 +21,7 @@ import { RealDebridClient } from "./real-debrid"; import path from "node:path"; import { logger } from "../logger"; import { db, downloadsSublevel, gamesSublevel, levelKeys } from "@main/level"; -import { sortBy } from "lodash-es"; +import { orderBy } from "lodash-es"; import { TorBoxClient } from "./torbox"; import { GameFilesManager } from "../game-files-manager"; import { HydraDebridClient } from "./hydra-debrid"; @@ -323,7 +323,8 @@ export class DownloadManager { this.sendProgressUpdate(progress, status, game); - if (progress === 1) { + const isComplete = progress === 1 || download.status === "complete"; + if (isComplete) { await this.handleDownloadCompletion(download, game, gameId); } } @@ -422,10 +423,10 @@ export class DownloadManager { .values() .all() .then((games) => - sortBy( + orderBy( games.filter((game) => game.status === "paused" && game.queued), - "timestamp", - "DESC" + ["timestamp"], + ["desc"] ) ); diff --git a/src/main/services/download/js-http-downloader.ts b/src/main/services/download/js-http-downloader.ts index c90c1a95..b488300b 100644 --- a/src/main/services/download/js-http-downloader.ts +++ b/src/main/services/download/js-http-downloader.ts @@ -320,10 +320,17 @@ export class JsHttpDownloader { return null; } + let progress = 0; + if (this.status === "complete") { + progress = 1; + } else if (this.fileSize > 0) { + progress = this.bytesDownloaded / this.fileSize; + } + return { folderName: this.folderName, fileSize: this.fileSize, - progress: this.fileSize > 0 ? this.bytesDownloaded / this.fileSize : 0, + progress, downloadSpeed: this.downloadSpeed, numPeers: 0, numSeeds: 0, diff --git a/src/renderer/src/pages/downloads/download-group.tsx b/src/renderer/src/pages/downloads/download-group.tsx index 6f969a2a..6a2dd478 100644 --- a/src/renderer/src/pages/downloads/download-group.tsx +++ b/src/renderer/src/pages/downloads/download-group.tsx @@ -47,9 +47,9 @@ function hexToRgb(hex: string): [number, number, number] { if (h.length === 3) { h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2]; } - const r = parseInt(h.substring(0, 2), 16) || 0; - const g = parseInt(h.substring(2, 4), 16) || 0; - const b = parseInt(h.substring(4, 6), 16) || 0; + const r = Number.parseInt(h.substring(0, 2), 16) || 0; + const g = Number.parseInt(h.substring(2, 4), 16) || 0; + const b = Number.parseInt(h.substring(4, 6), 16) || 0; return [r, g, b]; }