feat: i18n and refactor

This commit is contained in:
Zamitto
2025-05-16 05:19:33 -03:00
parent 6f43da8d28
commit 0a4bdf160c
10 changed files with 94 additions and 97 deletions

View File

@@ -363,7 +363,14 @@
"install_common_redist": "Install",
"installing_common_redist": "Installing…",
"show_download_speed_in_megabytes": "Show download speed in megabytes per second",
"extract_files_by_default": "Extract files by default after download"
"extract_files_by_default": "Extract files by default after download",
"achievement_custom_notification_position": "Enable achievements custom notification",
"top_left": "Top left",
"top_center": "Top center",
"top_right": "Top right",
"bottom_left": "Bottom left",
"bottom_center": "Bottom center",
"bottom_right": "Bottom right"
},
"notifications": {
"download_complete": "Download complete",
@@ -379,7 +386,9 @@
"new_friend_request_title": "New friend request",
"extraction_complete": "Extraction complete",
"game_extracted": "{{title}} extracted successfully",
"friend_started_playing_game": "{{displayName}} started playing a game"
"friend_started_playing_game": "{{displayName}} started playing a game",
"test_achievement_notification_title": "This is a test notification",
"test_achievement_notification_description": "Pretty cool, huh?"
},
"system_tray": {
"open": "Open Hydra",

View File

@@ -349,7 +349,14 @@
"install_common_redist": "Instalar",
"installing_common_redist": "Instalando…",
"show_download_speed_in_megabytes": "Exibir taxas de download em megabytes por segundo",
"extract_files_by_default": "Extrair arquivos automaticamente após o download"
"extract_files_by_default": "Extrair arquivos automaticamente após o download",
"enable_achievement_custom_notifications": "Habilitar notificações de conquistas customizadas",
"top_left": "Superior esquerdo",
"top_center": "Superior central",
"top_right": "Superior direito",
"bottom_left": "Inferior esquerdo",
"bottom_right": "Inferior direito",
"bottom_center": "Inferior central"
},
"notifications": {
"download_complete": "Download concluído",
@@ -363,7 +370,9 @@
"new_friend_request_description": "{{displayName}} te enviou um pedido de amizade",
"extraction_complete": "Extração concluída",
"game_extracted": "{{title}} extraído com sucesso",
"friend_started_playing_game": "{{displayName}} começou a jogar"
"friend_started_playing_game": "{{displayName}} começou a jogar",
"test_achievement_notification_title": "Esta é uma notificação de teste",
"test_achievement_notification_description": "Bem legal, né?"
},
"system_tray": {
"open": "Abrir Hydra",

View File

@@ -1,4 +1,5 @@
import type {
AchievementNotificationInfo,
Game,
GameShop,
UnlockedAchievement,
@@ -86,7 +87,7 @@ export const mergeAchievements = async (
publishNotification &&
userPreferences?.achievementNotificationsEnabled
) {
const achievementsInfo = newAchievements
const achievementsInfo: AchievementNotificationInfo[] = newAchievements
.toSorted((a, b) => {
return a.unlockTime - b.unlockTime;
})
@@ -101,7 +102,12 @@ export const mergeAchievements = async (
.filter((achievement) => Boolean(achievement))
.map((achievement) => {
return {
displayName: achievement!.displayName,
title: achievement!.displayName,
description: achievement!.description,
points: achievement!.points,
isHidden: achievement!.hidden,
isRare: false,
isPlatinum: false,
iconUrl: achievement!.icon,
};
});
@@ -109,8 +115,6 @@ export const mergeAchievements = async (
if (userPreferences?.achievementCustomNotificationsEnabled !== false) {
WindowManager.notificationWindow?.webContents.send(
"on-achievement-unlocked",
game.objectId,
game.shop,
userPreferences.achievementCustomNotificationPosition ?? "top_left",
achievementsInfo
);

View File

@@ -162,7 +162,7 @@ export const publishExtractionCompleteNotification = async (game: Game) => {
};
export const publishNewAchievementNotification = async (info: {
achievements: { displayName: string; iconUrl: string }[];
achievements: { title: string; iconUrl: string }[];
unlockedAchievementCount: number;
totalAchievementCount: number;
gameTitle: string;
@@ -176,12 +176,12 @@ export const publishNewAchievementNotification = async (info: {
gameTitle: info.gameTitle,
achievementCount: info.achievements.length,
}),
body: info.achievements.map((a) => a.displayName).join(", "),
body: info.achievements.map((a) => a.title).join(", "),
icon: (await downloadImage(info.gameIcon)) ?? icon,
}
: {
title: t("achievement_unlocked", { ns: "achievement" }),
body: info.achievements[0].displayName,
body: info.achievements[0].title,
icon: (await downloadImage(info.achievements[0].iconUrl)) ?? icon,
};

View File

@@ -20,6 +20,7 @@ import { db, gamesSublevel, levelKeys } from "@main/level";
import { orderBy, slice } from "lodash-es";
import type {
AchievementCustomNotificationPosition,
AchievementNotificationInfo,
ScreenState,
UserPreferences,
} from "@types";
@@ -371,10 +372,28 @@ export class WindowManager {
}
if (showTestNotification) {
const language = userPreferences.language ?? "en";
setTimeout(() => {
this.notificationWindow?.webContents.send(
"on-test-achievement-notification",
userPreferences.achievementCustomNotificationPosition ?? "top_left"
"on-achievement-unlocked",
userPreferences.achievementCustomNotificationPosition ?? "top_left",
[
{
title: t("test_achievement_notification_title", {
ns: "notifications",
lng: language,
}),
description: t("test_achievement_notification_description", {
ns: "notifications",
lng: language,
}),
iconUrl: "https://cdn.losbroxas.org/favicon.svg",
points: 100,
isHidden: false,
isRare: false,
isPlatinum: false,
},
] as AchievementNotificationInfo[]
);
}, 1000);
}

View File

@@ -19,6 +19,7 @@ import type {
ShortcutLocation,
ShopAssets,
AchievementCustomNotificationPosition,
AchievementNotificationInfo,
} from "@types";
import type { AuthPage, CatalogueCategory } from "@shared";
import type { AxiosProgressEvent } from "axios";
@@ -409,31 +410,15 @@ contextBridge.exposeInMainWorld("electron", {
ipcRenderer.invoke("publishNewRepacksNotification", newRepacksCount),
onAchievementUnlocked: (
cb: (
objectId: string,
shop: GameShop,
position: AchievementCustomNotificationPosition,
achievements?: {
displayName: string;
iconUrl: string;
isHidden: boolean;
isRare: boolean;
isPlatinum: boolean;
}[]
position?: AchievementCustomNotificationPosition,
achievements?: AchievementNotificationInfo[]
) => void
) => {
const listener = (
_event: Electron.IpcRendererEvent,
objectId: string,
shop: GameShop,
position: AchievementCustomNotificationPosition,
achievements?: {
displayName: string;
iconUrl: string;
isHidden: boolean;
isRare: boolean;
isPlatinum: boolean;
}[]
) => cb(objectId, shop, position, achievements);
position?: AchievementCustomNotificationPosition,
achievements?: AchievementNotificationInfo[]
) => cb(position, achievements);
ipcRenderer.on("on-achievement-unlocked", listener);
return () =>
ipcRenderer.removeListener("on-achievement-unlocked", listener);
@@ -455,17 +440,6 @@ contextBridge.exposeInMainWorld("electron", {
return () =>
ipcRenderer.removeListener("on-combined-achievements-unlocked", listener);
},
onTestAchievementNotification: (
cb: (position: AchievementCustomNotificationPosition) => void
) => {
const listener = (
_event: Electron.IpcRendererEvent,
position: AchievementCustomNotificationPosition
) => cb(position);
ipcRenderer.on("on-test-achievement-notification", listener);
return () =>
ipcRenderer.removeListener("on-test-achievement-notification", listener);
},
updateAchievementCustomNotificationWindow: () =>
ipcRenderer.invoke("updateAchievementCustomNotificationWindow"),

View File

@@ -36,6 +36,7 @@ import type {
ShopAssets,
ShopDetailsWithAssets,
AchievementCustomNotificationPosition,
AchievementNotificationInfo,
} from "@types";
import type { AxiosProgressEvent } from "axios";
import type disk from "diskusage";
@@ -325,16 +326,8 @@ declare global {
publishNewRepacksNotification: (newRepacksCount: number) => Promise<void>;
onAchievementUnlocked: (
cb: (
objectId: string,
shop: GameShop,
position: AchievementCustomNotificationPosition,
achievements?: {
displayName: string;
iconUrl: string;
isHidden: boolean;
isRare: boolean;
isPlatinum: boolean;
}[]
position?: AchievementCustomNotificationPosition,
achievements?: AchievementNotificationInfo[]
) => void
) => () => Electron.IpcRenderer;
onCombinedAchievementsUnlocked: (
@@ -344,9 +337,6 @@ declare global {
position: AchievementCustomNotificationPosition
) => void
) => () => Electron.IpcRenderer;
onTestAchievementNotification: (
cb: (position: AchievementCustomNotificationPosition) => void
) => Electron.IpcRenderer;
updateAchievementCustomNotificationWindow: () => Promise<void>;
/* Themes */

View File

@@ -3,14 +3,12 @@ import achievementSound from "@renderer/assets/audio/achievement.wav";
import { useTranslation } from "react-i18next";
import cn from "classnames";
import "./achievement-notification.scss";
import { AchievementCustomNotificationPosition } from "@types";
import {
AchievementCustomNotificationPosition,
AchievementNotificationInfo,
} from "@types";
interface AchievementInfo {
displayName: string;
iconUrl: string;
}
const NOTIFICATION_TIMEOUT = 4000;
const NOTIFICATION_TIMEOUT = 6000;
export function AchievementNotification() {
const { t } = useTranslation("achievement");
@@ -20,9 +18,11 @@ export function AchievementNotification() {
const [position, setPosition] =
useState<AchievementCustomNotificationPosition>("top_left");
const [achievements, setAchievements] = useState<AchievementInfo[]>([]);
const [achievements, setAchievements] = useState<
AchievementNotificationInfo[]
>([]);
const [currentAchievement, setCurrentAchievement] =
useState<AchievementInfo | null>(null);
useState<AchievementNotificationInfo | null>(null);
const achievementAnimation = useRef(-1);
const closingAnimation = useRef(-1);
@@ -43,10 +43,14 @@ export function AchievementNotification() {
setAchievements([
{
displayName: t("new_achievements_unlocked", {
title: t("new_achievements_unlocked", {
gameCount,
achievementCount,
}),
isHidden: false,
isRare: false,
isPlatinum: false,
points: 0,
iconUrl:
"https://avatars.githubusercontent.com/u/164102380?s=400&u=01a13a7b4f0c642f7e547b8e1d70440ea06fa750&v=4",
},
@@ -63,10 +67,12 @@ export function AchievementNotification() {
useEffect(() => {
const unsubscribe = window.electron.onAchievementUnlocked(
(_object, _shop, position, achievements) => {
(position, achievements) => {
if (!achievements?.length) return;
if (position) {
setPosition(position);
}
setPosition(position);
setAchievements((ach) => ach.concat(achievements));
playAudio();
@@ -78,29 +84,6 @@ export function AchievementNotification() {
};
}, [playAudio]);
useEffect(() => {
const unsubscribe = window.electron.onTestAchievementNotification(
(position) => {
setPosition(position);
setAchievements((ach) =>
ach.concat([
{
displayName: "Test Achievement",
iconUrl:
"https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fc.tenor.com%2FRwKr7hVnXREAAAAC%2Fnyan-cat.gif&f=1&nofb=1&ipt=706fd8b00cbfb5b2d2621603834d5f32c0f34cce7113de228d2fcc2247a80318",
},
])
);
playAudio();
}
);
return () => {
unsubscribe();
};
}, [playAudio]);
const hasAchievementsPending = achievements.length > 0;
const startAnimateClosing = useCallback(() => {
@@ -169,15 +152,15 @@ export function AchievementNotification() {
<div className="achievement-notification__content">
<img
src={currentAchievement.iconUrl}
alt={currentAchievement.displayName}
alt={currentAchievement.title}
className="achievement-notification__icon"
/>
<div className="achievement-notification__text-container">
<p className="achievement-notification__title">
{t("achievement_unlocked")}
{currentAchievement.title}
</p>
<p className="achievement-notification__description">
{currentAchievement.displayName}
{currentAchievement.description}
</p>
</div>
</div>

View File

@@ -122,8 +122,8 @@ export function SettingsGeneral() {
"top_center",
"top_right",
"bottom_left",
"bottom_right",
"bottom_center",
"bottom_right",
].map((position) => ({
key: position,
value: position,

View File

@@ -263,6 +263,15 @@ export type GameAchievementFiles = {
[id: string]: AchievementFile[];
};
export interface AchievementNotificationInfo {
title: string;
description?: string;
iconUrl: string;
isHidden: boolean;
isRare: boolean;
isPlatinum: boolean;
points?: number;
}
export interface GameArtifact {
id: string;
artifactLengthInBytes: number;