Compare commits

..

85 Commits

Author SHA1 Message Date
Zamitto
ba9232b821 Merge branch 'main' into feature/accessibility-improvements 2024-11-01 09:19:02 -03:00
Zamitto
c8485adac5 Merge pull request #1184 from Sir-Kam/Sir-Kam-English-Translation-Modifications
Some checks failed
Release / build (ubuntu-latest) (push) Has been cancelled
Release / build (windows-latest) (push) Has been cancelled
[translation] Update en translation.json
2024-11-01 09:16:32 -03:00
Cameron W. A.
d623828fe5 Update translation.json
Fixed the English of some entries as they weren't quite right or didn't flow the best.
2024-10-31 22:06:18 -04:00
Zamitto
1d6db5b76b Merge pull request #1181 from expload233/main
Some checks are pending
Release / build (ubuntu-latest) (push) Waiting to run
Release / build (windows-latest) (push) Waiting to run
🎯Update Chinese Translation to fit new version
2024-10-31 17:53:43 -03:00
Zamitto
0b896b8b7c Merge branch 'main' into main 2024-10-31 17:52:08 -03:00
expload
dcb6eb9ba6 Update translation of new functions 2024-10-31 16:03:24 +00:00
expload
e43365e568 🎯Update&Complete Chinese Translation 2024-10-31 16:03:17 +00:00
Eight
760030841a Merge pull request #1177 from hydralauncher/feature/quality-of-life
Some checks are pending
Release / build (ubuntu-latest) (push) Waiting to run
Release / build (windows-latest) (push) Waiting to run
feature/add-start-minimized-option
2024-10-31 12:25:25 -03:00
Hachi-R
5fb08b39fa refactor: rename migration to be more descriptive 2024-10-31 11:39:18 -03:00
Hachi-R
43558f8c0e feat: prevent main window creation when --hidden arg is present 2024-10-31 11:32:20 -03:00
Hachi-R
554889fe7d refactor: remove launch app minimized logic 2024-10-31 09:26:55 -03:00
expload
25e1a15828 Merge branch 'hydralauncher:main' into main 2024-10-31 15:51:45 +08:00
Hachi-R
619090961b chore: refactor hidden startup logic and tray click event 2024-10-31 01:09:10 -03:00
Hachi-R
0ecd27d4d5 lint 2024-10-30 20:00:50 -03:00
Hachi-R
f585e343d9 feat: use argv to start app minimized on system launch 2024-10-30 19:59:37 -03:00
Hachi-R
0d68851cf4 fix: ensure both values are passed to object to prevent false outcome 2024-10-30 17:46:52 -03:00
Hachi-R
36813d5f86 chore: remove unnecessary log 2024-10-30 17:34:38 -03:00
Hachi-R
ad3b84b0ae lint 2024-10-30 15:47:47 -03:00
Hachi-R
9e020652c9 feat: implement launch minimized option 2024-10-30 15:47:42 -03:00
Hachi-R
7af56cd7cc feat: add option to start minimized in user settings 2024-10-30 15:47:33 -03:00
Hachi-R
6dd454a982 feat: add 'startMinimized' property to user preferences 2024-10-30 15:47:22 -03:00
cj do gta sander pegando fogo aaa aaa aaa
a432306b1d Merge branch 'main' into feature/accessibility-improvements 2024-10-30 01:16:04 -03:00
Zamitto
0a86ec89aa chore: bump version
Some checks failed
Release / build (ubuntu-latest) (push) Has been cancelled
Release / build (windows-latest) (push) Has been cancelled
2024-10-29 18:49:30 -03:00
cj do gta sander pegando fogo aaa aaa aaa
69d96cc290 Merge branch 'main' into feature/accessibility-improvements 2024-10-29 18:27:49 -03:00
Zamitto
238d207590 Merge pull request #1159 from hydralauncher/fix/remove-wine-prefix-not-null-on-pre-search
fix: remove wine prefix on pre search
2024-10-29 16:41:19 -03:00
Zamitto
98e2d2ec0d chore: update steam-games.json 2024-10-29 11:12:55 -03:00
Zamitto
717dab5c90 Merge pull request #1158 from Zormein/patch-2
Some checks are pending
Release / build (ubuntu-latest) (push) Waiting to run
Release / build (windows-latest) (push) Waiting to run
Update translation.json
2024-10-28 19:51:40 -03:00
Zamitto
d73a46aac2 feat: remove wine prefix on pre search 2024-10-28 19:50:16 -03:00
Zamitto
9f9a4eba18 feat: remove wine prefix on pre search 2024-10-28 18:12:17 -03:00
Zamitto
3bddd7e76b feat: remove wine prefix on pre search 2024-10-28 17:38:27 -03:00
Zormein
7cfc871be2 Update translation.json
Fix little grammar error
2024-10-28 21:25:01 +02:00
Zamitto
5705de7d7a chore: add FAQ to issue template
Some checks failed
Release / build (ubuntu-latest) (push) Has been cancelled
Release / build (windows-latest) (push) Has been cancelled
2024-10-27 10:56:32 -03:00
Zamitto
b6fb29ca2d fix: french translation
Some checks are pending
Release / build (ubuntu-latest) (push) Waiting to run
Release / build (windows-latest) (push) Waiting to run
2024-10-26 23:57:10 -03:00
expload
d094230614 merge with en translation 2024-10-26 14:44:17 +00:00
expload
742abb06ac Merge branch 'hydralauncher:main' into main 2024-10-26 21:57:39 +08:00
Zamitto
e89f459c78 Merge pull request #1127 from bernardofernandezz/patch-1
Some checks failed
Release / build (ubuntu-latest) (push) Has been cancelled
Release / build (windows-latest) (push) Has been cancelled
fix: not redirecting to readme with correct language
2024-10-25 18:20:16 -03:00
Zamitto
0dac4d5cf3 feat: remove silent auto install 2024-10-25 16:30:44 -03:00
Bernardo Fernandez
93fb26c89b fix: not redirecting to readme with correct language 2024-10-25 08:26:46 -03:00
Zamitto
446d6b75c0 chore: bump version
Some checks are pending
Release / build (ubuntu-latest) (push) Waiting to run
Release / build (windows-latest) (push) Waiting to run
2024-10-24 21:07:38 -03:00
Zamitto
8dd29c7461 feat: disable lottie loop on game page 2024-10-24 20:56:29 -03:00
Zamitto
0ad1a2e3fe fix: shortcut and animation on home
Some checks are pending
Release / build (ubuntu-latest) (push) Waiting to run
Release / build (windows-latest) (push) Waiting to run
2024-10-24 19:32:04 -03:00
cj-do-gta-sander
8f4919615f lint: how-long-to-beat-section.tsx 2024-10-24 14:53:36 -03:00
cj-do-gta-sander
a25a960235 feat: add aria labels to How Long to Beat and Achievements in game details sidebar 2024-10-24 14:52:54 -03:00
cj-do-gta-sander
c754710171 feat: enable tab navigation through carousel previews, replacing arrow buttons 2024-10-24 14:27:54 -03:00
cj-do-gta-sander
bf6ce2b465 feat: add ability to blur search bar with Escape key 2024-10-24 12:24:20 -03:00
cj-do-gta-sander
3adc8662dc feat: add keyboard shortcut to focus header search bar 2024-10-24 12:11:49 -03:00
cj-do-gta-sander
2a6346cb69 feat: add tooltip and screen reader support to sidebar resize button 2024-10-24 11:50:01 -03:00
cj-do-gta-sander
455016c1a7 lint: sidebar.tsx 2024-10-24 11:46:37 -03:00
cj-do-gta-sander
e0ec79b105 feat: add keyboard control for sidebar size adjustment 2024-10-24 11:45:41 -03:00
cj-do-gta-sander
ba7e4c979d feat: add tooltip and screen reader support to clear search button 2024-10-24 11:06:03 -03:00
cj-do-gta-sander
a54983c339 feat: add tabindex 0 to search bar icon for direct focus 2024-10-24 11:02:48 -03:00
cj-do-gta-sander
b754b1e052 feat: add tooltip and screen reader support to back button in header 2024-10-24 10:58:00 -03:00
cj-do-gta-sander
79763b6072 chore: remove redundancy in "View Profile" and "Game" button for screen readers 2024-10-24 10:49:01 -03:00
cj-do-gta-sander
3ff15d2d61 chore: remove redundancy in list semantic markup 2024-10-24 10:04:34 -03:00
cj-do-gta-sander
50303251a2 feat: add semantic roles and aria labels to achievement list 2024-10-24 09:57:11 -03:00
cj-do-gta-sander
012f872f60 chore: chore: remove unnecessary locale string and use locked status strings 2024-10-24 09:55:22 -03:00
cj-do-gta-sander
e9f68977fe feat: add aria label to achievement summary 2024-10-24 09:39:15 -03:00
cj-do-gta-sander
c5d4db0a1e chore: change locale param names to match with args 2024-10-24 09:37:51 -03:00
cj-do-gta-sander
3b02a3c43f chore: move aria-labels strings to their respective pages in locales 2024-10-24 09:27:39 -03:00
cj-do-gta-sander
ab7625a314 feat: add aria-label strings to locales for achievements page 2024-10-24 09:03:48 -03:00
Zamitto
3c03d5ce16 Merge pull request #1113 from SoloQTKiller/correcao_traducao
Some checks are pending
Release / build (ubuntu-latest) (push) Waiting to run
Release / build (windows-latest) (push) Waiting to run
Translation Correction
2024-10-23 22:58:47 -03:00
SoloQTKiller
8d8b714c68 Correçao de Tradução 2024-10-23 22:29:26 -03:00
Zamitto
7d79048a25 Merge pull request #1094 from hydralauncher/feat/refactor-achievements-files
feat: refactor achievements file
2024-10-23 21:52:27 -03:00
Zamitto
4a3ba43dae feat: remove uneeded logs 2024-10-23 21:14:04 -03:00
Zamitto
dc413736e8 fix: sign in to see achievements overlay 2024-10-23 20:17:08 -03:00
Zamitto
2d98addd02 Merge branch 'main' into feat/refactor-achievements-files 2024-10-23 13:31:22 -03:00
Zamitto
b85e712d8c chore: bump version 2024-10-23 13:24:09 -03:00
Zamitto
582c276e95 fix: N/A problem on repacks 2024-10-23 13:23:44 -03:00
Zamitto
430b07eb89 Merge pull request #1096 from Zormein/patch-1
Some checks are pending
Release / build (ubuntu-latest) (push) Waiting to run
Release / build (windows-latest) (push) Waiting to run
Update Estonian translation
2024-10-23 06:32:04 -03:00
Zamitto
b46f46bc45 Merge branch 'main' into feat/refactor-achievements-files 2024-10-23 06:24:03 -03:00
Zamitto
fd41ec5070 Merge branch 'main' into patch-1 2024-10-23 06:23:35 -03:00
Zamitto
25d0f77c1d Merge pull request #1095 from Lianela/main
feat: new strings translated to spanish
2024-10-23 06:19:05 -03:00
Zormein
c5b2a8242c Update Estonian translation
Added new translations & removed duplicate lines.
2024-10-23 09:16:27 +03:00
Lianela
8b7ce6b062 fix: comma... (second one) 2024-10-22 23:19:00 -06:00
Lianela
db58ff0ba3 fix: comma...
(this is akward from my side)
2024-10-22 23:18:28 -06:00
Lianela
e951e11e62 Update translation.json 2024-10-22 23:09:47 -06:00
Lianela
0b854eda7c feat: new strings translated to spanish 2024-10-22 23:08:48 -06:00
Zamitto
648083fbf4 chore: bump version
Some checks are pending
Release / build (ubuntu-latest) (push) Waiting to run
Release / build (windows-latest) (push) Waiting to run
2024-10-23 00:07:50 -03:00
Zamitto
09316dac9e fix: isPortableVersion 2024-10-23 00:07:45 -03:00
Zamitto
670df826af chore: bump version 2024-10-23 00:06:54 -03:00
Zamitto
912f9611ea fix: isPortableVersion 2024-10-22 23:47:35 -03:00
Zamitto
d8254353b5 feat: refactor achievements file 2024-10-22 23:24:22 -03:00
Zamitto
73185e7cbc fix: small adjustment 2024-10-22 22:33:23 -03:00
Zamitto
c36c940a79 fix: subscription 2024-10-22 22:25:35 -03:00
expload
2edb96ba5b Create pull-request-template.md 2024-07-10 14:16:30 +08:00
43 changed files with 506 additions and 225 deletions

View File

@@ -1,5 +1,5 @@
name: Bug Report name: Bug Report
description: Create a report to help us improve. Write in English, please. description: Create a report to help us improve. Write in English.
title: "[BUG] Write a title for your bug" title: "[BUG] Write a title for your bug"
labels: ["bug"] labels: ["bug"]
body: body:
@@ -61,3 +61,5 @@ body:
required: true required: true
- label: I am aware that Hydra team does not offer any support or help regarding the downloaded games. - label: I am aware that Hydra team does not offer any support or help regarding the downloaded games.
required: true required: true
- label: I have read the [Frequently Asked Questions (FAQ)](https://github.com/hydralauncher/hydra/wiki/FAQ).
required: true

12
.github/pull-request-template.md vendored Normal file
View File

@@ -0,0 +1,12 @@
<!-- Please be sure to add one of the labels in the right hand side Labels option before creating a PR: [feature], [fix], [documentation],[translation]. This will allow Actions to automatically categorize PRs when generating Releases. -->
**When submitting this pull request, I confirm the following (please check the boxes):**
- [ ] I have read and understood the [Contributor Guidelines](https://github.com/hydralauncher/hydra?tab=readme-ov-file#ways-you-can-contribute).
- [ ] I have checked that there are no duplicate pull requests related to this request.
- [ ] I have considered, and confirm that this submission is valuable to others.
- [ ] I accept that this submission may not be used and the pull request may be closed at the discretion of the maintainers.
**Fill in the PR content:**
-

View File

@@ -13,19 +13,19 @@
[![build](https://img.shields.io/github/actions/workflow/status/hydralauncher/hydra/build.yml)](https://github.com/hydralauncher/hydra/actions) [![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) [![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) [![pt-BR](https://img.shields.io/badge/lang-pt--BR-green.svg)](./docs/README.pt-BR.md)
[![en](https://img.shields.io/badge/lang-en-red.svg)](./README.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) [![ru](https://img.shields.io/badge/lang-ru-yellow.svg)](./docs/README.ru.md)
[![uk-UA](https://img.shields.io/badge/lang-uk--UA-blue)](./README.uk-UA.md) [![uk-UA](https://img.shields.io/badge/lang-uk--UA-blue)](./docs/README.uk-UA.md)
[![be](https://img.shields.io/badge/lang-be-orange)](./README.be.md) [![be](https://img.shields.io/badge/lang-be-orange)](./docs/README.be.md)
[![es](https://img.shields.io/badge/lang-es-red)](./README.es.md) [![es](https://img.shields.io/badge/lang-es-red)](./docs/README.es.md)
[![fr](https://img.shields.io/badge/lang-fr-blue)](./README.fr.md) [![fr](https://img.shields.io/badge/lang-fr-blue)](./docs/README.fr.md)
[![de](https://img.shields.io/badge/lang-de-black)](./README.de.md) [![de](https://img.shields.io/badge/lang-de-black)](./docs/README.de.md)
[![ita](https://img.shields.io/badge/lang-it-red)](./README.it.md) [![ita](https://img.shields.io/badge/lang-it-red)](./docs/README.it.md)
[![cs](https://img.shields.io/badge/lang-cs-purple)](./README.cs.md) [![cs](https://img.shields.io/badge/lang-cs-purple)](./docs/README.cs.md)
[![da](https://img.shields.io/badge/lang-da-red)](./README.da.md) [![da](https://img.shields.io/badge/lang-da-red)](./docs/README.da.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](./README.nb.md) [![nb](https://img.shields.io/badge/lang-nb-blue)](./docs/README.nb.md)
[![ee](https://img.shields.io/badge/lang-et-blue.svg)](./README.et.md) [![ee](https://img.shields.io/badge/lang-et-blue.svg)](./docs/README.et.md)
![Hydra Catalogue](./docs/screenshot.png) ![Hydra Catalogue](./docs/screenshot.png)

View File

@@ -1,6 +1,6 @@
{ {
"name": "hydralauncher", "name": "hydralauncher",
"version": "3.0.0", "version": "3.0.4",
"description": "Hydra", "description": "Hydra",
"main": "./out/main/index.js", "main": "./out/main/index.js",
"author": "Los Broxas", "author": "Los Broxas",

File diff suppressed because one or more lines are too long

View File

@@ -25,7 +25,9 @@
"queued": "{{title}} (Queued)", "queued": "{{title}} (Queued)",
"game_has_no_executable": "Game has no executable selected", "game_has_no_executable": "Game has no executable selected",
"sign_in": "Sign in", "sign_in": "Sign in",
"friends": "Friends" "friends": "Friends",
"aria_view_profile": "View profile",
"resize_sidebar": "Resize sidebar"
}, },
"header": { "header": {
"search": "Search games", "search": "Search games",
@@ -35,7 +37,9 @@
"search_results": "Search results", "search_results": "Search results",
"settings": "Settings", "settings": "Settings",
"version_available_install": "Version {{version}} available. Click here to restart and install.", "version_available_install": "Version {{version}} available. Click here to restart and install.",
"version_available_download": "Version {{version}} available. Click here to download." "version_available_download": "Version {{version}} available. Click here to download.",
"back": "Back",
"clear_search": "Clear search"
}, },
"bottom_panel": { "bottom_panel": {
"no_downloads_in_progress": "No downloads in progress", "no_downloads_in_progress": "No downloads in progress",
@@ -130,8 +134,9 @@
"download": "Download", "download": "Download",
"executable_path_in_use": "Executable already in use by \"{{game}}\"", "executable_path_in_use": "Executable already in use by \"{{game}}\"",
"warning": "Warning:", "warning": "Warning:",
"hydra_needs_to_remain_open": "for this download, Hydra needs to remain open util its conclusion. In case Hydra closes before the conclusion, you will lose your progress.", "hydra_needs_to_remain_open": "for this download, Hydra needs to remain open util it's completed. If Hydra closes before completing, you will lose your progress.",
"achievements": "Achievements", "achievements": "Achievements",
"achievement": "Achievement",
"achievements_count": "Achievements {{unlockedCount}}/{{achievementsCount}}", "achievements_count": "Achievements {{unlockedCount}}/{{achievementsCount}}",
"cloud_save": "Cloud save", "cloud_save": "Cloud save",
"cloud_save_description": "Save your progress in the cloud and continue playing on any device", "cloud_save_description": "Save your progress in the cloud and continue playing on any device",
@@ -215,7 +220,7 @@
"language": "Language", "language": "Language",
"real_debrid_api_token": "API Token", "real_debrid_api_token": "API Token",
"enable_real_debrid": "Enable Real-Debrid", "enable_real_debrid": "Enable Real-Debrid",
"real_debrid_description": "Real-Debrid is an unrestricted downloader that allows you to download files instantly and at the best of your Internet speed.", "real_debrid_description": "Real-Debrid is an unrestricted downloader that allows you to quickly download files, only limited by your internet speed.",
"real_debrid_invalid_token": "Invalid API token", "real_debrid_invalid_token": "Invalid API token",
"real_debrid_api_token_hint": "You can get your API token <0>here</0>", "real_debrid_api_token_hint": "You can get your API token <0>here</0>",
"real_debrid_free_account_error": "The account \"{{username}}\" is a free account. Please subscribe to Real-Debrid", "real_debrid_free_account_error": "The account \"{{username}}\" is a free account. Please subscribe to Real-Debrid",
@@ -230,7 +235,7 @@
"download_count_one": "{{countFormatted}} download option", "download_count_one": "{{countFormatted}} download option",
"download_count_other": "{{countFormatted}} download options", "download_count_other": "{{countFormatted}} download options",
"download_source_url": "Download source URL", "download_source_url": "Download source URL",
"add_download_source_description": "Insert the URL containing the .json file", "add_download_source_description": "Insert the URL of the .json file",
"download_source_up_to_date": "Up-to-date", "download_source_up_to_date": "Up-to-date",
"download_source_errored": "Errored", "download_source_errored": "Errored",
"sync_download_sources": "Sync sources", "sync_download_sources": "Sync sources",
@@ -249,11 +254,12 @@
"profile_visibility": "Profile visibility", "profile_visibility": "Profile visibility",
"profile_visibility_description": "Choose who can see your profile and library", "profile_visibility_description": "Choose who can see your profile and library",
"required_field": "This field is required", "required_field": "This field is required",
"source_already_exists": "This source has been already added", "source_already_exists": "This source has already been added",
"must_be_valid_url": "The source must be a valid URL", "must_be_valid_url": "The source must be a valid URL",
"blocked_users": "Blocked users", "blocked_users": "Blocked users",
"user_unblocked": "User has been unblocked", "user_unblocked": "User has been unblocked",
"enable_achievement_notifications": "When an achievement in unlocked" "enable_achievement_notifications": "When an achievement is unlocked",
"launch_minimized": "Launch Hydra minimized"
}, },
"notifications": { "notifications": {
"download_complete": "Download complete", "download_complete": "Download complete",
@@ -288,7 +294,7 @@
"amount_hours": "{{amount}} hours", "amount_hours": "{{amount}} hours",
"amount_minutes": "{{amount}} minutes", "amount_minutes": "{{amount}} minutes",
"last_time_played": "Last played {{period}}", "last_time_played": "Last played {{period}}",
"activity": "Recent activity", "activity": "Recent Activity",
"library": "Library", "library": "Library",
"total_play_time": "Total playtime: {{amount}}", "total_play_time": "Total playtime: {{amount}}",
"no_recent_activity_title": "Hmmm… nothing here", "no_recent_activity_title": "Hmmm… nothing here",
@@ -327,7 +333,7 @@
"user_block_modal_text": "This will block {{displayName}}", "user_block_modal_text": "This will block {{displayName}}",
"blocked_users": "Blocked users", "blocked_users": "Blocked users",
"unblock": "Unblock", "unblock": "Unblock",
"no_friends_added": "You still don't have added friends", "no_friends_added": "You have no added friends",
"pending": "Pending", "pending": "Pending",
"no_pending_invites": "You have no pending invites", "no_pending_invites": "You have no pending invites",
"no_blocked_users": "You have no blocked users", "no_blocked_users": "You have no blocked users",
@@ -357,11 +363,13 @@
}, },
"achievement": { "achievement": {
"achievement_unlocked": "Achievement unlocked", "achievement_unlocked": "Achievement unlocked",
"achievement_locked": "Achievement locked",
"user_achievements": "{{displayName}}'s Achievements", "user_achievements": "{{displayName}}'s Achievements",
"your_achievements": "Your Achievements", "your_achievements": "Your Achievements",
"unlocked_at": "Unlocked at:", "unlocked_at": "Unlocked at:",
"subscription_needed": "A Hydra Cloud subscription is required 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" "new_achievements_unlocked": "Unlocked {{achievementCount}} new achievements from {{gameCount}} games",
"aria_achievement_summary": "{{userDisplayName}} achievements for {{gameTitle}}, {{userAchievementCount}} unlocked of {{userTotalAchievementCount}}, {{percentage}} completed"
}, },
"tour": { "tour": {
"subscription_tour_title": "Hydra Cloud Subscription", "subscription_tour_title": "Hydra Cloud Subscription",

View File

@@ -9,7 +9,8 @@
"no_results": "Sin resultados encontrados", "no_results": "Sin resultados encontrados",
"start_typing": "Empieza a escribir para buscar...", "start_typing": "Empieza a escribir para buscar...",
"hot": "Popular Ahora", "hot": "Popular Ahora",
"weekly": "📅 Mejores juegos de la semana" "weekly": "📅 Mejores juegos de la semana",
"achievements": "🏆 Juegos para completar"
}, },
"sidebar": { "sidebar": {
"catalogue": "Catálogo", "catalogue": "Catálogo",
@@ -160,7 +161,11 @@
"no_download_option_info": "Sin información disponible", "no_download_option_info": "Sin información disponible",
"backup_deletion_failed": "La eliminación de la copia de seguridad falló", "backup_deletion_failed": "La eliminación de la copia de seguridad falló",
"max_number_of_artifacts_reached": "Número máximo de copias de seguridad de este juego alcanzadas", "max_number_of_artifacts_reached": "Número máximo de copias de seguridad de este juego alcanzadas",
"achievements_not_sync": "Tus logros no están sincronizadas" "achievements_not_sync": "Tus logros no están sincronizados",
"manage_files_description": "Gestiona los archivos que serán respaldados y restaurados",
"select_folder": "Seleccionar carpeta",
"backup_from": "Copia de seguridad de {{date}}",
"custom_backup_location_set": "Se configuró la carpeta de copia de seguridad"
}, },
"activation": { "activation": {
"title": "Activar Hydra", "title": "Activar Hydra",
@@ -347,7 +352,8 @@
"profile_reported": "Perfil reportado", "profile_reported": "Perfil reportado",
"your_friend_code": "Tu código de amigo:", "your_friend_code": "Tu código de amigo:",
"upload_banner": "Subir un banner", "upload_banner": "Subir un banner",
"uploading_banner": "Subiendo banner…" "uploading_banner": "Subiendo banner…",
"background_image_updated": "Imagen de fondo actualizada"
}, },
"achievement": { "achievement": {
"achievement_unlocked": "Logro desbloqueado", "achievement_unlocked": "Logro desbloqueado",

View File

@@ -9,7 +9,8 @@
"no_results": "Tulemusi ei leitud", "no_results": "Tulemusi ei leitud",
"start_typing": "Alusta otsimiseks kirjutamist...", "start_typing": "Alusta otsimiseks kirjutamist...",
"hot": "Praegu kuum", "hot": "Praegu kuum",
"weekly": "📅 Nädala top mängud" "weekly": "📅 Nädala top mängud",
"achievements": "🏆 Mängud, mida läbida"
}, },
"sidebar": { "sidebar": {
"catalogue": "Kataloog", "catalogue": "Kataloog",
@@ -160,95 +161,11 @@
"no_download_option_info": "Info pole saadaval", "no_download_option_info": "Info pole saadaval",
"backup_deletion_failed": "Varunduse kustutamine ebaõnnestus", "backup_deletion_failed": "Varunduse kustutamine ebaõnnestus",
"max_number_of_artifacts_reached": "Selle mängu varunduste maksimaalne arv on saavutatud", "max_number_of_artifacts_reached": "Selle mängu varunduste maksimaalne arv on saavutatud",
"achievements_not_sync": "Sinu saavutused pole sünkroniseeritud" "achievements_not_sync": "Sinu saavutused pole sünkroniseeritud",
}, "manage_files_description": "Hallake, millised failid varundatakse ja taastatakse",
"activation": { "select_folder": "Vali kaust",
"title": "Aktiveeri Hydra", "backup_from": "Varundamine kuupäevast {{date}}",
"installation_id": "Installatsiooni ID:", "custom_backup_location_set": "Kohandatud varundamise asukoht määratud"
"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>siit</0>",
"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": { "activation": {
"title": "Aktiveeri Hydra", "title": "Aktiveeri Hydra",
@@ -395,7 +312,7 @@
"sending": "Saatmine", "sending": "Saatmine",
"friend_request_sent": "Sõbrakutse saadetud", "friend_request_sent": "Sõbrakutse saadetud",
"friends": "Sõbrad", "friends": "Sõbrad",
"friends_list": "Sõbrade nimekiri", "friends_list": "Sõprade nimekiri",
"user_not_found": "Kasutajat ei leitud", "user_not_found": "Kasutajat ei leitud",
"block_user": "Blokeeri kasutaja", "block_user": "Blokeeri kasutaja",
"add_friend": "Lisa sõbraks", "add_friend": "Lisa sõbraks",
@@ -435,7 +352,8 @@
"profile_reported": "Profiilist teatatud", "profile_reported": "Profiilist teatatud",
"your_friend_code": "Sinu sõbrakood:", "your_friend_code": "Sinu sõbrakood:",
"upload_banner": "Lae üles bänner", "upload_banner": "Lae üles bänner",
"uploading_banner": "Bänneri üleslaadimine…" "uploading_banner": "Bänneri üleslaadimine…",
"background_image_updated": "Bänner uuendatud"
}, },
"achievement": { "achievement": {
"achievement_unlocked": "Saavutus avatud", "achievement_unlocked": "Saavutus avatud",

View File

@@ -57,14 +57,14 @@
"remove_from_library": "Supprimer de la bibliothèque", "remove_from_library": "Supprimer de la bibliothèque",
"no_downloads": "Aucun téléchargement disponible", "no_downloads": "Aucun téléchargement disponible",
"next_suggestion": "Suggestion suivante", "next_suggestion": "Suggestion suivante",
"play_time": "Joué pour {{montant}}", "play_time": "Joué pour {{amount}}",
"install": "Installer", "install": "Installer",
"play": "Jouer", "play": "Jouer",
"not_played_yet": "Vous n'avez pas encore joué à {{title}}", "not_played_yet": "Vous n'avez pas encore joué à {{title}}",
"close": "Fermer", "close": "Fermer",
"deleting": "Suppression du programme d'installation…", "deleting": "Suppression du programme d'installation…",
"playing_now": "Jeu en cours", "playing_now": "Jeu en cours",
"last_time_played": "Dernièrement joué {{période}}" "last_time_played": "Dernièrement joué {{period}}"
}, },
"activation": { "activation": {
"title": "Activer Hydra", "title": "Activer Hydra",

View File

@@ -25,7 +25,9 @@
"queued": "{{title}} (Na fila)", "queued": "{{title}} (Na fila)",
"game_has_no_executable": "Jogo não possui executável selecionado", "game_has_no_executable": "Jogo não possui executável selecionado",
"sign_in": "Login", "sign_in": "Login",
"friends": "Amigos" "friends": "Amigos",
"aria_view_profile": "Ver perfil",
"resize_sidebar": "Redimensionar barra lateral"
}, },
"header": { "header": {
"search": "Buscar jogos", "search": "Buscar jogos",
@@ -35,7 +37,9 @@
"settings": "Ajustes", "settings": "Ajustes",
"home": "Início", "home": "Início",
"version_available_install": "Versão {{version}} disponível. Clique aqui para reiniciar e instalar.", "version_available_install": "Versão {{version}} disponível. Clique aqui para reiniciar e instalar.",
"version_available_download": "Versão {{version}} disponível. Clique aqui para fazer o download." "version_available_download": "Versão {{version}} disponível. Clique aqui para fazer o download.",
"back": "Voltar",
"clear_search": "Limpar busca"
}, },
"bottom_panel": { "bottom_panel": {
"no_downloads_in_progress": "Sem downloads em andamento", "no_downloads_in_progress": "Sem downloads em andamento",
@@ -128,9 +132,10 @@
"warning": "Aviso:", "warning": "Aviso:",
"hydra_needs_to_remain_open": "para este download, o Hydra precisa ficar aberto até a conclusão. Caso o Hydra encerre antes da conclusão, perderá seu progresso.", "hydra_needs_to_remain_open": "para este download, o Hydra precisa ficar aberto até a conclusão. Caso o Hydra encerre antes da conclusão, perderá seu progresso.",
"achievements": "Conquistas", "achievements": "Conquistas",
"achievement": "Conquista",
"achievements_count": "Conquistas ({{unlockedCount}}/{{achievementsCount}})", "achievements_count": "Conquistas ({{unlockedCount}}/{{achievementsCount}})",
"cloud_save": "Salvamento em nuvem", "cloud_save": "Salvamento em nuvem",
"cloud_save_description": "Matenha seu progresso na nuvem e continue de onde parou em qualquer dispositivo", "cloud_save_description": "Mantenha seu progresso na nuvem e continue de onde parou em qualquer dispositivo",
"backups": "Backups", "backups": "Backups",
"install_backup": "Restaurar", "install_backup": "Restaurar",
"delete_backup": "Apagar", "delete_backup": "Apagar",
@@ -190,7 +195,7 @@
"install": "Instalar", "install": "Instalar",
"download_in_progress": "Baixando agora", "download_in_progress": "Baixando agora",
"queued_downloads": "Na fila", "queued_downloads": "Na fila",
"downloads_completed": "Completo", "downloads_completed": "Concluído",
"queued": "Na fila", "queued": "Na fila",
"no_downloads_title": "Nada por aqui…", "no_downloads_title": "Nada por aqui…",
"no_downloads_description": "Você ainda não baixou nada pelo Hydra, mas nunca é tarde para começar.", "no_downloads_description": "Você ainda não baixou nada pelo Hydra, mas nunca é tarde para começar.",
@@ -249,7 +254,8 @@
"must_be_valid_url": "A fonte deve ser uma URL válida", "must_be_valid_url": "A fonte deve ser uma URL válida",
"blocked_users": "Usuários bloqueados", "blocked_users": "Usuários bloqueados",
"user_unblocked": "Usuário desbloqueado", "user_unblocked": "Usuário desbloqueado",
"enable_achievement_notifications": "Quando uma conquista é desbloqueada" "enable_achievement_notifications": "Quando uma conquista é desbloqueada",
"launch_minimized": "Iniciar o Hydra minimizado"
}, },
"notifications": { "notifications": {
"download_complete": "Download concluído", "download_complete": "Download concluído",
@@ -355,11 +361,13 @@
}, },
"achievement": { "achievement": {
"achievement_unlocked": "Conquista desbloqueada", "achievement_unlocked": "Conquista desbloqueada",
"achievement_locked": "Conquista bloqueada",
"your_achievements": "Suas Conquistas", "your_achievements": "Suas Conquistas",
"user_achievements": "Conquistas de {{displayName}}", "user_achievements": "Conquistas de {{displayName}}",
"unlocked_at": "Desbloqueado em:", "unlocked_at": "Desbloqueado em:",
"subscription_needed": "Você precisa de uma assinatura Hydra Cloud para visualizar este conteúdo", "subscription_needed": "Você precisa de uma assinatura Hydra Cloud para visualizar este conteúdo",
"new_achievements_unlocked": "{{achievementCount}} novas conquistas de {{gameCount}} jogos" "new_achievements_unlocked": "{{achievementCount}} novas conquistas de {{gameCount}} jogos",
"aria_achievement_summary": "Conquistas de {{userDisplayName}} em {{gameTitle}}, {{userAchievementCount}} desbloqueadas de {{userTotalAchievementCount}}, {{percentage}} concluídas"
}, },
"tour": { "tour": {
"subscription_tour_title": "Assinatura Hydra Cloud", "subscription_tour_title": "Assinatura Hydra Cloud",

View File

@@ -6,7 +6,11 @@
"home": { "home": {
"featured": "特色推荐", "featured": "特色推荐",
"surprise_me": "向我推荐", "surprise_me": "向我推荐",
"no_results": "没有找到结果" "no_results": "没有找到结果",
"start_typing": "键入以开始搜素...",
"hot": "当下热门",
"weekly": "📅本周热门游戏",
"achievements": "🏆尝试击败"
}, },
"sidebar": { "sidebar": {
"catalogue": "游戏目录", "catalogue": "游戏目录",
@@ -20,7 +24,8 @@
"home": "主页", "home": "主页",
"queued": "{{title}} (已加入下载队列)", "queued": "{{title}} (已加入下载队列)",
"game_has_no_executable": "未选择游戏的可执行文件", "game_has_no_executable": "未选择游戏的可执行文件",
"sign_in": "登入" "sign_in": "登入",
"friends": "好友"
}, },
"header": { "header": {
"search": "搜索游戏", "search": "搜索游戏",
@@ -36,17 +41,18 @@
"no_downloads_in_progress": "没有正在进行的下载", "no_downloads_in_progress": "没有正在进行的下载",
"downloading_metadata": "正在下载{{title}}的元数据…", "downloading_metadata": "正在下载{{title}}的元数据…",
"downloading": "正在下载{{title}}… ({{percentage}}完成) - 剩余时间{{eta}} - 速度{{speed}}", "downloading": "正在下载{{title}}… ({{percentage}}完成) - 剩余时间{{eta}} - 速度{{speed}}",
"calculating_eta": "正在下载 {{title}}… (已完成{{percentage}}.) - 正在计算剩余时间..." "calculating_eta": "正在下载 {{title}}… (已完成{{percentage}}.) - 正在计算剩余时间...",
"checking_files": "正在校验 {{title}} 的文件... ({{percentage}} 已完成)"
}, },
"catalogue": { "catalogue": {
"next_page": "下一页", "next_page": "下一页",
"previous_page": "上一页" "previous_page": "上一页"
}, },
"game_details": { "game_details": {
"open_download_options": "打开下载选项", "open_download_options": "打开下载菜单",
"download_options_zero": "无下载项", "download_options_zero": "无下载项",
"download_options_one": "{{count}}个下载项", "download_options_one": "{{count}}个下载项",
"download_options_other": "{{count}}个下载项", "download_options_other": "{{count}}个下载项",
"updated_at": "更新于{{updated_at}}", "updated_at": "更新于{{updated_at}}",
"install": "安装", "install": "安装",
"resume": "恢复", "resume": "恢复",
@@ -55,11 +61,13 @@
"remove": "移除", "remove": "移除",
"space_left_on_disk": "磁盘剩余空间{{space}}", "space_left_on_disk": "磁盘剩余空间{{space}}",
"eta": "预计完成时间{{eta}}", "eta": "预计完成时间{{eta}}",
"calculating_eta": "正在计算剩余时间…",
"downloading_metadata": "正在下载元数据…", "downloading_metadata": "正在下载元数据…",
"filter": "筛选重打包", "filter": "筛选重打包",
"requirements": "配置要求", "requirements": "配置要求",
"minimum": "最低要求", "minimum": "最低要求",
"recommended": "推荐要求", "recommended": "推荐要求",
"paused": "已暂停",
"release_date": "发布于{{date}}", "release_date": "发布于{{date}}",
"publisher": "发行商{{publisher}}", "publisher": "发行商{{publisher}}",
"hours": "小时", "hours": "小时",
@@ -80,15 +88,18 @@
"playing_now": "正在游戏中", "playing_now": "正在游戏中",
"change": "更改", "change": "更改",
"repacks_modal_description": "选择您想要下载的重打包", "repacks_modal_description": "选择您想要下载的重打包",
"select_folder_hint": "要更改默认文件夹,请访问", "select_folder_hint": "要更改默认文件夹,请访问<0>设置</0>",
"download_now": "立即下载", "download_now": "立即下载",
"no_shop_details": "无法检索商店详细信息.",
"download_options": "下载选项",
"download_path": "下载路径",
"previous_screenshot": "上一张截图", "previous_screenshot": "上一张截图",
"next_screenshot": "下一张截图", "next_screenshot": "下一张截图",
"screenshot": "截图 {{number}}", "screenshot": "截图 {{number}}",
"open_screenshot": "打开截图 {{number}}", "open_screenshot": "打开截图 {{number}}",
"download_settings": "下载设置", "download_settings": "下载设置",
"downloader": "下载器", "downloader": "下载器",
"select_executable": "选择", "select_executable": "选择可执行文件",
"no_executable_selected": "没有可执行文件被指定", "no_executable_selected": "没有可执行文件被指定",
"open_folder": "打开目录", "open_folder": "打开目录",
"open_download_location": "查看已下载的文件", "open_download_location": "查看已下载的文件",
@@ -107,7 +118,54 @@
"download_paused": "下载暂停", "download_paused": "下载暂停",
"last_downloaded_option": "上次下载的选项", "last_downloaded_option": "上次下载的选项",
"create_shortcut_success": "成功创建快捷方式", "create_shortcut_success": "成功创建快捷方式",
"create_shortcut_error": "创建快捷方式出错" "create_shortcut_error": "创建快捷方式出错",
"nsfw_content_title": "本游戏包含不适合展示的内容",
"nsfw_content_description": "{{title}}包含可能不适合所有年龄段的内容。您确定要继续吗?",
"allow_nsfw_content": "继续",
"refuse_nsfw_content": "返回",
"stats": "统计数据",
"download_count": "下载量",
"player_count": "活跃玩家",
"download_error": "此下载选项不可用",
"download": "下载",
"executable_path_in_use": "可执行文件已经被以下游戏 \"{{game}}\" 使用",
"warning": "警告:",
"hydra_needs_to_remain_open": "对于此次下载,Hydra必须保持开启直至其完成。若海德拉在完成前关闭,您的进度将丢失。",
"achievements": "成就",
"achievements_count": "成就 {{unlockedCount}}/{{achievementsCount}}",
"cloud_save": "云存档",
"cloud_save_description": "将您的进度保存在云端,便可在任何设备上继续游戏。",
"backups": "备份",
"install_backup": "安装",
"delete_backup": "删除",
"create_backup": "新备份",
"last_backup_date": "最后一次备份于{{date}}",
"no_backup_preview": "未找到此游戏标题的存档",
"restoring_backup": "正在恢复备份({{progress}}已完成)…",
"uploading_backup": "上传备份中…",
"no_backups": "您尚未为这款游戏创建任何备份",
"backup_uploaded": "备份已上传",
"backup_deleted": "备份已删除",
"backup_restored": "备份已恢复",
"see_all_achievements": "查看所有成就",
"sign_in_to_see_achievements": "登入以查看所有成就",
"mapping_method_automatic": "自动",
"mapping_method_manual": "常规",
"mapping_method_label": "索引类型",
"files_automatically_mapped": "文件已自动索引",
"no_backups_created": "没有为此游戏创建过备份",
"manage_files": "管理文件",
"loading_save_preview": "正在查找要保存的游戏…",
"wine_prefix": "Wine 前置",
"wine_prefix_description": "运行该游戏所用的 Wine 前置",
"no_download_option_info": "无可用信息",
"backup_deletion_failed": "删除备份失败",
"max_number_of_artifacts_reached": "已达到该游戏备份上限",
"achievements_not_sync": "你的成就未同步",
"manage_files_description": "管理哪些文件要备份和恢复",
"select_folder": "选择文件夹",
"backup_from": "{{date}} 时备份",
"custom_backup_location_set": "自定义备份文件位置"
}, },
"activation": { "activation": {
"title": "激活 Hydra", "title": "激活 Hydra",
@@ -124,6 +182,7 @@
"paused": "已暂停", "paused": "已暂停",
"verifying": "正在验证…", "verifying": "正在验证…",
"completed": "已完成", "completed": "已完成",
"removed": "未下载",
"cancel": "取消", "cancel": "取消",
"filter": "筛选已下载游戏", "filter": "筛选已下载游戏",
"remove": "移除", "remove": "移除",
@@ -138,7 +197,8 @@
"downloads_completed": "已完成", "downloads_completed": "已完成",
"queued": "下载列表", "queued": "下载列表",
"no_downloads_title": "空空如也", "no_downloads_title": "空空如也",
"no_downloads_description": "你还未使用Hydra下载任何游戏,但什么时候开始,都为时不晚。" "no_downloads_description": "你还未使用Hydra下载任何游戏,但什么时候开始,都为时不晚。",
"checking_files": "正在校验文件…"
}, },
"settings": { "settings": {
"downloads_path": "下载路径", "downloads_path": "下载路径",
@@ -181,14 +241,49 @@
"found_download_option_zero": "未找到下载选项", "found_download_option_zero": "未找到下载选项",
"found_download_option_one": "找到 {{countFormatted}} 个下载选项", "found_download_option_one": "找到 {{countFormatted}} 个下载选项",
"found_download_option_other": "找到 {{countFormatted}} 个下载选项", "found_download_option_other": "找到 {{countFormatted}} 个下载选项",
"import": "导入" "import": "导入",
"public": "公开",
"private": "私密",
"friends_only": "仅限朋友",
"privacy": "隐私",
"profile_visibility": "资料可见性",
"profile_visibility_description": "选择谁可以查看您的个人资料和资料库",
"required_field": "该字段为必填字段",
"source_already_exists": "已添加此来源",
"must_be_valid_url": "来源必须是有效的 URL",
"blocked_users": "已屏蔽用户",
"user_unblocked": "用户已经被屏蔽",
"enable_achievement_notifications": "当成就解锁时"
}, },
"modal": { "notifications": {
"close": "关闭按钮" "download_complete": "下载完成",
"game_ready_to_install": "{{title}} 已准备就绪",
"repack_list_updated": "重打包列表已更新",
"repack_count_one": "{{count}} 重打包已添加",
"repack_count_other": "{{count}} 重打包已添加",
"new_update_available": "版本 {{version}} 可用",
"restart_to_install_update": "重启 Hydra 以安装更新",
"notification_achievement_unlocked_title": "{{game}} 的成绩已解锁",
"notification_achievement_unlocked_body": "{{achievement}} 和其他 {{count}} 已解锁"
},
"system_tray": {
"open": "打开 Hydra",
"quit": "退出"
},
"game_card": {
"no_downloads": "无可用下载选项"
},
"binary_not_found_modal": {
"title": "程序未安装",
"description": "您的系统中找不到 Wine 或 Lutris 的可执行文件",
"instructions": "请检查在 Linux 发行版上安装这些软件的正确方法,以便游戏能够正常运行"
}, },
"forms": { "forms": {
"toggle_password_visibility": "切换密码可见性" "toggle_password_visibility": "切换密码可见性"
}, },
"modal": {
"close": "关闭按钮"
},
"user_profile": { "user_profile": {
"amount_hours": "{{amount}} 小时", "amount_hours": "{{amount}} 小时",
"amount_minutes": "{{amount}} 分钟", "amount_minutes": "{{amount}} 分钟",
@@ -208,7 +303,74 @@
"cancel": "取消", "cancel": "取消",
"successfully_signed_out": "登出成功", "successfully_signed_out": "登出成功",
"sign_out": "登出", "sign_out": "登出",
"playing_for": "Playing for {{amount}}", "playing_for": "已经玩了{{amount}}",
"sign_out_modal_text": "您的资料库与您当前的账户相关联。注销后,您的资料库将不再可见,任何进度也不会保存。继续退出吗?" "sign_out_modal_text": "您的资料库与您当前的账户相关联。注销后,您的资料库将不再可见,任何进度也不会保存。继续退出吗?",
"add_friends": "添加好友",
"add": "添加",
"friend_code": "好友代码",
"see_profile": "查看资料",
"sending": "发送中",
"friend_request_sent": "好友请求已发送",
"friends": "好友",
"friends_list": "好友列表",
"user_not_found": "未找到此用户",
"block_user": "屏蔽此用户",
"add_friend": "添加好友",
"request_sent": "请求已发送",
"request_received": "已收到请求",
"accept_request": "同意申请",
"ignore_request": "忽略申请",
"cancel_request": "取消申请",
"undo_friendship": "解除好友关系",
"request_accepted": "请求已通过",
"user_blocked_successfully": "成功屏蔽此用户",
"user_block_modal_text": "这将会屏蔽 {{displayName}}",
"blocked_users": "黑名单用户",
"unblock": "解除屏蔽",
"no_friends_added": "你还没有添加过好友",
"pending": "待处理",
"no_pending_invites": "您没有待处理的邀请",
"no_blocked_users": "你没有已经拉人黑名单的用户",
"friend_code_copied": "好友代码已复制",
"undo_friendship_modal_text": "这将使你与 {{displayName}} 解除好友关系",
"privacy_hint": "要调整谁可以看到你的个人资料,可以去<0>设置</0>中修改",
"locked_profile": "此个人资料是私密的",
"image_process_failure": "处理图片时发生错误",
"required_field": "此字段为必填项",
"displayname_min_length": "显示名称最少必须为3个字符。",
"displayname_max_length": "显示名称最多必须为50个字符",
"report_profile": "举报此资料",
"report_reason": "为什么你要举报此资料?",
"report_description": "额外信息",
"report_description_placeholder": "额外信息",
"report": "举报",
"report_reason_hate": "Hate speech",
"report_reason_sexual_content": "色情内容",
"report_reason_violence": "暴力",
"report_reason_spam": "骚扰",
"report_reason_other": "其他",
"profile_reported": "个人资料已举报",
"your_friend_code": "你的好友代码:",
"upload_banner": "上传横幅",
"uploading_banner": "上传横幅中…",
"background_image_updated": "背景图片已更新"
},
"achievement": {
"achievement_unlocked": "成就已解锁",
"user_achievements": "{{displayName}}的成就",
"your_achievements": "你的成就",
"unlocked_at": "解锁于:",
"subscription_needed": "需要订阅 Hydra Cloud 才能看到此内容",
"new_achievements_unlocked": "从 {{gameCount}} 游戏中解锁 {{achievementCount}} 新成就"
},
"tour": {
"subscription_tour_title": "Hydra 云订阅",
"subscribe_now": "现在订购",
"cloud_saving": "云存档",
"cloud_achievements": "将你的成就保存至云端",
"animated_profile_picture": "动画头像",
"premium_support": "高级技术支持",
"show_and_compare_achievements": "展示并与其他用户比较您的成就",
"animated_profile_banner": "动态个人简介横幅"
} }
} }

View File

@@ -35,6 +35,9 @@ export class UserPreferences {
@Column("boolean", { default: false }) @Column("boolean", { default: false })
runAtStartup: boolean; runAtStartup: boolean;
@Column("boolean", { default: false })
startMinimized: boolean;
@CreateDateColumn() @CreateDateColumn()
createdAt: Date; createdAt: Date;

View File

@@ -7,7 +7,7 @@ const { autoUpdater } = updater;
const restartAndInstallUpdate = async (_event: Electron.IpcMainInvokeEvent) => { const restartAndInstallUpdate = async (_event: Electron.IpcMainInvokeEvent) => {
autoUpdater.removeAllListeners(); autoUpdater.removeAllListeners();
if (app.isPackaged) { if (app.isPackaged) {
autoUpdater.quitAndInstall(true, true); autoUpdater.quitAndInstall(false);
} }
}; };

View File

@@ -24,6 +24,7 @@ const createGameShortcut = async (
const options = { const options = {
filePath, filePath,
name: removeSymbolsFromName(game.title), name: removeSymbolsFromName(game.title),
outputPath: app.getPath("desktop"),
}; };
return createDesktopShortcut({ return createDesktopShortcut({

View File

@@ -16,15 +16,16 @@ const windowsStartupPath = path.join(
const autoLaunch = async ( const autoLaunch = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
enabled: boolean autoLaunchProps: { enabled: boolean; minimized: boolean }
) => { ) => {
if (!app.isPackaged) return; if (!app.isPackaged) return;
const appLauncher = new AutoLaunch({ const appLauncher = new AutoLaunch({
name: app.getName(), name: app.getName(),
isHidden: autoLaunchProps.minimized,
}); });
if (enabled) { if (autoLaunchProps.enabled) {
appLauncher.enable().catch((err) => { appLauncher.enable().catch((err) => {
logger.error(err); logger.error(err);
}); });

View File

@@ -26,8 +26,9 @@ export const requestWebPage = async (url: string) => {
return window.document; return window.document;
}; };
export const isPortableVersion = () => export const isPortableVersion = () => {
process.env.PORTABLE_EXECUTABLE_FILE !== null; return !!process.env.PORTABLE_EXECUTABLE_FILE;
};
export const normalizePath = (str: string) => export const normalizePath = (str: string) =>
path.posix.normalize(str).replace(/\\/g, "/"); path.posix.normalize(str).replace(/\\/g, "/");

View File

@@ -101,7 +101,10 @@ app.whenReady().then(async () => {
i18n.changeLanguage(userPreferences.language); i18n.changeLanguage(userPreferences.language);
} }
WindowManager.createMainWindow(); if (!process.argv.includes("--hidden")) {
WindowManager.createMainWindow();
}
WindowManager.createNotificationWindow(); WindowManager.createNotificationWindow();
WindowManager.createSystemTray(userPreferences?.language || "en"); WindowManager.createSystemTray(userPreferences?.language || "en");
}); });

View File

@@ -11,7 +11,7 @@ import { AddAchievementNotificationPreference } from "./migrations/2024101301290
import { CreateUserSubscription } from "./migrations/20241015235142_create_user_subscription"; import { CreateUserSubscription } from "./migrations/20241015235142_create_user_subscription";
import { AddBackgroundImageUrl } from "./migrations/20241016100249_add_background_image_url"; import { AddBackgroundImageUrl } from "./migrations/20241016100249_add_background_image_url";
import { AddWinePrefixToGame } from "./migrations/20241019081648_add_wine_prefix_to_game"; import { AddWinePrefixToGame } from "./migrations/20241019081648_add_wine_prefix_to_game";
import { AddStartMinimizedColumn } from "./migrations/20241030171454_add_start_minimized_column";
export type HydraMigration = Knex.Migration & { name: string }; export type HydraMigration = Knex.Migration & { name: string };
class MigrationSource implements Knex.MigrationSource<HydraMigration> { class MigrationSource implements Knex.MigrationSource<HydraMigration> {
@@ -27,6 +27,7 @@ class MigrationSource implements Knex.MigrationSource<HydraMigration> {
CreateUserSubscription, CreateUserSubscription,
AddBackgroundImageUrl, AddBackgroundImageUrl,
AddWinePrefixToGame, AddWinePrefixToGame,
AddStartMinimizedColumn,
]); ]);
} }
getMigrationName(migration: HydraMigration): string { getMigrationName(migration: HydraMigration): string {

View File

@@ -0,0 +1,17 @@
import type { HydraMigration } from "@main/knex-client";
import type { Knex } from "knex";
export const AddStartMinimizedColumn: HydraMigration = {
name: "AddStartMinimizedColumn",
up: (knex: Knex) => {
return knex.schema.alterTable("user_preferences", (table) => {
return table.boolean("startMinimized").notNullable().defaultTo(0);
});
},
down: async (knex: Knex) => {
return knex.schema.alterTable("user_preferences", (table) => {
return table.dropColumn("startMinimized");
});
},
};

View File

@@ -219,7 +219,6 @@ export class AchievementWatcherManager {
const games = await gameRepository.find({ const games = await gameRepository.find({
where: { where: {
isDeleted: false, isDeleted: false,
winePrefixPath: Not(IsNull()),
}, },
}); });
@@ -242,10 +241,18 @@ export class AchievementWatcherManager {
? await this.preSearchAchievementsWindows() ? await this.preSearchAchievementsWindows()
: await this.preSearchAchievementsWithWine(); : await this.preSearchAchievementsWithWine();
const totalNewGamesWithAchievements = newAchievementsCount.filter(
(achievements) => achievements
).length;
const totalNewAchievements = newAchievementsCount.reduce(
(acc, val) => acc + val,
0
);
WindowManager.notificationWindow?.webContents.send( WindowManager.notificationWindow?.webContents.send(
"on-combined-achievements-unlocked", "on-combined-achievements-unlocked",
newAchievementsCount.filter((achievements) => achievements).length, totalNewGamesWithAchievements,
newAchievementsCount.reduce((acc, val) => acc + val, 0) totalNewAchievements
); );
this.hasFinishedMergingWithRemote = true; this.hasFinishedMergingWithRemote = true;

View File

@@ -79,11 +79,11 @@ const getPathFromCracker = (cracker: Cracker) => {
return [ return [
{ {
folderPath: path.join(publicDocuments, "Steam", "CODEX"), folderPath: path.join(publicDocuments, "Steam", "CODEX"),
fileLocation: ["achievements.ini"], fileLocation: ["<objectId>", "achievements.ini"],
}, },
{ {
folderPath: path.join(appData, "Steam", "CODEX"), folderPath: path.join(appData, "Steam", "CODEX"),
fileLocation: ["achievements.ini"], fileLocation: ["<objectId>", "achievements.ini"],
}, },
]; ];
} }
@@ -92,7 +92,7 @@ const getPathFromCracker = (cracker: Cracker) => {
return [ return [
{ {
folderPath: path.join(publicDocuments, "Steam", "RUNE"), folderPath: path.join(publicDocuments, "Steam", "RUNE"),
fileLocation: ["achievements.ini"], fileLocation: ["<objectId>", "achievements.ini"],
}, },
]; ];
} }
@@ -101,11 +101,11 @@ const getPathFromCracker = (cracker: Cracker) => {
return [ return [
{ {
folderPath: path.join(publicDocuments, "OnlineFix"), folderPath: path.join(publicDocuments, "OnlineFix"),
fileLocation: ["Stats", "Achievements.ini"], fileLocation: ["<objectId>", "Stats", "Achievements.ini"],
}, },
{ {
folderPath: path.join(publicDocuments, "OnlineFix"), folderPath: path.join(publicDocuments, "OnlineFix"),
fileLocation: ["Achievements.ini"], fileLocation: ["<objectId>", "Achievements.ini"],
}, },
]; ];
} }
@@ -114,11 +114,11 @@ const getPathFromCracker = (cracker: Cracker) => {
return [ return [
{ {
folderPath: path.join(appData, "Goldberg SteamEmu Saves"), folderPath: path.join(appData, "Goldberg SteamEmu Saves"),
fileLocation: ["achievements.json"], fileLocation: ["<objectId>", "achievements.json"],
}, },
{ {
folderPath: path.join(appData, "GSE Saves"), folderPath: path.join(appData, "GSE Saves"),
fileLocation: ["achievements.json"], fileLocation: ["<objectId>", "achievements.json"],
}, },
]; ];
} }
@@ -131,19 +131,19 @@ const getPathFromCracker = (cracker: Cracker) => {
return [ return [
{ {
folderPath: path.join(programData, "RLD!"), folderPath: path.join(programData, "RLD!"),
fileLocation: ["achievements.ini"], fileLocation: ["<objectId>", "achievements.ini"],
}, },
{ {
folderPath: path.join(programData, "Steam", "Player"), folderPath: path.join(programData, "Steam", "Player"),
fileLocation: ["stats", "achievements.ini"], fileLocation: ["<objectId>", "stats", "achievements.ini"],
}, },
{ {
folderPath: path.join(programData, "Steam", "RLD!"), folderPath: path.join(programData, "Steam", "RLD!"),
fileLocation: ["stats", "achievements.ini"], fileLocation: ["<objectId>", "stats", "achievements.ini"],
}, },
{ {
folderPath: path.join(programData, "Steam", "dodi"), folderPath: path.join(programData, "Steam", "dodi"),
fileLocation: ["stats", "achievements.ini"], fileLocation: ["<objectId>", "stats", "achievements.ini"],
}, },
]; ];
} }
@@ -152,11 +152,16 @@ const getPathFromCracker = (cracker: Cracker) => {
return [ return [
{ {
folderPath: path.join(appData, "EMPRESS", "remote"), folderPath: path.join(appData, "EMPRESS", "remote"),
fileLocation: ["achievements.json"], fileLocation: ["<objectId>", "achievements.json"],
}, },
{ {
folderPath: path.join(publicDocuments, "EMPRESS", "remote"), folderPath: path.join(publicDocuments, "EMPRESS"),
fileLocation: ["achievements.json"], fileLocation: [
"<objectId>",
"remote",
"<objectId>",
"achievements.json",
],
}, },
]; ];
} }
@@ -165,15 +170,15 @@ const getPathFromCracker = (cracker: Cracker) => {
return [ return [
{ {
folderPath: path.join(documents, "SKIDROW"), folderPath: path.join(documents, "SKIDROW"),
fileLocation: ["SteamEmu", "UserStats", "achiev.ini"], fileLocation: ["<objectId>", "SteamEmu", "UserStats", "achiev.ini"],
}, },
{ {
folderPath: path.join(documents, "Player"), folderPath: path.join(documents, "Player"),
fileLocation: ["SteamEmu", "UserStats", "achiev.ini"], fileLocation: ["<objectId>", "SteamEmu", "UserStats", "achiev.ini"],
}, },
{ {
folderPath: path.join(localAppData, "SKIDROW"), folderPath: path.join(localAppData, "SKIDROW"),
fileLocation: ["SteamEmu", "UserStats", "achiev.ini"], fileLocation: ["<objectId>", "SteamEmu", "UserStats", "achiev.ini"],
}, },
]; ];
} }
@@ -182,7 +187,7 @@ const getPathFromCracker = (cracker: Cracker) => {
return [ return [
{ {
folderPath: path.join(appData, "CreamAPI"), folderPath: path.join(appData, "CreamAPI"),
fileLocation: ["stats", "CreamAPI.Achievements.cfg"], fileLocation: ["<objectId>", "stats", "CreamAPI.Achievements.cfg"],
}, },
]; ];
} }
@@ -191,7 +196,7 @@ const getPathFromCracker = (cracker: Cracker) => {
return [ return [
{ {
folderPath: path.join(appData, "SmartSteamEmu"), folderPath: path.join(appData, "SmartSteamEmu"),
fileLocation: ["User", "Achievements.ini"], fileLocation: ["<objectId>", "User", "Achievements.ini"],
}, },
]; ];
} }
@@ -213,11 +218,11 @@ const getPathFromCracker = (cracker: Cracker) => {
return [ return [
{ {
folderPath: path.join(appData, "RLE"), folderPath: path.join(appData, "RLE"),
fileLocation: ["achievements.ini"], fileLocation: ["<objectId>", "achievements.ini"],
}, },
{ {
folderPath: path.join(appData, "RLE"), folderPath: path.join(appData, "RLE"),
fileLocation: ["Achievements.ini"], fileLocation: ["<objectId>", "Achievements.ini"],
}, },
]; ];
} }
@@ -226,7 +231,7 @@ const getPathFromCracker = (cracker: Cracker) => {
return [ return [
{ {
folderPath: path.join(appData, ".1911"), folderPath: path.join(appData, ".1911"),
fileLocation: ["achievement"], fileLocation: ["<objectId>", "achievement"],
}, },
]; ];
} }
@@ -253,8 +258,7 @@ export const findAchievementFiles = (game: Game) => {
const filePath = path.join( const filePath = path.join(
game.winePrefixPath ?? "", game.winePrefixPath ?? "",
folderPath, folderPath,
objectId, ...mapFileLocationWithObjectId(fileLocation, objectId)
...fileLocation
); );
if (fs.existsSync(filePath)) { if (fs.existsSync(filePath)) {
@@ -303,6 +307,15 @@ export const findAchievementFileInExecutableDirectory = (
]; ];
}; };
const mapFileLocationWithObjectId = (
fileLocation: string[],
objectId: string
) => {
return fileLocation.map((location) =>
location.replace("<objectId>", objectId)
);
};
export const findAllAchievementFiles = () => { export const findAllAchievementFiles = () => {
const gameAchievementFiles = new Map<string, AchievementFile[]>(); const gameAchievementFiles = new Map<string, AchievementFile[]>();
@@ -315,7 +328,10 @@ export const findAllAchievementFiles = () => {
const objectIds = fs.readdirSync(folderPath); const objectIds = fs.readdirSync(folderPath);
for (const objectId of objectIds) { for (const objectId of objectIds) {
const filePath = path.join(folderPath, objectId, ...fileLocation); const filePath = path.join(
folderPath,
...mapFileLocationWithObjectId(fileLocation, objectId)
);
if (!fs.existsSync(filePath)) continue; if (!fs.existsSync(filePath)) continue;

View File

@@ -8,7 +8,6 @@ import { HydraApi } from "../hydra-api";
import { getUnlockedAchievements } from "@main/events/user/get-unlocked-achievements"; import { getUnlockedAchievements } from "@main/events/user/get-unlocked-achievements";
import { Game } from "@main/entity"; import { Game } from "@main/entity";
import { achievementsLogger } from "../logger"; import { achievementsLogger } from "../logger";
import { SubscriptionRequiredError } from "@shared";
const saveAchievementsOnLocal = async ( const saveAchievementsOnLocal = async (
objectId: string, objectId: string,
@@ -119,14 +118,10 @@ export const mergeAchievements = async (
const mergedLocalAchievements = unlockedAchievements.concat(newAchievements); const mergedLocalAchievements = unlockedAchievements.concat(newAchievements);
if (game.remoteId) { if (game.remoteId) {
await HydraApi.put( await HydraApi.put("/profile/games/achievements", {
"/profile/games/achievements", id: game.remoteId,
{ achievements: mergedLocalAchievements,
id: game.remoteId, })
achievements: mergedLocalAchievements,
},
{ needsSubscription: true }
)
.then((response) => { .then((response) => {
return saveAchievementsOnLocal( return saveAchievementsOnLocal(
response.objectId, response.objectId,
@@ -136,9 +131,7 @@ export const mergeAchievements = async (
); );
}) })
.catch((err) => { .catch((err) => {
if (!(err instanceof SubscriptionRequiredError)) { achievementsLogger.error(err);
achievementsLogger.error(err);
}
return saveAchievementsOnLocal( return saveAchievementsOnLocal(
game.objectID, game.objectID,

View File

@@ -65,6 +65,11 @@ export const parseAchievementFile = (
return processCreamAPI(parsed); return processCreamAPI(parsed);
} }
if (type === Cracker.empress) {
const parsed = jsonParse(filePath);
return processGoldberg(parsed);
}
if (type === Cracker.razor1911) { if (type === Cracker.razor1911) {
return processRazor1911(filePath); return processRazor1911(filePath);
} }
@@ -118,7 +123,7 @@ const jsonParse = (filePath: string) => {
const processRazor1911 = (filePath: string): UnlockedAchievement[] => { const processRazor1911 = (filePath: string): UnlockedAchievement[] => {
try { try {
const fileContent = readFileSync(filePath, "utf-8"); const fileContent = readFileSync(filePath, "utf-8");
achievementsLogger.log("processing file", filePath, fileContent);
const lines = const lines =
fileContent.charCodeAt(0) === 0xfeff fileContent.charCodeAt(0) === 0xfeff
? fileContent.slice(1).split(/[\r\n]+/) ? fileContent.slice(1).split(/[\r\n]+/)
@@ -136,7 +141,7 @@ const processRazor1911 = (filePath: string): UnlockedAchievement[] => {
}); });
} }
} }
achievementsLogger.log("processing file", achievements);
return achievements; return achievements;
} catch (err) { } catch (err) {
achievementsLogger.error(`Error processing ${filePath}`, err); achievementsLogger.error(`Error processing ${filePath}`, err);

View File

@@ -44,7 +44,7 @@ export class HydraApi {
return this.userAuth.authToken !== ""; return this.userAuth.authToken !== "";
} }
private static hasCloudSubscription() { private static hasActiveSubscription() {
return ( return (
this.userAuth.subscription?.expiresAt && this.userAuth.subscription?.expiresAt &&
this.userAuth.subscription.expiresAt > new Date() this.userAuth.subscription.expiresAt > new Date()
@@ -279,7 +279,7 @@ export class HydraApi {
} }
if (needsSubscription) { if (needsSubscription) {
if (!(await this.hasCloudSubscription())) { if (!(await this.hasActiveSubscription())) {
throw new SubscriptionRequiredError(); throw new SubscriptionRequiredError();
} }
} }

View File

@@ -310,14 +310,15 @@ export class WindowManager {
if (process.platform !== "darwin") { if (process.platform !== "darwin") {
tray.addListener("click", () => { tray.addListener("click", () => {
if (this.mainWindow) { if (this.mainWindow) {
if (WindowManager.mainWindow?.isMinimized()) if (
WindowManager.mainWindow.restore(); WindowManager.mainWindow?.isMinimized() ||
!WindowManager.mainWindow?.isVisible()
WindowManager.mainWindow?.focus(); ) {
return; WindowManager.mainWindow?.show();
}
} else {
this.createMainWindow();
} }
this.createMainWindow();
}); });
tray.addListener("right-click", showContextMenu); tray.addListener("right-click", showContextMenu);

View File

@@ -101,7 +101,8 @@ contextBridge.exposeInMainWorld("electron", {
getUserPreferences: () => ipcRenderer.invoke("getUserPreferences"), getUserPreferences: () => ipcRenderer.invoke("getUserPreferences"),
updateUserPreferences: (preferences: UserPreferences) => updateUserPreferences: (preferences: UserPreferences) =>
ipcRenderer.invoke("updateUserPreferences", preferences), ipcRenderer.invoke("updateUserPreferences", preferences),
autoLaunch: (enabled: boolean) => ipcRenderer.invoke("autoLaunch", enabled), autoLaunch: (autoLaunchProps: { enabled: boolean; minimized: boolean }) =>
ipcRenderer.invoke("autoLaunch", autoLaunchProps),
authenticateRealDebrid: (apiToken: string) => authenticateRealDebrid: (apiToken: string) =>
ipcRenderer.invoke("authenticateRealDebrid", apiToken), ipcRenderer.invoke("authenticateRealDebrid", apiToken),

View File

@@ -65,6 +65,24 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
navigate(-1); navigate(-1);
}; };
useEffect(() => {
window.onkeydown = (event: KeyboardEvent) => {
const { key, ctrlKey } = event;
if (!isFocused && ctrlKey && key === "k") {
focusInput();
}
if (isFocused && key === "Escape" && inputRef.current) {
inputRef.current.blur();
handleBlur();
}
};
return () => {
window.onkeydown = null;
};
}, [isFocused]);
return ( return (
<> <>
<header <header
@@ -81,6 +99,7 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
})} })}
onClick={handleBackButtonClick} onClick={handleBackButtonClick}
disabled={location.key === "default"} disabled={location.key === "default"}
title={t("back")}
> >
<ArrowLeftIcon /> <ArrowLeftIcon />
</button> </button>
@@ -100,6 +119,8 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
type="button" type="button"
className={styles.actionButton} className={styles.actionButton}
onClick={focusInput} onClick={focusInput}
tabIndex={-1}
title={t("search")}
> >
<SearchIcon /> <SearchIcon />
</button> </button>
@@ -121,6 +142,7 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
type="button" type="button"
onClick={onClear} onClick={onClear}
className={styles.actionButton} className={styles.actionButton}
title={t("clear_search")}
> >
<XIcon /> <XIcon />
</button> </button>

View File

@@ -8,7 +8,7 @@ import { UserFriendModalTab } from "@renderer/pages/shared-modals/user-friend-mo
import SteamLogo from "@renderer/assets/steam-logo.svg?react"; import SteamLogo from "@renderer/assets/steam-logo.svg?react";
import { Avatar } from "../avatar/avatar"; import { Avatar } from "../avatar/avatar";
const LONG_POLLING_INTERVAL = 60_000; const LONG_POLLING_INTERVAL = 120_000;
export function SidebarProfile() { export function SidebarProfile() {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -89,6 +89,7 @@ export function SidebarProfile() {
type="button" type="button"
className={styles.profileButton} className={styles.profileButton}
onClick={handleProfileClick} onClick={handleProfileClick}
aria-label={t("aria_view_profile")}
> >
<div className={styles.profileButtonContent}> <div className={styles.profileButtonContent}>
<Avatar <Avatar

View File

@@ -68,6 +68,26 @@ export function Sidebar() {
sidebarRef.current?.clientWidth || SIDEBAR_INITIAL_WIDTH; sidebarRef.current?.clientWidth || SIDEBAR_INITIAL_WIDTH;
}; };
const handleKeyDown: React.KeyboardEventHandler<HTMLButtonElement> = (
event
) => {
const { key } = event;
if (key === "ArrowRight") {
setSidebarWidth((prevWidth) =>
prevWidth < SIDEBAR_INITIAL_WIDTH
? SIDEBAR_INITIAL_WIDTH
: SIDEBAR_MAX_WIDTH
);
} else if (key === "ArrowLeft") {
setSidebarWidth((prevWidth) =>
prevWidth > SIDEBAR_INITIAL_WIDTH
? SIDEBAR_INITIAL_WIDTH
: SIDEBAR_MIN_WIDTH
);
}
};
const handleFilter: React.ChangeEventHandler<HTMLInputElement> = (event) => { const handleFilter: React.ChangeEventHandler<HTMLInputElement> = (event) => {
setFilteredLibrary( setFilteredLibrary(
sortedLibrary.filter((game) => sortedLibrary.filter((game) =>
@@ -219,6 +239,7 @@ export function Sidebar() {
type="button" type="button"
className={styles.menuItemButton} className={styles.menuItemButton}
onClick={(event) => handleSidebarGameClick(event, game)} onClick={(event) => handleSidebarGameClick(event, game)}
aria-label={game.title}
> >
{game.iconUrl ? ( {game.iconUrl ? (
<img <img
@@ -245,6 +266,8 @@ export function Sidebar() {
type="button" type="button"
className={styles.handle} className={styles.handle}
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onKeyDown={handleKeyDown}
title={t("resize_sidebar")}
/> />
</aside> </aside>
); );

View File

@@ -114,7 +114,10 @@ declare global {
updateUserPreferences: ( updateUserPreferences: (
preferences: Partial<UserPreferences> preferences: Partial<UserPreferences>
) => Promise<void>; ) => Promise<void>;
autoLaunch: (enabled: boolean) => Promise<void>; autoLaunch: (autoLaunchProps: {
enabled: boolean;
minimized: boolean;
}) => Promise<void>;
authenticateRealDebrid: (apiToken: string) => Promise<RealDebridUser>; authenticateRealDebrid: (apiToken: string) => Promise<RealDebridUser>;
/* Download sources */ /* Download sources */

View File

@@ -77,6 +77,8 @@ export function useDate() {
}, },
formatDate: (date: number | Date | string): string => { formatDate: (date: number | Date | string): string => {
if (isNaN(new Date(date).getDate())) return "N/A";
const locale = getDateLocale(); const locale = getDateLocale();
return format(date, locale == enUS ? "MM/dd/yyyy" : "dd/MM/yyyy"); return format(date, locale == enUS ? "MM/dd/yyyy" : "dd/MM/yyyy");
}, },

View File

@@ -128,12 +128,8 @@ export function useUserDetails() {
const unblockUser = (userId: string) => window.electron.unblockUser(userId); const unblockUser = (userId: string) => window.electron.unblockUser(userId);
const hasActiveSubscription = useMemo(() => { const hasActiveSubscription = useMemo(() => {
if (!userDetails?.subscription?.plan) {
return false;
}
return ( return (
userDetails.subscription.expiresAt == null || userDetails?.subscription?.expiresAt &&
new Date(userDetails.subscription.expiresAt) > new Date() new Date(userDetails.subscription.expiresAt) > new Date()
); );
}, [userDetails]); }, [userDetails]);

View File

@@ -16,6 +16,7 @@ import { average } from "color.js";
import Color from "color"; import Color from "color";
import { Link } from "@renderer/components"; import { Link } from "@renderer/components";
import { ComparedAchievementList } from "./compared-achievement-list"; import { ComparedAchievementList } from "./compared-achievement-list";
import { TFunction } from "i18next/typescript/t";
interface UserInfo { interface UserInfo {
id: string; id: string;
@@ -39,10 +40,35 @@ interface AchievementSummaryProps {
isComparison?: boolean; isComparison?: boolean;
} }
const ariaLabelSummary = (
t: TFunction,
gameTitle: string,
user: UserInfo
): string => {
return t("aria_achievement_summary", {
userDisplayName: user.displayName,
gameTitle: gameTitle,
userAchievementCount: user.unlockedAchievementCount,
userTotalAchievementCount: user.totalAchievementCount,
percentage: formatDownloadProgress(
user.unlockedAchievementCount / user.totalAchievementCount
),
});
};
const ariaLabelAchievement = (
t: TFunction,
achievement: UserAchievement
): string => {
return `${
achievement.unlocked ? t("achievement_unlocked") : t("achievement_locked")
}, ${achievement.displayName}, ${achievement.description}`;
};
function AchievementSummary({ user, isComparison }: AchievementSummaryProps) { function AchievementSummary({ user, isComparison }: AchievementSummaryProps) {
const { t } = useTranslation("achievement"); const { t } = useTranslation("achievement");
const { userDetails, hasActiveSubscription } = useUserDetails(); const { userDetails, hasActiveSubscription } = useUserDetails();
const { handleClickOpenCheckout } = useContext(gameDetailsContext); const { handleClickOpenCheckout, gameTitle } = useContext(gameDetailsContext);
const getProfileImage = ( const getProfileImage = (
user: Pick<UserInfo, "profileImageUrl" | "displayName"> user: Pick<UserInfo, "profileImageUrl" | "displayName">
@@ -124,6 +150,8 @@ function AchievementSummary({ user, isComparison }: AchievementSummaryProps) {
alignItems: "center", alignItems: "center",
padding: `${SPACING_UNIT}px`, padding: `${SPACING_UNIT}px`,
}} }}
role="region"
aria-label={ariaLabelSummary(t, gameTitle, user)}
> >
{getProfileImage(user)} {getProfileImage(user)}
<div <div
@@ -178,7 +206,12 @@ function AchievementList({ achievements }: AchievementListProps) {
return ( return (
<ul className={styles.list}> <ul className={styles.list}>
{achievements.map((achievement, index) => ( {achievements.map((achievement, index) => (
<li key={index} className={styles.listItem} style={{ display: "flex" }}> <li
key={index}
className={styles.listItem}
style={{ display: "flex" }}
aria-label={ariaLabelAchievement(t, achievement)}
>
<img <img
className={styles.listItemImage({ className={styles.listItemImage({
unlocked: achievement.unlocked, unlocked: achievement.unlocked,

View File

@@ -140,7 +140,7 @@ export function GallerySlider() {
direction: "left", direction: "left",
})} })}
aria-label={t("previous_screenshot")} aria-label={t("previous_screenshot")}
tabIndex={0} tabIndex={-1}
> >
<ChevronLeftIcon size={36} /> <ChevronLeftIcon size={36} />
</button> </button>
@@ -153,7 +153,7 @@ export function GallerySlider() {
direction: "right", direction: "right",
})} })}
aria-label={t("next_screenshot")} aria-label={t("next_screenshot")}
tabIndex={0} tabIndex={-1}
> >
<ChevronRightIcon size={36} /> <ChevronRightIcon size={36} />
</button> </button>
@@ -169,6 +169,7 @@ export function GallerySlider() {
})} })}
onClick={() => setMediaIndex(i)} onClick={() => setMediaIndex(i)}
aria-label={t("open_screenshot", { number: i + 1 })} aria-label={t("open_screenshot", { number: i + 1 })}
onFocus={() => setMediaIndex(i)}
> >
<img <img
src={media.thumbnail} src={media.thumbnail}

View File

@@ -167,7 +167,7 @@ export function GameDetailsContent() {
> >
<Lottie <Lottie
animationData={cloudAnimation} animationData={cloudAnimation}
loop loop={false}
autoplay autoplay
style={{ width: 26, position: "absolute", top: -3 }} style={{ width: 26, position: "absolute", top: -3 }}
/> />

View File

@@ -202,7 +202,7 @@ export default function GameDetails() {
top: -28, top: -28,
left: -27, left: -27,
}} }}
loop loop={false}
/> />
</div> </div>
{t("next_suggestion")} {t("next_suggestion")}

View File

@@ -18,7 +18,7 @@ export const panel = recipe({
position: "sticky", position: "sticky",
overflow: "hidden", overflow: "hidden",
top: "0", top: "0",
zIndex: "1", zIndex: "2",
}, },
variants: { variants: {
stuck: { stuck: {

View File

@@ -1,8 +1,7 @@
import { format } from "date-fns";
import { useContext } from "react"; import { useContext } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useDownload } from "@renderer/hooks"; import { useDate, useDownload } from "@renderer/hooks";
import { HeroPanelActions } from "./hero-panel-actions"; import { HeroPanelActions } from "./hero-panel-actions";
import * as styles from "./hero-panel.css"; import * as styles from "./hero-panel.css";
@@ -17,6 +16,8 @@ export interface HeroPanelProps {
export function HeroPanel({ isHeaderStuck }: HeroPanelProps) { export function HeroPanel({ isHeaderStuck }: HeroPanelProps) {
const { t } = useTranslation("game_details"); const { t } = useTranslation("game_details");
const { formatDate } = useDate();
const { game, repacks, gameColor } = useContext(gameDetailsContext); const { game, repacks, gameColor } = useContext(gameDetailsContext);
const { lastPacket } = useDownload(); const { lastPacket } = useDownload();
@@ -29,7 +30,9 @@ export function HeroPanel({ isHeaderStuck }: HeroPanelProps) {
const [latestRepack] = repacks; const [latestRepack] = repacks;
if (latestRepack) { if (latestRepack) {
const lastUpdate = format(latestRepack.uploadDate!, "dd/MM/yyyy"); const lastUpdate = latestRepack.uploadDate
? formatDate(latestRepack.uploadDate!)
: "";
const repacksCount = repacks.length; const repacksCount = repacks.length;
return ( return (

View File

@@ -38,6 +38,9 @@ export function HowLongToBeatSection({
<li <li
key={category.title} key={category.title}
className={styles.howLongToBeatCategory} className={styles.howLongToBeatCategory}
aria-label={`${category.title}, ${getDuration(
category.duration
)}`}
> >
<p <p
className={styles.howLongToBeatCategoryLabel} className={styles.howLongToBeatCategoryLabel}

View File

@@ -126,7 +126,7 @@ export function Sidebar() {
<div <div
style={{ style={{
position: "absolute", position: "absolute",
zIndex: 2, zIndex: 1,
inset: 0, inset: 0,
width: "100%", width: "100%",
height: "100%", height: "100%",
@@ -194,6 +194,10 @@ export function Sidebar() {
})} })}
className={styles.listItem} className={styles.listItem}
title={achievement.description} title={achievement.description}
aria-label={`
${t("achievement")} ${index + 1},
${achievement.displayName}
`}
> >
<img <img
className={styles.listItemImage({ className={styles.listItemImage({

View File

@@ -126,7 +126,7 @@ export default function Home() {
<Lottie <Lottie
lottieRef={flameAnimationRef} lottieRef={flameAnimationRef}
animationData={flameAnimation} animationData={flameAnimation}
loop loop={false}
autoplay={false} autoplay={false}
style={{ style={{
width: 30, width: 30,
@@ -153,7 +153,7 @@ export default function Home() {
<Lottie <Lottie
animationData={starsAnimation} animationData={starsAnimation}
style={{ width: 70, position: "absolute", top: -28, left: -27 }} style={{ width: 70, position: "absolute", top: -28, left: -27 }}
loop={Boolean(randomGame)} loop={false}
/> />
</div> </div>
{t("surprise_me")} {t("surprise_me")}
@@ -165,7 +165,7 @@ export default function Home() {
<div style={{ width: 24, height: 24, position: "relative" }}> <div style={{ width: 24, height: 24, position: "relative" }}>
<Lottie <Lottie
animationData={flameAnimation} animationData={flameAnimation}
loop loop={false}
autoplay autoplay
style={{ style={{
width: 40, width: 40,

View File

@@ -17,6 +17,7 @@ export function SettingsBehavior() {
const [form, setForm] = useState({ const [form, setForm] = useState({
preferQuitInsteadOfHiding: false, preferQuitInsteadOfHiding: false,
runAtStartup: false, runAtStartup: false,
startMinimized: false,
}); });
const { t } = useTranslation("settings"); const { t } = useTranslation("settings");
@@ -26,6 +27,7 @@ export function SettingsBehavior() {
setForm({ setForm({
preferQuitInsteadOfHiding: userPreferences.preferQuitInsteadOfHiding, preferQuitInsteadOfHiding: userPreferences.preferQuitInsteadOfHiding,
runAtStartup: userPreferences.runAtStartup, runAtStartup: userPreferences.runAtStartup,
startMinimized: userPreferences.startMinimized,
}); });
} }
}, [userPreferences]); }, [userPreferences]);
@@ -58,11 +60,32 @@ export function SettingsBehavior() {
label={t("launch_with_system")} label={t("launch_with_system")}
onChange={() => { onChange={() => {
handleChange({ runAtStartup: !form.runAtStartup }); handleChange({ runAtStartup: !form.runAtStartup });
window.electron.autoLaunch(!form.runAtStartup); window.electron.autoLaunch({
enabled: !form.runAtStartup,
minimized: form.startMinimized,
});
}} }}
checked={form.runAtStartup} checked={form.runAtStartup}
/> />
)} )}
{showRunAtStartup && (
<div style={{ opacity: form.runAtStartup ? 1 : 0.5 }}>
<CheckboxField
label={t("launch_minimized")}
style={{ cursor: form.runAtStartup ? "pointer" : "not-allowed" }}
checked={form.runAtStartup && form.startMinimized}
disabled={!form.runAtStartup}
onChange={() => {
handleChange({ startMinimized: !form.startMinimized });
window.electron.autoLaunch({
minimized: !form.startMinimized,
enabled: form.runAtStartup,
});
}}
/>
</div>
)}
</> </>
); );
} }

View File

@@ -160,6 +160,7 @@ export interface UserPreferences {
realDebridApiToken: string | null; realDebridApiToken: string | null;
preferQuitInsteadOfHiding: boolean; preferQuitInsteadOfHiding: boolean;
runAtStartup: boolean; runAtStartup: boolean;
startMinimized: boolean;
} }
export interface Steam250Game { export interface Steam250Game {