diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 00000000..0b0c009c --- /dev/null +++ b/.cursorrules @@ -0,0 +1,29 @@ +# Hydra Project Rules + +## Logging + +- **Always use `logger` instead of `console` for logging** in both main and renderer processes +- In main process: `import { logger } from "@main/services";` +- In renderer process: `import { logger } from "@renderer/logger";` +- Replace all instances of: + - `console.log()` → `logger.log()` + - `console.error()` → `logger.error()` + - `console.warn()` → `logger.warn()` + - `console.info()` → `logger.info()` + - `console.debug()` → `logger.debug()` +- Do not use `console` for any logging purposes + +## Internationalization (i18n) + +- All user-facing strings must be translated using i18next +- Use the `useTranslation` hook in React components: `const { t } = useTranslation("namespace");` +- Add new translation keys to `src/locales/en/translation.json` +- Never hardcode English strings in the UI code +- Placeholder text in form fields must also be translated + +## Code Style + +- Use ESLint and Prettier for code formatting +- Follow TypeScript strict mode conventions +- Use async/await instead of promises when possible +- Prefer named exports over default exports for utilities and services diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 46bdb28c..bfc4a379 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -428,6 +428,8 @@ "validate_download_source": "Validate", "remove_download_source": "Remove", "add_download_source": "Add source", + "adding": "Adding…", + "failed_add_download_source": "Failed to add download source. Please try again.", "download_count_zero": "No download options", "download_count_one": "{{countFormatted}} download option", "download_count_other": "{{countFormatted}} download options", @@ -435,9 +437,16 @@ "add_download_source_description": "Insert the URL of the .json file", "download_source_up_to_date": "Up-to-date", "download_source_errored": "Errored", + "download_source_pending_matching": "Updating soon", + "download_source_matched": "Up to date", + "download_source_matching": "Updating", + "download_source_failed": "Error", + "download_source_no_information": "No information available", "sync_download_sources": "Sync sources", "removed_download_source": "Download source removed", "removed_download_sources": "Download sources removed", + "removed_all_download_sources": "All download sources removed", + "download_sources_synced_successfully": "All download sources are synced", "cancel_button_confirmation_delete_all_sources": "No", "confirm_button_confirmation_delete_all_sources": "Yes, delete everything", "title_confirmation_delete_all_sources": "Delete all download sources", @@ -468,6 +477,7 @@ "seed_after_download_complete": "Seed after download complete", "show_hidden_achievement_description": "Show hidden achievements description before unlocking them", "account": "Account", + "hydra_cloud": "Hydra Cloud", "no_users_blocked": "You have no blocked users", "subscription_active_until": "Your Hydra Cloud is active until {{date}}", "manage_subscription": "Manage subscription", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 4ea77015..968483a6 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -423,7 +423,13 @@ "add_download_source_description": "Insira a URL contendo o arquivo .json", "download_source_up_to_date": "Sincronizada", "download_source_errored": "Falhou", + "download_source_pending_matching": "Importando em breve", + "download_source_matched": "Sincronizada", + "download_source_matching": "Sincronizando", + "download_source_failed": "Erro", + "download_source_no_information": "Sem informações", "sync_download_sources": "Sincronizar", + "download_sources_synced_successfully": "Fontes de download sincronizadas", "removed_download_source": "Fonte removida", "removed_download_sources": "Fontes removidas", "cancel_button_confirmation_delete_all_sources": "Não", diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index 962504d4..2894cf65 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -252,7 +252,13 @@ "add_download_source_description": "Insere o URL que contém o ficheiro .json", "download_source_up_to_date": "Sincronizada", "download_source_errored": "Falhou", + "download_source_pending_matching": "A atualizar em breve", + "download_source_matched": "Atualizado", + "download_source_matching": "A atualizar", + "download_source_failed": "Erro", + "download_source_no_information": "Sem informações", "sync_download_sources": "Sincronizar", + "download_sources_synced_successfully": "Fontes de download sincronizadas", "removed_download_source": "Fonte removida", "cancel_button_confirmation_delete_all_sources": "Não", "confirm_button_confirmation_delete_all_sources": "Sim, apague tudo", diff --git a/src/renderer/src/pages/settings/add-download-source-modal.tsx b/src/renderer/src/pages/settings/add-download-source-modal.tsx index 2b45ed72..d7071391 100644 --- a/src/renderer/src/pages/settings/add-download-source-modal.tsx +++ b/src/renderer/src/pages/settings/add-download-source-modal.tsx @@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next"; import { Button, Modal, TextField } from "@renderer/components"; import { settingsContext } from "@renderer/context"; import { useForm } from "react-hook-form"; +import { logger } from "@renderer/logger"; import * as yup from "yup"; import { yupResolver } from "@hookform/resolvers/yup"; @@ -55,10 +56,10 @@ export function AddDownloadSourceModal({ onClose(); onAddDownloadSource(); } catch (error) { - console.error("Failed to add download source:", error); + logger.error("Failed to add download source:", error); setError("url", { type: "server", - message: "Failed to add download source. Please try again.", + message: t("failed_add_download_source"), }); } finally { setIsLoading(false); diff --git a/src/renderer/src/pages/settings/settings-account.tsx b/src/renderer/src/pages/settings/settings-account.tsx index 9cf35541..f2825cca 100644 --- a/src/renderer/src/pages/settings/settings-account.tsx +++ b/src/renderer/src/pages/settings/settings-account.tsx @@ -201,7 +201,7 @@ export function SettingsAccount() {
-

Hydra Cloud

+

{t("hydra_cloud")}

{getHydraCloudSectionContent().description}
diff --git a/src/renderer/src/pages/settings/settings-download-sources.scss b/src/renderer/src/pages/settings/settings-download-sources.scss index a12bdff3..df0f5c8b 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.scss +++ b/src/renderer/src/pages/settings/settings-download-sources.scss @@ -1,5 +1,14 @@ @use "../../scss/globals.scss"; +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + .settings-download-sources { &__list { padding: 0; @@ -22,6 +31,17 @@ &--syncing { opacity: globals.$disabled-opacity; } + + &--pending { + opacity: 0.6; + } + } + + &__spinner { + animation: spin 1s linear infinite; + margin-right: calc(globals.$spacing-unit / 2); + width: 12px; + height: 12px; } &__item-header { diff --git a/src/renderer/src/pages/settings/settings-download-sources.tsx b/src/renderer/src/pages/settings/settings-download-sources.tsx index 08670145..75f0cc73 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.tsx +++ b/src/renderer/src/pages/settings/settings-download-sources.tsx @@ -22,6 +22,7 @@ import { settingsContext } from "@renderer/context"; import { useNavigate } from "react-router-dom"; import { setFilters, clearFilters } from "@renderer/features"; import "./settings-download-sources.scss"; +import { logger } from "@renderer/logger"; export function SettingsDownloadSources() { const [ @@ -58,6 +59,30 @@ export function SettingsDownloadSources() { fetchDownloadSources(); }, []); + useEffect(() => { + const hasPendingOrMatchingSource = downloadSources.some( + (source) => + source.status === DownloadSourceStatus.PendingMatching || + source.status === DownloadSourceStatus.Matching + ); + + if (!hasPendingOrMatchingSource || !downloadSources.length) { + return; + } + + const intervalId = setInterval(async () => { + try { + await window.electron.syncDownloadSources(); + const sources = await window.electron.getDownloadSources(); + setDownloadSources(sources); + } catch (error) { + logger.error("Failed to fetch download sources:", error); + } + }, 5000); + + return () => clearInterval(intervalId); + }, [downloadSources]); + const handleRemoveSource = async (downloadSource: DownloadSource) => { setIsRemovingDownloadSource(true); @@ -67,7 +92,7 @@ export function SettingsDownloadSources() { setDownloadSources(sources as DownloadSource[]); showSuccessToast(t("removed_download_source")); } catch (error) { - console.error("Failed to remove download source:", error); + logger.error("Failed to remove download source:", error); } finally { setIsRemovingDownloadSource(false); } @@ -82,7 +107,7 @@ export function SettingsDownloadSources() { setDownloadSources(sources as DownloadSource[]); showSuccessToast(t("removed_all_download_sources")); } catch (error) { - console.error("Failed to remove all download sources:", error); + logger.error("Failed to remove all download sources:", error); } finally { setIsRemovingDownloadSource(false); setShowConfirmationDeleteAllSourcesModal(false); @@ -94,7 +119,7 @@ export function SettingsDownloadSources() { const sources = await window.electron.getDownloadSources(); setDownloadSources(sources as DownloadSource[]); } catch (error) { - console.error("Failed to refresh download sources:", error); + logger.error("Failed to refresh download sources:", error); } }; @@ -127,7 +152,7 @@ export function SettingsDownloadSources() { const navigateToCatalogue = (fingerprint?: string) => { if (!fingerprint) { - console.error("Cannot navigate: fingerprint is undefined"); + logger.error("Cannot navigate: fingerprint is undefined"); return; } @@ -202,16 +227,25 @@ export function SettingsDownloadSources() {