diff --git a/README.md b/README.md
index 1012c05d..a07414dc 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,7 @@
[![cs](https://img.shields.io/badge/lang-cs-purple)](./README.cs.md)
[![da](https://img.shields.io/badge/lang-da-red)](./README.da.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](./README.nb.md)
+[![ee](https://img.shields.io/badge/lang-et-blue.svg)](./README.et.md)
![Hydra Catalogue](./docs/screenshot.png)
diff --git a/docs/README.be.md b/docs/README.be.md
index b861d582..2bdde26d 100644
--- a/docs/README.be.md
+++ b/docs/README.be.md
@@ -25,6 +25,7 @@
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![da](https://img.shields.io/badge/lang-da-red)](README.da.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
+[![et](https://img.shields.io/badge/lang-et-blue.svg)](README.et.md)
![Hydra Catalogue](./screenshot.png)
diff --git a/docs/README.cs.md b/docs/README.cs.md
index 866a841c..30bb39b7 100644
--- a/docs/README.cs.md
+++ b/docs/README.cs.md
@@ -25,6 +25,7 @@
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![da](https://img.shields.io/badge/lang-da-red)](README.da.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
+[![et](https://img.shields.io/badge/lang-et-blue.svg)](README.et.md)
![Hydra Katalog](./screenshot.png)
diff --git a/docs/README.da.md b/docs/README.da.md
index abfe7817..79513fe3 100644
--- a/docs/README.da.md
+++ b/docs/README.da.md
@@ -24,6 +24,7 @@
[![ita](https://img.shields.io/badge/lang-it-red)](README.it.md)
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![da](https://img.shields.io/badge/lang-da-red)](README.da.md)
+[![et](https://img.shields.io/badge/lang-et-blue.svg)](README.et.md)
![Hydra Catalogue](./screenshot.png)
diff --git a/docs/README.de.md b/docs/README.de.md
index a1629fbb..45ef0fec 100644
--- a/docs/README.de.md
+++ b/docs/README.de.md
@@ -25,6 +25,7 @@
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![da](https://img.shields.io/badge/lang-da-red)](README.da.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
+[![et](https://img.shields.io/badge/lang-et-blue.svg)](README.et.md)
![Hydra Katalog](./screenshot.png)
diff --git a/docs/README.es.md b/docs/README.es.md
index 525d3e02..4209e94f 100644
--- a/docs/README.es.md
+++ b/docs/README.es.md
@@ -25,6 +25,7 @@
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![da](https://img.shields.io/badge/lang-da-red)](README.da.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
+[![et](https://img.shields.io/badge/lang-et-blue.svg)](README.et.md)
![Hydra Catalogue](./screenshot.png)
diff --git a/docs/README.et.md b/docs/README.et.md
new file mode 100644
index 00000000..c54c5b71
--- /dev/null
+++ b/docs/README.et.md
@@ -0,0 +1,186 @@
+
+
+[
](https://hydralauncher.site)
+
+
Hydra Launcher
+
+
+ Hydra on mängulauncher oma sisseehitatud bittorrenti kliendiga.
+
+
+[![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)
+[![da](https://img.shields.io/badge/lang-da-red)](./README.da.md)
+[![nb](https://img.shields.io/badge/lang-nb-blue)](./README.nb.md)
+[![ee](https://img.shields.io/badge/lang-et-blue.svg)](./README.et.md)
+
+![Hydra Kataloog](./screenshot.png)
+
+
+
+## Sisukord
+
+- [Sisukord](#sisukord)
+- [Tutvustus](#tutvustus)
+- [Funktsioonid](#funktsioonid)
+- [Paigaldamine](#paigaldamine)
+- [Panustamine](#panustamine)
+ - [Liitu meie Telegramiga](#liitu-meie-telegramiga)
+ - [Forki ja klooni oma repositoorium](#forki-ja-klooni-oma-repositoorium)
+ - [Viisid panustamiseks](#viisid-panustamiseks)
+ - [Projekti Struktuur](#projekti-struktuur)
+- [Lähtekoodi kompileerimine](#lähtekoodi-kompileerimine)
+ - [Node.js paigaldamine](#nodejs-paigaldamine)
+ - [Yarn'i paigaldamine](#yarni-paigaldamine)
+ - [Node sõltuvuste paigaldamine](#node-sõltuvuste-paigaldamine)
+ - [Python 3.9 paigaldamine](#python-39-paigaldamine)
+ - [Python'i sõltuvuste paigaldamine](#pythoni-sõltuvuste-paigaldamine)
+- [Keskkonna muutujad](#keskkonna-muutujad)
+- [Käivitamine](#käivitamine)
+- [Kompileerimine](#kompileerimine)
+ - [Bittorrenti kliendi kompileerimine](#bittorrenti-kliendi-kompileerimine)
+ - [Electron rakenduse kompileerimine](#electron-rakenduse-kompileerimine)
+- [Panustajad](#panustajad)
+- [Litsents](#litsents)
+
+## Tutvustus
+
+**Hydra** on **Mängulauncher** oma sisseehitatud **BitTorrent Kliendiga**.
+
+Launcher on kirjutatud TypeScriptis (Electron) ja Pythonis, mis haldab torrentide süsteemi kasutades libtorrenti.
+
+## Funktsioonid
+
+- Sisseehitatud bittorrenti klient
+- How Long To Beat (HLTB) integratsioon mängu lehel
+- Allalaadimiste kausta kohandamine
+- Windowsi ja Linuxi tugi
+- Pidevad uuendused
+- Ja palju muud ...
+
+## Paigaldamine
+
+Järgi paigaldamiseks järgmisi samme:
+
+1. Lae alla Hydra uusim versioon [Releases](https://github.com/hydralauncher/hydra/releases/latest) lehelt.
+ - Lae alla ainult .exe fail, kui soovid paigaldada Hydrat Windowsile.
+ - Lae alla .deb või .rpm või .zip fail, kui soovid paigaldada Hydrat Linuxile. (sõltub sinu Linuxi distrost)
+2. Käivita allalaaditud fail.
+3. Naudi Hydrat!
+
+## Panustamine
+
+### Liitu meie Telegramiga
+
+Me keskendume aruteludele meie [Telegrami](https://t.me/hydralauncher) kanalis.
+
+### Forki ja klooni oma repositoorium
+
+1. Forki repositoorium [(klõpsa siia forkimiseks)](https://github.com/hydralauncher/hydra/fork)
+2. Klooni oma forkitud kood `git clone https://github.com/your_username/hydra`
+3. Loo uus haru
+4. Pushi oma commitid
+5. Esita uus Pull Request
+
+### Viisid panustamiseks
+
+- Tõlkimine: Me soovime, et Hydra oleks kättesaadav võimalikult paljudele inimestele. Võid aidata tõlkida uutesse keeltesse või uuendada ja parandada juba olemasolevaid tõlkeid Hydras.
+- Kood: Hydra on ehitatud kasutades TypeScripti, Electroni ja natuke Pythonit. Kui soovid panustada, liitu meie [Telegramiga](https://t.me/hydralauncher)!
+
+### Projekti Struktuur
+
+- torrent-client: Kasutame libtorrenti, Pythoni teeki, torrentide allalaadimiste haldamiseks
+- src/renderer: rakenduse kasutajaliides
+- src/main: kogu loogika asub siin.
+
+## Lähtekoodi kompileerimine
+
+### Node.js paigaldamine
+
+Veendu, et Node.js on sinu arvutisse paigaldatud. Kui ei ole, lae alla ja paigalda see [nodejs.org](https://nodejs.org/) lehelt.
+
+### Yarn'i paigaldamine
+
+Yarn on Node.js paketihaldur. Kui sa pole Yarni veel paigaldanud, saad seda teha järgides juhiseid [yarnpkg.com](https://classic.yarnpkg.com/lang/en/docs/install/) lehel.
+
+### Node sõltuvuste paigaldamine
+
+Liigu projekti kausta ja paigalda Node sõltuvused kasutades Yarni:
+
+```bash
+cd hydra
+yarn
+```
+
+### Python 3.9 paigaldamine
+
+Veendu, et Python 3.9 on sinu arvutisse paigaldatud. Saad selle alla laadida ja paigaldada [python.org](https://www.python.org/downloads/release/python-3913/) lehelt.
+
+### Python'i sõltuvuste paigaldamine
+
+Paigalda vajalikud Pythoni sõltuvused kasutades pip'i:
+
+```bash
+pip install -r requirements.txt
+```
+
+## Keskkonna muutujad
+
+Sul on vaja SteamGridDB API võtit, et laadida alla mängude ikoone paigaldamisel.
+
+Kui sul on see olemas, saad kopeerida või ümber nimetada `.env.example` faili `.env` failiks ja lisada sinna `STEAMGRIDDB_API_KEY`.
+
+## Käivitamine
+
+Kui kõik on seadistatud, saad käivitada järgmise käsu, et käivitada nii Electroni protsess kui ka bittorrenti klient:
+
+```bash
+yarn dev
+```
+
+## Kompileerimine
+
+### Bittorrenti kliendi kompileerimine
+
+Kompileeri bittorrenti klient kasutades järgmist käsku:
+
+```bash
+python torrent-client/setup.py build
+```
+
+### Electron rakenduse kompileerimine
+
+Kompileeri Electron rakendus kasutades järgmist käsku:
+
+Windowsil:
+
+```bash
+yarn build:win
+```
+
+Linuxil:
+
+```bash
+yarn build:linux
+```
+
+## Panustajad
+
+
+
+
+
+## Litsents
+
+Hydra on litsentseeritud [MIT Litsentsi](LICENSE) all.
diff --git a/docs/README.fr.md b/docs/README.fr.md
index 648c30ea..0c35e059 100644
--- a/docs/README.fr.md
+++ b/docs/README.fr.md
@@ -25,6 +25,7 @@
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![da](https://img.shields.io/badge/lang-da-red)](README.da.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
+[![et](https://img.shields.io/badge/lang-et-blue.svg)](README.et.md)
![Catalogue Hydra](./screenshot.png)
diff --git a/docs/README.it.md b/docs/README.it.md
index 656a9aac..e87cf28e 100644
--- a/docs/README.it.md
+++ b/docs/README.it.md
@@ -25,6 +25,7 @@
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![da](https://img.shields.io/badge/lang-da-red)](README.da.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
+[![et](https://img.shields.io/badge/lang-et-blue.svg)](README.et.md)
![Hydra Catalogue](./screenshot.png)
diff --git a/docs/README.nb.md b/docs/README.nb.md
index 62f04781..1ec4744b 100644
--- a/docs/README.nb.md
+++ b/docs/README.nb.md
@@ -24,6 +24,7 @@
[![ita](https://img.shields.io/badge/lang-it-red)](README.it.md)
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
+[![et](https://img.shields.io/badge/lang-et-blue.svg)](README.et.md)
![Hydra Catalogue](./screenshot.png)
diff --git a/docs/README.pl.md b/docs/README.pl.md
index 2ee4e847..2fb31a6d 100644
--- a/docs/README.pl.md
+++ b/docs/README.pl.md
@@ -25,6 +25,7 @@
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![da](https://img.shields.io/badge/lang-da-red)](README.da.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
+[![et](https://img.shields.io/badge/lang-et-blue.svg)](README.et.md)
![Hydra Catalogue](./screenshot.png)
diff --git a/docs/README.pt-BR.md b/docs/README.pt-BR.md
index 83f0c420..7a2971c8 100644
--- a/docs/README.pt-BR.md
+++ b/docs/README.pt-BR.md
@@ -25,6 +25,7 @@
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![da](https://img.shields.io/badge/lang-da-red)](README.da.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
+[![et](https://img.shields.io/badge/lang-et-blue.svg)](README.et.md)
![Hydra Catalogue](./screenshot.png)
diff --git a/docs/README.ru.md b/docs/README.ru.md
index 6c0a6a0f..1ce831f7 100644
--- a/docs/README.ru.md
+++ b/docs/README.ru.md
@@ -25,6 +25,7 @@
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![da](https://img.shields.io/badge/lang-da-red)](README.da.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
+[![et](https://img.shields.io/badge/lang-et-blue.svg)](README.et.md)
![Hydra Catalogue](./screenshot.png)
diff --git a/docs/README.uk-UA.md b/docs/README.uk-UA.md
index db2f2c12..8fec8508 100644
--- a/docs/README.uk-UA.md
+++ b/docs/README.uk-UA.md
@@ -25,6 +25,7 @@
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![da](https://img.shields.io/badge/lang-da-red)](README.da.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
+[![et](https://img.shields.io/badge/lang-et-blue.svg)](README.et.md)
![Hydra Catalogue](./screenshot.png)
diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json
index 3ce8b3af..21082e59 100644
--- a/src/locales/en/translation.json
+++ b/src/locales/en/translation.json
@@ -41,7 +41,7 @@
"bottom_panel": {
"no_downloads_in_progress": "No downloads in progress",
"downloading_metadata": "Downloading {{title}} metadata…",
- "downloading": "Downloading {{title}}… ({{percentage}} complete) - Conclusion {{eta}} - {{speed}}",
+ "downloading": "Downloading {{title}}… ({{percentage}} complete) - Completion {{eta}} - {{speed}}",
"calculating_eta": "Downloading {{title}}… ({{percentage}} complete) - Calculating remaining time…",
"checking_files": "Checking {{title}} files… ({{percentage}} complete)"
},
@@ -358,7 +358,7 @@
"user_achievements": "{{displayName}}'s Achievements",
"your_achievements": "Your Achievements",
"unlocked_at": "Unlocked at:",
- "subscription_needed": "A Hydra Cloud subscription is needed to see this content",
+ "subscription_needed": "A Hydra Cloud subscription is required to see this content",
"new_achievements_unlocked": "Unlocked {{achievementCount}} new achievements from {{gameCount}} games"
},
"tour": {
diff --git a/src/locales/et/translation.json b/src/locales/et/translation.json
new file mode 100644
index 00000000..04a47e30
--- /dev/null
+++ b/src/locales/et/translation.json
@@ -0,0 +1,459 @@
+{
+ "language_name": "Eesti",
+ "app": {
+ "successfully_signed_in": "Edukalt sisse logitud"
+ },
+ "home": {
+ "featured": "Esile toodud",
+ "trending": "Populaarne",
+ "surprise_me": "Üllata mind",
+ "no_results": "Tulemusi ei leitud",
+ "start_typing": "Alusta otsimiseks kirjutamist...",
+ "hot": "Praegu kuum",
+ "weekly": "📅 Nädala top mängud"
+ },
+ "sidebar": {
+ "catalogue": "Kataloog",
+ "downloads": "Allalaadimised",
+ "settings": "Seaded",
+ "my_library": "Minu kogu",
+ "downloading_metadata": "{{title}} (Metaandmete allalaadimine…)",
+ "paused": "{{title}} (Peatatud)",
+ "downloading": "{{title}} ({{percentage}} - Allalaadimine…)",
+ "filter": "Filtreeri kogu",
+ "home": "Avaleht",
+ "queued": "{{title}} (Järjekorras)",
+ "game_has_no_executable": "Mängul pole käivitusfaili valitud",
+ "sign_in": "Logi sisse",
+ "friends": "Sõbrad"
+ },
+ "header": {
+ "search": "Otsi mänge",
+ "home": "Avaleht",
+ "catalogue": "Kataloog",
+ "downloads": "Allalaadimised",
+ "search_results": "Otsingutulemused",
+ "settings": "Seaded",
+ "version_available_install": "Versioon {{version}} on saadaval. Klõpsa siia taaskäivitamiseks ja installimiseks.",
+ "version_available_download": "Versioon {{version}} on saadaval. Klõpsa siia allalaadimiseks."
+ },
+ "bottom_panel": {
+ "no_downloads_in_progress": "Allalaadimisi pole pooleli",
+ "downloading_metadata": "{{title}} metaandmete allalaadimine…",
+ "downloading": "{{title}} allalaadimine… ({{percentage}} valmis) - Lõpp {{eta}} - {{speed}}",
+ "calculating_eta": "{{title}} allalaadimine… ({{percentage}} valmis) - Järelejäänud aja arvutamine…",
+ "checking_files": "{{title}} failide kontrollimine… ({{percentage}} valmis)"
+ },
+ "catalogue": {
+ "next_page": "Järgmine leht",
+ "previous_page": "Eelmine leht"
+ },
+ "game_details": {
+ "open_download_options": "Ava allalaadimise valikud",
+ "download_options_zero": "Allalaadimise valikuid pole",
+ "download_options_one": "{{count}} allalaadimise valik",
+ "download_options_other": "{{count}} allalaadimise valikut",
+ "updated_at": "Uuendatud {{updated_at}}",
+ "install": "Installi",
+ "resume": "Jätka",
+ "pause": "Peata",
+ "cancel": "Tühista",
+ "remove": "Eemalda",
+ "space_left_on_disk": "{{space}} kettaruumi järel",
+ "eta": "Lõpp {{eta}}",
+ "calculating_eta": "Järelejäänud aja arvutamine…",
+ "downloading_metadata": "Metaandmete allalaadimine…",
+ "filter": "Filtreeri repacke",
+ "requirements": "Süsteeminõuded",
+ "minimum": "Miinimum",
+ "recommended": "Soovitatav",
+ "paused": "Peatatud",
+ "release_date": "Välja antud {{date}}",
+ "publisher": "Avaldaja {{publisher}}",
+ "hours": "tundi",
+ "minutes": "minutit",
+ "amount_hours": "{{amount}} tundi",
+ "amount_minutes": "{{amount}} minutit",
+ "accuracy": "{{accuracy}}% täpsus",
+ "add_to_library": "Lisa kogusse",
+ "remove_from_library": "Eemalda kogust",
+ "no_downloads": "Allalaadimisi pole saadaval",
+ "play_time": "Mängitud {{amount}}",
+ "last_time_played": "Viimati mängitud {{period}}",
+ "not_played_yet": "Sa pole veel {{title}} mänginud",
+ "next_suggestion": "Järgmine soovitus",
+ "play": "Mängi",
+ "deleting": "Installeri kustutamine…",
+ "close": "Sulge",
+ "playing_now": "Mängib praegu",
+ "change": "Muuda",
+ "repacks_modal_description": "Vali repack, mida soovid alla laadida",
+ "select_folder_hint": "Vaikimisi kausta muutmiseks mine <0>Seadetesse0>",
+ "download_now": "Laadi alla kohe",
+ "no_shop_details": "Poe andmeid ei õnnestunud laadida.",
+ "download_options": "Allalaadimise valikud",
+ "download_path": "Allalaadimise tee",
+ "previous_screenshot": "Eelmine kuvatõmmis",
+ "next_screenshot": "Järgmine kuvatõmmis",
+ "screenshot": "Kuvatõmmis {{number}}",
+ "open_screenshot": "Ava kuvatõmmis {{number}}",
+ "download_settings": "Allalaadimise seaded",
+ "downloader": "Allalaadija",
+ "select_executable": "Vali",
+ "no_executable_selected": "Käivitusfaili pole valitud",
+ "open_folder": "Ava kaust",
+ "open_download_location": "Vaata allalaaditud faile",
+ "create_shortcut": "Loo töölaua otsetee",
+ "remove_files": "Eemalda failid",
+ "remove_from_library_title": "Oled sa kindel?",
+ "remove_from_library_description": "See eemaldab {{game}} sinu kogust",
+ "options": "Valikud",
+ "executable_section_title": "Käivitusfail",
+ "executable_section_description": "Faili tee, mida käivitatakse \"Mängi\" nupule vajutades",
+ "downloads_secion_title": "Allalaadimised",
+ "downloads_section_description": "Vaata uuendusi või selle mängu teisi versioone",
+ "danger_zone_section_title": "Ohutsoon",
+ "danger_zone_section_description": "Eemalda see mäng oma kogust või Hydra poolt allalaaditud failid",
+ "download_in_progress": "Allalaadimine käimas",
+ "download_paused": "Allalaadimine peatatud",
+ "last_downloaded_option": "Viimane allalaaditud variant",
+ "create_shortcut_success": "Otsetee edukalt loodud",
+ "create_shortcut_error": "Viga otsetee loomisel",
+ "nsfw_content_title": "See mäng sisaldab sobimatut sisu",
+ "nsfw_content_description": "{{title}} sisaldab sisu, mis ei pruugi sobida kõigile vanusegruppidele. Kas soovid kindlasti jätkata?",
+ "allow_nsfw_content": "Jätka",
+ "refuse_nsfw_content": "Mine tagasi",
+ "stats": "Statistika",
+ "download_count": "Allalaadimised",
+ "player_count": "Aktiivsed mängijad",
+ "download_error": "See allalaadimise valik pole saadaval",
+ "download": "Laadi alla",
+ "executable_path_in_use": "Käivitusfail on juba kasutusel mängus \"{{game}}\"",
+ "warning": "Hoiatus:",
+ "hydra_needs_to_remain_open": "selle allalaadimise jaoks peab Hydra jääma avatuks kuni lõpuni. Kui Hydra sulgub enne lõppu, kaotad oma progressi.",
+ "achievements": "Saavutused",
+ "achievements_count": "Saavutused {{unlockedCount}}/{{achievementsCount}}",
+ "cloud_save": "Pilvesalvestus",
+ "cloud_save_description": "Salvesta oma progress pilve ja jätka mängimist mistahes seadmes",
+ "backups": "Varundused",
+ "install_backup": "Installi",
+ "delete_backup": "Kustuta",
+ "create_backup": "Uus varundus",
+ "last_backup_date": "Viimane varundus {{date}}",
+ "no_backup_preview": "Selle mängu jaoks ei leitud salvestusi",
+ "restoring_backup": "Varunduse taastamine ({{progress}} valmis)…",
+ "uploading_backup": "Varunduse üleslaadimine…",
+ "no_backups": "Sa pole veel selle mängu jaoks varundusi loonud",
+ "backup_uploaded": "Varundus üles laaditud",
+ "backup_deleted": "Varundus kustutatud",
+ "backup_restored": "Varundus taastatud",
+ "see_all_achievements": "Vaata kõiki saavutusi",
+ "sign_in_to_see_achievements": "Logi sisse, et näha saavutusi",
+ "mapping_method_automatic": "Automaatne",
+ "mapping_method_manual": "Käsitsi",
+ "mapping_method_label": "Kaardistamise meetod",
+ "files_automatically_mapped": "Failid automaatselt kaardistatud",
+ "no_backups_created": "Selle mängu jaoks pole varundusi loodud",
+ "manage_files": "Halda faile",
+ "loading_save_preview": "Salvestuste otsimine…",
+ "wine_prefix": "Wine Prefix",
+ "wine_prefix_description": "Wine prefix, mida kasutatakse selle mängu käivitamiseks",
+ "no_download_option_info": "Info pole saadaval",
+ "backup_deletion_failed": "Varunduse kustutamine ebaõnnestus",
+ "max_number_of_artifacts_reached": "Selle mängu varunduste maksimaalne arv on saavutatud",
+ "achievements_not_sync": "Sinu saavutused pole sünkroniseeritud"
+ },
+ "activation": {
+ "title": "Aktiveeri Hydra",
+ "installation_id": "Installatsiooni ID:",
+ "enter_activation_code": "Sisesta oma aktiveerimiskood",
+ "message": "Kui sa ei tea, kust seda küsida, siis sa ei peaks seda omama.",
+ "activate": "Aktiveeri",
+ "loading": "Laadimine…"
+ },
+ "downloads": {
+ "resume": "Jätka",
+ "pause": "Peata",
+ "eta": "Lõpp {{eta}}",
+ "paused": "Peatatud",
+ "verifying": "Kontrollimine…",
+ "completed": "Lõpetatud",
+ "removed": "Pole alla laaditud",
+ "cancel": "Tühista",
+ "filter": "Filtreeri allalaaditud mänge",
+ "remove": "Eemalda",
+ "downloading_metadata": "Metaandmete allalaadimine…",
+ "deleting": "Installeri kustutamine…",
+ "delete": "Eemalda installer",
+ "delete_modal_title": "Oled sa kindel?",
+ "delete_modal_description": "See eemaldab kõik installifailid sinu arvutist",
+ "install": "Installi",
+ "download_in_progress": "Töös",
+ "queued_downloads": "Järjekorras allalaadimised",
+ "downloads_completed": "Lõpetatud",
+ "queued": "Järjekorras",
+ "no_downloads_title": "Nii tühi",
+ "no_downloads_description": "Sa pole veel Hydraga midagi alla laadinud, aga pole kunagi hilja alustada.",
+ "checking_files": "Failide kontrollimine…"
+ },
+ "settings": {
+ "downloads_path": "Allalaadimiste tee",
+ "change": "Uuenda",
+ "notifications": "Teavitused",
+ "enable_download_notifications": "Kui allalaadimine on lõpetatud",
+ "enable_repack_list_notifications": "Kui uus repack on lisatud",
+ "real_debrid_api_token_label": "Real-Debrid API võti",
+ "quit_app_instead_hiding": "Ära peida Hydrat sulgemisel",
+ "launch_with_system": "Käivita Hydra süsteemi käivitamisel",
+ "general": "Üldine",
+ "behavior": "Käitumine",
+ "download_sources": "Allalaadimise allikad",
+ "language": "Keel",
+ "real_debrid_api_token": "API Võti",
+ "enable_real_debrid": "Luba Real-Debrid",
+ "real_debrid_description": "Real-Debrid on piiranguteta allalaadija, mis võimaldab sul faile alla laadida koheselt ja sinu internetiühenduse parima kiirusega.",
+ "real_debrid_invalid_token": "Vigane API võti",
+ "real_debrid_api_token_hint": "Sa saad oma API võtme <0>siit0>",
+ "real_debrid_free_account_error": "Konto \"{{username}}\" on tasuta konto. Palun telli Real-Debrid",
+ "real_debrid_linked_message": "Konto \"{{username}}\" ühendatud",
+ "save_changes": "Salvesta muudatused",
+ "changes_saved": "Muudatused edukalt salvestatud",
+ "download_sources_description": "Hydra laeb allalaadimise lingid nendest allikatest. Allika URL peab olema otsene link .json failile, mis sisaldab allalaadimise linke.",
+ "validate_download_source": "Valideeri",
+ "remove_download_source": "Eemalda",
+ "add_download_source": "Lisa allikas",
+ "download_count_zero": "Allalaadimise valikuid pole",
+ "download_count_one": "{{countFormatted}} allalaadimise valik",
+ "download_count_other": "{{countFormatted}} allalaadimise valikut",
+ "download_source_url": "Allalaadimise allika URL",
+ "add_download_source_description": "Sisesta URL, mis sisaldab .json faili",
+ "download_source_up_to_date": "Ajakohane",
+ "download_source_errored": "Vigane",
+ "sync_download_sources": "Sünkroniseeri allikad",
+ "removed_download_source": "Allalaadimise allikas eemaldatud",
+ "added_download_source": "Allalaadimise allikas lisatud",
+ "download_sources_synced": "Kõik allalaadimise allikad on sünkroniseeritud",
+ "insert_valid_json_url": "Sisesta kehtiv JSON url",
+ "found_download_option_zero": "Allalaadimise valikuid ei leitud",
+ "found_download_option_one": "Leitud {{countFormatted}} allalaadimise valik",
+ "found_download_option_other": "Leitud {{countFormatted}} allalaadimise valikut",
+ "import": "Impordi",
+ "public": "Avalik",
+ "private": "Privaatne",
+ "friends_only": "Ainult sõpradele",
+ "privacy": "Privaatsus",
+ "profile_visibility": "Profiili nähtavus",
+ "profile_visibility_description": "Vali, kes saavad näha sinu profiili ja kogu",
+ "required_field": "See väli on kohustuslik",
+ "source_already_exists": "See allikas on juba lisatud",
+ "must_be_valid_url": "Allikas peab olema kehtiv URL",
+ "blocked_users": "Blokeeritud kasutajad",
+ "user_unblocked": "Kasutaja blokeering on eemaldatud",
+ "enable_achievement_notifications": "Kui saavutus avatakse"
+ },
+ "activation": {
+ "title": "Aktiveeri Hydra",
+ "installation_id": "Installatsiooni ID:",
+ "enter_activation_code": "Sisesta oma aktiveerimiskood",
+ "message": "Kui sa ei tea, kust seda küsida, siis sa ei peaks seda omama.",
+ "activate": "Aktiveeri",
+ "loading": "Laadimine…"
+ },
+ "downloads": {
+ "resume": "Jätka",
+ "pause": "Peata",
+ "eta": "Lõpp {{eta}}",
+ "paused": "Peatatud",
+ "verifying": "Kontrollimine…",
+ "completed": "Lõpetatud",
+ "removed": "Pole alla laaditud",
+ "cancel": "Tühista",
+ "filter": "Filtreeri allalaaditud mänge",
+ "remove": "Eemalda",
+ "downloading_metadata": "Metaandmete allalaadimine…",
+ "deleting": "Installeri kustutamine…",
+ "delete": "Eemalda installer",
+ "delete_modal_title": "Oled sa kindel?",
+ "delete_modal_description": "See eemaldab kõik installifailid sinu arvutist",
+ "install": "Installi",
+ "download_in_progress": "Töös",
+ "queued_downloads": "Järjekorras allalaadimised",
+ "downloads_completed": "Lõpetatud",
+ "queued": "Järjekorras",
+ "no_downloads_title": "Nii tühi",
+ "no_downloads_description": "Sa pole veel Hydraga midagi alla laadinud, aga pole kunagi hilja alustada.",
+ "checking_files": "Failide kontrollimine…"
+ },
+ "settings": {
+ "downloads_path": "Allalaadimiste tee",
+ "change": "Uuenda",
+ "notifications": "Teavitused",
+ "enable_download_notifications": "Kui allalaadimine on lõpetatud",
+ "enable_repack_list_notifications": "Kui uus repack on lisatud",
+ "real_debrid_api_token_label": "Real-Debrid API võti",
+ "quit_app_instead_hiding": "Ära peida Hydrat sulgemisel",
+ "launch_with_system": "Käivita Hydra süsteemi käivitamisel",
+ "general": "Üldine",
+ "behavior": "Käitumine",
+ "download_sources": "Allalaadimise allikad",
+ "language": "Keel",
+ "real_debrid_api_token": "API Võti",
+ "enable_real_debrid": "Luba Real-Debrid",
+ "real_debrid_description": "Real-Debrid on piiranguteta allalaadija, mis võimaldab sul faile alla laadida koheselt ja sinu internetiühenduse parima kiirusega.",
+ "real_debrid_invalid_token": "Vigane API võti",
+ "real_debrid_api_token_hint": "Sa saad oma API võtme <0>siit0>",
+ "real_debrid_free_account_error": "Konto \"{{username}}\" on tasuta konto. Palun telli Real-Debrid",
+ "real_debrid_linked_message": "Konto \"{{username}}\" ühendatud",
+ "save_changes": "Salvesta muudatused",
+ "changes_saved": "Muudatused edukalt salvestatud",
+ "download_sources_description": "Hydra laeb allalaadimise lingid nendest allikatest. Allika URL peab olema otsene link .json failile, mis sisaldab allalaadimise linke.",
+ "validate_download_source": "Valideeri",
+ "remove_download_source": "Eemalda",
+ "add_download_source": "Lisa allikas",
+ "download_count_zero": "Allalaadimise valikuid pole",
+ "download_count_one": "{{countFormatted}} allalaadimise valik",
+ "download_count_other": "{{countFormatted}} allalaadimise valikut",
+ "download_source_url": "Allalaadimise allika URL",
+ "add_download_source_description": "Sisesta URL, mis sisaldab .json faili",
+ "download_source_up_to_date": "Ajakohane",
+ "download_source_errored": "Vigane",
+ "sync_download_sources": "Sünkroniseeri allikad",
+ "removed_download_source": "Allalaadimise allikas eemaldatud",
+ "added_download_source": "Allalaadimise allikas lisatud",
+ "download_sources_synced": "Kõik allalaadimise allikad on sünkroniseeritud",
+ "insert_valid_json_url": "Sisesta kehtiv JSON url",
+ "found_download_option_zero": "Allalaadimise valikuid ei leitud",
+ "found_download_option_one": "Leitud {{countFormatted}} allalaadimise valik",
+ "found_download_option_other": "Leitud {{countFormatted}} allalaadimise valikut",
+ "import": "Impordi",
+ "public": "Avalik",
+ "private": "Privaatne",
+ "friends_only": "Ainult sõpradele",
+ "privacy": "Privaatsus",
+ "profile_visibility": "Profiili nähtavus",
+ "profile_visibility_description": "Vali, kes saavad näha sinu profiili ja kogu",
+ "required_field": "See väli on kohustuslik",
+ "source_already_exists": "See allikas on juba lisatud",
+ "must_be_valid_url": "Allikas peab olema kehtiv URL",
+ "blocked_users": "Blokeeritud kasutajad",
+ "user_unblocked": "Kasutaja blokeering on eemaldatud",
+ "enable_achievement_notifications": "Kui saavutus avatakse"
+ },
+ "notifications": {
+ "download_complete": "Allalaadimine lõpetatud",
+ "game_ready_to_install": "{{title}} on valmis installimiseks",
+ "repack_list_updated": "Repackide nimekiri uuendatud",
+ "repack_count_one": "{{count}} repack lisatud",
+ "repack_count_other": "{{count}} repacki lisatud",
+ "new_update_available": "Versioon {{version}} saadaval",
+ "restart_to_install_update": "Taaskäivita Hydra uuenduse installimiseks",
+ "notification_achievement_unlocked_title": "Saavutus avatud mängus {{game}}",
+ "notification_achievement_unlocked_body": "{{achievement}} ja veel {{count}} avati"
+ },
+ "system_tray": {
+ "open": "Ava Hydra",
+ "quit": "Välju"
+ },
+ "game_card": {
+ "no_downloads": "Allalaadimisi pole saadaval"
+ },
+ "binary_not_found_modal": {
+ "title": "Programmid pole installitud",
+ "description": "Wine või Lutrise käivitusfaile ei leitud sinu süsteemist",
+ "instructions": "Kontrolli õiget viisi nende installimiseks oma Linuxi distrol, et mäng saaks normaalselt töötada"
+ },
+ "modal": {
+ "close": "Sulgemise nupp"
+ },
+ "forms": {
+ "toggle_password_visibility": "Lülita parooli nähtavust"
+ },
+ "user_profile": {
+ "amount_hours": "{{amount}} tundi",
+ "amount_minutes": "{{amount}} minutit",
+ "last_time_played": "Viimati mängitud {{period}}",
+ "activity": "Hiljutine aktiivsus",
+ "library": "Kogu",
+ "total_play_time": "Kogu mängitud aeg: {{amount}}",
+ "no_recent_activity_title": "Hmmm… siin pole midagi",
+ "no_recent_activity_description": "Sa pole hiljuti ühtegi mängu mänginud. On aeg seda muuta!",
+ "display_name": "Kuvatav nimi",
+ "saving": "Salvestamine",
+ "save": "Salvesta",
+ "edit_profile": "Muuda profiili",
+ "saved_successfully": "Edukalt salvestatud",
+ "try_again": "Palun proovi uuesti",
+ "sign_out_modal_title": "Oled sa kindel?",
+ "cancel": "Tühista",
+ "successfully_signed_out": "Edukalt välja logitud",
+ "sign_out": "Logi välja",
+ "playing_for": "Mängib {{amount}}",
+ "sign_out_modal_text": "Sinu kogu on seotud sinu praeguse kontoga. Välja logides pole sinu kogu enam nähtav ja edasist progressi ei salvestata. Jätkata väljalogimisega?",
+ "add_friends": "Lisa sõpru",
+ "add": "Lisa",
+ "friend_code": "Sõbrakood",
+ "see_profile": "Vaata profiili",
+ "sending": "Saatmine",
+ "friend_request_sent": "Sõbrakutse saadetud",
+ "friends": "Sõbrad",
+ "friends_list": "Sõbrade nimekiri",
+ "user_not_found": "Kasutajat ei leitud",
+ "block_user": "Blokeeri kasutaja",
+ "add_friend": "Lisa sõbraks",
+ "request_sent": "Kutse saadetud",
+ "request_received": "Kutse saadud",
+ "accept_request": "Võta kutse vastu",
+ "ignore_request": "Ignoreeri kutset",
+ "cancel_request": "Tühista kutse",
+ "undo_friendship": "Tühista sõprus",
+ "request_accepted": "Kutse vastu võetud",
+ "user_blocked_successfully": "Kasutaja edukalt blokeeritud",
+ "user_block_modal_text": "See blokeerib kasutaja {{displayName}}",
+ "blocked_users": "Blokeeritud kasutajad",
+ "unblock": "Eemalda blokeering",
+ "no_friends_added": "Sul pole veel lisatud sõpru",
+ "pending": "Ootel",
+ "no_pending_invites": "Sul pole ootel kutseid",
+ "no_blocked_users": "Sul pole blokeeritud kasutajaid",
+ "friend_code_copied": "Sõbrakood kopeeritud",
+ "undo_friendship_modal_text": "See tühistab sinu sõpruse kasutajaga {{displayName}}",
+ "privacy_hint": "Et muuta, kes seda näevad, mine <0>Seadetesse0>",
+ "locked_profile": "See profiil on privaatne",
+ "image_process_failure": "Viga pildi töötlemisel",
+ "required_field": "See väli on kohustuslik",
+ "displayname_min_length": "Kuvatav nimi peab olema vähemalt 3 tähemärki pikk",
+ "displayname_max_length": "Kuvatav nimi võib olla maksimaalselt 50 tähemärki pikk",
+ "report_profile": "Teata sellest profiilist",
+ "report_reason": "Miks sa sellest profiilist teatad?",
+ "report_description": "Lisainfo",
+ "report_description_placeholder": "Lisainfo",
+ "report": "Teata",
+ "report_reason_hate": "Vaenukõne",
+ "report_reason_sexual_content": "Seksuaalne sisu",
+ "report_reason_violence": "Vägivald",
+ "report_reason_spam": "Rämpspost",
+ "report_reason_other": "Muu",
+ "profile_reported": "Profiilist teatatud",
+ "your_friend_code": "Sinu sõbrakood:",
+ "upload_banner": "Lae üles bänner",
+ "uploading_banner": "Bänneri üleslaadimine…"
+ },
+ "achievement": {
+ "achievement_unlocked": "Saavutus avatud",
+ "user_achievements": "{{displayName}} saavutused",
+ "your_achievements": "Sinu saavutused",
+ "unlocked_at": "Avatud:",
+ "subscription_needed": "Selle sisu nägemiseks on vaja Hydra Cloud tellimust",
+ "new_achievements_unlocked": "Avatud {{achievementCount}} uut saavutust {{gameCount}} mängust"
+ },
+ "tour": {
+ "subscription_tour_title": "Hydra Cloud Tellimus",
+ "subscribe_now": "Telli kohe",
+ "cloud_saving": "Pilvesalvestus (kuni {{gameCount}} mängu)",
+ "cloud_achievements": "Salvesta oma saavutused pilve",
+ "animated_profile_picture": "Animeeritud profiilipildid",
+ "premium_support": "Premium tugi",
+ "show_and_compare_achievements": "Näita ja võrdle oma saavutusi teiste kasutajatega",
+ "animated_profile_banner": "Animeeritud profiilibänner"
+ }
+}
diff --git a/src/locales/index.ts b/src/locales/index.ts
index 8b5f9d9a..26d15b61 100644
--- a/src/locales/index.ts
+++ b/src/locales/index.ts
@@ -23,6 +23,7 @@ import ca from "./ca/translation.json";
import kk from "./kk/translation.json";
import cs from "./cs/translation.json";
import nb from "./nb/translation.json";
+import et from "./et/translation.json";
export default {
"pt-BR": ptBR,
@@ -50,4 +51,5 @@ export default {
kk,
cs,
nb,
+ et,
};
diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json
index c79c0847..e71668e8 100644
--- a/src/locales/pt-BR/translation.json
+++ b/src/locales/pt-BR/translation.json
@@ -367,6 +367,7 @@
"animated_profile_picture": "Fotos de perfil animadas",
"premium_support": "Suporte Premium",
"show_and_compare_achievements": "Exiba e compare suas conquistas com outros usuários",
- "animated_profile_banner": "Banner animado no perfil"
+ "animated_profile_banner": "Banner animado no perfil",
+ "cloud_saving": "Saves na Cloud (até {{gameCount}} jogos)"
}
}
diff --git a/src/main/events/auth/sign-out.ts b/src/main/events/auth/sign-out.ts
index 724626b7..ed399f6d 100644
--- a/src/main/events/auth/sign-out.ts
+++ b/src/main/events/auth/sign-out.ts
@@ -7,7 +7,7 @@ import {
gamesPlaytime,
} from "@main/services";
import { dataSource } from "@main/data-source";
-import { DownloadQueue, Game, UserAuth } from "@main/entity";
+import { DownloadQueue, Game, UserAuth, UserSubscription } from "@main/entity";
const signOut = async (_event: Electron.IpcMainInvokeEvent) => {
const databaseOperations = dataSource
@@ -19,6 +19,10 @@ const signOut = async (_event: Electron.IpcMainInvokeEvent) => {
await transactionalEntityManager
.getRepository(UserAuth)
.delete({ id: 1 });
+
+ await transactionalEntityManager
+ .getRepository(UserSubscription)
+ .delete({ id: 1 });
})
.then(() => {
/* Removes all games being played */
diff --git a/src/main/events/profile/get-me.ts b/src/main/events/profile/get-me.ts
index 8ab78bf9..4f34ca32 100644
--- a/src/main/events/profile/get-me.ts
+++ b/src/main/events/profile/get-me.ts
@@ -1,43 +1,11 @@
import { registerEvent } from "../register-event";
-import { logger } from "@main/services";
-import type { ProfileVisibility, UserDetails } from "@types";
-import { userAuthRepository } from "@main/repository";
-import { UserNotLoggedInError } from "@shared";
+import type { UserDetails } from "@types";
import { getUserData } from "@main/services/user/get-user-data";
const getMe = async (
_event: Electron.IpcMainInvokeEvent
): Promise => {
- return getUserData().catch(async (err) => {
- if (err instanceof UserNotLoggedInError) {
- return null;
- }
- logger.error("Failed to get logged user", err);
- const loggedUser = await userAuthRepository.findOne({ where: { id: 1 } });
-
- if (loggedUser) {
- return {
- ...loggedUser,
- id: loggedUser.userId,
- username: "",
- bio: "",
- profileVisibility: "PUBLIC" as ProfileVisibility,
- subscription: loggedUser.subscription
- ? {
- id: loggedUser.subscription.subscriptionId,
- status: loggedUser.subscription.status,
- plan: {
- id: loggedUser.subscription.planId,
- name: loggedUser.subscription.planName,
- },
- expiresAt: loggedUser.subscription.expiresAt,
- }
- : null,
- };
- }
-
- return null;
- });
+ return getUserData();
};
registerEvent("getMe", getMe);
diff --git a/src/main/main.ts b/src/main/main.ts
index a680c824..69bc62e0 100644
--- a/src/main/main.ts
+++ b/src/main/main.ts
@@ -12,7 +12,6 @@ import { UserPreferences } from "./entity";
import { RealDebridClient } from "./services/real-debrid";
import { HydraApi } from "./services/hydra-api";
import { uploadGamesBatch } from "./services/library-sync";
-import { getUserData } from "./services/user/get-user-data";
const loadState = async (userPreferences: UserPreferences | null) => {
import("./events");
@@ -23,8 +22,7 @@ const loadState = async (userPreferences: UserPreferences | null) => {
Ludusavi.addManifestToLudusaviConfig();
- await HydraApi.setupApi().then(async () => {
- await getUserData().catch(() => {});
+ HydraApi.setupApi().then(() => {
uploadGamesBatch();
});
diff --git a/src/main/services/achievements/find-achivement-files.ts b/src/main/services/achievements/find-achivement-files.ts
index 9195c13a..85c5b767 100644
--- a/src/main/services/achievements/find-achivement-files.ts
+++ b/src/main/services/achievements/find-achivement-files.ts
@@ -71,6 +71,7 @@ const crackers = [
Cracker.smartSteamEmu,
Cracker.empress,
Cracker.flt,
+ Cracker.razor1911,
];
const getPathFromCracker = (cracker: Cracker) => {
@@ -221,6 +222,15 @@ const getPathFromCracker = (cracker: Cracker) => {
];
}
+ if (cracker == Cracker.razor1911) {
+ return [
+ {
+ folderPath: path.join(appData, ".1911"),
+ fileLocation: ["achievement"],
+ },
+ ];
+ }
+
achievementsLogger.error(`Cracker ${cracker} not implemented`);
throw new Error(`Cracker ${cracker} not implemented`);
};
diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts
index b1ae0273..41bf575a 100644
--- a/src/main/services/achievements/merge-achievements.ts
+++ b/src/main/services/achievements/merge-achievements.ts
@@ -125,7 +125,7 @@ export const mergeAchievements = async (
id: game.remoteId,
achievements: mergedLocalAchievements,
},
- { needsCloud: true }
+ { needsSubscription: true }
)
.then((response) => {
return saveAchievementsOnLocal(
diff --git a/src/main/services/achievements/parse-achievement-file.ts b/src/main/services/achievements/parse-achievement-file.ts
index 07854935..38b388ca 100644
--- a/src/main/services/achievements/parse-achievement-file.ts
+++ b/src/main/services/achievements/parse-achievement-file.ts
@@ -65,6 +65,10 @@ export const parseAchievementFile = (
return processCreamAPI(parsed);
}
+ if (type === Cracker.razor1911) {
+ return processRazor1911(filePath);
+ }
+
achievementsLogger.log(
`Unprocessed ${type} achievements found on ${filePath}`
);
@@ -111,6 +115,35 @@ const jsonParse = (filePath: string) => {
}
};
+const processRazor1911 = (filePath: string): UnlockedAchievement[] => {
+ try {
+ const fileContent = readFileSync(filePath, "utf-8");
+ achievementsLogger.log("processing file", filePath, fileContent);
+ const lines =
+ fileContent.charCodeAt(0) === 0xfeff
+ ? fileContent.slice(1).split(/[\r\n]+/)
+ : fileContent.split(/[\r\n]+/);
+
+ const achievements: UnlockedAchievement[] = [];
+ for (const line of lines) {
+ if (!line.length) continue;
+
+ const [name, unlocked, unlockTime] = line.split(" ");
+ if (unlocked === "1") {
+ achievements.push({
+ name,
+ unlockTime: Number(unlockTime) * 1000,
+ });
+ }
+ }
+ achievementsLogger.log("processing file", achievements);
+ return achievements;
+ } catch (err) {
+ achievementsLogger.error(`Error processing ${filePath}`, err);
+ return [];
+ }
+};
+
const processOnlineFix = (unlockedAchievements: any): UnlockedAchievement[] => {
const parsedUnlockedAchievements: UnlockedAchievement[] = [];
diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts
index ffe2fcb7..41d76408 100644
--- a/src/main/services/hydra-api.ts
+++ b/src/main/services/hydra-api.ts
@@ -11,10 +11,18 @@ import { logger } from "./logger";
import { UserNotLoggedInError, SubscriptionRequiredError } from "@shared";
import { omit } from "lodash-es";
import { appVersion } from "@main/constants";
+import { getUserData } from "./user/get-user-data";
interface HydraApiOptions {
needsAuth?: boolean;
- needsCloud?: boolean;
+ needsSubscription?: boolean;
+}
+
+interface HydraApiUserAuth {
+ authToken: string;
+ refreshToken: string;
+ expirationTimestamp: number;
+ subscription: { expiresAt: Date | null } | null;
}
export class HydraApi {
@@ -25,27 +33,22 @@ export class HydraApi {
private static secondsToMilliseconds = (seconds: number) => seconds * 1000;
- private static userAuth = {
+ private static userAuth: HydraApiUserAuth = {
authToken: "",
refreshToken: "",
expirationTimestamp: 0,
+ subscription: null,
};
private static isLoggedIn() {
return this.userAuth.authToken !== "";
}
- private static async hasCloudSubscription() {
- return userSubscriptionRepository
- .findOne({ where: { id: 1 } })
- .then((userSubscription) => {
- if (!userSubscription) return false;
-
- return (
- !userSubscription.expiresAt ||
- userSubscription!.expiresAt > new Date()
- );
- });
+ private static hasCloudSubscription() {
+ return (
+ this.userAuth.subscription?.expiresAt &&
+ this.userAuth.subscription.expiresAt > new Date()
+ );
}
static async handleExternalAuth(uri: string) {
@@ -67,6 +70,7 @@ export class HydraApi {
authToken: accessToken,
refreshToken: refreshToken,
expirationTimestamp: tokenExpirationTimestamp,
+ subscription: null,
};
logger.log(
@@ -84,6 +88,16 @@ export class HydraApi {
["id"]
);
+ await getUserData().then((userDetails) => {
+ if (userDetails?.subscription) {
+ this.userAuth.subscription = {
+ expiresAt: userDetails.subscription.expiresAt
+ ? new Date(userDetails.subscription.expiresAt)
+ : null,
+ };
+ }
+ });
+
if (WindowManager.mainWindow) {
WindowManager.mainWindow.webContents.send("on-signin");
await clearGamesRemoteIds();
@@ -96,6 +110,7 @@ export class HydraApi {
authToken: "",
refreshToken: "",
expirationTimestamp: 0,
+ subscription: null,
};
}
@@ -161,14 +176,20 @@ export class HydraApi {
);
}
+ await getUserData();
+
const userAuth = await userAuthRepository.findOne({
where: { id: 1 },
+ relations: { subscription: true },
});
this.userAuth = {
authToken: userAuth?.accessToken ?? "",
refreshToken: userAuth?.refreshToken ?? "",
expirationTimestamp: userAuth?.tokenExpirationTimestamp ?? 0,
+ subscription: userAuth?.subscription
+ ? { expiresAt: userAuth.subscription?.expiresAt }
+ : null,
};
}
@@ -236,9 +257,11 @@ export class HydraApi {
authToken: "",
expirationTimestamp: 0,
refreshToken: "",
+ subscription: null,
};
userAuthRepository.delete({ id: 1 });
+ userSubscriptionRepository.delete({ id: 1 });
this.sendSignOutEvent();
}
@@ -248,14 +271,14 @@ export class HydraApi {
private static async validateOptions(options?: HydraApiOptions) {
const needsAuth = options?.needsAuth == undefined || options.needsAuth;
- const needsCloud = options?.needsCloud === true;
+ const needsSubscription = options?.needsSubscription === true;
if (needsAuth) {
if (!this.isLoggedIn()) throw new UserNotLoggedInError();
await this.revalidateAccessTokenIfExpired();
}
- if (needsCloud) {
+ if (needsSubscription) {
if (!(await this.hasCloudSubscription())) {
throw new SubscriptionRequiredError();
}
diff --git a/src/main/services/user/get-user-data.ts b/src/main/services/user/get-user-data.ts
index 5035b296..c3ca6eb8 100644
--- a/src/main/services/user/get-user-data.ts
+++ b/src/main/services/user/get-user-data.ts
@@ -1,43 +1,79 @@
-import type { UserDetails } from "@types";
+import type { ProfileVisibility, UserDetails } from "@types";
import { HydraApi } from "../hydra-api";
import {
userAuthRepository,
userSubscriptionRepository,
} from "@main/repository";
import * as Sentry from "@sentry/electron/main";
+import { UserNotLoggedInError } from "@shared";
+import { logger } from "../logger";
export const getUserData = () => {
- return HydraApi.get(`/profile/me`).then(async (me) => {
- userAuthRepository.upsert(
- {
- id: 1,
- displayName: me.displayName,
- profileImageUrl: me.profileImageUrl,
- backgroundImageUrl: me.backgroundImageUrl,
- userId: me.id,
- },
- ["id"]
- );
-
- if (me.subscription) {
- await userSubscriptionRepository.upsert(
+ return HydraApi.get(`/profile/me`)
+ .then(async (me) => {
+ userAuthRepository.upsert(
{
id: 1,
- subscriptionId: me.subscription?.id || "",
- status: me.subscription?.status || "",
- planId: me.subscription?.plan.id || "",
- planName: me.subscription?.plan.name || "",
- expiresAt: me.subscription?.expiresAt || null,
- user: { id: 1 },
+ displayName: me.displayName,
+ profileImageUrl: me.profileImageUrl,
+ backgroundImageUrl: me.backgroundImageUrl,
+ userId: me.id,
},
["id"]
);
- } else {
- await userSubscriptionRepository.delete({ id: 1 });
- }
- Sentry.setUser({ id: me.id, username: me.username });
+ if (me.subscription) {
+ await userSubscriptionRepository.upsert(
+ {
+ id: 1,
+ subscriptionId: me.subscription?.id || "",
+ status: me.subscription?.status || "",
+ planId: me.subscription?.plan.id || "",
+ planName: me.subscription?.plan.name || "",
+ expiresAt: me.subscription?.expiresAt || null,
+ user: { id: 1 },
+ },
+ ["id"]
+ );
+ } else {
+ await userSubscriptionRepository.delete({ id: 1 });
+ }
- return me;
- });
+ Sentry.setUser({ id: me.id, username: me.username });
+
+ return me;
+ })
+ .catch(async (err) => {
+ if (err instanceof UserNotLoggedInError) {
+ return null;
+ }
+ logger.error("Failed to get logged user", err);
+ const loggedUser = await userAuthRepository.findOne({
+ where: { id: 1 },
+ relations: { subscription: true },
+ });
+
+ if (loggedUser) {
+ return {
+ ...loggedUser,
+ id: loggedUser.userId,
+ username: "",
+ bio: "",
+ profileVisibility: "PUBLIC" as ProfileVisibility,
+ subscription: loggedUser.subscription
+ ? {
+ id: loggedUser.subscription.subscriptionId,
+ status: loggedUser.subscription.status,
+ plan: {
+ id: loggedUser.subscription.planId,
+ name: loggedUser.subscription.planName,
+ },
+ expiresAt: loggedUser.subscription.expiresAt,
+ }
+ : null,
+ } as UserDetails;
+ }
+
+ return null;
+ });
};
diff --git a/src/renderer/src/pages/achievements/achievements-content.tsx b/src/renderer/src/pages/achievements/achievements-content.tsx
index aa93bc30..72dfe83f 100644
--- a/src/renderer/src/pages/achievements/achievements-content.tsx
+++ b/src/renderer/src/pages/achievements/achievements-content.tsx
@@ -42,6 +42,7 @@ interface AchievementSummaryProps {
function AchievementSummary({ user, isComparison }: AchievementSummaryProps) {
const { t } = useTranslation("achievement");
const { userDetails, hasActiveSubscription } = useUserDetails();
+ const { handleClickOpenCheckout } = useContext(gameDetailsContext);
const getProfileImage = (
user: Pick
@@ -90,7 +91,12 @@ function AchievementSummary({ user, isComparison }: AchievementSummaryProps) {
>
- {t("subscription_needed")}
+
{
+ const playAudio = useCallback(() => {
const audio = new Audio(achievementSound);
audio.volume = 0.2;
- audio.preload = "auto";
- return audio;
+ audio.play();
}, []);
useEffect(() => {
@@ -47,14 +46,14 @@ export function AchievementNotification() {
},
]);
- audio.play();
+ playAudio();
}
);
return () => {
unsubscribe();
};
- }, [audio]);
+ }, [playAudio]);
useEffect(() => {
const unsubscribe = window.electron.onAchievementUnlocked(
@@ -63,14 +62,14 @@ export function AchievementNotification() {
setAchievements((ach) => ach.concat(achievements));
- audio.play();
+ playAudio();
}
);
return () => {
unsubscribe();
};
- }, [audio]);
+ }, [playAudio]);
const hasAchievementsPending = achievements.length > 0;
diff --git a/src/shared/constants.ts b/src/shared/constants.ts
index f0f6d2f0..2d313abb 100644
--- a/src/shared/constants.ts
+++ b/src/shared/constants.ts
@@ -39,4 +39,5 @@ export enum Cracker {
_3dm = "3dm",
flt = "FLT",
rle = "RLE",
+ razor1911 = "RAZOR1911",
}
diff --git a/src/types/index.ts b/src/types/index.ts
index 41a51e40..7f970d63 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -238,7 +238,7 @@ export interface Subscription {
id: string;
status: SubscriptionStatus;
plan: { id: string; name: string };
- expiresAt: Date | null;
+ expiresAt: string | null;
}
export interface UserDetails {