mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-11 13:56:16 +00:00
Merge pull request #1780 from saraiva1989/feature-add-low-price-and-game-language
Feature add low price and game language
This commit is contained in:
@@ -3,3 +3,5 @@ MAIN_VITE_AUTH_URL=
|
|||||||
MAIN_VITE_WS_URL=
|
MAIN_VITE_WS_URL=
|
||||||
RENDERER_VITE_REAL_DEBRID_REFERRAL_ID=
|
RENDERER_VITE_REAL_DEBRID_REFERRAL_ID=
|
||||||
RENDERER_VITE_TORBOX_REFERRAL_CODE=
|
RENDERER_VITE_TORBOX_REFERRAL_CODE=
|
||||||
|
VITE_GG_DEALS_API_URL=https://api.gg.deals/v1/prices/by-steam-app-id
|
||||||
|
VITE_GG_DEALS_API_KEY=
|
||||||
17
src/locales/en/translation.json
Normal file → Executable file
17
src/locales/en/translation.json
Normal file → Executable file
@@ -230,7 +230,21 @@
|
|||||||
"backup_frozen": "Backup pinned",
|
"backup_frozen": "Backup pinned",
|
||||||
"backup_unfrozen": "Backup unpinned",
|
"backup_unfrozen": "Backup unpinned",
|
||||||
"backup_freeze_failed": "Failed to freeze backup",
|
"backup_freeze_failed": "Failed to freeze backup",
|
||||||
"backup_freeze_failed_description": "You must leave at least one free slot for automatic backups"
|
"backup_freeze_failed_description": "You must leave at least one free slot for automatic backups",
|
||||||
|
"game_details": "Game Details",
|
||||||
|
"currency_symbol": "$",
|
||||||
|
"currency_country": "us",
|
||||||
|
"prices": "Prices",
|
||||||
|
"no_prices_found": "No prices found",
|
||||||
|
"view_all_prices": "Click to view all prices",
|
||||||
|
"retail_price": "Retail price",
|
||||||
|
"keyshop_price": "Keyshop price",
|
||||||
|
"historical_retail": "Historical retail",
|
||||||
|
"historical_keyshop": "Historical keyshop",
|
||||||
|
"supported_languages": "Supported languages",
|
||||||
|
"language": "Language",
|
||||||
|
"caption": "Caption",
|
||||||
|
"audio": "Audio"
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Activate Hydra",
|
"title": "Activate Hydra",
|
||||||
@@ -276,6 +290,7 @@
|
|||||||
"change": "Update",
|
"change": "Update",
|
||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
"enable_download_notifications": "When a download is complete",
|
"enable_download_notifications": "When a download is complete",
|
||||||
|
"gg_deals_api_key_description": "gg deals api key. Used to show the lowest price. (https://gg.deals/api/)",
|
||||||
"enable_repack_list_notifications": "When a new repack is added",
|
"enable_repack_list_notifications": "When a new repack is added",
|
||||||
"real_debrid_api_token_label": "Real-Debrid API token",
|
"real_debrid_api_token_label": "Real-Debrid API token",
|
||||||
"quit_app_instead_hiding": "Don't hide Hydra when closing",
|
"quit_app_instead_hiding": "Don't hide Hydra when closing",
|
||||||
|
|||||||
17
src/locales/pt-BR/translation.json
Normal file → Executable file
17
src/locales/pt-BR/translation.json
Normal file → Executable file
@@ -207,7 +207,21 @@
|
|||||||
"backup_frozen": "Backup fixado",
|
"backup_frozen": "Backup fixado",
|
||||||
"backup_unfrozen": "Backup removido dos fixados",
|
"backup_unfrozen": "Backup removido dos fixados",
|
||||||
"backup_freeze_failed": "Falha ao fixar backup",
|
"backup_freeze_failed": "Falha ao fixar backup",
|
||||||
"backup_freeze_failed_description": "Você deve deixar pelo menos um espaço livre para backups automáticos"
|
"backup_freeze_failed_description": "Você deve deixar pelo menos um espaço livre para backups automáticos",
|
||||||
|
"game_details": "Detalhes do Jogo",
|
||||||
|
"currency_symbol": "R$",
|
||||||
|
"currency_country": "br",
|
||||||
|
"prices": "Preços",
|
||||||
|
"no_prices_found": "Nenhum preço encontrado",
|
||||||
|
"view_all_prices": "Clique para ver todos os preços",
|
||||||
|
"retail_price": "Preço de lojas oficiais",
|
||||||
|
"keyshop_price": "Preço em keyshops",
|
||||||
|
"historical_retail": "Preço histórico de lojas oficiais",
|
||||||
|
"historical_keyshop": "Preço histórico em keyshops",
|
||||||
|
"supported_languages": "Idiomas suportados",
|
||||||
|
"language": "Idioma",
|
||||||
|
"caption": "Legenda",
|
||||||
|
"audio": "Áudio"
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Ativação",
|
"title": "Ativação",
|
||||||
@@ -253,6 +267,7 @@
|
|||||||
"change": "Explorar...",
|
"change": "Explorar...",
|
||||||
"notifications": "Notificações",
|
"notifications": "Notificações",
|
||||||
"enable_download_notifications": "Quando um download for concluído",
|
"enable_download_notifications": "Quando um download for concluído",
|
||||||
|
"gg_deals_api_key_description": "gg deals api key. Usado para mostrar o menor preço. (https://gg.deals/api/)",
|
||||||
"enable_repack_list_notifications": "Quando a lista de repacks for atualizada",
|
"enable_repack_list_notifications": "Quando a lista de repacks for atualizada",
|
||||||
"real_debrid_api_token_label": "Token de API do Real-Debrid",
|
"real_debrid_api_token_label": "Token de API do Real-Debrid",
|
||||||
"quit_app_instead_hiding": "Encerrar o Hydra em vez de apenas minimizá-lo ao fechar",
|
"quit_app_instead_hiding": "Encerrar o Hydra em vez de apenas minimizá-lo ao fechar",
|
||||||
|
|||||||
66
src/renderer/src/pages/game-details/sidebar/game-language-section.tsx
Executable file
66
src/renderer/src/pages/game-details/sidebar/game-language-section.tsx
Executable file
@@ -0,0 +1,66 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { gameDetailsContext } from "@renderer/context/game-details/game-details.context";
|
||||||
|
import { SidebarSection } from "../sidebar-section/sidebar-section";
|
||||||
|
|
||||||
|
export function GameLanguageSection() {
|
||||||
|
const { t } = useTranslation("game_details");
|
||||||
|
const { shopDetails, objectId } = useContext(gameDetailsContext);
|
||||||
|
|
||||||
|
const getLanguages = () => {
|
||||||
|
let languages = shopDetails?.supported_languages;
|
||||||
|
if (!languages) return [];
|
||||||
|
languages = languages?.split("<br>")[0];
|
||||||
|
const arrayIdiomas = languages?.split(",");
|
||||||
|
const listLanguages: {
|
||||||
|
language: string;
|
||||||
|
caption: string;
|
||||||
|
audio: string;
|
||||||
|
}[] = [];
|
||||||
|
arrayIdiomas?.forEach((lang) => {
|
||||||
|
const objectLanguage = {
|
||||||
|
language: lang.replace("<strong>*</strong>", ""),
|
||||||
|
caption: "✔",
|
||||||
|
audio: lang.includes("*") ? "✔" : "",
|
||||||
|
};
|
||||||
|
listLanguages.push(objectLanguage);
|
||||||
|
});
|
||||||
|
return listLanguages;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarSection title={t("language")}>
|
||||||
|
<div>
|
||||||
|
<h4>{t("supported_languages")}</h4>
|
||||||
|
<table className="table-languages">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{t("language")}</th>
|
||||||
|
<th>{t("caption")}</th>
|
||||||
|
<th>{t("audio")}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{getLanguages().map((lang) => (
|
||||||
|
<tr key={lang.language}>
|
||||||
|
<td>{lang.language}</td>
|
||||||
|
<td>{lang.caption}</td>
|
||||||
|
<td>{lang.audio}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="list__item"
|
||||||
|
href={`https://store.steampowered.com/app/${objectId}`}
|
||||||
|
>
|
||||||
|
Link Steam
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</SidebarSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
86
src/renderer/src/pages/game-details/sidebar/game-prices-section.tsx
Executable file
86
src/renderer/src/pages/game-details/sidebar/game-prices-section.tsx
Executable file
@@ -0,0 +1,86 @@
|
|||||||
|
import { useCallback, useContext, useEffect, useState } from "react";
|
||||||
|
import { SidebarSection } from "../sidebar-section/sidebar-section";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { gameDetailsContext } from "@renderer/context/game-details/game-details.context";
|
||||||
|
import { useAppSelector } from "@renderer/hooks";
|
||||||
|
|
||||||
|
export function GamePricesSection() {
|
||||||
|
const userPreferences = useAppSelector(
|
||||||
|
(state) => state.userPreferences.value
|
||||||
|
);
|
||||||
|
const { t } = useTranslation("game_details");
|
||||||
|
const [priceData, setPriceData] = useState<any>(null);
|
||||||
|
const [isLoadingPrices, setIsLoadingPrices] = useState(false);
|
||||||
|
const { objectId } = useContext(gameDetailsContext);
|
||||||
|
|
||||||
|
const fetchGamePrices = useCallback(async (steamAppId: string) => {
|
||||||
|
setIsLoadingPrices(true);
|
||||||
|
try {
|
||||||
|
const apiKey =
|
||||||
|
userPreferences?.ggDealsApiKey || import.meta.env.VITE_GG_DEALS_API_KEY;
|
||||||
|
if (!apiKey) {
|
||||||
|
setPriceData(null);
|
||||||
|
setIsLoadingPrices(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = `${import.meta.env.VITE_GG_DEALS_API_URL}/?ids=${steamAppId}&key=${apiKey}®ion=br`;
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Network response was not ok");
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
setPriceData(data.data?.[steamAppId] ?? null);
|
||||||
|
} catch (error) {
|
||||||
|
setPriceData(null);
|
||||||
|
} finally {
|
||||||
|
setIsLoadingPrices(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (objectId) {
|
||||||
|
fetchGamePrices(objectId.toString());
|
||||||
|
}
|
||||||
|
}, [objectId, fetchGamePrices]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarSection title={t("prices")}>
|
||||||
|
{isLoadingPrices ? (
|
||||||
|
<div>{t("loading")}</div>
|
||||||
|
) : priceData ? (
|
||||||
|
<div>
|
||||||
|
<ul className="">
|
||||||
|
<li>
|
||||||
|
<b>{t("retail_price")}</b>: {t("currency_symbol")}
|
||||||
|
{priceData.prices.currentRetail}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>{t("keyshop_price")}</b>: {t("currency_symbol")}
|
||||||
|
{priceData.prices.currentKeyshops}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>{t("historical_retail")}</b>: {t("currency_symbol")}
|
||||||
|
{priceData.prices.historicalRetail}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>{t("historical_keyshop")}</b>: {t("currency_symbol")}
|
||||||
|
{priceData.prices.historicalKeyshops}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href={priceData.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="list__item"
|
||||||
|
>
|
||||||
|
{t("view_all_prices")}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>{t("no_prices_found")}</div>
|
||||||
|
)}
|
||||||
|
</SidebarSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
22
src/renderer/src/pages/game-details/sidebar/sidebar.scss
Normal file → Executable file
22
src/renderer/src/pages/game-details/sidebar/sidebar.scss
Normal file → Executable file
@@ -194,3 +194,25 @@
|
|||||||
.achievements-placeholder__blur {
|
.achievements-placeholder__blur {
|
||||||
filter: blur(4px);
|
filter: blur(4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-languages {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
padding: globals.$spacing-unit;
|
||||||
|
border-bottom: solid 1px globals.$border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-size: globals.$small-font-size;
|
||||||
|
color: globals.$muted-color;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
font-size: globals.$body-font-size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
5
src/renderer/src/pages/game-details/sidebar/sidebar.tsx
Normal file → Executable file
5
src/renderer/src/pages/game-details/sidebar/sidebar.tsx
Normal file → Executable file
@@ -21,6 +21,8 @@ import { SidebarSection } from "../sidebar-section/sidebar-section";
|
|||||||
import { buildGameAchievementPath } from "@renderer/helpers";
|
import { buildGameAchievementPath } from "@renderer/helpers";
|
||||||
import { useSubscription } from "@renderer/hooks/use-subscription";
|
import { useSubscription } from "@renderer/hooks/use-subscription";
|
||||||
import "./sidebar.scss";
|
import "./sidebar.scss";
|
||||||
|
import { GamePricesSection } from "./game-prices-section";
|
||||||
|
import { GameLanguageSection } from "./game-language-section";
|
||||||
|
|
||||||
const achievementsPlaceholder: UserAchievement[] = [
|
const achievementsPlaceholder: UserAchievement[] = [
|
||||||
{
|
{
|
||||||
@@ -115,6 +117,9 @@ export function Sidebar() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="content-sidebar">
|
<aside className="content-sidebar">
|
||||||
|
<GameLanguageSection />
|
||||||
|
<GamePricesSection />
|
||||||
|
|
||||||
{userDetails === null && (
|
{userDetails === null && (
|
||||||
<SidebarSection title={t("achievements")}>
|
<SidebarSection title={t("achievements")}>
|
||||||
<div className="achievements-placeholder">
|
<div className="achievements-placeholder">
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export function SettingsGeneral() {
|
|||||||
|
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState({
|
||||||
downloadsPath: "",
|
downloadsPath: "",
|
||||||
|
ggDealsApiKey: "",
|
||||||
downloadNotificationsEnabled: false,
|
downloadNotificationsEnabled: false,
|
||||||
repackUpdatesNotificationsEnabled: false,
|
repackUpdatesNotificationsEnabled: false,
|
||||||
friendRequestNotificationsEnabled: false,
|
friendRequestNotificationsEnabled: false,
|
||||||
@@ -100,6 +101,7 @@ export function SettingsGeneral() {
|
|||||||
setForm((prev) => ({
|
setForm((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
downloadsPath: userPreferences.downloadsPath ?? defaultDownloadsPath,
|
downloadsPath: userPreferences.downloadsPath ?? defaultDownloadsPath,
|
||||||
|
ggDealsApiKey: userPreferences.ggDealsApiKey ?? "",
|
||||||
downloadNotificationsEnabled:
|
downloadNotificationsEnabled:
|
||||||
userPreferences.downloadNotificationsEnabled ?? false,
|
userPreferences.downloadNotificationsEnabled ?? false,
|
||||||
repackUpdatesNotificationsEnabled:
|
repackUpdatesNotificationsEnabled:
|
||||||
@@ -206,6 +208,12 @@ export function SettingsGeneral() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label={t("gg_deals_api_key_description")}
|
||||||
|
value={form.ggDealsApiKey}
|
||||||
|
onChange={(e) => handleChange({ ggDealsApiKey: e.target.value })}
|
||||||
|
/>
|
||||||
|
|
||||||
<SelectField
|
<SelectField
|
||||||
label={t("language")}
|
label={t("language")}
|
||||||
value={form.language}
|
value={form.language}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export type AchievementCustomNotificationPosition =
|
|||||||
|
|
||||||
export interface UserPreferences {
|
export interface UserPreferences {
|
||||||
downloadsPath?: string | null;
|
downloadsPath?: string | null;
|
||||||
|
ggDealsApiKey?: string | null;
|
||||||
language?: string;
|
language?: string;
|
||||||
realDebridApiToken?: string | null;
|
realDebridApiToken?: string | null;
|
||||||
torBoxApiToken?: string | null;
|
torBoxApiToken?: string | null;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export interface SteamAppDetails {
|
|||||||
publishers: string[];
|
publishers: string[];
|
||||||
genres: SteamGenre[];
|
genres: SteamGenre[];
|
||||||
movies?: SteamMovies[];
|
movies?: SteamMovies[];
|
||||||
|
supported_languages: string;
|
||||||
screenshots?: SteamScreenshot[];
|
screenshots?: SteamScreenshot[];
|
||||||
pc_requirements: {
|
pc_requirements: {
|
||||||
minimum: string;
|
minimum: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user