From 0a4bdf160c06e9f451158b8abba022b9a933cf14 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 16 May 2025 05:19:33 -0300 Subject: [PATCH] feat: i18n and refactor --- src/locales/en/translation.json | 13 +++- src/locales/pt-BR/translation.json | 13 +++- .../achievements/merge-achievements.ts | 12 ++-- src/main/services/notifications/index.ts | 6 +- src/main/services/window-manager.ts | 23 +++++++- src/preload/index.ts | 38 ++---------- src/renderer/src/declaration.d.ts | 16 +---- .../notification/achievement-notification.tsx | 59 +++++++------------ .../src/pages/settings/settings-general.tsx | 2 +- src/types/index.ts | 9 +++ 10 files changed, 94 insertions(+), 97 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 2902218e..d342cc46 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -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", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 5509e07b..81490142 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -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", diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index 761c60e6..6e070c7d 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -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 ); diff --git a/src/main/services/notifications/index.ts b/src/main/services/notifications/index.ts index 77866a47..fa9ac593 100644 --- a/src/main/services/notifications/index.ts +++ b/src/main/services/notifications/index.ts @@ -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, }; diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 807294c7..dc07e673 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -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); } diff --git a/src/preload/index.ts b/src/preload/index.ts index 48e79d36..c80f60ba 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -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"), diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index ac3bc9d2..5cdd8c04 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -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; 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; /* Themes */ diff --git a/src/renderer/src/pages/achievements/notification/achievement-notification.tsx b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx index 271c42ea..4689e0d9 100644 --- a/src/renderer/src/pages/achievements/notification/achievement-notification.tsx +++ b/src/renderer/src/pages/achievements/notification/achievement-notification.tsx @@ -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("top_left"); - const [achievements, setAchievements] = useState([]); + const [achievements, setAchievements] = useState< + AchievementNotificationInfo[] + >([]); const [currentAchievement, setCurrentAchievement] = - useState(null); + useState(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() {
{currentAchievement.displayName}

- {t("achievement_unlocked")} + {currentAchievement.title}

- {currentAchievement.displayName} + {currentAchievement.description}

diff --git a/src/renderer/src/pages/settings/settings-general.tsx b/src/renderer/src/pages/settings/settings-general.tsx index 8d9de622..792c4603 100644 --- a/src/renderer/src/pages/settings/settings-general.tsx +++ b/src/renderer/src/pages/settings/settings-general.tsx @@ -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, diff --git a/src/types/index.ts b/src/types/index.ts index aa71b5e5..c842d999 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -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;