diff --git a/README.be.md b/README.be.md index 5dfadf5d..8c104a81 100644 --- a/README.be.md +++ b/README.be.md @@ -22,6 +22,7 @@ [![fr](https://img.shields.io/badge/lang-fr-blue)](README.fr.md) [![de](https://img.shields.io/badge/lang-de-black)](README.de.md) [![ita](https://img.shields.io/badge/lang-it-red)](README.it.md) +[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md) ![Hydra Catalogue](./docs/screenshot.png) diff --git a/README.cs.md b/README.cs.md new file mode 100644 index 00000000..86f869b9 --- /dev/null +++ b/README.cs.md @@ -0,0 +1,185 @@ +
+ +
+ +[](https://hydralauncher.site) + +

Hydra Launcher

+ +

+ Hydra je herní launcher s vlastním vestavěným Bittorrent klientem. +

+ +[![build](https://img.shields.io/github/actions/workflow/status/hydralauncher/hydra/build.yml)](https://github.com/hydralauncher/hydra/actions) +[![release](https://img.shields.io/github/package-json/v/hydralauncher/hydra)](https://github.com/hydralauncher/hydra/releases) + +[![pt-BR](https://img.shields.io/badge/lang-pt--BR-green.svg)](README.pt-BR.md) +[![en](https://img.shields.io/badge/lang-en-red.svg)](README.md) +[![ru](https://img.shields.io/badge/lang-ru-yellow.svg)](README.ru.md) +[![uk-UA](https://img.shields.io/badge/lang-uk--UA-blue)](README.uk-UA.md) +[![be](https://img.shields.io/badge/lang-be-orange)](README.be.md) +[![es](https://img.shields.io/badge/lang-es-red)](README.es.md) +[![fr](https://img.shields.io/badge/lang-fr-blue)](README.fr.md) +[![de](https://img.shields.io/badge/lang-de-black)](README.de.md) +[![ita](https://img.shields.io/badge/lang-it-red)](README.it.md) +[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md) + +![Hydra Katalog](./docs/screenshot.png) + +
+ +## Seznam obsahu + +- [Seznam obsahu](#seznam-obsahu) +- [O projektu](#o-projektu) +- [Funkce](#funkce) +- [Instalace](#instalace) +- [Přispívání](#přispívání) + - [Připoj se na náš telegram](#připoj-se-na-náš-telegram) + - [Vytvořte fork a naklonujte svůj repozitář](#vytvořte-fork-a-naklonujte-svůj-repozitář) + - [Způsoby jak můžete přispět](#způsoby-jak-můžete-přispět) + - [Struktura projektu](#struktura-projektu) +- [Sestavení ze zdroje](#sestavení-ze-zdroje) + - [Instalace Node.js](#instalace-nodejs) + - [Instalace Yarn](#instalace-yarn) + - [Instalace Požadavků pro Node.js](#instalace-požadavků-pro-nodejs) + - [Instalace Pythonu 3.9](#instalace-pythonu-39) + - [Instalace Požadavků pro Python](#instalace-požadavků-pro-python) +- [Proměnné prostředí](#proměnné-prostředí) +- [Spuštění](#spuštění) +- [Sestavení](#sestavení) + - [Sestavení bittorrent klientu](#sestavení-bittorrent-klientu) + - [Sestavení electron aplikace](#sestavení-electron-aplikace) +- [Přispěvatelé](#přispěvatelé) +- [Licence](#licence) + +## O projektu + +**Hydra** je **Herní Launcher** s jeho vlastním vestavěným **BitTorrent Klientem**. +
+Launcher je napsán v TypeScriptu (Electron) a Pythonu, který má na starosti torrentovací systém za pomocí knihovny libtorrent. + +## Funkce + +- Vlastní vestavěný BitTorrent klient +- How Long To Beat (HLTB) integrace na stránce hry +- Vlastní místa pro uložení hry +- Windows a Linux podpora +- Časté aktualizace +- A další ... + +## Instalace + +Následuj kroky: + +1. Stáhni nejnovější verzi Hydry ze stránky [Vydání](https://github.com/hydralauncher/hydra/releases/latest). + - Stáhni .exe, pokud chceš instalovat Hydru na Windows. + - Stáhni .deb nebo .rpm nebo .zip, pokud chceš instalovat Hydru na Linux. (záleží na tvé Linux distribuci) +2. Spusť stažený instalační soubor. +3. Užívej Hydru! + +## Přispívání + +### Připoj se na náš telegram + +Vedeme diskuzi v našem [Telegramovém](https://t.me/hydralauncher) kanálu. + +### Vytvořte fork a naklonujte svůj repozitář + +1. Vytvoř fork repozitáře [(klikni sem pro vytvoření forku)](https://github.com/hydralauncher/hydra/fork) +2. Naklonuj kód forku `git clone https://github.com/tvoje_jméno/hydra` +3. Vytvoř nové odvětví (branch) +4. Odešli svoje změny +5. Odešli nový Pull Request + +### Způsoby jak můžete přispět + +- Překládání: Chceme, aby Hydra byla co nejvíce dostupná. Můžete přispět novým jazykem, nebo úpravou současného! +- Kód: Hydra je postavena na Typescriptu, Electronu a trochou Pythonu. Pokud chceš přispět, připoj se na náš [Telegram](https://t.me/hydralauncher)! + +### Struktura projektu + +- torrent-client: Používáme libtorrent, Pythonovou knihovnu, pro správu torrent stahování +- src/renderer: uživatelské rozhraní aplikace (UI) +- src/main: celá logika projektu + +## Sestavení ze zdroje + +### Instalace Node.js + +Ujistěte se, že máte Node.js nainstalován na svém zařízení. Pokud ne, stáhněte ho, a nainstalujte z [nodejs.org](https://nodejs.org/). + +### Instalace Yarn + +Yarn je balíčkový správce pro Node.js. Pokud ještě nemáte yarn, můžete ho stáhnout za pomoci pokynů na [yarnpkg.com](https://classic.yarnpkg.com/lang/en/docs/install/). + +### Instalace Požadavků pro Node.js + +Jděte do složky projektu, otevřte v ní konzole a nainstalujte požadavky pro Node pomocí Yarn: + +```bash +cd hydra +yarn +``` + +### Instalace Pythonu 3.9 + +Ujistěte se, že máte Python 3.9 nainstalován na svém zařízení. Můžete ho stáhnout z [python.org](https://www.python.org/downloads/release/python-3913/). + +### Instalace Požadavků pro Python + +Nainstalujte požadavky pro Python za pomoci pip: + +```bash +pip install -r requirements.txt +``` + +## Proměnné prostředí + +Budete potřebovat SteamGridDB API klíč, abyste mohli načítat ikony u her. + +Jakmile ho máte, můžete zkopírovat, nebo přejmenovat `.env.example` soubor na `.env` a dát ho do `STEAMGRIDDB_API_KEY`. + +## Spuštění + +Jakmile máte vše nastaveno, můžete spustit jak Electron proces tak bittorrent client: + +```bash +yarn dev +``` + +## Sestavení + +### Sestavení bittorrent klientu + +Sestavit bittorrent klient můžete pomocí: + +```bash +python torrent-client/setup.py build +``` + +### Sestavení electron aplikace + +Sestavit Electron aplikaci můžete pomocí následujících kroků: + +Na Windows: + +```bash +yarn build:win +``` + +Na Linux: + +```bash +yarn build:linux +``` + +## Přispěvatelé + + + + + +## Licence + +Hydra je licencována pod [MIT Licencí](LICENSE). diff --git a/README.de.md b/README.de.md index bd8c1b71..3f6e9d90 100644 --- a/README.de.md +++ b/README.de.md @@ -22,6 +22,7 @@ [![es](https://img.shields.io/badge/lang-es-red)](README.es.md) [![fr](https://img.shields.io/badge/lang-fr-blue)](README.fr.md) [![de](https://img.shields.io/badge/lang-de-black)](README.de.md) +[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md) ![Hydra Katalog](./docs/screenshot.png) diff --git a/README.es.md b/README.es.md index 50fecb2b..eead7276 100644 --- a/README.es.md +++ b/README.es.md @@ -22,6 +22,7 @@ [![fr](https://img.shields.io/badge/lang-fr-blue)](README.fr.md) [![de](https://img.shields.io/badge/lang-de-black)](README.de.md) [![ita](https://img.shields.io/badge/lang-it-red)](README.it.md) +[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md) ![Hydra Catalogue](./docs/screenshot.png) diff --git a/README.fr.md b/README.fr.md index 0b91962f..f34fd037 100644 --- a/README.fr.md +++ b/README.fr.md @@ -22,6 +22,7 @@ [![fr](https://img.shields.io/badge/lang-fr-blue)](README.fr.md) [![de](https://img.shields.io/badge/lang-de-black)](README.de.md) [![ita](https://img.shields.io/badge/lang-it-red)](README.it.md) +[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md) ![Catalogue Hydra](./docs/screenshot.png) diff --git a/README.it.md b/README.it.md index 678febac..d8ca75fb 100644 --- a/README.it.md +++ b/README.it.md @@ -22,6 +22,7 @@ [![fr](https://img.shields.io/badge/lang-fr-blue)](README.fr.md) [![de](https://img.shields.io/badge/lang-de-black)](README.de.md) [![ita](https://img.shields.io/badge/lang-it-red)](README.it.md) +[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md) ![Hydra Catalogue](./docs/screenshot.png) diff --git a/README.md b/README.md index f76da369..29ff6b24 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ [![fr](https://img.shields.io/badge/lang-fr-blue)](README.fr.md) [![de](https://img.shields.io/badge/lang-de-black)](README.de.md) [![ita](https://img.shields.io/badge/lang-it-red)](README.it.md) +[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md) ![Hydra Catalogue](./docs/screenshot.png) diff --git a/README.pl.md b/README.pl.md index a4a65630..9120baac 100644 --- a/README.pl.md +++ b/README.pl.md @@ -22,6 +22,7 @@ [![fr](https://img.shields.io/badge/lang-fr-blue)](README.fr.md) [![de](https://img.shields.io/badge/lang-de-black)](README.de.md) [![ita](https://img.shields.io/badge/lang-it-red)](README.it.md) +[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md) ![Hydra Catalogue](./docs/screenshot.png) diff --git a/README.pt-BR.md b/README.pt-BR.md index 3d828dc0..610fc5c5 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -22,6 +22,7 @@ [![fr](https://img.shields.io/badge/lang-fr-blue)](README.fr.md) [![de](https://img.shields.io/badge/lang-de-black)](README.de.md) [![ita](https://img.shields.io/badge/lang-it-red)](README.it.md) +[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md) ![Hydra Catalogue](./docs/screenshot.png) diff --git a/README.ru.md b/README.ru.md index 409681a0..29fda4a3 100644 --- a/README.ru.md +++ b/README.ru.md @@ -22,6 +22,7 @@ [![fr](https://img.shields.io/badge/lang-fr-blue)](README.fr.md) [![de](https://img.shields.io/badge/lang-de-black)](README.de.md) [![ita](https://img.shields.io/badge/lang-it-red)](README.it.md) +[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md) ![Hydra Catalogue](./docs/screenshot.png) diff --git a/README.uk-UA.md b/README.uk-UA.md index 251f9e11..ed37aeca 100644 --- a/README.uk-UA.md +++ b/README.uk-UA.md @@ -22,6 +22,7 @@ [![fr](https://img.shields.io/badge/lang-fr-blue)](README.fr.md) [![de](https://img.shields.io/badge/lang-de-black)](README.de.md) [![ita](https://img.shields.io/badge/lang-it-red)](README.it.md) +[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md) ![Hydra Catalogue](./docs/screenshot.png) diff --git a/src/locales/cs/translation.json b/src/locales/cs/translation.json new file mode 100644 index 00000000..e7cbbe6f --- /dev/null +++ b/src/locales/cs/translation.json @@ -0,0 +1,277 @@ +{ + "language_name": "Čeština", + "app": { + "successfully_signed_in": "Úspěšně přihlášen" + }, + "home": { + "featured": "Doporučené", + "trending": "Trendy", + "surprise_me": "Překvap mě", + "no_results": "Výsledek nenalezen" + }, + "sidebar": { + "catalogue": "Katalog", + "downloads": "Stažené", + "settings": "Nastavení", + "my_library": "Moje knihovna", + "downloading_metadata": "{{title}} (Stahuji metadata…)", + "paused": "{{title}} (Pozastaveno)", + "downloading": "{{title}} ({{percentage}} - Stahuji…)", + "filter": "Filtrovat knihovnu", + "home": "Domov", + "queued": "{{title}} (V řadě)", + "game_has_no_executable": "Hra nemá zvolen žádný spustitelný soubor", + "sign_in": "Přihlásit se" + }, + "header": { + "search": "Vyhledat hry", + "home": "Domov", + "catalogue": "Katalog", + "downloads": "Stažené", + "search_results": "Výsledky vyhledávání", + "settings": "Nastavení", + "version_available_install": "Je dostupná nová verze: {{version}}. Klikni sem pro restart a instalaci.", + "version_available_download": "Je dostupná nová verze: {{version}}. Klikni sem pro stažení." + }, + "bottom_panel": { + "no_downloads_in_progress": "Neprobíhá žádné stahování", + "downloading_metadata": "Stahuji metadata: {{title}}…", + "downloading": "Stahuji {{title}}… ({{percentage}} staženo) - Odhadovaný čas {{eta}} - {{speed}}", + "calculating_eta": "Stahuji {{title}}… ({{percentage}} staženo) - Počítám zbývající čas…", + "checking_files": "Kontroluji soubory: {{title}}… ({{percentage}} ověřeno)" + }, + "catalogue": { + "next_page": "Další strana", + "previous_page": "Předchozí strana" + }, + "game_details": { + "open_download_options": "Otevřít možnosti stahování", + "download_options_zero": "Žádné možnosti stahování", + "download_options_one": "{{count}} možnost stažení", + "download_options_other": "{{count}} možnosti stažení", + "updated_at": "Aktualizováno {{updated_at}}", + "install": "Instalovat", + "resume": "Obnovit", + "pause": "Pozastavit", + "cancel": "Zrušit", + "remove": "Odebrat", + "space_left_on_disk": "{{space}} zbývá na disku", + "eta": "Odhadovaný čas: {{eta}}", + "calculating_eta": "Počítám zbývající čas…", + "downloading_metadata": "Stahuji metadata…", + "filter": "Filtrovat repacky", + "requirements": "Systémové požadavky", + "minimum": "Minimální", + "recommended": "Doporučené", + "paused": "Pozastaveno", + "release_date": "Datum vydání: {{date}}", + "publisher": "Publikováno: {{publisher}}", + "hours": "hodiny", + "minutes": "minuty", + "amount_hours": "{{amount}} hodin", + "amount_minutes": "{{amount}} minut", + "accuracy": "Přesnost {{accuracy}}%", + "add_to_library": "Přidat do knihovny", + "remove_from_library": "Odebrat z knihovny", + "no_downloads": "Žádné možnosti stahování nejsou dostupné", + "play_time": "Odehraný čas: {{amount}}", + "last_time_played": "Naposledy hráno {{period}}", + "not_played_yet": "Ješte jste nehráli {{title}}", + "next_suggestion": "Další doporučení", + "play": "Hrát", + "deleting": "Odstraňuji instalační program…", + "close": "Zavřít", + "playing_now": "Právě hraje", + "change": "Změnit", + "repacks_modal_description": "Vyber repack který chceš stáhnout", + "select_folder_hint": "Pro změnu základní složky, jdi do <0>Nastavení", + "download_now": "Stáhnout", + "no_shop_details": "Nepodařilo se mi načíst informace o obchodu.", + "download_options": "Možnosti stahování", + "download_path": "Umístění stahování", + "previous_screenshot": "Předchozí snímek obrazovky", + "next_screenshot": "Následující snímek obrazovky", + "screenshot": "Snímek obrazovky {{number}}", + "open_screenshot": "Otevřít snímek obrazovky {{number}}", + "download_settings": "Nastavení stahování", + "downloader": "Správce stahování", + "select_executable": "Vybrat", + "no_executable_selected": "Nebyl vybrán spustitelný soubor", + "open_folder": "Otevřít složku", + "open_download_location": "Zobrazit stažené soubory", + "create_shortcut": "Vytvořit zástupce na ploše", + "remove_files": "Odebrat soubory", + "remove_from_library_title": "Jste si jisti?", + "remove_from_library_description": "Tohle odstraní {{game}} z vaší knihovny", + "options": "Možnosti", + "executable_section_title": "Spustitelné", + "executable_section_description": "Umístění souboru který bude spuštěn při kliknutí na \"Hrát\"", + "downloads_secion_title": "Stažené soubory", + "downloads_section_description": "Zkontrolovat jestli není nová / odlišná verze hry", + "danger_zone_section_title": "Nebezpečná zóna", + "danger_zone_section_description": "Odebrat hru z knihovny / soubory stažené Hydrou", + "download_in_progress": "Probíhá stahování", + "download_paused": "Stahování pozastaveno", + "last_downloaded_option": "Poslední stažená možnost", + "create_shortcut_success": "Zástupce vytvořen úspěšně", + "create_shortcut_error": "Chyba při pokusu vytvořit zástupce" + }, + "activation": { + "title": "Aktivovat hydru", + "installation_id": "ID instalace:", + "enter_activation_code": "Zadej svůj aktivační kód", + "message": "Pokud nevíš, kde ten kód sehnat, tak by jsi k tomuhle neměl mít přístup.", + "activate": "Aktivovat", + "loading": "Načítání…" + }, + "downloads": { + "resume": "Pokračovat", + "pause": "Pozastavit", + "eta": "Odhadovaný čas: {{eta}}", + "paused": "Pozastaveno", + "verifying": "Ověřuji…", + "completed": "Hotovo", + "removed": "Není staženo", + "cancel": "Zrušit", + "filter": "Filtrovat stažené hry", + "remove": "Odebrat", + "downloading_metadata": "Stahuji metadata…", + "deleting": "Odstraňuji instalační program…", + "delete": "Odebrat instalační program", + "delete_modal_title": "Jste si jisti?", + "delete_modal_description": "Tohle odstraní všechny instalační soubory", + "install": "Instalovat", + "download_in_progress": "Probíhá stahování", + "queued_downloads": "Stahování v řadě", + "downloads_completed": "Dokončeno", + "queued": "V řadě", + "no_downloads_title": "Prázdno..", + "no_downloads_description": "Ještě jsi zatím nic nestáhl přes Hydru, ale furt není pozdě začít.", + "checking_files": "Kontroluji soubory…" + }, + "settings": { + "downloads_path": "Umístění stahování", + "change": "Aktualizovat", + "notifications": "Upozornění", + "enable_download_notifications": "Až bude stahování dokončeno", + "enable_repack_list_notifications": "Když bude přidán nový repack", + "real_debrid_api_token_label": "Real-Debrid API token", + "quit_app_instead_hiding": "Nezavírat Hydru při zavření okna", + "launch_with_system": "Spustit Hydru při startu systému", + "general": "Hlavní", + "behavior": "Chování", + "download_sources": "Zdroje stahování", + "language": "Jazyk", + "real_debrid_api_token": "API Token", + "enable_real_debrid": "Povolit Real-Debrid", + "real_debrid_description": "Real-Debrid je neomezený správce stahování, který umožňuje stahovat soubory v nejvyšší rychlosti vašeho internetu.", + "real_debrid_invalid_token": "Neplatný API token", + "real_debrid_api_token_hint": "API token můžeš sehnat <0>zde", + "real_debrid_free_account_error": "Účet \"{{username}}\" má základní úroveň. Prosím předplaťte si Real-Debrid", + "real_debrid_linked_message": "Účet \"{{username}}\" je propojen", + "save_changes": "Uložit změny", + "changes_saved": "Změny úspěšně uloženy", + "download_sources_description": "Hydra bude odsud sbírat soubory. Zdrojový odkaz musí být .json soubor obsahující odkazy na soubory.", + "validate_download_source": "Ověřit", + "remove_download_source": "Odebrat", + "add_download_source": "Přidat zdroj", + "download_count_zero": "Žádná možnost stažení", + "download_count_one": "{{countFormatted}} možnost stažení", + "download_count_other": "{{countFormatted}} možnosti stažení", + "download_source_url": "Stáhnout zdrojový odkaz", + "add_download_source_description": "Zadej odkaz odkazující na .json soubor", + "download_source_up_to_date": "Aktuální", + "download_source_errored": "Chyba", + "sync_download_sources": "Synchronizovat zdroje", + "removed_download_source": "Zdroj odebrán", + "added_download_source": "Zdroj přidán", + "download_sources_synced": "Všechny zdroje jsou synchronizovány", + "insert_valid_json_url": "Zadej platnou JSON adresu", + "found_download_option_zero": "Nenalezena žádná možnost stahování", + "found_download_option_one": "Nalezena {{countFormatted}} možnost stahování", + "found_download_option_other": "Nalezeny {{countFormatted}} možnosti stahování", + "import": "Importovat" + }, + "notifications": { + "download_complete": "Stahování dokončeno", + "game_ready_to_install": "{{title}} je připraveno k instalaci", + "repack_list_updated": "Seznam repacků byl aktualizován", + "repack_count_one": "{{count}} repack přidán", + "repack_count_other": "{{count}} repacky přidány", + "new_update_available": "Version {{version}} je dostupná", + "restart_to_install_update": "Restartuj Hydru pro aktualizaci" + }, + "system_tray": { + "open": "Otevřít Hydru", + "quit": "Odejít" + }, + "game_card": { + "no_downloads": "Žádné možnosti stahování nenalezeny" + }, + "binary_not_found_modal": { + "title": "Programy nenainstalovány", + "description": "Spustitelné soubory Wine nebo Lutris nebyly nalezeny ve vašem systému", + "instructions": "Zkonstroluj oficiální cestu jak je nainstalovat na tvoji Linux Distribuci, aby hry mohly běžet normálně" + }, + "modal": { + "close": "Tlačítko zavřít" + }, + "forms": { + "toggle_password_visibility": "Přepnout viditelnost hesla" + }, + "user_profile": { + "amount_hours": "{{amount}} hodin", + "amount_minutes": "{{amount}} minut", + "last_time_played": "Naposledy hráno {{period}}", + "activity": "Nedávná aktivita", + "library": "Knihovna", + "total_play_time": "Celkový odehraný čas: {{amount}}", + "no_recent_activity_title": "Hmmm… nic tu není", + "no_recent_activity_description": "V poslední době si nehrál žádnout hru, můžeš to ale napravit!", + "display_name": "Zobrazované jméno", + "saving": "Ukládání", + "save": "Uložit", + "edit_profile": "Upravit profil", + "saved_successfully": "Úspěšně uloženo", + "try_again": "Prosím, zkuste to znovu", + "sign_out_modal_title": "Jste si jisti?", + "cancel": "Zrušit", + "successfully_signed_out": "Úspěšně odhlášeno", + "sign_out": "Odhlásit se", + "playing_for": "Hraje po: {{amount}}", + "sign_out_modal_text": "Vaše knihovna je propojena s vaším současným účtem. Po odhlášení vaše knihovna již nebude vidět, a postup nebude uložen. Pokračovat?", + "add_friends": "Přidat přátele", + "add": "Přidat", + "friend_code": "Kód přítele", + "see_profile": "Zobrazit profil", + "sending": "Odesílání", + "friend_request_sent": "Žádost odeslána", + "friends": "Přátelé", + "friends_list": "Seznam přátel", + "user_not_found": "Uživatel nenalezen", + "block_user": "Zablokovat uživatele", + "add_friend": "Přidat přítele", + "request_sent": "Žádost odeslána", + "request_received": "Žádost obdržena", + "accept_request": "Přijmout žádost", + "ignore_request": "Ignorovat žádost", + "cancel_request": "Zrušit žádost", + "undo_friendship": "Odvolat přátelství", + "request_accepted": "Žádost přijata", + "user_blocked_successfully": "Uživatel úspěšně zablokován", + "user_block_modal_text": "Tohle zablokuje {{displayName}}", + "settings": "Nastavení", + "public": "Veřejné", + "private": "Soukromé", + "friends_only": "Pouze přátelé", + "privacy": "Soukromí", + "blocked_users": "Zablokovaní uživatelé", + "unblock": "Odblokovat", + "no_friends_added": "Nemáš přidané žádné přátele", + "pending": "Odchozí", + "no_pending_invites": "Nemáte žádné příchozí žádosti", + "no_blocked_users": "Nemáte nikoho zablokovaného", + "friend_code_copied": "Kód přítele zkopírován", + "undo_friendship_modal_text": "Tímto zrušíte své přátelství s {{displayName}}" + } +} diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index ed3ab8db..3f18f2af 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -7,7 +7,8 @@ "featured": "Featured", "trending": "Trending", "surprise_me": "Surprise me", - "no_results": "No results found" + "no_results": "No results found", + "start_typing": "Starting typing to search..." }, "sidebar": { "catalogue": "Catalogue", diff --git a/src/locales/index.ts b/src/locales/index.ts index e8426f01..fcb8d09e 100644 --- a/src/locales/index.ts +++ b/src/locales/index.ts @@ -21,6 +21,7 @@ import fa from "./fa/translation.json"; import ro from "./ro/translation.json"; import ca from "./ca/translation.json"; import kk from "./kk/translation.json"; +import cs from "./cs/translation.json"; export default { "pt-BR": ptBR, @@ -46,4 +47,5 @@ export default { ro, ca, kk, + cs, }; diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index fb7a8dd0..c06dd6bf 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -9,7 +9,8 @@ "hot": "🔥 Populares agora", "weekly": "📅 Mais baixados da semana", "surprise_me": "Surpreenda-me", - "no_results": "Nenhum resultado encontrado" + "no_results": "Nenhum resultado encontrado", + "start_typing": "Comece a digitar para pesquisar…" }, "sidebar": { "catalogue": "Catálogo", diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index 67f99921..3384bdf7 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -7,7 +7,8 @@ "featured": "Destaques", "trending": "Populares", "surprise_me": "Surpreende-me", - "no_results": "Nenhum resultado encontrado" + "no_results": "Nenhum resultado encontrado", + "start_typing": "Comece a digitar para pesquisar…" }, "sidebar": { "catalogue": "Catálogo", diff --git a/src/main/events/catalogue/get-random-game.ts b/src/main/events/catalogue/get-random-game.ts index 69f57800..72b93c33 100644 --- a/src/main/events/catalogue/get-random-game.ts +++ b/src/main/events/catalogue/get-random-game.ts @@ -3,7 +3,7 @@ import { shuffle } from "lodash-es"; import { getSteam250List } from "@main/services"; import { registerEvent } from "../register-event"; -import { searchSteamGames } from "../helpers/search-games"; +import { getSteamGameById } from "../helpers/search-games"; import type { Steam250Game } from "@types"; const state = { games: Array(), index: 0 }; @@ -12,14 +12,10 @@ const filterGames = async (games: Steam250Game[]) => { const results: Steam250Game[] = []; for (const game of games) { - const catalogue = await searchSteamGames({ query: game.title }); + const steamGame = await getSteamGameById(game.objectID); - if (catalogue.length) { - const [steamGame] = catalogue; - - if (steamGame.repacks.length) { - results.push(game); - } + if (steamGame?.repacks.length) { + results.push(game); } } diff --git a/src/main/events/catalogue/search-games.ts b/src/main/events/catalogue/search-games.ts index ec397599..ebe601f2 100644 --- a/src/main/events/catalogue/search-games.ts +++ b/src/main/events/catalogue/search-games.ts @@ -1,10 +1,25 @@ import { registerEvent } from "../register-event"; -import { searchSteamGames } from "../helpers/search-games"; +import { convertSteamGameToCatalogueEntry } from "../helpers/search-games"; import { CatalogueEntry } from "@types"; +import { HydraApi, RepacksManager } from "@main/services"; const searchGamesEvent = async ( _event: Electron.IpcMainInvokeEvent, query: string -): Promise => searchSteamGames({ query, limit: 12 }); +): Promise => { + const games = await HydraApi.get< + { objectId: string; title: string; shop: string }[] + >("/games/search", { title: query, take: 12, skip: 0 }, { needsAuth: false }); + + const steamGames = games.map((game) => { + return convertSteamGameToCatalogueEntry({ + id: Number(game.objectId), + name: game.title, + clientIcon: null, + }); + }); + + return RepacksManager.findRepacksForCatalogueEntries(steamGames); +}; registerEvent("searchGames", searchGamesEvent); diff --git a/src/main/events/helpers/search-games.ts b/src/main/events/helpers/search-games.ts index 29ec0245..5fb5098e 100644 --- a/src/main/events/helpers/search-games.ts +++ b/src/main/events/helpers/search-games.ts @@ -1,6 +1,3 @@ -import { orderBy } from "lodash-es"; -import flexSearch from "flexsearch"; - import type { GameShop, CatalogueEntry, SteamGame } from "@types"; import { steamGamesWorker } from "@main/workers"; @@ -23,20 +20,18 @@ export const convertSteamGameToCatalogueEntry = ( repacks: [], }); -export const searchSteamGames = async ( - options: flexSearch.SearchOptions -): Promise => { - const steamGames = (await steamGamesWorker.run(options, { - name: "search", - })) as SteamGame[]; +export const getSteamGameById = async ( + objectId: string +): Promise => { + const steamGame = await steamGamesWorker.run(Number(objectId), { + name: "getById", + }); - const result = RepacksManager.findRepacksForCatalogueEntries( - steamGames.map((game) => convertSteamGameToCatalogueEntry(game)) - ); + if (!steamGame) return null; - return orderBy( - result, - [({ repacks }) => repacks.length, "repacks"], - ["desc"] - ); + const catalogueEntry = convertSteamGameToCatalogueEntry(steamGame); + + const result = RepacksManager.findRepacksForCatalogueEntry(catalogueEntry); + + return result; }; diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 6f0e1905..fffd3f98 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -7,6 +7,10 @@ import { clearGamesRemoteIds } from "./library-sync/clear-games-remote-id"; import { logger } from "./logger"; import { UserNotLoggedInError } from "@shared"; +interface HydraApiOptions { + needsAuth: boolean; +} + export class HydraApi { private static instance: AxiosInstance; @@ -204,50 +208,76 @@ export class HydraApi { throw err; }; - static async get(url: string, params?: any) { - if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + static async get( + url: string, + params?: any, + options?: HydraApiOptions + ) { + if (!options || options.needsAuth) { + if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + await this.revalidateAccessTokenIfExpired(); + } - await this.revalidateAccessTokenIfExpired(); return this.instance .get(url, { params, ...this.getAxiosConfig() }) .then((response) => response.data) .catch(this.handleUnauthorizedError); } - static async post(url: string, data?: any) { - if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + static async post( + url: string, + data?: any, + options?: HydraApiOptions + ) { + if (!options || options.needsAuth) { + if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + await this.revalidateAccessTokenIfExpired(); + } - await this.revalidateAccessTokenIfExpired(); return this.instance .post(url, data, this.getAxiosConfig()) .then((response) => response.data) .catch(this.handleUnauthorizedError); } - static async put(url: string, data?: any) { - if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + static async put( + url: string, + data?: any, + options?: HydraApiOptions + ) { + if (!options || options.needsAuth) { + if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + await this.revalidateAccessTokenIfExpired(); + } - await this.revalidateAccessTokenIfExpired(); return this.instance .put(url, data, this.getAxiosConfig()) .then((response) => response.data) .catch(this.handleUnauthorizedError); } - static async patch(url: string, data?: any) { - if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + static async patch( + url: string, + data?: any, + options?: HydraApiOptions + ) { + if (!options || options.needsAuth) { + if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + await this.revalidateAccessTokenIfExpired(); + } - await this.revalidateAccessTokenIfExpired(); return this.instance .patch(url, data, this.getAxiosConfig()) .then((response) => response.data) .catch(this.handleUnauthorizedError); } - static async delete(url: string) { - if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + static async delete(url: string, options?: HydraApiOptions) { + if (!options || options.needsAuth) { + if (!this.isLoggedIn()) throw new UserNotLoggedInError(); + await this.revalidateAccessTokenIfExpired(); + } - await this.revalidateAccessTokenIfExpired(); return this.instance .delete(url, this.getAxiosConfig()) .then((response) => response.data) diff --git a/src/main/services/repacks-manager.ts b/src/main/services/repacks-manager.ts index 93157d6c..933d7431 100644 --- a/src/main/services/repacks-manager.ts +++ b/src/main/services/repacks-manager.ts @@ -49,6 +49,11 @@ export class RepacksManager { .map((index) => this.repacks[index]); } + public static findRepacksForCatalogueEntry(entry: CatalogueEntry) { + const repacks = this.search({ query: formatName(entry.title) }); + return { ...entry, repacks }; + } + public static findRepacksForCatalogueEntries(entries: CatalogueEntry[]) { return entries.map((entry) => { const repacks = this.search({ query: formatName(entry.title) }); diff --git a/src/main/workers/steam-games.worker.ts b/src/main/workers/steam-games.worker.ts index ad399943..9085082b 100644 --- a/src/main/workers/steam-games.worker.ts +++ b/src/main/workers/steam-games.worker.ts @@ -1,36 +1,15 @@ import { SteamGame } from "@types"; -import { orderBy, slice } from "lodash-es"; -import flexSearch from "flexsearch"; +import { slice } from "lodash-es"; import fs from "node:fs"; -import { formatName } from "@shared"; import { workerData } from "node:worker_threads"; -const steamGamesIndex = new flexSearch.Index({ - tokenize: "reverse", -}); - const { steamGamesPath } = workerData; const data = fs.readFileSync(steamGamesPath, "utf-8"); const steamGames = JSON.parse(data) as SteamGame[]; -for (let i = 0; i < steamGames.length; i++) { - const steamGame = steamGames[i]; - - const formattedName = formatName(steamGame.name); - - steamGamesIndex.add(i, formattedName); -} - -export const search = (options: flexSearch.SearchOptions) => { - const results = steamGamesIndex.search(options); - const games = results.map((index) => steamGames[index]); - - return orderBy(games, ["name"], ["asc"]); -}; - export const getById = (id: number) => steamGames.find((game) => game.id === id); diff --git a/src/renderer/src/pages/home/search-results.tsx b/src/renderer/src/pages/home/search-results.tsx index 30b3ea68..4ca72487 100644 --- a/src/renderer/src/pages/home/search-results.tsx +++ b/src/renderer/src/pages/home/search-results.tsx @@ -6,7 +6,7 @@ import type { CatalogueEntry } from "@types"; import type { DebouncedFunc } from "lodash"; import { debounce } from "lodash"; -import { InboxIcon } from "@primer/octicons-react"; +import { InboxIcon, SearchIcon } from "@primer/octicons-react"; import { clearSearch } from "@renderer/features"; import { useAppDispatch } from "@renderer/hooks"; import { useEffect, useRef, useState } from "react"; @@ -25,8 +25,10 @@ export function SearchResults() { const [searchResults, setSearchResults] = useState([]); const [isLoading, setIsLoading] = useState(false); + const [showTypingMessage, setShowTypingMessage] = useState(false); const debouncedFunc = useRef void> | null>(null); + const abortControllerRef = useRef(null); const navigate = useNavigate(); @@ -38,21 +40,64 @@ export function SearchResults() { useEffect(() => { setIsLoading(true); if (debouncedFunc.current) debouncedFunc.current.cancel(); + if (abortControllerRef.current) abortControllerRef.current.abort(); + + const abortController = new AbortController(); + abortControllerRef.current = abortController; debouncedFunc.current = debounce(() => { + const query = searchParams.get("query") ?? ""; + + if (query.length < 3) { + setIsLoading(false); + setShowTypingMessage(true); + setSearchResults([]); + return; + } + + setShowTypingMessage(false); window.electron - .searchGames(searchParams.get("query") ?? "") + .searchGames(query) .then((results) => { + if (abortController.signal.aborted) return; + setSearchResults(results); + setIsLoading(false); }) - .finally(() => { + .catch(() => { setIsLoading(false); }); - }, 300); + }, 500); debouncedFunc.current(); }, [searchParams, dispatch]); + const noResultsContent = () => { + if (isLoading) return null; + + if (showTypingMessage) { + return ( +
+ + +

{t("start_typing")}

+
+ ); + } + + if (searchResults.length === 0) { + return ( +
+ + +

{t("no_results")}

+
+ ); + } + + return null; + }; + return (
@@ -75,13 +120,7 @@ export function SearchResults() { )}
- {!isLoading && searchResults.length === 0 && ( -
- - -

{t("no_results")}

-
- )} + {noResultsContent()}
);