Merge pull request #1780 from saraiva1989/feature-add-low-price-and-game-language
Some checks failed
Release / build (ubuntu-latest) (push) Has been cancelled
Release / build (windows-latest) (push) Has been cancelled

Feature add low price and game language
This commit is contained in:
Chubby Granny Chaser
2025-09-28 01:02:18 +01:00
committed by GitHub
10 changed files with 223 additions and 2 deletions

View File

@@ -3,3 +3,5 @@ MAIN_VITE_AUTH_URL=
MAIN_VITE_WS_URL=
RENDERER_VITE_REAL_DEBRID_REFERRAL_ID=
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
View File

@@ -230,7 +230,21 @@
"backup_frozen": "Backup pinned",
"backup_unfrozen": "Backup unpinned",
"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": {
"title": "Activate Hydra",
@@ -276,6 +290,7 @@
"change": "Update",
"notifications": "Notifications",
"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",
"real_debrid_api_token_label": "Real-Debrid API token",
"quit_app_instead_hiding": "Don't hide Hydra when closing",

17
src/locales/pt-BR/translation.json Normal file → Executable file
View File

@@ -207,7 +207,21 @@
"backup_frozen": "Backup fixado",
"backup_unfrozen": "Backup removido dos fixados",
"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": {
"title": "Ativação",
@@ -253,6 +267,7 @@
"change": "Explorar...",
"notifications": "Notificações",
"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",
"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",

View 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>
);
}

View 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}&region=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>
);
}

View File

@@ -194,3 +194,25 @@
.achievements-placeholder__blur {
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;
}
}

View File

@@ -21,6 +21,8 @@ import { SidebarSection } from "../sidebar-section/sidebar-section";
import { buildGameAchievementPath } from "@renderer/helpers";
import { useSubscription } from "@renderer/hooks/use-subscription";
import "./sidebar.scss";
import { GamePricesSection } from "./game-prices-section";
import { GameLanguageSection } from "./game-language-section";
const achievementsPlaceholder: UserAchievement[] = [
{
@@ -115,6 +117,9 @@ export function Sidebar() {
return (
<aside className="content-sidebar">
<GameLanguageSection />
<GamePricesSection />
{userDetails === null && (
<SidebarSection title={t("achievements")}>
<div className="achievements-placeholder">

View File

@@ -35,6 +35,7 @@ export function SettingsGeneral() {
const [form, setForm] = useState({
downloadsPath: "",
ggDealsApiKey: "",
downloadNotificationsEnabled: false,
repackUpdatesNotificationsEnabled: false,
friendRequestNotificationsEnabled: false,
@@ -100,6 +101,7 @@ export function SettingsGeneral() {
setForm((prev) => ({
...prev,
downloadsPath: userPreferences.downloadsPath ?? defaultDownloadsPath,
ggDealsApiKey: userPreferences.ggDealsApiKey ?? "",
downloadNotificationsEnabled:
userPreferences.downloadNotificationsEnabled ?? false,
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
label={t("language")}
value={form.language}

View File

@@ -84,6 +84,7 @@ export type AchievementCustomNotificationPosition =
export interface UserPreferences {
downloadsPath?: string | null;
ggDealsApiKey?: string | null;
language?: string;
realDebridApiToken?: string | null;
torBoxApiToken?: string | null;

View File

@@ -32,6 +32,7 @@ export interface SteamAppDetails {
publishers: string[];
genres: SteamGenre[];
movies?: SteamMovies[];
supported_languages: string;
screenshots?: SteamScreenshot[];
pc_requirements: {
minimum: string;