mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-03 00:33:49 +03:00
Merge branch 'rc/v2.0' into feat/show-toast-after-create-shortcut
This commit is contained in:
commit
dcdc6a7114
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -1,6 +1,6 @@
|
|||||||
name: Build
|
name: Build
|
||||||
|
|
||||||
on: [pull_request]
|
on: pull_request
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@ -28,6 +28,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
MAIN_VITE_ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
MAIN_VITE_ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
||||||
MAIN_VITE_ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
MAIN_VITE_ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
||||||
|
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build Windows
|
- name: Build Windows
|
||||||
@ -36,6 +37,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
MAIN_VITE_ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
MAIN_VITE_ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
||||||
MAIN_VITE_ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
MAIN_VITE_ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
||||||
|
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Create artifact
|
- name: Create artifact
|
||||||
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@ -1,6 +1,6 @@
|
|||||||
name: Lint
|
name: Lint
|
||||||
|
|
||||||
on: [pull_request]
|
on: [pull_request, push]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
|
@ -5,7 +5,9 @@ directories:
|
|||||||
extraResources:
|
extraResources:
|
||||||
- aria2
|
- aria2
|
||||||
- seeds
|
- seeds
|
||||||
- fastlist.exe
|
- from: node_modules/ps-list/vendor/fastlist-0.3.0-x64.exe
|
||||||
|
to: fastlist.exe
|
||||||
|
- from: node_modules/create-desktop-shortcuts/src/windows.vbs
|
||||||
files:
|
files:
|
||||||
- "!**/.vscode/*"
|
- "!**/.vscode/*"
|
||||||
- "!src/*"
|
- "!src/*"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hydralauncher",
|
"name": "hydralauncher",
|
||||||
"version": "1.2.4",
|
"version": "2.0.0",
|
||||||
"description": "Hydra",
|
"description": "Hydra",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"author": "Los Broxas",
|
"author": "Los Broxas",
|
||||||
@ -53,12 +53,14 @@
|
|||||||
"electron-log": "^5.1.4",
|
"electron-log": "^5.1.4",
|
||||||
"electron-updater": "^6.1.8",
|
"electron-updater": "^6.1.8",
|
||||||
"fetch-cookie": "^3.0.1",
|
"fetch-cookie": "^3.0.1",
|
||||||
|
"file-type": "^19.0.0",
|
||||||
"flexsearch": "^0.7.43",
|
"flexsearch": "^0.7.43",
|
||||||
"i18next": "^23.11.2",
|
"i18next": "^23.11.2",
|
||||||
"i18next-browser-languagedetector": "^7.2.1",
|
"i18next-browser-languagedetector": "^7.2.1",
|
||||||
"icojs": "^0.19.3",
|
"icojs": "^0.19.3",
|
||||||
"iso-639-1": "3.1.2",
|
"iso-639-1": "3.1.2",
|
||||||
"jsdom": "^24.0.0",
|
"jsdom": "^24.0.0",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"lottie-react": "^2.4.0",
|
"lottie-react": "^2.4.0",
|
||||||
"parse-torrent": "^11.0.16",
|
"parse-torrent": "^11.0.16",
|
||||||
@ -81,7 +83,9 @@
|
|||||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||||
"@swc/core": "^1.4.16",
|
"@swc/core": "^1.4.16",
|
||||||
"@types/auto-launch": "^5.0.5",
|
"@types/auto-launch": "^5.0.5",
|
||||||
|
"@types/color": "^3.0.6",
|
||||||
"@types/jsdom": "^21.1.6",
|
"@types/jsdom": "^21.1.6",
|
||||||
|
"@types/jsonwebtoken": "^9.0.6",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^20.12.7",
|
"@types/node": "^20.12.7",
|
||||||
"@types/parse-torrent": "^5.8.7",
|
"@types/parse-torrent": "^5.8.7",
|
||||||
|
@ -47,11 +47,4 @@ const downloadAria2 = async () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.platform === "win32") {
|
|
||||||
fs.copyFileSync(
|
|
||||||
"node_modules/ps-list/vendor/fastlist-0.3.0-x64.exe",
|
|
||||||
"fastlist.exe"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadAria2();
|
downloadAria2();
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
"featured": "مميّز",
|
"featured": "مميّز",
|
||||||
"recently_added": "مضاف مؤخراً",
|
|
||||||
"trending": "شائع",
|
"trending": "شائع",
|
||||||
"surprise_me": "فاجئني",
|
"surprise_me": "فاجئني",
|
||||||
"no_results": "لم يتم العثور على نتائج"
|
"no_results": "لم يتم العثور على نتائج"
|
||||||
@ -15,12 +14,7 @@
|
|||||||
"paused": "{{title}} (متوقف)",
|
"paused": "{{title}} (متوقف)",
|
||||||
"downloading": "{{title}} ({{percentage}} - جارٍ التنزيل...)",
|
"downloading": "{{title}} ({{percentage}} - جارٍ التنزيل...)",
|
||||||
"filter": "بحث في المكتبة",
|
"filter": "بحث في المكتبة",
|
||||||
"follow_us": "تابعنا",
|
"home": "الرئيسية"
|
||||||
"home": "الرئيسية",
|
|
||||||
"discord": "انضم إلى الـDiscord الخاص بنا",
|
|
||||||
"telegram": "انضم إلى قناة Telegram الخاصة بنا",
|
|
||||||
"x": "تابعنا على X",
|
|
||||||
"github": "ساهم في مشروعنا على GitHub"
|
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "ابحث عن الألعاب",
|
"search": "ابحث عن الألعاب",
|
||||||
@ -50,7 +44,6 @@
|
|||||||
"pause": "إيقاف",
|
"pause": "إيقاف",
|
||||||
"cancel": "إلغاء",
|
"cancel": "إلغاء",
|
||||||
"remove": "إزالة",
|
"remove": "إزالة",
|
||||||
"remove_from_list": "إزالة",
|
|
||||||
"space_left_on_disk": "{{space}} متبقية على القرص",
|
"space_left_on_disk": "{{space}} متبقية على القرص",
|
||||||
"eta": "الوقت المتبقي {{eta}}",
|
"eta": "الوقت المتبقي {{eta}}",
|
||||||
"downloading_metadata": "جاري تنزيل البيانات الوصفية...",
|
"downloading_metadata": "جاري تنزيل البيانات الوصفية...",
|
||||||
@ -58,12 +51,8 @@
|
|||||||
"requirements": "متطلبات النظام",
|
"requirements": "متطلبات النظام",
|
||||||
"minimum": "الحد الأدنى",
|
"minimum": "الحد الأدنى",
|
||||||
"recommended": "موصى به",
|
"recommended": "موصى به",
|
||||||
"no_minimum_requirements": "{{title}} لا تتوفر معلومات عن الحد الأدنى للمتطلبات",
|
|
||||||
"no_recommended_requirements": "{{title}} لا تتوفر معلومات عن المتطلبات الموصى بها",
|
|
||||||
"release_date": "تم الإصدار في {{date}}",
|
"release_date": "تم الإصدار في {{date}}",
|
||||||
"publisher": "نشر بواسطة {{publisher}}",
|
"publisher": "نشر بواسطة {{publisher}}",
|
||||||
"copy_link_to_clipboard": "نسخ الرابط",
|
|
||||||
"copied_link_to_clipboard": "تم نسخ الرابط",
|
|
||||||
"hours": "ساعات",
|
"hours": "ساعات",
|
||||||
"minutes": "دقائق",
|
"minutes": "دقائق",
|
||||||
"amount_hours": "{{amount}} ساعات",
|
"amount_hours": "{{amount}} ساعات",
|
||||||
@ -84,14 +73,6 @@
|
|||||||
"repacks_modal_description": "اختر الحزمة التي تريد تنزيلها",
|
"repacks_modal_description": "اختر الحزمة التي تريد تنزيلها",
|
||||||
"select_folder_hint": "لتغيير المجلد الافتراضي، انتقل إلى الإعدادات",
|
"select_folder_hint": "لتغيير المجلد الافتراضي، انتقل إلى الإعدادات",
|
||||||
"download_now": "تنزيل الآن",
|
"download_now": "تنزيل الآن",
|
||||||
"installation_instructions": "إرشادات التثبيت",
|
|
||||||
"installation_instructions_description": "هناك خطوات إضافية مطلوبة لتثبيت هذه اللعبة",
|
|
||||||
"online_fix_instruction": "تتطلب ألعاب OnlineFix كلمة مرور لاستخراجها. عند الحاجة، استخدم كلمة المرور التالية:",
|
|
||||||
"dodi_installation_instruction": "عند فتح مثبت DODI، اضغط على مفتاح التشغيل لأعلى <0 /> لبدء عملية التثبيت:",
|
|
||||||
"dont_show_it_again": "لا تعرضها مرة أخرى",
|
|
||||||
"copy_to_clipboard": "نسخ",
|
|
||||||
"copied_to_clipboard": "تم النسخ",
|
|
||||||
"got_it": "حسنأ",
|
|
||||||
"no_shop_details": "لم يتم استرداد تفاصيل المتجر.",
|
"no_shop_details": "لم يتم استرداد تفاصيل المتجر.",
|
||||||
"download_options": "خيارات التنزيل",
|
"download_options": "خيارات التنزيل",
|
||||||
"download_path": "مسار التنزيل",
|
"download_path": "مسار التنزيل",
|
||||||
@ -114,17 +95,13 @@
|
|||||||
"eta": "الوقت المتبقي {{eta}}",
|
"eta": "الوقت المتبقي {{eta}}",
|
||||||
"paused": "متوقفة مؤقتًا",
|
"paused": "متوقفة مؤقتًا",
|
||||||
"verifying": "جار التحقق…",
|
"verifying": "جار التحقق…",
|
||||||
"completed_at": "اكتمل في {{date}}",
|
|
||||||
"completed": "اكتمل",
|
"completed": "اكتمل",
|
||||||
"download_again": "تحميل مرة أخرى",
|
|
||||||
"cancel": "إلغاء",
|
"cancel": "إلغاء",
|
||||||
"filter": "تصفية الألعاب التي تم تنزيلها",
|
"filter": "تصفية الألعاب التي تم تنزيلها",
|
||||||
"remove": "إزالة",
|
"remove": "إزالة",
|
||||||
"downloading_metadata": "جار تنزيل البيانات الوصفية…",
|
"downloading_metadata": "جار تنزيل البيانات الوصفية…",
|
||||||
"starting_download": "يبدأ التنزيل…",
|
|
||||||
"deleting": "جار حذف المثبت…",
|
"deleting": "جار حذف المثبت…",
|
||||||
"delete": "إزالة المثبت",
|
"delete": "إزالة المثبت",
|
||||||
"remove_from_list": "إزالة",
|
|
||||||
"delete_modal_title": "هل أنت متأكد؟",
|
"delete_modal_title": "هل أنت متأكد؟",
|
||||||
"delete_modal_description": "سيؤدي هذا إلى إزالة جميع ملفات التثبيت من جهاز الكمبيوتر الخاص بك",
|
"delete_modal_description": "سيؤدي هذا إلى إزالة جميع ملفات التثبيت من جهاز الكمبيوتر الخاص بك",
|
||||||
"install": "تثبيت"
|
"install": "تثبيت"
|
||||||
@ -135,8 +112,6 @@
|
|||||||
"notifications": "الإشعارات",
|
"notifications": "الإشعارات",
|
||||||
"enable_download_notifications": "عند اكتمال التنزيل",
|
"enable_download_notifications": "عند اكتمال التنزيل",
|
||||||
"enable_repack_list_notifications": "عند إضافة حزمة جديدة",
|
"enable_repack_list_notifications": "عند إضافة حزمة جديدة",
|
||||||
"telemetry": "القياس عن بعد",
|
|
||||||
"telemetry_description": "تفعيل إحصائيات الاستخدام مجهولة المصدر",
|
|
||||||
"real_debrid_api_token_label": "رمز واجهة برمجة التطبيقات (API) لـReal-Debrid ",
|
"real_debrid_api_token_label": "رمز واجهة برمجة التطبيقات (API) لـReal-Debrid ",
|
||||||
"quit_app_instead_hiding": "إنهاء هايدرا بدلاً من التصغير الى شريط الحالة",
|
"quit_app_instead_hiding": "إنهاء هايدرا بدلاً من التصغير الى شريط الحالة",
|
||||||
"launch_with_system": "تشغيل هايدرا عند بدء تشغيل النظام",
|
"launch_with_system": "تشغيل هايدرا عند بدء تشغيل النظام",
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
"featured": "Рэкамэндаванае",
|
"featured": "Рэкамэндаванае",
|
||||||
"recently_added": "Нядаўна дададзенае",
|
|
||||||
"trending": "Актуальнае",
|
"trending": "Актуальнае",
|
||||||
"surprise_me": "Здзіві мяне",
|
"surprise_me": "Здзіві мяне",
|
||||||
"no_results": "Няма вынікаў"
|
"no_results": "Няма вынікаў"
|
||||||
@ -15,12 +14,7 @@
|
|||||||
"paused": "{{title}} (Спынена)",
|
"paused": "{{title}} (Спынена)",
|
||||||
"downloading": "{{title}} ({{percentage}} - Сцягванне…)",
|
"downloading": "{{title}} ({{percentage}} - Сцягванне…)",
|
||||||
"filter": "Фільтар бібліятэкі",
|
"filter": "Фільтар бібліятэкі",
|
||||||
"follow_us": "Падпісвайцеся на нас",
|
"home": "Галоўная"
|
||||||
"home": "Галоўная",
|
|
||||||
"discord": "Далучайцеся да Discord",
|
|
||||||
"telegram": "Далучайцеся да Telegram",
|
|
||||||
"x": "Падпісвайцеся на X",
|
|
||||||
"github": "Зрабіць свой унёсак на GitHub"
|
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Пошук",
|
"search": "Пошук",
|
||||||
@ -50,7 +44,6 @@
|
|||||||
"pause": "Спыніць",
|
"pause": "Спыніць",
|
||||||
"cancel": "Скасаваць",
|
"cancel": "Скасаваць",
|
||||||
"remove": "Выдаліць",
|
"remove": "Выдаліць",
|
||||||
"remove_from_list": "Выдаліць",
|
|
||||||
"space_left_on_disk": "{{space}} засталося на дыску",
|
"space_left_on_disk": "{{space}} засталося на дыску",
|
||||||
"eta": "Канчатак {{eta}}",
|
"eta": "Канчатак {{eta}}",
|
||||||
"downloading_metadata": "Сцягванне мэтаданых…",
|
"downloading_metadata": "Сцягванне мэтаданых…",
|
||||||
@ -58,12 +51,8 @@
|
|||||||
"requirements": "Сістэмныя патрэбаванни",
|
"requirements": "Сістэмныя патрэбаванни",
|
||||||
"minimum": "Мінімальныя",
|
"minimum": "Мінімальныя",
|
||||||
"recommended": "Рэкамендуемыя",
|
"recommended": "Рэкамендуемыя",
|
||||||
"no_minimum_requirements": "{{title}} ня ўтрымлівае інфармацыі пра мінімальныя патрабаванні",
|
|
||||||
"no_recommended_requirements": "{{title}} ня ўтрымлівае інфармацыі пра рэкамендуемыя патрабаванні",
|
|
||||||
"release_date": "Выпушчана {{date}}",
|
"release_date": "Выпушчана {{date}}",
|
||||||
"publisher": "Выдана {{publisher}}",
|
"publisher": "Выдана {{publisher}}",
|
||||||
"copy_link_to_clipboard": "Скапіяваць спасылку",
|
|
||||||
"copied_link_to_clipboard": "Спасылка скапіявана",
|
|
||||||
"hours": "гадзін",
|
"hours": "гадзін",
|
||||||
"minutes": "хвілін",
|
"minutes": "хвілін",
|
||||||
"amount_hours": "{{amount}} гадзін",
|
"amount_hours": "{{amount}} гадзін",
|
||||||
@ -83,15 +72,7 @@
|
|||||||
"change": "Змяніць",
|
"change": "Змяніць",
|
||||||
"repacks_modal_description": "Абярыце рэпак, які хочаце сцягнуць",
|
"repacks_modal_description": "Абярыце рэпак, які хочаце сцягнуць",
|
||||||
"select_folder_hint": "Каб змяніць папку па змоўчанні, адкрыйце",
|
"select_folder_hint": "Каб змяніць папку па змоўчанні, адкрыйце",
|
||||||
"download_now": "Сцягнуць зараз",
|
"download_now": "Сцягнуць зараз"
|
||||||
"installation_instructions": "Інструкцыя ўсталёўкі",
|
|
||||||
"installation_instructions_description": "Усталёўка гэтай гульні патрабуе дадатковых крокаў",
|
|
||||||
"online_fix_instruction": "Гульні з OnlineFix патрабуюць пароль для вымання. Калі неабходна, выкарыстоўвайце наступны пароль:",
|
|
||||||
"dodi_installation_instruction": "Калі вы адкрыеце ўсталёўшчык DODI, націсніце на клявіятуры клявішу 'уверх' <0 />, каб пачаць працэс усталёўкі:",
|
|
||||||
"dont_show_it_again": "Не паказваць зноў",
|
|
||||||
"copy_to_clipboard": "Капіяваць",
|
|
||||||
"copied_to_clipboard": "Скапіявана",
|
|
||||||
"got_it": "Зразумела"
|
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Актываваць Hydra",
|
"title": "Актываваць Hydra",
|
||||||
@ -107,17 +88,13 @@
|
|||||||
"eta": "Канчатак {{eta}}",
|
"eta": "Канчатак {{eta}}",
|
||||||
"paused": "Спынена",
|
"paused": "Спынена",
|
||||||
"verifying": "Праверка…",
|
"verifying": "Праверка…",
|
||||||
"completed_at": "Скончана а {{date}}",
|
|
||||||
"completed": "Скончана",
|
"completed": "Скончана",
|
||||||
"download_again": "Сцягнуць зноў",
|
|
||||||
"cancel": "Скасаваць",
|
"cancel": "Скасаваць",
|
||||||
"filter": "Фільтар сцягнутых гульняў",
|
"filter": "Фільтар сцягнутых гульняў",
|
||||||
"remove": "Выдаліць",
|
"remove": "Выдаліць",
|
||||||
"downloading_metadata": "Сцягванне мэтаданых…",
|
"downloading_metadata": "Сцягванне мэтаданых…",
|
||||||
"starting_download": "Пачатак сцягвання…",
|
|
||||||
"deleting": "Выдаленне ўсталёўшчыка…",
|
"deleting": "Выдаленне ўсталёўшчыка…",
|
||||||
"delete": "Выдаліць усталёўшчык",
|
"delete": "Выдаліць усталёўшчык",
|
||||||
"remove_from_list": "Выдаліць",
|
|
||||||
"delete_modal_title": "Вы ўпэўнены?",
|
"delete_modal_title": "Вы ўпэўнены?",
|
||||||
"delete_modal_description": "Гэта выдаліць усе файлы ўсталёвак з вашага кампутара",
|
"delete_modal_description": "Гэта выдаліць усе файлы ўсталёвак з вашага кампутара",
|
||||||
"install": "Усталяваць"
|
"install": "Усталяваць"
|
||||||
@ -128,8 +105,6 @@
|
|||||||
"notifications": "Апавяшчэнні",
|
"notifications": "Апавяшчэнні",
|
||||||
"enable_download_notifications": "Па сканчэнні сцягванні",
|
"enable_download_notifications": "Па сканчэнні сцягванні",
|
||||||
"enable_repack_list_notifications": "Пры даданні новага рэпака",
|
"enable_repack_list_notifications": "Пры даданні новага рэпака",
|
||||||
"telemetry": "Тэлеметрыя",
|
|
||||||
"telemetry_description": "Уключыць ананімную статыстыку выкарыстання",
|
|
||||||
"behavior": "Паводзіны",
|
"behavior": "Паводзіны",
|
||||||
"quit_app_instead_hiding": "Закрываць праграму замест таго, каб хаваць яе ў трэй",
|
"quit_app_instead_hiding": "Закрываць праграму замест таго, каб хаваць яе ў трэй",
|
||||||
"launch_with_system": "Запускаць праграму пры запуску сыстэмы"
|
"launch_with_system": "Запускаць праграму пры запуску сыстэмы"
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
"featured": "Anbefalet",
|
"featured": "Anbefalet",
|
||||||
"recently_added": "Nyligt tilføjet",
|
|
||||||
"trending": "Trender",
|
"trending": "Trender",
|
||||||
"surprise_me": "Overrask mig",
|
"surprise_me": "Overrask mig",
|
||||||
"no_results": "Ingen resultater fundet"
|
"no_results": "Ingen resultater fundet"
|
||||||
@ -15,12 +14,7 @@
|
|||||||
"paused": "{{title}} (Paused)",
|
"paused": "{{title}} (Paused)",
|
||||||
"downloading": "{{title}} ({{percentage}} - Downloading…)",
|
"downloading": "{{title}} ({{percentage}} - Downloading…)",
|
||||||
"filter": "Filtrer bibliotek",
|
"filter": "Filtrer bibliotek",
|
||||||
"follow_us": "Følg os",
|
"home": "Hjem"
|
||||||
"home": "Hjem",
|
|
||||||
"discord": "Tilslut dig vores Discord",
|
|
||||||
"telegram": "Tilslut dig vores Telegram",
|
|
||||||
"x": "Følg på X",
|
|
||||||
"github": "Bidrag på GitHub"
|
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Søg spil",
|
"search": "Søg spil",
|
||||||
@ -50,7 +44,6 @@
|
|||||||
"pause": "Pause",
|
"pause": "Pause",
|
||||||
"cancel": "Annullér",
|
"cancel": "Annullér",
|
||||||
"remove": "Fjern",
|
"remove": "Fjern",
|
||||||
"remove_from_list": "Fjern",
|
|
||||||
"space_left_on_disk": "{{space}} tilbage på harddisken",
|
"space_left_on_disk": "{{space}} tilbage på harddisken",
|
||||||
"eta": "Konklusion {{eta}}",
|
"eta": "Konklusion {{eta}}",
|
||||||
"downloading_metadata": "Downloader metadata…",
|
"downloading_metadata": "Downloader metadata…",
|
||||||
@ -58,12 +51,8 @@
|
|||||||
"requirements": "System behov",
|
"requirements": "System behov",
|
||||||
"minimum": "Mindste",
|
"minimum": "Mindste",
|
||||||
"recommended": "Anbefalet",
|
"recommended": "Anbefalet",
|
||||||
"no_minimum_requirements": "{{title}} angiver ikke mindste behov informationer",
|
|
||||||
"no_recommended_requirements": "{{title}} angiver ikke anbefalet behov informationer",
|
|
||||||
"release_date": "Offentliggjort den {{date}}",
|
"release_date": "Offentliggjort den {{date}}",
|
||||||
"publisher": "Udgivet af {{publisher}}",
|
"publisher": "Udgivet af {{publisher}}",
|
||||||
"copy_link_to_clipboard": "Kopier link",
|
|
||||||
"copied_link_to_clipboard": "Link kopieret",
|
|
||||||
"hours": "timer",
|
"hours": "timer",
|
||||||
"minutes": "minutter",
|
"minutes": "minutter",
|
||||||
"amount_hours": "{{amount}} timer",
|
"amount_hours": "{{amount}} timer",
|
||||||
@ -83,15 +72,7 @@
|
|||||||
"change": "Ændré",
|
"change": "Ændré",
|
||||||
"repacks_modal_description": "Vælg den repack du vil downloade",
|
"repacks_modal_description": "Vælg den repack du vil downloade",
|
||||||
"select_folder_hint": "For at ændre standard mappen, gå til <0>Instillingerne</0>",
|
"select_folder_hint": "For at ændre standard mappen, gå til <0>Instillingerne</0>",
|
||||||
"download_now": "Download nu",
|
"download_now": "Download nu"
|
||||||
"installation_instructions": "Installations Instrukser",
|
|
||||||
"installation_instructions_description": "Yderligere skridt er krævet for at installere dette spil",
|
|
||||||
"online_fix_instruction": "OnlineFix spil kræver et kodeord for at kunne blive udpakket. Når krævet, brug det følgende kodeord:",
|
|
||||||
"dodi_installation_instruction": "Når du åbner DODI installatør, tryk på op-knappen på dit tastatur <0 /> for at starte installations processen:",
|
|
||||||
"dont_show_it_again": "Vis ikke igen",
|
|
||||||
"copy_to_clipboard": "Kopier",
|
|
||||||
"copied_to_clipboard": "Kopieret",
|
|
||||||
"got_it": "Forstået"
|
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Aktivér Hydra",
|
"title": "Aktivér Hydra",
|
||||||
@ -107,17 +88,13 @@
|
|||||||
"eta": "Konklusion {{eta}}",
|
"eta": "Konklusion {{eta}}",
|
||||||
"paused": "Pauset",
|
"paused": "Pauset",
|
||||||
"verifying": "Verificerer…",
|
"verifying": "Verificerer…",
|
||||||
"completed_at": "Færdiggjort på {{date}}",
|
|
||||||
"completed": "Færdigt",
|
"completed": "Færdigt",
|
||||||
"download_again": "Download igen",
|
|
||||||
"cancel": "Annullér",
|
"cancel": "Annullér",
|
||||||
"filter": "Filtrer downloadet spil",
|
"filter": "Filtrer downloadet spil",
|
||||||
"remove": "Fjern",
|
"remove": "Fjern",
|
||||||
"downloading_metadata": "Downloader metadata…",
|
"downloading_metadata": "Downloader metadata…",
|
||||||
"starting_download": "Starter download…",
|
|
||||||
"deleting": "Sletter installatør…",
|
"deleting": "Sletter installatør…",
|
||||||
"delete": "Fjern installatør",
|
"delete": "Fjern installatør",
|
||||||
"remove_from_list": "Fjern",
|
|
||||||
"delete_modal_title": "Er du sikker?",
|
"delete_modal_title": "Er du sikker?",
|
||||||
"delete_modal_description": "Dette vil fjerne alle installations filerne fra din computer",
|
"delete_modal_description": "Dette vil fjerne alle installations filerne fra din computer",
|
||||||
"install": "Installér"
|
"install": "Installér"
|
||||||
@ -128,8 +105,6 @@
|
|||||||
"notifications": "Notifikationer",
|
"notifications": "Notifikationer",
|
||||||
"enable_download_notifications": "Når et download bliver færdigt",
|
"enable_download_notifications": "Når et download bliver færdigt",
|
||||||
"enable_repack_list_notifications": "Når en ny repack bliver tilføjet",
|
"enable_repack_list_notifications": "Når en ny repack bliver tilføjet",
|
||||||
"telemetry": "Telemetri",
|
|
||||||
"telemetry_description": "Slå anonymt brugs statistik til",
|
|
||||||
"quit_app_instead_hiding": "Afslut Hydra instedet for at minimere til processlinjen",
|
"quit_app_instead_hiding": "Afslut Hydra instedet for at minimere til processlinjen",
|
||||||
"launch_with_system": "Åben Hydra ved start af systemet",
|
"launch_with_system": "Åben Hydra ved start af systemet",
|
||||||
"general": "Generelt",
|
"general": "Generelt",
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"app": {
|
||||||
|
"successfully_signed_in": "Successfully signed in"
|
||||||
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"featured": "Featured",
|
"featured": "Featured",
|
||||||
"trending": "Trending",
|
"trending": "Trending",
|
||||||
@ -16,7 +19,8 @@
|
|||||||
"filter": "Filter library",
|
"filter": "Filter library",
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"queued": "{{title}} (Queued)",
|
"queued": "{{title}} (Queued)",
|
||||||
"game_has_no_executable": "Game has no executable selected"
|
"game_has_no_executable": "Game has no executable selected",
|
||||||
|
"sign_in": "Sign in"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Search games",
|
"search": "Search games",
|
||||||
@ -49,7 +53,6 @@
|
|||||||
"pause": "Pause",
|
"pause": "Pause",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"remove": "Remove",
|
"remove": "Remove",
|
||||||
"remove_from_list": "Remove",
|
|
||||||
"space_left_on_disk": "{{space}} left on disk",
|
"space_left_on_disk": "{{space}} left on disk",
|
||||||
"eta": "Conclusion {{eta}}",
|
"eta": "Conclusion {{eta}}",
|
||||||
"calculating_eta": "Calculating remaining time…",
|
"calculating_eta": "Calculating remaining time…",
|
||||||
@ -58,13 +61,9 @@
|
|||||||
"requirements": "System requirements",
|
"requirements": "System requirements",
|
||||||
"minimum": "Minimum",
|
"minimum": "Minimum",
|
||||||
"recommended": "Recommended",
|
"recommended": "Recommended",
|
||||||
"no_minimum_requirements": "{{title}} doesn't provide minimum requirements information",
|
|
||||||
"no_recommended_requirements": "{{title}} doesn't provide recommended requirements information",
|
|
||||||
"paused": "Paused",
|
"paused": "Paused",
|
||||||
"release_date": "Released on {{date}}",
|
"release_date": "Released on {{date}}",
|
||||||
"publisher": "Published by {{publisher}}",
|
"publisher": "Published by {{publisher}}",
|
||||||
"copy_link_to_clipboard": "Copy link",
|
|
||||||
"copied_link_to_clipboard": "Link copied",
|
|
||||||
"hours": "hours",
|
"hours": "hours",
|
||||||
"minutes": "minutes",
|
"minutes": "minutes",
|
||||||
"amount_hours": "{{amount}} hours",
|
"amount_hours": "{{amount}} hours",
|
||||||
@ -85,14 +84,6 @@
|
|||||||
"repacks_modal_description": "Choose the repack you want to download",
|
"repacks_modal_description": "Choose the repack you want to download",
|
||||||
"select_folder_hint": "To change the default folder, go to the <0>Settings</0>",
|
"select_folder_hint": "To change the default folder, go to the <0>Settings</0>",
|
||||||
"download_now": "Download now",
|
"download_now": "Download now",
|
||||||
"installation_instructions": "Installation Instructions",
|
|
||||||
"installation_instructions_description": "Additional steps are required to install this game",
|
|
||||||
"online_fix_instruction": "OnlineFix games requires a password to be extracted. When required, use the following password:",
|
|
||||||
"dodi_installation_instruction": "When you open DODI installer, press your keyboard up key <0 /> to start the installation process:",
|
|
||||||
"dont_show_it_again": "Don't show it again",
|
|
||||||
"copy_to_clipboard": "Copy",
|
|
||||||
"copied_to_clipboard": "Copied",
|
|
||||||
"got_it": "Got it",
|
|
||||||
"no_shop_details": "Could not retrieve shop details.",
|
"no_shop_details": "Could not retrieve shop details.",
|
||||||
"download_options": "Download options",
|
"download_options": "Download options",
|
||||||
"download_path": "Download path",
|
"download_path": "Download path",
|
||||||
@ -137,18 +128,14 @@
|
|||||||
"eta": "Conclusion {{eta}}",
|
"eta": "Conclusion {{eta}}",
|
||||||
"paused": "Paused",
|
"paused": "Paused",
|
||||||
"verifying": "Verifying…",
|
"verifying": "Verifying…",
|
||||||
"completed_at": "Completed in {{date}}",
|
|
||||||
"completed": "Completed",
|
"completed": "Completed",
|
||||||
"removed": "Not downloaded",
|
"removed": "Not downloaded",
|
||||||
"download_again": "Download again",
|
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"filter": "Filter downloaded games",
|
"filter": "Filter downloaded games",
|
||||||
"remove": "Remove",
|
"remove": "Remove",
|
||||||
"downloading_metadata": "Downloading metadata…",
|
"downloading_metadata": "Downloading metadata…",
|
||||||
"starting_download": "Starting download…",
|
|
||||||
"deleting": "Deleting installer…",
|
"deleting": "Deleting installer…",
|
||||||
"delete": "Remove installer",
|
"delete": "Remove installer",
|
||||||
"remove_from_list": "Remove",
|
|
||||||
"delete_modal_title": "Are you sure?",
|
"delete_modal_title": "Are you sure?",
|
||||||
"delete_modal_description": "This will remove all the installation files from your computer",
|
"delete_modal_description": "This will remove all the installation files from your computer",
|
||||||
"install": "Install",
|
"install": "Install",
|
||||||
@ -165,8 +152,6 @@
|
|||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
"enable_download_notifications": "When a download is complete",
|
"enable_download_notifications": "When a download is complete",
|
||||||
"enable_repack_list_notifications": "When a new repack is added",
|
"enable_repack_list_notifications": "When a new repack is added",
|
||||||
"telemetry": "Telemetry",
|
|
||||||
"telemetry_description": "Enable anonymous usage statistics",
|
|
||||||
"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",
|
||||||
"launch_with_system": "Launch Hydra on system start-up",
|
"launch_with_system": "Launch Hydra on system start-up",
|
||||||
@ -200,7 +185,12 @@
|
|||||||
"sync_download_sources": "Sync sources",
|
"sync_download_sources": "Sync sources",
|
||||||
"removed_download_source": "Download source removed",
|
"removed_download_source": "Download source removed",
|
||||||
"added_download_source": "Added download source",
|
"added_download_source": "Added download source",
|
||||||
"download_sources_synced": "All download sources are synced"
|
"download_sources_synced": "All download sources are synced",
|
||||||
|
"insert_valid_json_url": "Insert a valid JSON url",
|
||||||
|
"found_download_option_zero": "No download option found",
|
||||||
|
"found_download_option_one": "Found {{countFormatted}} download option",
|
||||||
|
"found_download_option_other": "Found {{countFormatted}} download options",
|
||||||
|
"import": "Import"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Download complete",
|
"download_complete": "Download complete",
|
||||||
@ -226,5 +216,27 @@
|
|||||||
},
|
},
|
||||||
"forms": {
|
"forms": {
|
||||||
"toggle_password_visibility": "Toggle password visibility"
|
"toggle_password_visibility": "Toggle password visibility"
|
||||||
|
},
|
||||||
|
"user_profile": {
|
||||||
|
"amount_hours": "{{amount}} hours",
|
||||||
|
"amount_minutes": "{{amount}} minutes",
|
||||||
|
"last_time_played": "Last played {{period}}",
|
||||||
|
"activity": "Recent activity",
|
||||||
|
"library": "Library",
|
||||||
|
"total_play_time": "Total playtime: {{amount}}",
|
||||||
|
"no_recent_activity_title": "Hmmm… nothing here",
|
||||||
|
"no_recent_activity_description": "You haven't played any games recently. It's time to change that!",
|
||||||
|
"display_name": "Display name",
|
||||||
|
"saving": "Saving",
|
||||||
|
"save": "Save",
|
||||||
|
"edit_profile": "Edit Profile",
|
||||||
|
"saved_successfully": "Saved successfully",
|
||||||
|
"try_again": "Please, try again",
|
||||||
|
"sign_out_modal_title": "Are you sure?",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"successfully_signed_out": "Successfully signed out",
|
||||||
|
"sign_out": "Sign out",
|
||||||
|
"playing_for": "Playing for {{amount}}",
|
||||||
|
"sign_out_modal_text": "Your library is linked with your current account. When signing out, your library will not be visible anymore, and any progress will not be saved. Continue with sign out?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"app": {
|
||||||
|
"successfully_signed_in": "Successfully signed in (TRANSLATE ME)"
|
||||||
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"featured": "Destacado",
|
"featured": "Destacado",
|
||||||
"trending": "Tendencias",
|
"trending": "Tendencias",
|
||||||
@ -16,7 +19,8 @@
|
|||||||
"filter": "Buscar en la biblioteca",
|
"filter": "Buscar en la biblioteca",
|
||||||
"home": "Inicio",
|
"home": "Inicio",
|
||||||
"queued": "{{title}} (En Cola)",
|
"queued": "{{title}} (En Cola)",
|
||||||
"game_has_no_executable": "El juego no tiene un ejecutable"
|
"game_has_no_executable": "El juego no tiene un ejecutable",
|
||||||
|
"sign_in": "Sign in (TRANSLATE ME)"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Buscar juegos",
|
"search": "Buscar juegos",
|
||||||
@ -49,7 +53,6 @@
|
|||||||
"pause": "Pausa",
|
"pause": "Pausa",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"remove": "Eliminar",
|
"remove": "Eliminar",
|
||||||
"remove_from_list": "Quitar",
|
|
||||||
"space_left_on_disk": "{{space}} restantes en el disco",
|
"space_left_on_disk": "{{space}} restantes en el disco",
|
||||||
"eta": "Tiempo restante: {{eta}}",
|
"eta": "Tiempo restante: {{eta}}",
|
||||||
"calculating_eta": "Calculando tiempo restante…",
|
"calculating_eta": "Calculando tiempo restante…",
|
||||||
@ -58,13 +61,9 @@
|
|||||||
"requirements": "Requisitos del Sistema",
|
"requirements": "Requisitos del Sistema",
|
||||||
"minimum": "Mínimos",
|
"minimum": "Mínimos",
|
||||||
"recommended": "Recomendados",
|
"recommended": "Recomendados",
|
||||||
"no_minimum_requirements": "Sin requisitos mínimos para {{title}}",
|
|
||||||
"no_recommended_requirements": "{{title}} no tiene requisitos recomendados",
|
|
||||||
"paused": "Pausado",
|
"paused": "Pausado",
|
||||||
"release_date": "Fecha de lanzamiento: {{date}}",
|
"release_date": "Fecha de lanzamiento: {{date}}",
|
||||||
"publisher": "Publicado por: {{publisher}}",
|
"publisher": "Publicado por: {{publisher}}",
|
||||||
"copy_link_to_clipboard": "Copiar enlace",
|
|
||||||
"copied_link_to_clipboard": "Enlace copiado",
|
|
||||||
"hours": "horas",
|
"hours": "horas",
|
||||||
"minutes": "minutos",
|
"minutes": "minutos",
|
||||||
"amount_hours": "{{amount}} horas",
|
"amount_hours": "{{amount}} horas",
|
||||||
@ -85,14 +84,6 @@
|
|||||||
"repacks_modal_description": "Selecciona el repack que quieres descargar",
|
"repacks_modal_description": "Selecciona el repack que quieres descargar",
|
||||||
"select_folder_hint": "Para cambiar la carpeta predeterminada, ve a <0>Ajustes</0>",
|
"select_folder_hint": "Para cambiar la carpeta predeterminada, ve a <0>Ajustes</0>",
|
||||||
"download_now": "Descargar ahora",
|
"download_now": "Descargar ahora",
|
||||||
"installation_instructions": "Instrucciones de instalación",
|
|
||||||
"installation_instructions_description": "Se requieren de pasos adicionales para instalar este juego",
|
|
||||||
"online_fix_instruction": "Los juegos de OnlineFix requieren una contraseña para ser extraídos. Cuando se requiera, usa la siguiente contraseña:",
|
|
||||||
"dodi_installation_instruction": "Cuando abras el instalador de DODI, presiona la tecla hacia arriba del teclado <0 /> para iniciar el proceso de instalación:",
|
|
||||||
"dont_show_it_again": "No mostrar de nuevo",
|
|
||||||
"copy_to_clipboard": "Copiar",
|
|
||||||
"copied_to_clipboard": "Copiado",
|
|
||||||
"got_it": "Entendido",
|
|
||||||
"no_shop_details": "No se pudieron obtener detalles de la tienda.",
|
"no_shop_details": "No se pudieron obtener detalles de la tienda.",
|
||||||
"download_options": "Opciones de descarga",
|
"download_options": "Opciones de descarga",
|
||||||
"download_path": "Ruta de descarga",
|
"download_path": "Ruta de descarga",
|
||||||
@ -135,18 +126,14 @@
|
|||||||
"eta": "Finalizando en {{eta}}",
|
"eta": "Finalizando en {{eta}}",
|
||||||
"paused": "En Pausa",
|
"paused": "En Pausa",
|
||||||
"verifying": "Verificando…",
|
"verifying": "Verificando…",
|
||||||
"completed_at": "Completado el {{date}}",
|
|
||||||
"completed": "Completado",
|
"completed": "Completado",
|
||||||
"removed": "No descargado",
|
"removed": "No descargado",
|
||||||
"download_again": "Descargar de nuevo",
|
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"filter": "Buscar juegos descargados",
|
"filter": "Buscar juegos descargados",
|
||||||
"remove": "Eliminar",
|
"remove": "Eliminar",
|
||||||
"downloading_metadata": "Descargando metadatos…",
|
"downloading_metadata": "Descargando metadatos…",
|
||||||
"starting_download": "Iniciando descarga…",
|
|
||||||
"deleting": "Eliminando instalador…",
|
"deleting": "Eliminando instalador…",
|
||||||
"delete": "Eliminar instalador",
|
"delete": "Eliminar instalador",
|
||||||
"remove_from_list": "Eliminar",
|
|
||||||
"delete_modal_title": "¿Estás seguro?",
|
"delete_modal_title": "¿Estás seguro?",
|
||||||
"delete_modal_description": "Esto eliminará todos los archivos de instalación de tu computadora.",
|
"delete_modal_description": "Esto eliminará todos los archivos de instalación de tu computadora.",
|
||||||
"install": "Instalar",
|
"install": "Instalar",
|
||||||
@ -163,8 +150,6 @@
|
|||||||
"notifications": "Notificaciones",
|
"notifications": "Notificaciones",
|
||||||
"enable_download_notifications": "Cuando se completa una descarga",
|
"enable_download_notifications": "Cuando se completa una descarga",
|
||||||
"enable_repack_list_notifications": "Cuando se añade un repack nuevo",
|
"enable_repack_list_notifications": "Cuando se añade un repack nuevo",
|
||||||
"telemetry": "Telemetría",
|
|
||||||
"telemetry_description": "Habilitar recopilación de datos de manera anónima",
|
|
||||||
"real_debrid_api_token_label": "Token API de Real-Debrid",
|
"real_debrid_api_token_label": "Token API de Real-Debrid",
|
||||||
"quit_app_instead_hiding": "Salir de Hydra en vez de minimizar en la bandeja del sistema",
|
"quit_app_instead_hiding": "Salir de Hydra en vez de minimizar en la bandeja del sistema",
|
||||||
"launch_with_system": "Iniciar Hydra al inicio del sistema",
|
"launch_with_system": "Iniciar Hydra al inicio del sistema",
|
||||||
@ -198,7 +183,12 @@
|
|||||||
"sync_download_sources": "Sincronizar fuentes",
|
"sync_download_sources": "Sincronizar fuentes",
|
||||||
"removed_download_source": "Fuente de descarga eliminada",
|
"removed_download_source": "Fuente de descarga eliminada",
|
||||||
"added_download_source": "Fuente de descarga añadida",
|
"added_download_source": "Fuente de descarga añadida",
|
||||||
"download_sources_synced": "Todas las fuentes de descarga estánn actualizadas"
|
"download_sources_synced": "Todas las fuentes de descarga estánn actualizadas (TRANSLATE ME)",
|
||||||
|
"insert_valid_json_url": "Insert a valid JSON url (TRANSLATE ME)",
|
||||||
|
"found_download_option_zero": "No download option found (TRANSLATE ME)",
|
||||||
|
"found_download_option_one": "Found {{countFormatted}} download option (TRANSLATE ME)",
|
||||||
|
"found_download_option_other": "Found {{countFormatted}} download options (TRANSLATE ME)",
|
||||||
|
"import": "Import (TRANSLATE ME)"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Descarga completada",
|
"download_complete": "Descarga completada",
|
||||||
@ -224,5 +214,27 @@
|
|||||||
},
|
},
|
||||||
"forms": {
|
"forms": {
|
||||||
"toggle_password_visibility": "Cambiar visibilidad de contraseña"
|
"toggle_password_visibility": "Cambiar visibilidad de contraseña"
|
||||||
|
},
|
||||||
|
"user_profile": {
|
||||||
|
"amount_hours": "{{amount}} hours (TRANSLATE ME)",
|
||||||
|
"amount_minutes": "{{amount}} minutes (TRANSLATE ME)",
|
||||||
|
"last_time_played": "Last played {{period}} (TRANSLATE ME)",
|
||||||
|
"activity": "Recent activity (TRANSLATE ME)",
|
||||||
|
"library": "Library (TRANSLATE ME)",
|
||||||
|
"total_play_time": "Total playtime: {{amount}} (TRANSLATE ME)",
|
||||||
|
"no_recent_activity_title": "Hmmm… nothing here (TRANSLATE ME)",
|
||||||
|
"no_recent_activity_description": "You haven't played any games recently. It's time to change that! (TRANSLATE ME)",
|
||||||
|
"display_name": "Display name (TRANSLATE ME)",
|
||||||
|
"saving": "Saving (TRANSLATE ME)",
|
||||||
|
"save": "Save (TRANSLATE ME)",
|
||||||
|
"edit_profile": "Edit Profile (TRANSLATE ME)",
|
||||||
|
"saved_successfully": "Saved successfully (TRANSLATE ME)",
|
||||||
|
"try_again": "Please, try again (TRANSLATE ME)",
|
||||||
|
"sign_out_modal_title": "Are you sure? (TRANSLATE ME)",
|
||||||
|
"cancel": "Cancel (TRANSLATE ME)",
|
||||||
|
"successfully_signed_out": "Successfully signed out (TRANSLATE ME)",
|
||||||
|
"sign_out": "Sign out (TRANSLATE ME)",
|
||||||
|
"playing_for": "Playing for {{amount}} (TRANSLATE ME)",
|
||||||
|
"sign_out_modal_text": "Your library is linked with your current account. When signing out, your library will not be visible anymore, and any progress will not be saved. Continue with sign out? (TRANSLATE ME)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
"featured": "پیشنهادی",
|
"featured": "پیشنهادی",
|
||||||
"recently_added": "تازه اضافه شده",
|
|
||||||
"trending": "پرطرفدار",
|
"trending": "پرطرفدار",
|
||||||
"surprise_me": "سوپرایزم کن",
|
"surprise_me": "سوپرایزم کن",
|
||||||
"no_results": "اتمامای پیدا نشد"
|
"no_results": "اتمامای پیدا نشد"
|
||||||
@ -15,12 +14,7 @@
|
|||||||
"paused": "{{title}} (متوقف شده)",
|
"paused": "{{title}} (متوقف شده)",
|
||||||
"downloading": "{{title}} ({{percentage}} - در حال دانلود…)",
|
"downloading": "{{title}} ({{percentage}} - در حال دانلود…)",
|
||||||
"filter": "فیلتر کردن کتابخانه",
|
"filter": "فیلتر کردن کتابخانه",
|
||||||
"follow_us": "دنبال کردن ما",
|
"home": "خانه"
|
||||||
"home": "خانه",
|
|
||||||
"discord": "عضویت در دیسکورد ما",
|
|
||||||
"telegram": "عضویت در تلگرام ما",
|
|
||||||
"x": "دنبال کرد در ایکس",
|
|
||||||
"github": "مشارکت در گیتهاب"
|
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "جستجوی بازیها",
|
"search": "جستجوی بازیها",
|
||||||
@ -50,7 +44,6 @@
|
|||||||
"pause": "توقف",
|
"pause": "توقف",
|
||||||
"cancel": "بیخیال",
|
"cancel": "بیخیال",
|
||||||
"remove": "حذف",
|
"remove": "حذف",
|
||||||
"remove_from_list": "حذف",
|
|
||||||
"space_left_on_disk": "{{space}} فضا در دیسک باقیمانده",
|
"space_left_on_disk": "{{space}} فضا در دیسک باقیمانده",
|
||||||
"eta": "اتمام {{eta}}",
|
"eta": "اتمام {{eta}}",
|
||||||
"downloading_metadata": "در حال دانلود متادیتاها…",
|
"downloading_metadata": "در حال دانلود متادیتاها…",
|
||||||
@ -58,12 +51,8 @@
|
|||||||
"requirements": "سیستم مورد نیاز",
|
"requirements": "سیستم مورد نیاز",
|
||||||
"minimum": "حداقل",
|
"minimum": "حداقل",
|
||||||
"recommended": "پیشنهادی",
|
"recommended": "پیشنهادی",
|
||||||
"no_minimum_requirements": "{{title}} اطلاعات حداقل سیستم مورد نیاز را فراهم نکرده",
|
|
||||||
"no_recommended_requirements": "{{title}} اطلاعات پیشنهادی سیستم مورد نیاز را فراهم نکرده",
|
|
||||||
"release_date": "منتشر شده در {{date}}",
|
"release_date": "منتشر شده در {{date}}",
|
||||||
"publisher": "منتشر شده توسط {{publisher}}",
|
"publisher": "منتشر شده توسط {{publisher}}",
|
||||||
"copy_link_to_clipboard": "کپی لینک",
|
|
||||||
"copied_link_to_clipboard": "لینک کپی شد",
|
|
||||||
"hours": "ساعت",
|
"hours": "ساعت",
|
||||||
"minutes": "دقیقه",
|
"minutes": "دقیقه",
|
||||||
"amount_hours": "{{amount}} ساعت",
|
"amount_hours": "{{amount}} ساعت",
|
||||||
@ -83,15 +72,7 @@
|
|||||||
"change": "تغییر",
|
"change": "تغییر",
|
||||||
"repacks_modal_description": "ریپک مورد نظر برای دانلود را انتخاب کنید",
|
"repacks_modal_description": "ریپک مورد نظر برای دانلود را انتخاب کنید",
|
||||||
"select_folder_hint": "برای تغییر پوشهی پیشفرض به <0>Settings</0> بروید",
|
"select_folder_hint": "برای تغییر پوشهی پیشفرض به <0>Settings</0> بروید",
|
||||||
"download_now": "الان دانلود کن",
|
"download_now": "الان دانلود کن"
|
||||||
"installation_instructions": "دستورات نصب",
|
|
||||||
"installation_instructions_description": "قدمهای دیگری برای نصب این بازی نیاز است",
|
|
||||||
"online_fix_instruction": "بازیهای OnlineFix برای اکسترکت شدن به پسوورد نیاز دارند. در صورت نیاز، از این پسوورد استفاده کنید:",
|
|
||||||
"dodi_installation_instruction": "زمانی که اینستالر DODI را باز کردید، دکمهی <0 /> را فشار دهید تا فرایند نصب شروع شود:",
|
|
||||||
"dont_show_it_again": "دیگر نمایش نده",
|
|
||||||
"copy_to_clipboard": "کپی",
|
|
||||||
"copied_to_clipboard": "کپی شد",
|
|
||||||
"got_it": "فهمیدم"
|
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "فعال کردن هایدرا",
|
"title": "فعال کردن هایدرا",
|
||||||
@ -107,17 +88,13 @@
|
|||||||
"eta": "اتمام {{eta}}",
|
"eta": "اتمام {{eta}}",
|
||||||
"paused": "متوقف شده",
|
"paused": "متوقف شده",
|
||||||
"verifying": "در حال اعتبارسنجی…",
|
"verifying": "در حال اعتبارسنجی…",
|
||||||
"completed_at": "پایان یافته در {{date}}",
|
|
||||||
"completed": "پایان یافته",
|
"completed": "پایان یافته",
|
||||||
"download_again": "دانلود مجدد",
|
|
||||||
"cancel": "لغو",
|
"cancel": "لغو",
|
||||||
"filter": "فیلتر بازیهای دانلود شده",
|
"filter": "فیلتر بازیهای دانلود شده",
|
||||||
"remove": "حذف",
|
"remove": "حذف",
|
||||||
"downloading_metadata": "در حال دانلود متادیتاها…",
|
"downloading_metadata": "در حال دانلود متادیتاها…",
|
||||||
"starting_download": "در حال آغار دانلود…",
|
|
||||||
"deleting": "در حال پاک کردن اینستالر…",
|
"deleting": "در حال پاک کردن اینستالر…",
|
||||||
"delete": "پاک کردن",
|
"delete": "پاک کردن",
|
||||||
"remove_from_list": "حذف",
|
|
||||||
"delete_modal_title": "مطمئنی؟",
|
"delete_modal_title": "مطمئنی؟",
|
||||||
"delete_modal_description": "این کار تمام فایلهای اینستالر را از کامپیوتر شما حذف میکند",
|
"delete_modal_description": "این کار تمام فایلهای اینستالر را از کامپیوتر شما حذف میکند",
|
||||||
"install": "نصف"
|
"install": "نصف"
|
||||||
@ -128,8 +105,6 @@
|
|||||||
"notifications": "نوتیفیکشنها",
|
"notifications": "نوتیفیکشنها",
|
||||||
"enable_download_notifications": "زمانی که یک دانلود تمام شد",
|
"enable_download_notifications": "زمانی که یک دانلود تمام شد",
|
||||||
"enable_repack_list_notifications": "زمانی که یک ریپک جدید اضافه شد",
|
"enable_repack_list_notifications": "زمانی که یک ریپک جدید اضافه شد",
|
||||||
"telemetry": "تلمتری",
|
|
||||||
"telemetry_description": "فعال کردن آمارگیری استفاده ناشناس",
|
|
||||||
"quit_app_instead_hiding": "به جای کوچک کردن، از هایدرا خارج شو",
|
"quit_app_instead_hiding": "به جای کوچک کردن، از هایدرا خارج شو",
|
||||||
"launch_with_system": "زمانی که سیستم روشن میشود، هایدرا را باز کن",
|
"launch_with_system": "زمانی که سیستم روشن میشود، هایدرا را باز کن",
|
||||||
"general": "کلی",
|
"general": "کلی",
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
"featured": "En vedette",
|
"featured": "En vedette",
|
||||||
"recently_added": "Récemment ajouté",
|
|
||||||
"trending": "Tendance",
|
"trending": "Tendance",
|
||||||
"surprise_me": "Surprenez-moi",
|
"surprise_me": "Surprenez-moi",
|
||||||
"no_results": "Aucun résultat trouvé"
|
"no_results": "Aucun résultat trouvé"
|
||||||
@ -15,8 +14,7 @@
|
|||||||
"paused": "{{title}} (En pause)",
|
"paused": "{{title}} (En pause)",
|
||||||
"downloading": "{{title}} ({{percentage}} - Téléchargement en cours…)",
|
"downloading": "{{title}} ({{percentage}} - Téléchargement en cours…)",
|
||||||
"filter": "Filtrer la bibliothèque",
|
"filter": "Filtrer la bibliothèque",
|
||||||
"home": "Page d’accueil",
|
"home": "Page d’accueil"
|
||||||
"follow_us": "Suivez-nous"
|
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Recherche",
|
"search": "Recherche",
|
||||||
@ -41,7 +39,6 @@
|
|||||||
"pause": "Pause",
|
"pause": "Pause",
|
||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
"remove": "Supprimer",
|
"remove": "Supprimer",
|
||||||
"remove_from_list": "Retirer",
|
|
||||||
"space_left_on_disk": "{{space}} restant sur le disque",
|
"space_left_on_disk": "{{space}} restant sur le disque",
|
||||||
"eta": "Fin dans {{eta}}",
|
"eta": "Fin dans {{eta}}",
|
||||||
"downloading_metadata": "Téléchargement des métadonnées en cours…",
|
"downloading_metadata": "Téléchargement des métadonnées en cours…",
|
||||||
@ -49,12 +46,8 @@
|
|||||||
"requirements": "Configuration requise",
|
"requirements": "Configuration requise",
|
||||||
"minimum": "Minimum",
|
"minimum": "Minimum",
|
||||||
"recommended": "Recommandée",
|
"recommended": "Recommandée",
|
||||||
"no_minimum_requirements": "{{title}} ne fournit pas d'informations sur les configurations minimales",
|
|
||||||
"no_recommended_requirements": "{{title}} ne fournit pas d'informations sur les configurations recommandées",
|
|
||||||
"release_date": "Sorti le {{date}}",
|
"release_date": "Sorti le {{date}}",
|
||||||
"publisher": "Édité par {{publisher}}",
|
"publisher": "Édité par {{publisher}}",
|
||||||
"copy_link_to_clipboard": "Copier le lien",
|
|
||||||
"copied_link_to_clipboard": "Lien copié",
|
|
||||||
"hours": "heures",
|
"hours": "heures",
|
||||||
"minutes": "minutes",
|
"minutes": "minutes",
|
||||||
"amount_hours": "{{amount}} heures",
|
"amount_hours": "{{amount}} heures",
|
||||||
@ -87,15 +80,11 @@
|
|||||||
"eta": "Fin dans {{eta}}",
|
"eta": "Fin dans {{eta}}",
|
||||||
"paused": "En pause",
|
"paused": "En pause",
|
||||||
"verifying": "Vérification en cours…",
|
"verifying": "Vérification en cours…",
|
||||||
"completed_at": "Terminé en {{date}}",
|
|
||||||
"completed": "Terminé",
|
"completed": "Terminé",
|
||||||
"download_again": "Télécharger à nouveau",
|
|
||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
"filter": "Filtrer les jeux téléchargés",
|
"filter": "Filtrer les jeux téléchargés",
|
||||||
"remove": "Supprimer",
|
"remove": "Supprimer",
|
||||||
"downloading_metadata": "Téléchargement des métadonnées en cours…",
|
"downloading_metadata": "Téléchargement des métadonnées en cours…",
|
||||||
"starting_download": "Démarrage du téléchargement…",
|
|
||||||
"remove_from_list": "Retirer",
|
|
||||||
"delete": "Supprimer le programme d'installation",
|
"delete": "Supprimer le programme d'installation",
|
||||||
"delete_modal_description": "Cela supprimera tous les fichiers d'installation de votre ordinateur",
|
"delete_modal_description": "Cela supprimera tous les fichiers d'installation de votre ordinateur",
|
||||||
"delete_modal_title": "Es-tu sûr?",
|
"delete_modal_title": "Es-tu sûr?",
|
||||||
@ -108,8 +97,6 @@
|
|||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
"enable_download_notifications": "Quand un téléchargement est terminé",
|
"enable_download_notifications": "Quand un téléchargement est terminé",
|
||||||
"enable_repack_list_notifications": "Quand un nouveau repack est ajouté",
|
"enable_repack_list_notifications": "Quand un nouveau repack est ajouté",
|
||||||
"telemetry": "Télémétrie",
|
|
||||||
"telemetry_description": "Activer les statistiques d'utilisation anonymes",
|
|
||||||
"language": "Langue"
|
"language": "Langue"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
"featured": "Featured",
|
"featured": "Featured",
|
||||||
"recently_added": "Nemrég hozzáadott",
|
|
||||||
"trending": "Népszerű",
|
"trending": "Népszerű",
|
||||||
"surprise_me": "Lepj meg",
|
"surprise_me": "Lepj meg",
|
||||||
"no_results": "Nem található"
|
"no_results": "Nem található"
|
||||||
@ -15,7 +14,6 @@
|
|||||||
"paused": "{{title}} (Szünet)",
|
"paused": "{{title}} (Szünet)",
|
||||||
"downloading": "{{title}} ({{percentage}} - Letöltés…)",
|
"downloading": "{{title}} ({{percentage}} - Letöltés…)",
|
||||||
"filter": "Könyvtár szűrése",
|
"filter": "Könyvtár szűrése",
|
||||||
"follow_us": "Kövess minket",
|
|
||||||
"home": "Főoldal"
|
"home": "Főoldal"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
@ -46,7 +44,6 @@
|
|||||||
"pause": "Szüneteltetés",
|
"pause": "Szüneteltetés",
|
||||||
"cancel": "Mégse",
|
"cancel": "Mégse",
|
||||||
"remove": "Eltávolítás",
|
"remove": "Eltávolítás",
|
||||||
"remove_from_list": "Eltávolítás",
|
|
||||||
"space_left_on_disk": "{{space}} szabad hely a lemezen",
|
"space_left_on_disk": "{{space}} szabad hely a lemezen",
|
||||||
"eta": "Befejezés {{eta}}",
|
"eta": "Befejezés {{eta}}",
|
||||||
"downloading_metadata": "Metaadatok letöltése…",
|
"downloading_metadata": "Metaadatok letöltése…",
|
||||||
@ -54,12 +51,8 @@
|
|||||||
"requirements": "Rendszerkövetelmények",
|
"requirements": "Rendszerkövetelmények",
|
||||||
"minimum": "Minimális",
|
"minimum": "Minimális",
|
||||||
"recommended": "Ajánlott",
|
"recommended": "Ajánlott",
|
||||||
"no_minimum_requirements": "{{title}} nem tartalmaz információt a minimális követelményekről",
|
|
||||||
"no_recommended_requirements": "{{title}} nem tartalmaz információt az ajánlott követelményekről",
|
|
||||||
"release_date": "Megjelenés: {{date}}",
|
"release_date": "Megjelenés: {{date}}",
|
||||||
"publisher": "Kiadta: {{publisher}}",
|
"publisher": "Kiadta: {{publisher}}",
|
||||||
"copy_link_to_clipboard": "Link másolása",
|
|
||||||
"copied_link_to_clipboard": "Link másolva",
|
|
||||||
"hours": "óra",
|
"hours": "óra",
|
||||||
"minutes": "perc",
|
"minutes": "perc",
|
||||||
"amount_hours": "{{amount}} óra",
|
"amount_hours": "{{amount}} óra",
|
||||||
@ -95,17 +88,13 @@
|
|||||||
"eta": "Befejezés {{eta}}",
|
"eta": "Befejezés {{eta}}",
|
||||||
"paused": "Szüneteltetve",
|
"paused": "Szüneteltetve",
|
||||||
"verifying": "Ellenőrzés…",
|
"verifying": "Ellenőrzés…",
|
||||||
"completed_at": "Befejezve {{date}}-kor",
|
|
||||||
"completed": "Befejezve",
|
"completed": "Befejezve",
|
||||||
"download_again": "Újra letöltés",
|
|
||||||
"cancel": "Mégse",
|
"cancel": "Mégse",
|
||||||
"filter": "Letöltött játékok szűrése",
|
"filter": "Letöltött játékok szűrése",
|
||||||
"remove": "Eltávolítás",
|
"remove": "Eltávolítás",
|
||||||
"downloading_metadata": "Metaadatok letöltése…",
|
"downloading_metadata": "Metaadatok letöltése…",
|
||||||
"starting_download": "Letöltés indítása…",
|
|
||||||
"deleting": "Telepítő törlése…",
|
"deleting": "Telepítő törlése…",
|
||||||
"delete": "Telepítő eltávolítása",
|
"delete": "Telepítő eltávolítása",
|
||||||
"remove_from_list": "Eltávolítás",
|
|
||||||
"delete_modal_title": "Biztos vagy benne?",
|
"delete_modal_title": "Biztos vagy benne?",
|
||||||
"delete_modal_description": "Ez eltávolít minden telepítési fájlt a számítógépedről",
|
"delete_modal_description": "Ez eltávolít minden telepítési fájlt a számítógépedről",
|
||||||
"install": "Telepítés"
|
"install": "Telepítés"
|
||||||
@ -115,9 +104,7 @@
|
|||||||
"change": "Frissítés",
|
"change": "Frissítés",
|
||||||
"notifications": "Értesítések",
|
"notifications": "Értesítések",
|
||||||
"enable_download_notifications": "Amikor egy letöltés befejeződik",
|
"enable_download_notifications": "Amikor egy letöltés befejeződik",
|
||||||
"enable_repack_list_notifications": "Amikor egy új repack hozzáadásra kerül",
|
"enable_repack_list_notifications": "Amikor egy új repack hozzáadásra kerül"
|
||||||
"telemetry": "Telemetria",
|
|
||||||
"telemetry_description": "Névtelen felhasználási statisztikák engedélyezése"
|
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Letöltés befejeződött",
|
"download_complete": "Letöltés befejeződött",
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
"featured": "Unggulan",
|
"featured": "Unggulan",
|
||||||
"recently_added": "Terbaru",
|
|
||||||
"trending": "Trending",
|
"trending": "Trending",
|
||||||
"surprise_me": "Kejutkan Saya",
|
"surprise_me": "Kejutkan Saya",
|
||||||
"no_results": "Tidak ada hasil"
|
"no_results": "Tidak ada hasil"
|
||||||
@ -15,12 +14,7 @@
|
|||||||
"paused": "{{title}} (Terhenti)",
|
"paused": "{{title}} (Terhenti)",
|
||||||
"downloading": "{{title}} ({{percentage}} - Mengunduh…)",
|
"downloading": "{{title}} ({{percentage}} - Mengunduh…)",
|
||||||
"filter": "Filter koleksi",
|
"filter": "Filter koleksi",
|
||||||
"follow_us": "Ikuti kami",
|
"home": "Beranda"
|
||||||
"home": "Beranda",
|
|
||||||
"discord": "Gabung Discord kami",
|
|
||||||
"telegram": "Gabung Telegram kami",
|
|
||||||
"x": "Ikuti akun X kami",
|
|
||||||
"github": "Kontribusi di GitHub"
|
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Pencarian",
|
"search": "Pencarian",
|
||||||
@ -50,7 +44,6 @@
|
|||||||
"pause": "Hentikan sementara",
|
"pause": "Hentikan sementara",
|
||||||
"cancel": "Batalkan",
|
"cancel": "Batalkan",
|
||||||
"remove": "Hapus",
|
"remove": "Hapus",
|
||||||
"remove_from_list": "Hapus",
|
|
||||||
"space_left_on_disk": "{{space}} tersisa pada disk",
|
"space_left_on_disk": "{{space}} tersisa pada disk",
|
||||||
"eta": "Perkiraan {{eta}}",
|
"eta": "Perkiraan {{eta}}",
|
||||||
"downloading_metadata": "Mengunduh metadata…",
|
"downloading_metadata": "Mengunduh metadata…",
|
||||||
@ -58,12 +51,8 @@
|
|||||||
"requirements": "Keperluan sistem",
|
"requirements": "Keperluan sistem",
|
||||||
"minimum": "Minimum",
|
"minimum": "Minimum",
|
||||||
"recommended": "Rekomendasi",
|
"recommended": "Rekomendasi",
|
||||||
"no_minimum_requirements": "{{title}} Tidak ada informasi kebutuhan sistem",
|
|
||||||
"no_recommended_requirements": "{{title}} Tidak ada informasi rekomendasi kebutuhan sistem",
|
|
||||||
"release_date": "Dirilis pada {{date}}",
|
"release_date": "Dirilis pada {{date}}",
|
||||||
"publisher": "Dipublikasikan oleh {{publisher}}",
|
"publisher": "Dipublikasikan oleh {{publisher}}",
|
||||||
"copy_link_to_clipboard": "Salin tautan",
|
|
||||||
"copied_link_to_clipboard": "Tautan tersalin",
|
|
||||||
"hours": "jam",
|
"hours": "jam",
|
||||||
"minutes": "menit",
|
"minutes": "menit",
|
||||||
"amount_hours": "{{amount}} jam",
|
"amount_hours": "{{amount}} jam",
|
||||||
@ -83,15 +72,7 @@
|
|||||||
"change": "Ubah",
|
"change": "Ubah",
|
||||||
"repacks_modal_description": "Pilih repack yang kamu ingin unduh",
|
"repacks_modal_description": "Pilih repack yang kamu ingin unduh",
|
||||||
"select_folder_hint": "Untuk merubah folder bawaan, akses melalui",
|
"select_folder_hint": "Untuk merubah folder bawaan, akses melalui",
|
||||||
"download_now": "Unduh sekarang",
|
"download_now": "Unduh sekarang"
|
||||||
"installation_instructions": "Instruksi Instalasi",
|
|
||||||
"installation_instructions_description": "Langkah tambahan dibutuhkan untuk meng-instal game ini",
|
|
||||||
"online_fix_instruction": "OnlineFix games mebutuhkan kata sandi untuk ekstraksi. Saat diperlukan, gunakan kata sandi ini:",
|
|
||||||
"dodi_installation_instruction": "Saat menjalankan DODI installer, tekan tombol atas pada keyboard <0 /> untuk melanjutkan proses instalasi:",
|
|
||||||
"dont_show_it_again": "Jangan tunjukkan lagi",
|
|
||||||
"copy_to_clipboard": "Salin",
|
|
||||||
"copied_to_clipboard": "Tersalin",
|
|
||||||
"got_it": "Paham"
|
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Aktivasi Hydra",
|
"title": "Aktivasi Hydra",
|
||||||
@ -107,17 +88,13 @@
|
|||||||
"eta": "Perkiraan {{eta}}",
|
"eta": "Perkiraan {{eta}}",
|
||||||
"paused": "Terhenti sementara",
|
"paused": "Terhenti sementara",
|
||||||
"verifying": "Memeriksa…",
|
"verifying": "Memeriksa…",
|
||||||
"completed_at": "Selesai pada {{date}}",
|
|
||||||
"completed": "Selesai",
|
"completed": "Selesai",
|
||||||
"download_again": "Unduh lagi",
|
|
||||||
"cancel": "Batalkan",
|
"cancel": "Batalkan",
|
||||||
"filter": "Saring game yang diunduh",
|
"filter": "Saring game yang diunduh",
|
||||||
"remove": "Hapus",
|
"remove": "Hapus",
|
||||||
"downloading_metadata": "Mengunduh metadata…",
|
"downloading_metadata": "Mengunduh metadata…",
|
||||||
"starting_download": "Memulai unduhan…",
|
|
||||||
"deleting": "Menghapus file instalasi…",
|
"deleting": "Menghapus file instalasi…",
|
||||||
"delete": "Hapus file instalasi",
|
"delete": "Hapus file instalasi",
|
||||||
"remove_from_list": "Hapus",
|
|
||||||
"delete_modal_title": "Kamu yakin?",
|
"delete_modal_title": "Kamu yakin?",
|
||||||
"delete_modal_description": "Proses ini akan menghapus semua file instalasi dari komputer kamu",
|
"delete_modal_description": "Proses ini akan menghapus semua file instalasi dari komputer kamu",
|
||||||
"install": "Install"
|
"install": "Install"
|
||||||
@ -128,8 +105,6 @@
|
|||||||
"notifications": "Pengingat",
|
"notifications": "Pengingat",
|
||||||
"enable_download_notifications": "Saat unduhan selesai",
|
"enable_download_notifications": "Saat unduhan selesai",
|
||||||
"enable_repack_list_notifications": "Saat repack terbaru ditambahkan",
|
"enable_repack_list_notifications": "Saat repack terbaru ditambahkan",
|
||||||
"telemetry": "Telemetri",
|
|
||||||
"telemetry_description": "Izinkan statistik penggunaan data anonim",
|
|
||||||
"behavior": "Perilaku",
|
"behavior": "Perilaku",
|
||||||
"quit_app_instead_hiding": "Tutup aplikasi alih-alih menyembunyikan aplikasi",
|
"quit_app_instead_hiding": "Tutup aplikasi alih-alih menyembunyikan aplikasi",
|
||||||
"launch_with_system": "Jalankan saat memulai sistem"
|
"launch_with_system": "Jalankan saat memulai sistem"
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
"featured": "In primo piano",
|
"featured": "In primo piano",
|
||||||
"recently_added": "Aggiunti di recente",
|
|
||||||
"trending": "Di tendenza",
|
"trending": "Di tendenza",
|
||||||
"surprise_me": "Sorprendimi",
|
"surprise_me": "Sorprendimi",
|
||||||
"no_results": "Nessun risultato trovato"
|
"no_results": "Nessun risultato trovato"
|
||||||
@ -15,12 +14,7 @@
|
|||||||
"paused": "{{title}} (In pausa)",
|
"paused": "{{title}} (In pausa)",
|
||||||
"downloading": "{{title}} ({{percentage}} - Download…)",
|
"downloading": "{{title}} ({{percentage}} - Download…)",
|
||||||
"filter": "Filtra libreria",
|
"filter": "Filtra libreria",
|
||||||
"follow_us": "Seguici",
|
"home": "Home"
|
||||||
"home": "Home",
|
|
||||||
"discord": "Unisciti al nostro Discord",
|
|
||||||
"telegram": "Unisciti al nostro Telegram",
|
|
||||||
"x": "Segui su X",
|
|
||||||
"github": "Contribuisci su GitHub"
|
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Cerca",
|
"search": "Cerca",
|
||||||
@ -50,7 +44,6 @@
|
|||||||
"pause": "Metti in pausa",
|
"pause": "Metti in pausa",
|
||||||
"cancel": "Annulla",
|
"cancel": "Annulla",
|
||||||
"remove": "Rimuovi",
|
"remove": "Rimuovi",
|
||||||
"remove_from_list": "Rimuovi",
|
|
||||||
"space_left_on_disk": "{{space}} rimasto sul disco",
|
"space_left_on_disk": "{{space}} rimasto sul disco",
|
||||||
"eta": "Conclusione {{eta}}",
|
"eta": "Conclusione {{eta}}",
|
||||||
"downloading_metadata": "Scaricamento metadati…",
|
"downloading_metadata": "Scaricamento metadati…",
|
||||||
@ -58,12 +51,8 @@
|
|||||||
"requirements": "Requisiti di sistema",
|
"requirements": "Requisiti di sistema",
|
||||||
"minimum": "Minimi",
|
"minimum": "Minimi",
|
||||||
"recommended": "Consigliati",
|
"recommended": "Consigliati",
|
||||||
"no_minimum_requirements": "{{title}} non fornisce informazioni sui requisiti minimi",
|
|
||||||
"no_recommended_requirements": "{{title}} non fornisce informazioni sui requisiti consigliati",
|
|
||||||
"release_date": "Rilasciato il {{date}}",
|
"release_date": "Rilasciato il {{date}}",
|
||||||
"publisher": "Pubblicato da {{publisher}}",
|
"publisher": "Pubblicato da {{publisher}}",
|
||||||
"copy_link_to_clipboard": "Copia link",
|
|
||||||
"copied_link_to_clipboard": "Link copiato",
|
|
||||||
"hours": "ore",
|
"hours": "ore",
|
||||||
"minutes": "minuti",
|
"minutes": "minuti",
|
||||||
"amount_hours": "{{amount}} ore",
|
"amount_hours": "{{amount}} ore",
|
||||||
@ -84,14 +73,6 @@
|
|||||||
"repacks_modal_description": "Scegli il repack che vuoi scaricare",
|
"repacks_modal_description": "Scegli il repack che vuoi scaricare",
|
||||||
"select_folder_hint": "Per cambiare la cartella predefinita, accedi alle",
|
"select_folder_hint": "Per cambiare la cartella predefinita, accedi alle",
|
||||||
"download_now": "Scarica ora",
|
"download_now": "Scarica ora",
|
||||||
"installation_instructions": "Istruzioni di installazione",
|
|
||||||
"installation_instructions_description": "Sono necessari passaggi aggiuntivi per installare questo gioco",
|
|
||||||
"online_fix_instruction": "I giochi OnlineFix richiedono una password per essere estratti. Quando richiesto, utilizza la seguente password:",
|
|
||||||
"dodi_installation_instruction": "Quando apri l'installatore di DODI, premi il tasto su della tua tastiera <0 /> per avviare il processo di installazione:",
|
|
||||||
"dont_show_it_again": "Non mostrarlo più",
|
|
||||||
"copy_to_clipboard": "Copia",
|
|
||||||
"copied_to_clipboard": "Copiato",
|
|
||||||
"got_it": "Capito",
|
|
||||||
"no_shop_details": "Impossibile recuperare i dettagli del negozio.",
|
"no_shop_details": "Impossibile recuperare i dettagli del negozio.",
|
||||||
"download_options": "Opzioni di download",
|
"download_options": "Opzioni di download",
|
||||||
"download_path": "Percorso di download",
|
"download_path": "Percorso di download",
|
||||||
@ -114,17 +95,13 @@
|
|||||||
"eta": "Conclusione {{eta}}",
|
"eta": "Conclusione {{eta}}",
|
||||||
"paused": "In pausa",
|
"paused": "In pausa",
|
||||||
"verifying": "Verifica…",
|
"verifying": "Verifica…",
|
||||||
"completed_at": "Completato in {{date}}",
|
|
||||||
"completed": "Completato",
|
"completed": "Completato",
|
||||||
"download_again": "Scarica di nuovo",
|
|
||||||
"cancel": "Annulla",
|
"cancel": "Annulla",
|
||||||
"filter": "Filtra giochi scaricati",
|
"filter": "Filtra giochi scaricati",
|
||||||
"remove": "Rimuovi",
|
"remove": "Rimuovi",
|
||||||
"downloading_metadata": "Scaricamento metadati…",
|
"downloading_metadata": "Scaricamento metadati…",
|
||||||
"starting_download": "Avvio download…",
|
|
||||||
"deleting": "Eliminazione dell'installer…",
|
"deleting": "Eliminazione dell'installer…",
|
||||||
"delete": "Rimuovi installer",
|
"delete": "Rimuovi installer",
|
||||||
"remove_from_list": "Rimuovi",
|
|
||||||
"delete_modal_title": "Sei sicuro?",
|
"delete_modal_title": "Sei sicuro?",
|
||||||
"delete_modal_description": "Questo rimuoverà tutti i file di installazione dal tuo computer",
|
"delete_modal_description": "Questo rimuoverà tutti i file di installazione dal tuo computer",
|
||||||
"install": "Installa"
|
"install": "Installa"
|
||||||
@ -135,8 +112,6 @@
|
|||||||
"notifications": "Notifiche",
|
"notifications": "Notifiche",
|
||||||
"enable_download_notifications": "Quando un download è completo",
|
"enable_download_notifications": "Quando un download è completo",
|
||||||
"enable_repack_list_notifications": "Quando viene aggiunto un nuovo repack",
|
"enable_repack_list_notifications": "Quando viene aggiunto un nuovo repack",
|
||||||
"telemetry": "Telemetria",
|
|
||||||
"telemetry_description": "Abilita statistiche di utilizzo anonime",
|
|
||||||
"real_debrid_api_token_label": "Token API Real Debrid",
|
"real_debrid_api_token_label": "Token API Real Debrid",
|
||||||
"quit_app_instead_hiding": "Esci da Hydra invece di nascondere nell'area di notifica",
|
"quit_app_instead_hiding": "Esci da Hydra invece di nascondere nell'area di notifica",
|
||||||
"launch_with_system": "Apri Hydra all'avvio",
|
"launch_with_system": "Apri Hydra all'avvio",
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
"featured": "추천",
|
"featured": "추천",
|
||||||
"recently_added": "최근 추가됨",
|
|
||||||
"trending": "인기",
|
"trending": "인기",
|
||||||
"surprise_me": "무작위 추천",
|
"surprise_me": "무작위 추천",
|
||||||
"no_results": "결과 없음"
|
"no_results": "결과 없음"
|
||||||
@ -15,12 +14,7 @@
|
|||||||
"paused": "{{title}} (일시 정지됨)",
|
"paused": "{{title}} (일시 정지됨)",
|
||||||
"downloading": "{{title}} ({{percentage}} - 다운로드 중…)",
|
"downloading": "{{title}} ({{percentage}} - 다운로드 중…)",
|
||||||
"filter": "라이브러리 정렬",
|
"filter": "라이브러리 정렬",
|
||||||
"follow_us": "공식 SNS",
|
"home": "홈"
|
||||||
"home": "홈",
|
|
||||||
"discord": "공식 디스코드",
|
|
||||||
"telegram": "공식 텔레그램",
|
|
||||||
"x": "공식 X (구 트위터)",
|
|
||||||
"github": "GitHub에서 기여하기"
|
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "게임 검색하기",
|
"search": "게임 검색하기",
|
||||||
@ -50,7 +44,6 @@
|
|||||||
"pause": "일시 정지",
|
"pause": "일시 정지",
|
||||||
"cancel": "취소",
|
"cancel": "취소",
|
||||||
"remove": "제거",
|
"remove": "제거",
|
||||||
"remove_from_list": "목록에서 제거",
|
|
||||||
"space_left_on_disk": "여유 저장 용량 {{space}} 남음",
|
"space_left_on_disk": "여유 저장 용량 {{space}} 남음",
|
||||||
"eta": "완료까지 {{eta}}",
|
"eta": "완료까지 {{eta}}",
|
||||||
"downloading_metadata": "메타데이터 다운로드 중…",
|
"downloading_metadata": "메타데이터 다운로드 중…",
|
||||||
@ -58,12 +51,8 @@
|
|||||||
"requirements": "시스템 사양",
|
"requirements": "시스템 사양",
|
||||||
"minimum": "최저 사양",
|
"minimum": "최저 사양",
|
||||||
"recommended": "권장 사양",
|
"recommended": "권장 사양",
|
||||||
"no_minimum_requirements": "{{title}}의 최저 사양을 제공받지 못 함",
|
|
||||||
"no_recommended_requirements": "{{title}}의 권장 사양을 제공받지 못 함",
|
|
||||||
"release_date": "{{date}}에 발매됨",
|
"release_date": "{{date}}에 발매됨",
|
||||||
"publisher": "{{publisher}} 배급",
|
"publisher": "{{publisher}} 배급",
|
||||||
"copy_link_to_clipboard": "링크 복사하기",
|
|
||||||
"copied_link_to_clipboard": "링크 복사됨",
|
|
||||||
"hours": "시",
|
"hours": "시",
|
||||||
"minutes": "분",
|
"minutes": "분",
|
||||||
"amount_hours": "{{amount}} 시간",
|
"amount_hours": "{{amount}} 시간",
|
||||||
@ -83,15 +72,7 @@
|
|||||||
"change": "바꾸기",
|
"change": "바꾸기",
|
||||||
"repacks_modal_description": "다운로드 할 리팩을 선택해 주세요",
|
"repacks_modal_description": "다운로드 할 리팩을 선택해 주세요",
|
||||||
"select_folder_hint": "기본 폴더를 바꾸려면 <0>설정</0>으로 가세요",
|
"select_folder_hint": "기본 폴더를 바꾸려면 <0>설정</0>으로 가세요",
|
||||||
"download_now": "지금 다운로드",
|
"download_now": "지금 다운로드"
|
||||||
"installation_instructions": "설치 방법",
|
|
||||||
"installation_instructions_description": "이 게임을 설치하기 위해서는 추가적인 단계가 필요합니다",
|
|
||||||
"online_fix_instruction": "OnlineFix 게임들은 압축 해제 시 암호가 필요합니다. 비밀번호를 물을 때 다음을 암호로 사용하기:",
|
|
||||||
"dodi_installation_instruction": "DODI 인스톨러를 실행했다면 키보드의 위 방향키를 눌러 설치를 시작하세요:",
|
|
||||||
"dont_show_it_again": "다시 보지 않기",
|
|
||||||
"copy_to_clipboard": "복사하기",
|
|
||||||
"copied_to_clipboard": "복사됨",
|
|
||||||
"got_it": "알았습니다"
|
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Hydra 실행",
|
"title": "Hydra 실행",
|
||||||
@ -107,17 +88,13 @@
|
|||||||
"eta": "완료까지 {{eta}}",
|
"eta": "완료까지 {{eta}}",
|
||||||
"paused": "일시 정지됨",
|
"paused": "일시 정지됨",
|
||||||
"verifying": "검증중…",
|
"verifying": "검증중…",
|
||||||
"completed_at": "{{date}}에 완료됨",
|
|
||||||
"completed": "완료됨",
|
"completed": "완료됨",
|
||||||
"download_again": "다시 다운로드 하기",
|
|
||||||
"cancel": "취소",
|
"cancel": "취소",
|
||||||
"filter": "다운로드 된 게임들을 정렬하기",
|
"filter": "다운로드 된 게임들을 정렬하기",
|
||||||
"remove": "제거하기",
|
"remove": "제거하기",
|
||||||
"downloading_metadata": "메타데이터 다운로드 중…",
|
"downloading_metadata": "메타데이터 다운로드 중…",
|
||||||
"starting_download": "다운로드 개시 중…",
|
|
||||||
"deleting": "인스톨러 삭제 중…",
|
"deleting": "인스톨러 삭제 중…",
|
||||||
"delete": "인스톨러 삭제하기",
|
"delete": "인스톨러 삭제하기",
|
||||||
"remove_from_list": "제거하기",
|
|
||||||
"delete_modal_title": "정말로 하시겠습니까?",
|
"delete_modal_title": "정말로 하시겠습니까?",
|
||||||
"delete_modal_description": "이 기기의 모든 설치 파일들이 제거될 것입니다",
|
"delete_modal_description": "이 기기의 모든 설치 파일들이 제거될 것입니다",
|
||||||
"install": "설치"
|
"install": "설치"
|
||||||
@ -128,8 +105,6 @@
|
|||||||
"notifications": "알림",
|
"notifications": "알림",
|
||||||
"enable_download_notifications": "다운로드가 완료되었을 때",
|
"enable_download_notifications": "다운로드가 완료되었을 때",
|
||||||
"enable_repack_list_notifications": "새 리팩이 추가되었을 때",
|
"enable_repack_list_notifications": "새 리팩이 추가되었을 때",
|
||||||
"telemetry": "자동 데이터 수집",
|
|
||||||
"telemetry_description": "익명 사용 통계를 활성화",
|
|
||||||
"quit_app_instead_hiding": "작업 표시줄 트레이로 최소화하는 대신 Hydra를 종료",
|
"quit_app_instead_hiding": "작업 표시줄 트레이로 최소화하는 대신 Hydra를 종료",
|
||||||
"launch_with_system": "컴퓨터가 시작되었을 때 Hydra 실행",
|
"launch_with_system": "컴퓨터가 시작되었을 때 Hydra 실행",
|
||||||
"general": "일반",
|
"general": "일반",
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
"featured": "Uitgelicht",
|
"featured": "Uitgelicht",
|
||||||
"recently_added": "Recent Toegevoegd",
|
|
||||||
"trending": "Trending",
|
"trending": "Trending",
|
||||||
"surprise_me": "Verrasing",
|
"surprise_me": "Verrasing",
|
||||||
"no_results": "Geen resultaten gevonden"
|
"no_results": "Geen resultaten gevonden"
|
||||||
@ -15,12 +14,7 @@
|
|||||||
"paused": "{{title}} (Gepauzeerd)",
|
"paused": "{{title}} (Gepauzeerd)",
|
||||||
"downloading": "{{title}} ({{percentage}} - Downloading…)",
|
"downloading": "{{title}} ({{percentage}} - Downloading…)",
|
||||||
"filter": "Filter Bibliotheek",
|
"filter": "Filter Bibliotheek",
|
||||||
"follow_us": "volg ons",
|
"home": "Home"
|
||||||
"home": "Home",
|
|
||||||
"discord": "Volg onze Discord",
|
|
||||||
"telegram": "Volg onze Telegram",
|
|
||||||
"x": "Volg ons op X",
|
|
||||||
"github": "Contribute op GitHub"
|
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Zoek spellen",
|
"search": "Zoek spellen",
|
||||||
@ -50,7 +44,6 @@
|
|||||||
"pause": "Pauze",
|
"pause": "Pauze",
|
||||||
"cancel": "Stoppen",
|
"cancel": "Stoppen",
|
||||||
"remove": "Verwijderen",
|
"remove": "Verwijderen",
|
||||||
"remove_from_list": "Verwijdere van lijst",
|
|
||||||
"space_left_on_disk": "{{space}} Over op schijf",
|
"space_left_on_disk": "{{space}} Over op schijf",
|
||||||
"eta": "Conclusie {{eta}}",
|
"eta": "Conclusie {{eta}}",
|
||||||
"downloading_metadata": "Downloading metadata…",
|
"downloading_metadata": "Downloading metadata…",
|
||||||
@ -58,12 +51,8 @@
|
|||||||
"requirements": "Systeem vereisten",
|
"requirements": "Systeem vereisten",
|
||||||
"minimum": "Minimaal",
|
"minimum": "Minimaal",
|
||||||
"recommended": "Aanbevolen",
|
"recommended": "Aanbevolen",
|
||||||
"no_minimum_requirements": "{{title}} biedt geen informatie over de minimale vereisten",
|
|
||||||
"no_recommended_requirements": "{{title}} biedt geen informatie over aanbevolen vereisten",
|
|
||||||
"release_date": "Uitgebracht op {{date}}",
|
"release_date": "Uitgebracht op {{date}}",
|
||||||
"publisher": "Gepubliceerd door {{publisher}}",
|
"publisher": "Gepubliceerd door {{publisher}}",
|
||||||
"copy_link_to_clipboard": "Kopieer link",
|
|
||||||
"copied_link_to_clipboard": "Link Gekopieerd",
|
|
||||||
"hours": "uren",
|
"hours": "uren",
|
||||||
"minutes": "minuten",
|
"minutes": "minuten",
|
||||||
"amount_hours": "{{amount}} uren",
|
"amount_hours": "{{amount}} uren",
|
||||||
@ -83,15 +72,7 @@
|
|||||||
"change": "Verander",
|
"change": "Verander",
|
||||||
"repacks_modal_description": "Kies de herverpakking die u wilt downloaden",
|
"repacks_modal_description": "Kies de herverpakking die u wilt downloaden",
|
||||||
"select_folder_hint": "Om de standaardmap te wijzigen, gaat u naar <0>instellingen</0>",
|
"select_folder_hint": "Om de standaardmap te wijzigen, gaat u naar <0>instellingen</0>",
|
||||||
"download_now": "Download nu",
|
"download_now": "Download nu"
|
||||||
"installation_instructions": "Installatie instructies",
|
|
||||||
"installation_instructions_description": "Er zijn extra stappen vereist om deze game te installeren",
|
|
||||||
"online_fix_instruction": "OnlineFix-spellen vereisen dat een wachtwoord wordt uitgepakt. Gebruik indien nodig het volgende wachtwoord:",
|
|
||||||
"dodi_installation_instruction": "Wanneer u het DODI-installatieprogramma opent, drukt u op de toets omhoog <0 /> op uw toetsenbord om het installatieproces te starten:",
|
|
||||||
"dont_show_it_again": "Laat het niet meer zien",
|
|
||||||
"copy_to_clipboard": "Kopiëren",
|
|
||||||
"copied_to_clipboard": "Gekopieerd",
|
|
||||||
"got_it": "Begrepen"
|
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Activeer Hydra",
|
"title": "Activeer Hydra",
|
||||||
@ -107,17 +88,13 @@
|
|||||||
"eta": "Conclusie{{eta}}",
|
"eta": "Conclusie{{eta}}",
|
||||||
"paused": "Gepauzeerd",
|
"paused": "Gepauzeerd",
|
||||||
"verifying": "Verifiëren…",
|
"verifying": "Verifiëren…",
|
||||||
"completed_at": "Voltooid binnen {{date}}",
|
|
||||||
"completed": "Voltooid",
|
"completed": "Voltooid",
|
||||||
"download_again": "Opnieuw downloaden",
|
|
||||||
"cancel": "Annuleren",
|
"cancel": "Annuleren",
|
||||||
"filter": "Filter gedownloade games",
|
"filter": "Filter gedownloade games",
|
||||||
"remove": "Verwijderen",
|
"remove": "Verwijderen",
|
||||||
"downloading_metadata": "Metagegevens downloaden",
|
"downloading_metadata": "Metagegevens downloaden",
|
||||||
"starting_download": "download starten",
|
|
||||||
"deleting": "Installatieprogramma verwijderen…",
|
"deleting": "Installatieprogramma verwijderen…",
|
||||||
"delete": "Installatieprogramma verwijderen",
|
"delete": "Installatieprogramma verwijderen",
|
||||||
"remove_from_list": "Verwijderen",
|
|
||||||
"delete_modal_title": "Weet je het zeker?",
|
"delete_modal_title": "Weet je het zeker?",
|
||||||
"delete_modal_description": "Hiermee worden alle installatiebestanden van uw computer verwijderd",
|
"delete_modal_description": "Hiermee worden alle installatiebestanden van uw computer verwijderd",
|
||||||
"install": "Installeren"
|
"install": "Installeren"
|
||||||
@ -128,8 +105,6 @@
|
|||||||
"notifications": "Meldingen",
|
"notifications": "Meldingen",
|
||||||
"enable_download_notifications": "Wanneer een download voltooid is",
|
"enable_download_notifications": "Wanneer een download voltooid is",
|
||||||
"enable_repack_list_notifications": "Wanneer een nieuwe herverpakking wordt toegevoegd",
|
"enable_repack_list_notifications": "Wanneer een nieuwe herverpakking wordt toegevoegd",
|
||||||
"telemetry": "Telemetrie",
|
|
||||||
"telemetry_description": "Schakel anonieme gebruiksstatistieken in",
|
|
||||||
"real_debrid_api_token_label": "Real-Debrid API token",
|
"real_debrid_api_token_label": "Real-Debrid API token",
|
||||||
"quit_app_instead_hiding": "Sluit Hydra af in plaats van te minimaliseren naar de lade",
|
"quit_app_instead_hiding": "Sluit Hydra af in plaats van te minimaliseren naar de lade",
|
||||||
"launch_with_system": "Start Hydra bij het opstarten van het systeem",
|
"launch_with_system": "Start Hydra bij het opstarten van het systeem",
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
"featured": "Wyróżnione",
|
"featured": "Wyróżnione",
|
||||||
"recently_added": "Ostatnio dodane",
|
|
||||||
"trending": "Trendujące",
|
"trending": "Trendujące",
|
||||||
"surprise_me": "Zaskocz mnie",
|
"surprise_me": "Zaskocz mnie",
|
||||||
"no_results": "Nie znaleziono wyników"
|
"no_results": "Nie znaleziono wyników"
|
||||||
@ -15,12 +14,7 @@
|
|||||||
"paused": "{{title}} (Zatrzymano)",
|
"paused": "{{title}} (Zatrzymano)",
|
||||||
"downloading": "{{title}} ({{percentage}} - Pobieranie…)",
|
"downloading": "{{title}} ({{percentage}} - Pobieranie…)",
|
||||||
"filter": "Filtruj biblioteke",
|
"filter": "Filtruj biblioteke",
|
||||||
"follow_us": "Śledź nas",
|
"home": "Główna"
|
||||||
"home": "Główna",
|
|
||||||
"discord": "Dołącz nasz Discord",
|
|
||||||
"telegram": "Dołącz nasz Telegram",
|
|
||||||
"x": "Śledź na X",
|
|
||||||
"github": "Przyczyń się na GitHub"
|
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Szukaj",
|
"search": "Szukaj",
|
||||||
@ -50,7 +44,6 @@
|
|||||||
"pause": "Zatrzymaj",
|
"pause": "Zatrzymaj",
|
||||||
"cancel": "Anuluj",
|
"cancel": "Anuluj",
|
||||||
"remove": "Usuń",
|
"remove": "Usuń",
|
||||||
"remove_from_list": "Usuń",
|
|
||||||
"space_left_on_disk": "{{space}} wolnego na dysku",
|
"space_left_on_disk": "{{space}} wolnego na dysku",
|
||||||
"eta": "Podsumowanie {{eta}}",
|
"eta": "Podsumowanie {{eta}}",
|
||||||
"downloading_metadata": "Pobieranie metadata…",
|
"downloading_metadata": "Pobieranie metadata…",
|
||||||
@ -58,12 +51,8 @@
|
|||||||
"requirements": "Wymagania systemowe",
|
"requirements": "Wymagania systemowe",
|
||||||
"minimum": "Minimalne",
|
"minimum": "Minimalne",
|
||||||
"recommended": "Zalecane",
|
"recommended": "Zalecane",
|
||||||
"no_minimum_requirements": "{{title}} nie zawiera informacji o minimalnych wymaganiach",
|
|
||||||
"no_recommended_requirements": "{{title}} nie zawiera informacji o zalecanych wymaganiach",
|
|
||||||
"release_date": "Wydano w {{date}}",
|
"release_date": "Wydano w {{date}}",
|
||||||
"publisher": "Opublikowany przez {{publisher}}",
|
"publisher": "Opublikowany przez {{publisher}}",
|
||||||
"copy_link_to_clipboard": "Kopiuj łącze",
|
|
||||||
"copied_link_to_clipboard": "Skopiowano łącze",
|
|
||||||
"hours": "godzin",
|
"hours": "godzin",
|
||||||
"minutes": "minut",
|
"minutes": "minut",
|
||||||
"amount_hours": "{{amount}} godzin",
|
"amount_hours": "{{amount}} godzin",
|
||||||
@ -84,14 +73,6 @@
|
|||||||
"repacks_modal_description": "Wybierz repack, który chcesz pobrać",
|
"repacks_modal_description": "Wybierz repack, który chcesz pobrać",
|
||||||
"select_folder_hint": "Aby zmienić domyślny folder, przejdź do",
|
"select_folder_hint": "Aby zmienić domyślny folder, przejdź do",
|
||||||
"download_now": "Pobierz teraz",
|
"download_now": "Pobierz teraz",
|
||||||
"installation_instructions": "Instrukcja instalacji",
|
|
||||||
"installation_instructions_description": "Do zainstalowania tej gry wymagane są dodatkowe kroki",
|
|
||||||
"online_fix_instruction": "Gry OnlineFix wymagają hasła do wyodrębnienia. W razie potrzeby użyj następującego hasła:",
|
|
||||||
"dodi_installation_instruction": "Po otwarciu instalatora DODI naciśnij klawisz <0 /> w górę, aby rozpocząć proces instalacji:",
|
|
||||||
"dont_show_it_again": "Nie pokazuj tego ponownie",
|
|
||||||
"copy_to_clipboard": "Skopiuj",
|
|
||||||
"copied_to_clipboard": "Skopiowano",
|
|
||||||
"got_it": "Rozumiem",
|
|
||||||
"no_shop_details": "Nie udało się pobrać danych sklepu.",
|
"no_shop_details": "Nie udało się pobrać danych sklepu.",
|
||||||
"download_options": "Opcje pobierania",
|
"download_options": "Opcje pobierania",
|
||||||
"download_path": "Ścieżka pobierania",
|
"download_path": "Ścieżka pobierania",
|
||||||
@ -114,17 +95,13 @@
|
|||||||
"eta": "Podsumowanie {{eta}}",
|
"eta": "Podsumowanie {{eta}}",
|
||||||
"paused": "Zatrzymano",
|
"paused": "Zatrzymano",
|
||||||
"verifying": "Weryfikowanie…",
|
"verifying": "Weryfikowanie…",
|
||||||
"completed_at": "Zakończono w {{date}}",
|
|
||||||
"completed": "Zakończono",
|
"completed": "Zakończono",
|
||||||
"download_again": "Pobierz ponownie",
|
|
||||||
"cancel": "Anuluj",
|
"cancel": "Anuluj",
|
||||||
"filter": "Filtruj pobrane gry",
|
"filter": "Filtruj pobrane gry",
|
||||||
"remove": "Usuń",
|
"remove": "Usuń",
|
||||||
"downloading_metadata": "Pobieranie metadata…",
|
"downloading_metadata": "Pobieranie metadata…",
|
||||||
"starting_download": "Rozpoczęto pobieranie…",
|
|
||||||
"deleting": "Usuwanie instalatora…",
|
"deleting": "Usuwanie instalatora…",
|
||||||
"delete": "Usuń instalator",
|
"delete": "Usuń instalator",
|
||||||
"remove_from_list": "Usuń",
|
|
||||||
"delete_modal_title": "Czy na pewno?",
|
"delete_modal_title": "Czy na pewno?",
|
||||||
"delete_modal_description": "Spowoduje to usunięcie wszystkich plików instalacyjnych z komputera",
|
"delete_modal_description": "Spowoduje to usunięcie wszystkich plików instalacyjnych z komputera",
|
||||||
"install": "Instaluj"
|
"install": "Instaluj"
|
||||||
@ -135,8 +112,6 @@
|
|||||||
"notifications": "Powiadomienia",
|
"notifications": "Powiadomienia",
|
||||||
"enable_download_notifications": "Gdy pobieranie zostanie zakończone",
|
"enable_download_notifications": "Gdy pobieranie zostanie zakończone",
|
||||||
"enable_repack_list_notifications": "Gdy dodawany jest nowy repack",
|
"enable_repack_list_notifications": "Gdy dodawany jest nowy repack",
|
||||||
"telemetry": "Telemetria",
|
|
||||||
"telemetry_description": "Włącz anonimowe statystyki użycia",
|
|
||||||
"real_debrid_api_token_label": "Real-Debrid API token",
|
"real_debrid_api_token_label": "Real-Debrid API token",
|
||||||
"quit_app_instead_hiding": "Zamknij Hydr zamiast minimalizować do zasobnika",
|
"quit_app_instead_hiding": "Zamknij Hydr zamiast minimalizować do zasobnika",
|
||||||
"launch_with_system": "Uruchom Hydra przy starcie systemu",
|
"launch_with_system": "Uruchom Hydra przy starcie systemu",
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"app": {
|
||||||
|
"successfully_signed_in": "Logado com sucesso"
|
||||||
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"featured": "Destaque",
|
"featured": "Destaque",
|
||||||
"trending": "Populares",
|
"trending": "Populares",
|
||||||
@ -15,9 +18,9 @@
|
|||||||
"downloading": "{{title}} ({{percentage}} - Baixando…)",
|
"downloading": "{{title}} ({{percentage}} - Baixando…)",
|
||||||
"filter": "Filtrar biblioteca",
|
"filter": "Filtrar biblioteca",
|
||||||
"home": "Início",
|
"home": "Início",
|
||||||
"follow_us": "Acompanhe-nos",
|
|
||||||
"queued": "{{title}} (Na fila)",
|
"queued": "{{title}} (Na fila)",
|
||||||
"game_has_no_executable": "Jogo não possui executável selecionado"
|
"game_has_no_executable": "Jogo não possui executável selecionado",
|
||||||
|
"sign_in": "Login"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Buscar jogos",
|
"search": "Buscar jogos",
|
||||||
@ -45,7 +48,6 @@
|
|||||||
"pause": "Pausar",
|
"pause": "Pausar",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"remove": "Remover",
|
"remove": "Remover",
|
||||||
"remove_from_list": "Remover",
|
|
||||||
"space_left_on_disk": "{{space}} livres em disco",
|
"space_left_on_disk": "{{space}} livres em disco",
|
||||||
"eta": "Conclusão {{eta}}",
|
"eta": "Conclusão {{eta}}",
|
||||||
"calculating_eta": "Calculando tempo restante…",
|
"calculating_eta": "Calculando tempo restante…",
|
||||||
@ -54,13 +56,9 @@
|
|||||||
"requirements": "Requisitos do sistema",
|
"requirements": "Requisitos do sistema",
|
||||||
"minimum": "Mínimos",
|
"minimum": "Mínimos",
|
||||||
"recommended": "Recomendados",
|
"recommended": "Recomendados",
|
||||||
"no_minimum_requirements": "{{title}} não possui informações de requisitos mínimos",
|
|
||||||
"no_recommended_requirements": "{{title}} não possui informações de requisitos recomendados",
|
|
||||||
"paused": "Pausado",
|
"paused": "Pausado",
|
||||||
"release_date": "Lançado em {{date}}",
|
"release_date": "Lançado em {{date}}",
|
||||||
"publisher": "Publicado por {{publisher}}",
|
"publisher": "Publicado por {{publisher}}",
|
||||||
"copy_link_to_clipboard": "Copiar link",
|
|
||||||
"copied_link_to_clipboard": "Link copiado",
|
|
||||||
"hours": "horas",
|
"hours": "horas",
|
||||||
"minutes": "minutos",
|
"minutes": "minutos",
|
||||||
"amount_hours": "{{amount}} horas",
|
"amount_hours": "{{amount}} horas",
|
||||||
@ -82,14 +80,6 @@
|
|||||||
"repacks_modal_description": "Escolha o repack do jogo que deseja baixar",
|
"repacks_modal_description": "Escolha o repack do jogo que deseja baixar",
|
||||||
"select_folder_hint": "Para trocar o diretório padrão, acesse a <0>Tela de Ajustes</0>",
|
"select_folder_hint": "Para trocar o diretório padrão, acesse a <0>Tela de Ajustes</0>",
|
||||||
"download_now": "Iniciar download",
|
"download_now": "Iniciar download",
|
||||||
"installation_instructions": "Instruções de Instalação",
|
|
||||||
"installation_instructions_description": "Passos adicionais são necessários para instalar esse jogo",
|
|
||||||
"online_fix_instruction": "Jogos OnlineFix precisam de uma senha para serem extraídos. Quando solicitado, utilize a seguinte senha:",
|
|
||||||
"dodi_installation_instruction": "Quando o instalador do DODI for aberto, pressione a seta para cima <0 /> do teclado para iniciar o processo de instalação:",
|
|
||||||
"dont_show_it_again": "Não mostrar novamente",
|
|
||||||
"copy_to_clipboard": "Copiar",
|
|
||||||
"copied_to_clipboard": "Copiado",
|
|
||||||
"got_it": "Entendi",
|
|
||||||
"no_shop_details": "Não foi possível obter os detalhes da loja.",
|
"no_shop_details": "Não foi possível obter os detalhes da loja.",
|
||||||
"download_options": "Opções de download",
|
"download_options": "Opções de download",
|
||||||
"download_path": "Diretório de download",
|
"download_path": "Diretório de download",
|
||||||
@ -134,16 +124,12 @@
|
|||||||
"eta": "Conclusão {{eta}}",
|
"eta": "Conclusão {{eta}}",
|
||||||
"paused": "Pausado",
|
"paused": "Pausado",
|
||||||
"verifying": "Verificando…",
|
"verifying": "Verificando…",
|
||||||
"completed_at": "Concluído em {{date}}",
|
|
||||||
"completed": "Concluído",
|
"completed": "Concluído",
|
||||||
"removed": "Cancelado",
|
"removed": "Cancelado",
|
||||||
"download_again": "Baixar novamente",
|
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"filter": "Filtrar jogos baixados",
|
"filter": "Filtrar jogos baixados",
|
||||||
"remove": "Remover",
|
"remove": "Remover",
|
||||||
"downloading_metadata": "Baixando metadados…",
|
"downloading_metadata": "Baixando metadados…",
|
||||||
"starting_download": "Iniciando download…",
|
|
||||||
"remove_from_list": "Remover",
|
|
||||||
"delete": "Remover instalador",
|
"delete": "Remover instalador",
|
||||||
"delete_modal_description": "Isso removerá todos os arquivos de instalação do seu computador",
|
"delete_modal_description": "Isso removerá todos os arquivos de instalação do seu computador",
|
||||||
"delete_modal_title": "Tem certeza?",
|
"delete_modal_title": "Tem certeza?",
|
||||||
@ -162,8 +148,6 @@
|
|||||||
"notifications": "Notificações",
|
"notifications": "Notificações",
|
||||||
"enable_download_notifications": "Quando um download for concluído",
|
"enable_download_notifications": "Quando um download for concluído",
|
||||||
"enable_repack_list_notifications": "Quando a lista de repacks for atualizada",
|
"enable_repack_list_notifications": "Quando a lista de repacks for atualizada",
|
||||||
"telemetry": "Telemetria",
|
|
||||||
"telemetry_description": "Habilitar estatísticas de uso anônimas",
|
|
||||||
"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 ao invés de minimizá-lo ao fechar",
|
"quit_app_instead_hiding": "Encerrar o Hydra ao invés de minimizá-lo ao fechar",
|
||||||
"launch_with_system": "Iniciar o Hydra junto com o sistema",
|
"launch_with_system": "Iniciar o Hydra junto com o sistema",
|
||||||
@ -197,7 +181,12 @@
|
|||||||
"sync_download_sources": "Sincronizar",
|
"sync_download_sources": "Sincronizar",
|
||||||
"removed_download_source": "Fonte removida",
|
"removed_download_source": "Fonte removida",
|
||||||
"added_download_source": "Fonte adicionada",
|
"added_download_source": "Fonte adicionada",
|
||||||
"download_sources_synced": "As fontes foram sincronizadas"
|
"download_sources_synced": "As fontes foram sincronizadas",
|
||||||
|
"insert_valid_json_url": "Insira a url de um JSON válido",
|
||||||
|
"found_download_option_zero": "Nenhuma opção de download encontrada",
|
||||||
|
"found_download_option_one": "{{countFormatted}} opção de download encontrada",
|
||||||
|
"found_download_option_other": "{{countFormatted}} opções de download encontradas",
|
||||||
|
"import": "Importar"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Download concluído",
|
"download_complete": "Download concluído",
|
||||||
@ -227,5 +216,27 @@
|
|||||||
},
|
},
|
||||||
"forms": {
|
"forms": {
|
||||||
"toggle_password_visibility": "Alternar visibilidade da senha"
|
"toggle_password_visibility": "Alternar visibilidade da senha"
|
||||||
|
},
|
||||||
|
"user_profile": {
|
||||||
|
"amount_hours": "{{amount}} horas",
|
||||||
|
"amount_minutes": "{{amount}} minutos",
|
||||||
|
"last_time_played": "Jogou {{period}}",
|
||||||
|
"activity": "Atividade recente",
|
||||||
|
"library": "Biblioteca",
|
||||||
|
"total_play_time": "Tempo total de jogo: {{amount}}",
|
||||||
|
"no_recent_activity_title": "Hmmm… nada por aqui",
|
||||||
|
"no_recent_activity_description": "Parece que você não jogou nada recentemente. Que tal começar agora?",
|
||||||
|
"display_name": "Nome de exibição",
|
||||||
|
"saving": "Salvando…",
|
||||||
|
"save": "Salvar",
|
||||||
|
"edit_profile": "Editar Perfil",
|
||||||
|
"saved_successfully": "Salvo com sucesso",
|
||||||
|
"try_again": "Por favor, tente novamente",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"successfully_signed_out": "Deslogado com sucesso",
|
||||||
|
"sign_out": "Sair da conta",
|
||||||
|
"sign_out_modal_title": "Tem certeza?",
|
||||||
|
"playing_for": "Jogando por {{amount}}",
|
||||||
|
"sign_out_modal_text": "Sua biblioteca de jogos está associada com a sua conta atual. Ao sair, sua biblioteca não aparecerá mais no Hydra e qualquer progresso não será salvo. Deseja continuar?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"app": {
|
||||||
|
"successfully_signed_in": "Successfully signed in (TRANSLATE ME)"
|
||||||
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"featured": "Рекомендованное",
|
"featured": "Рекомендованное",
|
||||||
"trending": "В тренде",
|
"trending": "В тренде",
|
||||||
@ -16,7 +19,8 @@
|
|||||||
"filter": "Фильтр библиотеки",
|
"filter": "Фильтр библиотеки",
|
||||||
"home": "Главная",
|
"home": "Главная",
|
||||||
"queued": "{{title}} (В очереди)",
|
"queued": "{{title}} (В очереди)",
|
||||||
"game_has_no_executable": "Файл запуска игры не выбран"
|
"game_has_no_executable": "Файл запуска игры не выбран",
|
||||||
|
"sign_in": "Sign in (TRANSLATE ME)"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Поиск",
|
"search": "Поиск",
|
||||||
@ -49,7 +53,6 @@
|
|||||||
"pause": "Приостановить",
|
"pause": "Приостановить",
|
||||||
"cancel": "Отменить",
|
"cancel": "Отменить",
|
||||||
"remove": "Удалить",
|
"remove": "Удалить",
|
||||||
"remove_from_list": "Удалить",
|
|
||||||
"space_left_on_disk": "{{space}} свободно на диске",
|
"space_left_on_disk": "{{space}} свободно на диске",
|
||||||
"eta": "Окончание {{eta}}",
|
"eta": "Окончание {{eta}}",
|
||||||
"calculating_eta": "Подсчёт оставшегося времени…",
|
"calculating_eta": "Подсчёт оставшегося времени…",
|
||||||
@ -58,13 +61,9 @@
|
|||||||
"requirements": "Системные требования",
|
"requirements": "Системные требования",
|
||||||
"minimum": "Минимальные",
|
"minimum": "Минимальные",
|
||||||
"recommended": "Рекомендуемые",
|
"recommended": "Рекомендуемые",
|
||||||
"no_minimum_requirements": "Для {{title}} не указаны минимальные требования",
|
|
||||||
"no_recommended_requirements": "Для {{title}} не указаны рекомендуемые требования",
|
|
||||||
"paused": "Приостановлено",
|
"paused": "Приостановлено",
|
||||||
"release_date": "Выпущено {{date}}",
|
"release_date": "Выпущено {{date}}",
|
||||||
"publisher": "Издатель {{publisher}}",
|
"publisher": "Издатель {{publisher}}",
|
||||||
"copy_link_to_clipboard": "Копировать ссылку",
|
|
||||||
"copied_link_to_clipboard": "Ссылка скопирована",
|
|
||||||
"hours": "часов",
|
"hours": "часов",
|
||||||
"minutes": "минут",
|
"minutes": "минут",
|
||||||
"amount_hours": "{{amount}} часов",
|
"amount_hours": "{{amount}} часов",
|
||||||
@ -85,14 +84,6 @@
|
|||||||
"repacks_modal_description": "Выберите репак для загрузки",
|
"repacks_modal_description": "Выберите репак для загрузки",
|
||||||
"select_folder_hint": "Чтобы изменить папку загрузок по умолчанию, откройте <0>Настройки</0>",
|
"select_folder_hint": "Чтобы изменить папку загрузок по умолчанию, откройте <0>Настройки</0>",
|
||||||
"download_now": "Загрузить сейчас",
|
"download_now": "Загрузить сейчас",
|
||||||
"installation_instructions": "Инструкция по установке",
|
|
||||||
"installation_instructions_description": "Для установки этой игры требуются дополнительные шаги",
|
|
||||||
"online_fix_instruction": "В играх с OnlineFix требуется ввести пароль для извлечения. При необходимости используйте следующий пароль:",
|
|
||||||
"dodi_installation_instruction": "Когда вы откроете установщик DODI, нажмите на клавиатуре клавишу 'вверх' <0 />, чтобы начать процесс установки:",
|
|
||||||
"dont_show_it_again": "Не показывать снова",
|
|
||||||
"copy_to_clipboard": "Копировать",
|
|
||||||
"copied_to_clipboard": "Скопировано",
|
|
||||||
"got_it": "Понятно",
|
|
||||||
"no_shop_details": "Не удалось получить описание",
|
"no_shop_details": "Не удалось получить описание",
|
||||||
"download_options": "Вариантов загрузки",
|
"download_options": "Вариантов загрузки",
|
||||||
"download_path": "Путь для загрузок",
|
"download_path": "Путь для загрузок",
|
||||||
@ -135,18 +126,14 @@
|
|||||||
"eta": "Окончание {{eta}}",
|
"eta": "Окончание {{eta}}",
|
||||||
"paused": "Приостановлено",
|
"paused": "Приостановлено",
|
||||||
"verifying": "Проверка…",
|
"verifying": "Проверка…",
|
||||||
"completed_at": "Завершено в {{date}}",
|
|
||||||
"completed": "Завершено",
|
"completed": "Завершено",
|
||||||
"removed": "Не скачано",
|
"removed": "Не скачано",
|
||||||
"download_again": "Загрузить снова",
|
|
||||||
"cancel": "Отмена",
|
"cancel": "Отмена",
|
||||||
"filter": "Фильтр загруженных игр",
|
"filter": "Фильтр загруженных игр",
|
||||||
"remove": "Удалить",
|
"remove": "Удалить",
|
||||||
"downloading_metadata": "Загрузка метаданных…",
|
"downloading_metadata": "Загрузка метаданных…",
|
||||||
"starting_download": "Начало загрузки…",
|
|
||||||
"deleting": "Удаление установщика…",
|
"deleting": "Удаление установщика…",
|
||||||
"delete": "Удалить установщик",
|
"delete": "Удалить установщик",
|
||||||
"remove_from_list": "Удалить",
|
|
||||||
"delete_modal_title": "Вы уверены?",
|
"delete_modal_title": "Вы уверены?",
|
||||||
"delete_modal_description": "Это удалит все установщики с вашего компьютера",
|
"delete_modal_description": "Это удалит все установщики с вашего компьютера",
|
||||||
"install": "Установить",
|
"install": "Установить",
|
||||||
@ -163,8 +150,6 @@
|
|||||||
"notifications": "Уведомления",
|
"notifications": "Уведомления",
|
||||||
"enable_download_notifications": "По завершении загрузки",
|
"enable_download_notifications": "По завершении загрузки",
|
||||||
"enable_repack_list_notifications": "При добавлении нового репака",
|
"enable_repack_list_notifications": "При добавлении нового репака",
|
||||||
"telemetry": "Телеметрия",
|
|
||||||
"telemetry_description": "Отправлять анонимную статистику использования",
|
|
||||||
"real_debrid_api_token_label": "Real-Debrid API-токен",
|
"real_debrid_api_token_label": "Real-Debrid API-токен",
|
||||||
"quit_app_instead_hiding": "Закрывать Hydra вместо того, чтобы сворачивать его в трей",
|
"quit_app_instead_hiding": "Закрывать Hydra вместо того, чтобы сворачивать его в трей",
|
||||||
"launch_with_system": "Запуск Hydra вместе с системой",
|
"launch_with_system": "Запуск Hydra вместе с системой",
|
||||||
@ -198,7 +183,12 @@
|
|||||||
"sync_download_sources": "Синхронизировать источники",
|
"sync_download_sources": "Синхронизировать источники",
|
||||||
"removed_download_source": "Источник загрузок удален",
|
"removed_download_source": "Источник загрузок удален",
|
||||||
"added_download_source": "Источник загрузок добавлен",
|
"added_download_source": "Источник загрузок добавлен",
|
||||||
"download_sources_synced": "Все источники загрузок синхронизированы"
|
"download_sources_synced": "All download sources are synced (TRANSLATE ME)",
|
||||||
|
"insert_valid_json_url": "Insert a valid JSON url (TRANSLATE ME)",
|
||||||
|
"found_download_option_zero": "No download option found (TRANSLATE ME)",
|
||||||
|
"found_download_option_one": "Found {{countFormatted}} download option (TRANSLATE ME)",
|
||||||
|
"found_download_option_other": "Found {{countFormatted}} download options (TRANSLATE ME)",
|
||||||
|
"import": "Import (TRANSLATE ME)"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Загрузка завершена",
|
"download_complete": "Загрузка завершена",
|
||||||
@ -224,5 +214,27 @@
|
|||||||
},
|
},
|
||||||
"forms": {
|
"forms": {
|
||||||
"toggle_password_visibility": "Показывать пароль"
|
"toggle_password_visibility": "Показывать пароль"
|
||||||
|
},
|
||||||
|
"user_profile": {
|
||||||
|
"amount_hours": "{{amount}} hours (TRANSLATE ME)",
|
||||||
|
"amount_minutes": "{{amount}} minutes (TRANSLATE ME)",
|
||||||
|
"last_time_played": "Last played {{period}} (TRANSLATE ME)",
|
||||||
|
"activity": "Recent activity (TRANSLATE ME)",
|
||||||
|
"library": "Library (TRANSLATE ME)",
|
||||||
|
"total_play_time": "Total playtime: {{amount}} (TRANSLATE ME)",
|
||||||
|
"no_recent_activity_title": "Hmmm… nothing here (TRANSLATE ME)",
|
||||||
|
"no_recent_activity_description": "You haven't played any games recently. It's time to change that! (TRANSLATE ME)",
|
||||||
|
"display_name": "Display name (TRANSLATE ME)",
|
||||||
|
"saving": "Saving (TRANSLATE ME)",
|
||||||
|
"save": "Save (TRANSLATE ME)",
|
||||||
|
"edit_profile": "Edit Profile (TRANSLATE ME)",
|
||||||
|
"saved_successfully": "Saved successfully (TRANSLATE ME)",
|
||||||
|
"try_again": "Please, try again (TRANSLATE ME)",
|
||||||
|
"sign_out_modal_title": "Are you sure? (TRANSLATE ME)",
|
||||||
|
"cancel": "Cancel (TRANSLATE ME)",
|
||||||
|
"successfully_signed_out": "Successfully signed out (TRANSLATE ME)",
|
||||||
|
"sign_out": "Sign out (TRANSLATE ME)",
|
||||||
|
"playing_for": "Playing for {{amount}} (TRANSLATE ME)",
|
||||||
|
"sign_out_modal_text": "Your library is linked with your current account. When signing out, your library will not be visible anymore, and any progress will not be saved. Continue with sign out? (TRANSLATE ME)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
"featured": "Öne çıkan",
|
"featured": "Öne çıkan",
|
||||||
"recently_added": "Son eklenen",
|
|
||||||
"trending": "Popüler",
|
"trending": "Popüler",
|
||||||
"surprise_me": "Şaşırt beni",
|
"surprise_me": "Şaşırt beni",
|
||||||
"no_results": "Sonuç bulunamadı"
|
"no_results": "Sonuç bulunamadı"
|
||||||
@ -15,12 +14,7 @@
|
|||||||
"paused": "{{title}} (Duraklatıldı)",
|
"paused": "{{title}} (Duraklatıldı)",
|
||||||
"downloading": "{{title}} ({{percentage}} - İndiriliyor…)",
|
"downloading": "{{title}} ({{percentage}} - İndiriliyor…)",
|
||||||
"filter": "Kütüphaneyi filtrele",
|
"filter": "Kütüphaneyi filtrele",
|
||||||
"follow_us": "Bizi takip et",
|
"home": "Ana menü"
|
||||||
"home": "Ana menü",
|
|
||||||
"discord": "Discord'umuza katıl",
|
|
||||||
"telegram": "Telegram'umuza katıl",
|
|
||||||
"x": "X'te bizi takip et",
|
|
||||||
"github": "GitHub'da bize katkı yap"
|
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Ara",
|
"search": "Ara",
|
||||||
@ -50,7 +44,6 @@
|
|||||||
"pause": "Duraklat",
|
"pause": "Duraklat",
|
||||||
"cancel": "İptal et",
|
"cancel": "İptal et",
|
||||||
"remove": "Sil",
|
"remove": "Sil",
|
||||||
"remove_from_list": "Sil",
|
|
||||||
"space_left_on_disk": "Diskte {{space}} yer kaldı",
|
"space_left_on_disk": "Diskte {{space}} yer kaldı",
|
||||||
"eta": "Bitiş {{eta}}",
|
"eta": "Bitiş {{eta}}",
|
||||||
"downloading_metadata": "Metadata indiriliyor…",
|
"downloading_metadata": "Metadata indiriliyor…",
|
||||||
@ -58,12 +51,8 @@
|
|||||||
"requirements": "Sistem gereksinimleri",
|
"requirements": "Sistem gereksinimleri",
|
||||||
"minimum": "Minimum",
|
"minimum": "Minimum",
|
||||||
"recommended": "Önerilen",
|
"recommended": "Önerilen",
|
||||||
"no_minimum_requirements": "{{title}} minimum sistem gereksinim bilgilerini karşılamıyor",
|
|
||||||
"no_recommended_requirements": "{{title}} önerilen sistem gereksinim bilgilerini karşılamıyor",
|
|
||||||
"release_date": "{{date}} tarihinde çıktı",
|
"release_date": "{{date}} tarihinde çıktı",
|
||||||
"publisher": "{{publisher}} tarihinde yayınlandı",
|
"publisher": "{{publisher}} tarihinde yayınlandı",
|
||||||
"copy_link_to_clipboard": "Link'i kopyala",
|
|
||||||
"copied_link_to_clipboard": "Link kopyalandı",
|
|
||||||
"hours": "saatler",
|
"hours": "saatler",
|
||||||
"minutes": "dakikalar",
|
"minutes": "dakikalar",
|
||||||
"amount_hours": "{{amount}} saat",
|
"amount_hours": "{{amount}} saat",
|
||||||
@ -83,15 +72,7 @@
|
|||||||
"change": "Değiştir",
|
"change": "Değiştir",
|
||||||
"repacks_modal_description": "İndirmek istediğiiniz repacki seçin",
|
"repacks_modal_description": "İndirmek istediğiiniz repacki seçin",
|
||||||
"select_folder_hint": "Varsayılan klasörü değiştirmek için ulaşmanız gereken ayar",
|
"select_folder_hint": "Varsayılan klasörü değiştirmek için ulaşmanız gereken ayar",
|
||||||
"download_now": "Şimdi",
|
"download_now": "Şimdi"
|
||||||
"installation_instructions": "Kurulum",
|
|
||||||
"installation_instructions_description": "Bu oyunu kurmak için ek adımlar gerekiyor",
|
|
||||||
"online_fix_instruction": "OnlineFix oyunlarını ayıklamak için parola gerekiyor. Gerekli olduğunda bu parolayı kullanın:",
|
|
||||||
"dodi_installation_instruction": "Dodi installerını açtığınızda, kurulumu başlatmak için bu tuşa basın <0 />:",
|
|
||||||
"dont_show_it_again": "Tekrar gösterme",
|
|
||||||
"copy_to_clipboard": "Kopyala",
|
|
||||||
"copied_to_clipboard": "Kopyalandı",
|
|
||||||
"got_it": "Tamam"
|
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Hydra'yı aktif et",
|
"title": "Hydra'yı aktif et",
|
||||||
@ -107,17 +88,13 @@
|
|||||||
"eta": "Bitiş {{eta}}",
|
"eta": "Bitiş {{eta}}",
|
||||||
"paused": "Duraklatıldı",
|
"paused": "Duraklatıldı",
|
||||||
"verifying": "Doğrulanıyor…",
|
"verifying": "Doğrulanıyor…",
|
||||||
"completed_at": "{{date}} tarihinde tamamlanacak",
|
|
||||||
"completed": "Tamamlandı",
|
"completed": "Tamamlandı",
|
||||||
"download_again": "Tekrar indir",
|
|
||||||
"cancel": "İptal et",
|
"cancel": "İptal et",
|
||||||
"filter": "Yüklü oyunları filtrele",
|
"filter": "Yüklü oyunları filtrele",
|
||||||
"remove": "Kaldır",
|
"remove": "Kaldır",
|
||||||
"downloading_metadata": "Metadata indiriliyor…",
|
"downloading_metadata": "Metadata indiriliyor…",
|
||||||
"starting_download": "İndirme başlatılıyor…",
|
|
||||||
"deleting": "Installer siliniyor…",
|
"deleting": "Installer siliniyor…",
|
||||||
"delete": "Installer'ı sil",
|
"delete": "Installer'ı sil",
|
||||||
"remove_from_list": "Kaldır",
|
|
||||||
"delete_modal_title": "Emin misiniz?",
|
"delete_modal_title": "Emin misiniz?",
|
||||||
"delete_modal_description": "Bu bilgisayarınızdan tüm kurulum dosyalarını silecek",
|
"delete_modal_description": "Bu bilgisayarınızdan tüm kurulum dosyalarını silecek",
|
||||||
"install": "Kur"
|
"install": "Kur"
|
||||||
@ -127,9 +104,7 @@
|
|||||||
"change": "Güncelle",
|
"change": "Güncelle",
|
||||||
"notifications": "Bildirimler",
|
"notifications": "Bildirimler",
|
||||||
"enable_download_notifications": "Bir indirme bittiğinde",
|
"enable_download_notifications": "Bir indirme bittiğinde",
|
||||||
"enable_repack_list_notifications": "Yeni bir repack eklendiğinde",
|
"enable_repack_list_notifications": "Yeni bir repack eklendiğinde"
|
||||||
"telemetry": "Telemetri",
|
|
||||||
"telemetry_description": "Anonim kullanım istatistiklerini aktifleştir"
|
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "İndirme tamamlandı",
|
"download_complete": "İndirme tamamlandı",
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
"featured": "Рекомендоване",
|
"featured": "Рекомендоване",
|
||||||
"recently_added": "Нове",
|
|
||||||
"trending": "У тренді",
|
"trending": "У тренді",
|
||||||
"surprise_me": "Здивуй мене",
|
"surprise_me": "Здивуй мене",
|
||||||
"no_results": "Результатів не знайдено"
|
"no_results": "Результатів не знайдено"
|
||||||
@ -15,12 +14,7 @@
|
|||||||
"paused": "{{title}} (Призупинено)",
|
"paused": "{{title}} (Призупинено)",
|
||||||
"downloading": "{{title}} ({{percentage}} - Завантаження…)",
|
"downloading": "{{title}} ({{percentage}} - Завантаження…)",
|
||||||
"filter": "Фільтр бібліотеки",
|
"filter": "Фільтр бібліотеки",
|
||||||
"follow_us": "Підписуйтесь на нас",
|
"home": "Головна"
|
||||||
"home": "Головна",
|
|
||||||
"discord": "Приєднуйтесь до Discord",
|
|
||||||
"telegram": "Приєднуйтесь до Telegram",
|
|
||||||
"x": "Підписуйтесь на X",
|
|
||||||
"github": "Зробіть свій внесок на GitHub"
|
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Пошук",
|
"search": "Пошук",
|
||||||
@ -50,7 +44,6 @@
|
|||||||
"pause": "Призупинити",
|
"pause": "Призупинити",
|
||||||
"cancel": "Скасувати",
|
"cancel": "Скасувати",
|
||||||
"remove": "Видалити",
|
"remove": "Видалити",
|
||||||
"remove_from_list": "Видалити",
|
|
||||||
"space_left_on_disk": "{{space}} вільно на диску",
|
"space_left_on_disk": "{{space}} вільно на диску",
|
||||||
"eta": "Закінчення {{eta}}",
|
"eta": "Закінчення {{eta}}",
|
||||||
"downloading_metadata": "Завантаження метаданих…",
|
"downloading_metadata": "Завантаження метаданих…",
|
||||||
@ -58,12 +51,8 @@
|
|||||||
"requirements": "Системні вимоги",
|
"requirements": "Системні вимоги",
|
||||||
"minimum": "Мінімальні",
|
"minimum": "Мінімальні",
|
||||||
"recommended": "Рекомендовані",
|
"recommended": "Рекомендовані",
|
||||||
"no_minimum_requirements": "Для {{title}} не вказані мінімальні вимоги",
|
|
||||||
"no_recommended_requirements": "Для {{title}} не вказані рекомендовані вимоги",
|
|
||||||
"release_date": "Випущено {{date}}",
|
"release_date": "Випущено {{date}}",
|
||||||
"publisher": "Видавець {{publisher}}",
|
"publisher": "Видавець {{publisher}}",
|
||||||
"copy_link_to_clipboard": "Скопіювати посилання",
|
|
||||||
"copied_link_to_clipboard": "Посилання скопійовано",
|
|
||||||
"hours": "годин",
|
"hours": "годин",
|
||||||
"minutes": "хвилин",
|
"minutes": "хвилин",
|
||||||
"amount_hours": "{{amount}} годин",
|
"amount_hours": "{{amount}} годин",
|
||||||
@ -83,15 +72,7 @@
|
|||||||
"change": "Змінити",
|
"change": "Змінити",
|
||||||
"repacks_modal_description": "Виберіть репак, який хочете завантажити",
|
"repacks_modal_description": "Виберіть репак, який хочете завантажити",
|
||||||
"select_folder_hint": "Щоб змінити теку за замовчуванням, відкрийте",
|
"select_folder_hint": "Щоб змінити теку за замовчуванням, відкрийте",
|
||||||
"download_now": "Завантажити зараз",
|
"download_now": "Завантажити зараз"
|
||||||
"installation_instructions": "Інструкція зі встановлення",
|
|
||||||
"installation_instructions_description": "Для встановлення цієї гри потрібні додаткові кроки",
|
|
||||||
"online_fix_instruction": "В іграх з OnlineFix потрібно ввести пароль для вилучення. За необхідності використовуйте наступний пароль:",
|
|
||||||
"dodi_installation_instruction": "Коли ви відкриєте інсталятор DODI, натисніть на клавіатурі клавішу 'вгору' <0 />, щоб почати процес встановлення:",
|
|
||||||
"dont_show_it_again": "Не показувати це знову",
|
|
||||||
"copy_to_clipboard": "Копіювати",
|
|
||||||
"copied_to_clipboard": "Скопійовано",
|
|
||||||
"got_it": "Зрозуміло"
|
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Активувати Hydra",
|
"title": "Активувати Hydra",
|
||||||
@ -107,17 +88,13 @@
|
|||||||
"eta": "Закінчення {{eta}}",
|
"eta": "Закінчення {{eta}}",
|
||||||
"paused": "Призупинено",
|
"paused": "Призупинено",
|
||||||
"verifying": "Перевірка…",
|
"verifying": "Перевірка…",
|
||||||
"completed_at": "Завершено в {{date}}",
|
|
||||||
"completed": "Завершено",
|
"completed": "Завершено",
|
||||||
"download_again": "Завантажити знову",
|
|
||||||
"cancel": "Скасувати",
|
"cancel": "Скасувати",
|
||||||
"filter": "Фільтр завантажених ігор",
|
"filter": "Фільтр завантажених ігор",
|
||||||
"remove": "Видалити",
|
"remove": "Видалити",
|
||||||
"downloading_metadata": "Завантаження метаданих…",
|
"downloading_metadata": "Завантаження метаданих…",
|
||||||
"starting_download": "Початок завантаження…",
|
|
||||||
"deleting": "Видалення інсталятора…",
|
"deleting": "Видалення інсталятора…",
|
||||||
"delete": "Видалити інсталятор",
|
"delete": "Видалити інсталятор",
|
||||||
"remove_from_list": "Видалити",
|
|
||||||
"delete_modal_title": "Ви впевнені?",
|
"delete_modal_title": "Ви впевнені?",
|
||||||
"delete_modal_description": "Це видалить усі інсталяційні файли з вашого комп'ютера",
|
"delete_modal_description": "Це видалить усі інсталяційні файли з вашого комп'ютера",
|
||||||
"install": "Встановити"
|
"install": "Встановити"
|
||||||
@ -128,8 +105,6 @@
|
|||||||
"notifications": "Повідомлення",
|
"notifications": "Повідомлення",
|
||||||
"enable_download_notifications": "Після завершення завантаження",
|
"enable_download_notifications": "Після завершення завантаження",
|
||||||
"enable_repack_list_notifications": "Коли додається новий репак",
|
"enable_repack_list_notifications": "Коли додається новий репак",
|
||||||
"telemetry": "Телеметрія",
|
|
||||||
"telemetry_description": "Відправляти анонімну статистику використання",
|
|
||||||
"behavior": "Поведінка",
|
"behavior": "Поведінка",
|
||||||
"quit_app_instead_hiding": "Закривати програму замість того, щоб згортати її в трей",
|
"quit_app_instead_hiding": "Закривати програму замість того, щоб згортати її в трей",
|
||||||
"launch_with_system": "Запускати програми із запуском комп'ютера"
|
"launch_with_system": "Запускати програми із запуском комп'ютера"
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
"featured": "特色推荐",
|
"featured": "特色推荐",
|
||||||
"recently_added": "最近添加",
|
|
||||||
"trending": "最近热门",
|
"trending": "最近热门",
|
||||||
"surprise_me": "向我推荐",
|
"surprise_me": "向我推荐",
|
||||||
"no_results": "没有找到结果"
|
"no_results": "没有找到结果"
|
||||||
@ -15,12 +14,7 @@
|
|||||||
"paused": "{{title}} (已暂停)",
|
"paused": "{{title}} (已暂停)",
|
||||||
"downloading": "{{title}} ({{percentage}} - 正在下载…)",
|
"downloading": "{{title}} ({{percentage}} - 正在下载…)",
|
||||||
"filter": "筛选游戏库",
|
"filter": "筛选游戏库",
|
||||||
"follow_us": "关注我们",
|
"home": "主页"
|
||||||
"home": "主页",
|
|
||||||
"discord": "加入我们的Discord",
|
|
||||||
"telegram": "加入我们的Telegram",
|
|
||||||
"x": "在X上关注我们",
|
|
||||||
"github": "在GitHub上贡献"
|
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "搜索",
|
"search": "搜索",
|
||||||
@ -50,7 +44,6 @@
|
|||||||
"pause": "暂停",
|
"pause": "暂停",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"remove": "移除",
|
"remove": "移除",
|
||||||
"remove_from_list": "从列表中移除",
|
|
||||||
"space_left_on_disk": "磁盘剩余空间{{space}}",
|
"space_left_on_disk": "磁盘剩余空间{{space}}",
|
||||||
"eta": "预计完成时间{{eta}}",
|
"eta": "预计完成时间{{eta}}",
|
||||||
"downloading_metadata": "正在下载元数据…",
|
"downloading_metadata": "正在下载元数据…",
|
||||||
@ -58,12 +51,8 @@
|
|||||||
"requirements": "配置要求",
|
"requirements": "配置要求",
|
||||||
"minimum": "最低要求",
|
"minimum": "最低要求",
|
||||||
"recommended": "推荐要求",
|
"recommended": "推荐要求",
|
||||||
"no_minimum_requirements": "{{title}}没有提供最低要求信息",
|
|
||||||
"no_recommended_requirements": "{{title}}没有提供推荐要求信息",
|
|
||||||
"release_date": "发布于{{date}}",
|
"release_date": "发布于{{date}}",
|
||||||
"publisher": "发行商{{publisher}}",
|
"publisher": "发行商{{publisher}}",
|
||||||
"copy_link_to_clipboard": "复制链接",
|
|
||||||
"copied_link_to_clipboard": "链接已复制",
|
|
||||||
"hours": "小时",
|
"hours": "小时",
|
||||||
"minutes": "分钟",
|
"minutes": "分钟",
|
||||||
"amount_hours": "{{amount}}小时",
|
"amount_hours": "{{amount}}小时",
|
||||||
@ -84,13 +73,6 @@
|
|||||||
"repacks_modal_description": "选择您想要下载的重打包",
|
"repacks_modal_description": "选择您想要下载的重打包",
|
||||||
"select_folder_hint": "要更改默认文件夹,请访问",
|
"select_folder_hint": "要更改默认文件夹,请访问",
|
||||||
"download_now": "立即下载",
|
"download_now": "立即下载",
|
||||||
"installation_instructions": "安装说明",
|
|
||||||
"installation_instructions_description": "安装这个游戏需要额外的步骤",
|
|
||||||
"online_fix_instruction": "OnlineFix游戏需要密码才能解压。需要时,使用以下密码:",
|
|
||||||
"dodi_installation_instruction": "打开DODI安装程序时,按键盘上的键<0 />开始安装过程:",
|
|
||||||
"dont_show_it_again": "不再显示",
|
|
||||||
"copied_to_clipboard": "已复制到剪贴板",
|
|
||||||
"got_it": "我已知晓",
|
|
||||||
"previous_screenshot": "上一张截图",
|
"previous_screenshot": "上一张截图",
|
||||||
"next_screenshot": "下一张截图",
|
"next_screenshot": "下一张截图",
|
||||||
"screenshot": "截图 {{number}}",
|
"screenshot": "截图 {{number}}",
|
||||||
@ -110,17 +92,13 @@
|
|||||||
"eta": "预计完成时间{{eta}}",
|
"eta": "预计完成时间{{eta}}",
|
||||||
"paused": "已暂停",
|
"paused": "已暂停",
|
||||||
"verifying": "正在验证…",
|
"verifying": "正在验证…",
|
||||||
"completed_at": "完成于{{date}}",
|
|
||||||
"completed": "已完成",
|
"completed": "已完成",
|
||||||
"download_again": "再次下载",
|
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"filter": "筛选已下载游戏",
|
"filter": "筛选已下载游戏",
|
||||||
"remove": "移除",
|
"remove": "移除",
|
||||||
"downloading_metadata": "正在下载元数据…",
|
"downloading_metadata": "正在下载元数据…",
|
||||||
"starting_download": "开始下载…",
|
|
||||||
"deleting": "正在删除安装程序…",
|
"deleting": "正在删除安装程序…",
|
||||||
"delete": "移除安装程序",
|
"delete": "移除安装程序",
|
||||||
"remove_from_list": "移除",
|
|
||||||
"delete_modal_title": "您确定吗?",
|
"delete_modal_title": "您确定吗?",
|
||||||
"delete_modal_description": "这将从您的电脑上移除所有的安装文件",
|
"delete_modal_description": "这将从您的电脑上移除所有的安装文件",
|
||||||
"install": "安装"
|
"install": "安装"
|
||||||
@ -131,8 +109,6 @@
|
|||||||
"notifications": "通知",
|
"notifications": "通知",
|
||||||
"enable_download_notifications": "下载完成时",
|
"enable_download_notifications": "下载完成时",
|
||||||
"enable_repack_list_notifications": "添加新重打包时",
|
"enable_repack_list_notifications": "添加新重打包时",
|
||||||
"telemetry": "遥测",
|
|
||||||
"telemetry_description": "启用匿名使用统计",
|
|
||||||
"behavior": "行为",
|
"behavior": "行为",
|
||||||
"general": "常规",
|
"general": "常规",
|
||||||
"quit_app_instead_hiding": "关闭应用程序而不是最小化到托盘",
|
"quit_app_instead_hiding": "关闭应用程序而不是最小化到托盘",
|
||||||
|
@ -22,6 +22,9 @@ export class Game {
|
|||||||
@Column("text", { unique: true })
|
@Column("text", { unique: true })
|
||||||
objectID: string;
|
objectID: string;
|
||||||
|
|
||||||
|
@Column("text", { unique: true, nullable: true })
|
||||||
|
remoteId: string | null;
|
||||||
|
|
||||||
@Column("text")
|
@Column("text")
|
||||||
title: string;
|
title: string;
|
||||||
|
|
||||||
|
@ -4,3 +4,4 @@ export * from "./user-preferences.entity";
|
|||||||
export * from "./game-shop-cache.entity";
|
export * from "./game-shop-cache.entity";
|
||||||
export * from "./download-source.entity";
|
export * from "./download-source.entity";
|
||||||
export * from "./download-queue.entity";
|
export * from "./download-queue.entity";
|
||||||
|
export * from "./user-auth";
|
||||||
|
@ -11,6 +11,15 @@ export class UserAuth {
|
|||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
|
@Column("text", { default: "" })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@Column("text", { default: "" })
|
||||||
|
displayName: string;
|
||||||
|
|
||||||
|
@Column("text", { nullable: true })
|
||||||
|
profileImageUrl: string | null;
|
||||||
|
|
||||||
@Column("text", { default: "" })
|
@Column("text", { default: "" })
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
|
|
||||||
|
14
src/main/events/auth/get-session-hash.ts
Normal file
14
src/main/events/auth/get-session-hash.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
|
import { userAuthRepository } from "@main/repository";
|
||||||
|
import { registerEvent } from "../register-event";
|
||||||
|
|
||||||
|
const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||||
|
const auth = await userAuthRepository.findOne({ where: { id: 1 } });
|
||||||
|
|
||||||
|
if (!auth) return null;
|
||||||
|
const payload = jwt.decode(auth.accessToken) as jwt.JwtPayload;
|
||||||
|
return payload.sessionId;
|
||||||
|
};
|
||||||
|
|
||||||
|
registerEvent("getSessionHash", getSessionHash);
|
7
src/main/events/auth/open-auth-window.ts
Normal file
7
src/main/events/auth/open-auth-window.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { registerEvent } from "../register-event";
|
||||||
|
import { WindowManager } from "@main/services";
|
||||||
|
|
||||||
|
const openAuthWindow = async (_event: Electron.IpcMainInvokeEvent) =>
|
||||||
|
WindowManager.openAuthWindow();
|
||||||
|
|
||||||
|
registerEvent("openAuthWindow", openAuthWindow);
|
31
src/main/events/auth/sign-out.ts
Normal file
31
src/main/events/auth/sign-out.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { registerEvent } from "../register-event";
|
||||||
|
import { DownloadManager, HydraApi, gamesPlaytime } from "@main/services";
|
||||||
|
import { dataSource } from "@main/data-source";
|
||||||
|
import { DownloadQueue, Game, UserAuth } from "@main/entity";
|
||||||
|
|
||||||
|
const signOut = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||||
|
const databaseOperations = dataSource
|
||||||
|
.transaction(async (transactionalEntityManager) => {
|
||||||
|
await transactionalEntityManager.getRepository(DownloadQueue).delete({});
|
||||||
|
|
||||||
|
await transactionalEntityManager.getRepository(Game).delete({});
|
||||||
|
|
||||||
|
await transactionalEntityManager
|
||||||
|
.getRepository(UserAuth)
|
||||||
|
.delete({ id: 1 });
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
/* Removes all games being played */
|
||||||
|
gamesPlaytime.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Disconnects aria2 */
|
||||||
|
DownloadManager.disconnect();
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
databaseOperations,
|
||||||
|
HydraApi.post("/auth/logout").catch(),
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
registerEvent("signOut", signOut);
|
@ -22,6 +22,7 @@ import "./library/open-game-installer-path";
|
|||||||
import "./library/update-executable-path";
|
import "./library/update-executable-path";
|
||||||
import "./library/remove-game";
|
import "./library/remove-game";
|
||||||
import "./library/remove-game-from-library";
|
import "./library/remove-game-from-library";
|
||||||
|
import "./misc/is-user-logged-in";
|
||||||
import "./misc/open-external";
|
import "./misc/open-external";
|
||||||
import "./misc/show-open-dialog";
|
import "./misc/show-open-dialog";
|
||||||
import "./torrenting/cancel-game-download";
|
import "./torrenting/cancel-game-download";
|
||||||
@ -39,6 +40,12 @@ import "./download-sources/validate-download-source";
|
|||||||
import "./download-sources/add-download-source";
|
import "./download-sources/add-download-source";
|
||||||
import "./download-sources/remove-download-source";
|
import "./download-sources/remove-download-source";
|
||||||
import "./download-sources/sync-download-sources";
|
import "./download-sources/sync-download-sources";
|
||||||
|
import "./auth/sign-out";
|
||||||
|
import "./auth/open-auth-window";
|
||||||
|
import "./auth/get-session-hash";
|
||||||
|
import "./user/get-user";
|
||||||
|
import "./profile/get-me";
|
||||||
|
import "./profile/update-profile";
|
||||||
|
|
||||||
ipcMain.handle("ping", () => "pong");
|
ipcMain.handle("ping", () => "pong");
|
||||||
ipcMain.handle("getVersion", () => app.getVersion());
|
ipcMain.handle("getVersion", () => app.getVersion());
|
||||||
|
@ -6,6 +6,7 @@ import type { GameShop } from "@types";
|
|||||||
import { getFileBase64, getSteamAppAsset } from "@main/helpers";
|
import { getFileBase64, getSteamAppAsset } from "@main/helpers";
|
||||||
|
|
||||||
import { steamGamesWorker } from "@main/workers";
|
import { steamGamesWorker } from "@main/workers";
|
||||||
|
import { createGame } from "@main/services/library-sync";
|
||||||
|
|
||||||
const addGameToLibrary = async (
|
const addGameToLibrary = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
@ -49,6 +50,21 @@ const addGameToLibrary = async (
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const game = await gameRepository.findOne({ where: { objectID } });
|
||||||
|
|
||||||
|
createGame(game!).then((response) => {
|
||||||
|
const {
|
||||||
|
id: remoteId,
|
||||||
|
playTimeInMilliseconds,
|
||||||
|
lastTimePlayed,
|
||||||
|
} = response.data;
|
||||||
|
|
||||||
|
gameRepository.update(
|
||||||
|
{ objectID },
|
||||||
|
{ remoteId, playTimeInMilliseconds, lastTimePlayed }
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@ import { gameRepository } from "@main/repository";
|
|||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import { IsNull, Not } from "typeorm";
|
import { IsNull, Not } from "typeorm";
|
||||||
import createDesktopShortcut from "create-desktop-shortcuts";
|
import createDesktopShortcut from "create-desktop-shortcuts";
|
||||||
|
import path from "node:path";
|
||||||
|
import { app } from "electron";
|
||||||
|
|
||||||
const createGameShortcut = async (
|
const createGameShortcut = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
@ -14,10 +16,17 @@ const createGameShortcut = async (
|
|||||||
if (game) {
|
if (game) {
|
||||||
const filePath = game.executablePath;
|
const filePath = game.executablePath;
|
||||||
|
|
||||||
const options = { filePath, name: game.title };
|
const windowVbsPath = app.isPackaged
|
||||||
|
? path.join(process.resourcesPath, "windows.vbs")
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
filePath,
|
||||||
|
name: game.title,
|
||||||
|
};
|
||||||
|
|
||||||
return createDesktopShortcut({
|
return createDesktopShortcut({
|
||||||
windows: options,
|
windows: { ...options, VBScriptPath: windowVbsPath },
|
||||||
linux: options,
|
linux: options,
|
||||||
osx: options,
|
osx: options,
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import { gameRepository } from "../../repository";
|
import { gameRepository } from "../../repository";
|
||||||
|
import { HydraApi, logger } from "@main/services";
|
||||||
|
|
||||||
const removeGameFromLibrary = async (
|
const removeGameFromLibrary = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
@ -9,6 +10,18 @@ const removeGameFromLibrary = async (
|
|||||||
{ id: gameId },
|
{ id: gameId },
|
||||||
{ isDeleted: true, executablePath: null }
|
{ isDeleted: true, executablePath: null }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
removeRemoveGameFromLibrary(gameId).catch((err) => {
|
||||||
|
logger.error("removeRemoveGameFromLibrary", err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeRemoveGameFromLibrary = async (gameId: number) => {
|
||||||
|
const game = await gameRepository.findOne({ where: { id: gameId } });
|
||||||
|
|
||||||
|
if (game?.remoteId) {
|
||||||
|
HydraApi.delete(`/games/${game.remoteId}`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent("removeGameFromLibrary", removeGameFromLibrary);
|
registerEvent("removeGameFromLibrary", removeGameFromLibrary);
|
||||||
|
8
src/main/events/misc/is-user-logged-in.ts
Normal file
8
src/main/events/misc/is-user-logged-in.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { registerEvent } from "../register-event";
|
||||||
|
import { HydraApi } from "@main/services";
|
||||||
|
|
||||||
|
const isUserLoggedIn = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||||
|
return HydraApi.isLoggedIn();
|
||||||
|
};
|
||||||
|
|
||||||
|
registerEvent("isUserLoggedIn", isUserLoggedIn);
|
32
src/main/events/profile/get-me.ts
Normal file
32
src/main/events/profile/get-me.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { registerEvent } from "../register-event";
|
||||||
|
import { HydraApi } from "@main/services";
|
||||||
|
import { UserProfile } from "@types";
|
||||||
|
import { userAuthRepository } from "@main/repository";
|
||||||
|
import { logger } from "@main/services";
|
||||||
|
|
||||||
|
const getMe = async (
|
||||||
|
_event: Electron.IpcMainInvokeEvent
|
||||||
|
): Promise<UserProfile | null> => {
|
||||||
|
return HydraApi.get(`/profile/me`)
|
||||||
|
.then((response) => {
|
||||||
|
const me = response.data;
|
||||||
|
|
||||||
|
userAuthRepository.upsert(
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
displayName: me.displayName,
|
||||||
|
profileImageUrl: me.profileImageUrl,
|
||||||
|
userId: me.id,
|
||||||
|
},
|
||||||
|
["id"]
|
||||||
|
);
|
||||||
|
|
||||||
|
return me;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
logger.error("getMe", err);
|
||||||
|
return userAuthRepository.findOne({ where: { id: 1 } });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
registerEvent("getMe", getMe);
|
61
src/main/events/profile/update-profile.ts
Normal file
61
src/main/events/profile/update-profile.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { registerEvent } from "../register-event";
|
||||||
|
import { HydraApi } from "@main/services";
|
||||||
|
import axios from "axios";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileTypeFromFile } from "file-type";
|
||||||
|
import { UserProfile } from "@types";
|
||||||
|
|
||||||
|
const patchUserProfile = async (
|
||||||
|
displayName: string,
|
||||||
|
profileImageUrl?: string
|
||||||
|
) => {
|
||||||
|
if (profileImageUrl) {
|
||||||
|
return HydraApi.patch("/profile", {
|
||||||
|
displayName,
|
||||||
|
profileImageUrl,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return HydraApi.patch("/profile", {
|
||||||
|
displayName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateProfile = async (
|
||||||
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
|
displayName: string,
|
||||||
|
newProfileImagePath: string | null
|
||||||
|
): Promise<UserProfile> => {
|
||||||
|
if (!newProfileImagePath) {
|
||||||
|
return (await patchUserProfile(displayName)).data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stats = fs.statSync(newProfileImagePath);
|
||||||
|
const fileBuffer = fs.readFileSync(newProfileImagePath);
|
||||||
|
const fileSizeInBytes = stats.size;
|
||||||
|
|
||||||
|
const profileImageUrl = await HydraApi.post(`/presigned-urls/profile-image`, {
|
||||||
|
imageExt: path.extname(newProfileImagePath).slice(1),
|
||||||
|
imageLength: fileSizeInBytes,
|
||||||
|
})
|
||||||
|
.then(async (preSignedResponse) => {
|
||||||
|
const { presignedUrl, profileImageUrl } = preSignedResponse.data;
|
||||||
|
|
||||||
|
const mimeType = await fileTypeFromFile(newProfileImagePath);
|
||||||
|
|
||||||
|
await axios.put(presignedUrl, fileBuffer, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": mimeType?.mime,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return profileImageUrl;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (await patchUserProfile(displayName, profileImageUrl)).data;
|
||||||
|
};
|
||||||
|
|
||||||
|
registerEvent("updateProfile", updateProfile);
|
@ -12,6 +12,7 @@ import { DownloadManager } from "@main/services";
|
|||||||
|
|
||||||
import { Not } from "typeorm";
|
import { Not } from "typeorm";
|
||||||
import { steamGamesWorker } from "@main/workers";
|
import { steamGamesWorker } from "@main/workers";
|
||||||
|
import { createGame } from "@main/services/library-sync";
|
||||||
|
|
||||||
const startGameDownload = async (
|
const startGameDownload = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
@ -94,6 +95,19 @@ const startGameDownload = async (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
createGame(updatedGame!).then((response) => {
|
||||||
|
const {
|
||||||
|
id: remoteId,
|
||||||
|
playTimeInMilliseconds,
|
||||||
|
lastTimePlayed,
|
||||||
|
} = response.data;
|
||||||
|
|
||||||
|
gameRepository.update(
|
||||||
|
{ objectID },
|
||||||
|
{ remoteId, playTimeInMilliseconds, lastTimePlayed }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
await downloadQueueRepository.delete({ game: { id: updatedGame!.id } });
|
await downloadQueueRepository.delete({ game: { id: updatedGame!.id } });
|
||||||
await downloadQueueRepository.insert({ game: { id: updatedGame!.id } });
|
await downloadQueueRepository.insert({ game: { id: updatedGame!.id } });
|
||||||
|
|
||||||
|
56
src/main/events/user/get-user.ts
Normal file
56
src/main/events/user/get-user.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { registerEvent } from "../register-event";
|
||||||
|
import { HydraApi } from "@main/services";
|
||||||
|
import { steamGamesWorker } from "@main/workers";
|
||||||
|
import { UserProfile } from "@types";
|
||||||
|
import { convertSteamGameToCatalogueEntry } from "../helpers/search-games";
|
||||||
|
import { getSteamAppAsset } from "@main/helpers";
|
||||||
|
|
||||||
|
const getUser = async (
|
||||||
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
|
userId: string
|
||||||
|
): Promise<UserProfile | null> => {
|
||||||
|
try {
|
||||||
|
const response = await HydraApi.get(`/user/${userId}`);
|
||||||
|
const profile = response.data;
|
||||||
|
|
||||||
|
const recentGames = await Promise.all(
|
||||||
|
profile.recentGames.map(async (game) => {
|
||||||
|
const steamGame = await steamGamesWorker.run(Number(game.objectId), {
|
||||||
|
name: "getById",
|
||||||
|
});
|
||||||
|
const iconUrl = steamGame?.clientIcon
|
||||||
|
? getSteamAppAsset("icon", game.objectId, steamGame.clientIcon)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...game,
|
||||||
|
...convertSteamGameToCatalogueEntry(steamGame),
|
||||||
|
iconUrl,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const libraryGames = await Promise.all(
|
||||||
|
profile.libraryGames.map(async (game) => {
|
||||||
|
const steamGame = await steamGamesWorker.run(Number(game.objectId), {
|
||||||
|
name: "getById",
|
||||||
|
});
|
||||||
|
const iconUrl = steamGame?.clientIcon
|
||||||
|
? getSteamAppAsset("icon", game.objectId, steamGame.clientIcon)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...game,
|
||||||
|
...convertSteamGameToCatalogueEntry(steamGame),
|
||||||
|
iconUrl,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return { ...profile, libraryGames, recentGames };
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
registerEvent("getUser", getUser);
|
@ -2,6 +2,7 @@ import { app, BrowserWindow, net, protocol } from "electron";
|
|||||||
import updater from "electron-updater";
|
import updater from "electron-updater";
|
||||||
import i18n from "i18next";
|
import i18n from "i18next";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import url from "node:url";
|
||||||
import { electronApp, optimizer } from "@electron-toolkit/utils";
|
import { electronApp, optimizer } from "@electron-toolkit/utils";
|
||||||
import { DownloadManager, logger, WindowManager } from "@main/services";
|
import { DownloadManager, logger, WindowManager } from "@main/services";
|
||||||
import { dataSource } from "@main/data-source";
|
import { dataSource } from "@main/data-source";
|
||||||
@ -50,9 +51,10 @@ if (process.defaultApp) {
|
|||||||
app.whenReady().then(async () => {
|
app.whenReady().then(async () => {
|
||||||
electronApp.setAppUserModelId("site.hydralauncher.hydra");
|
electronApp.setAppUserModelId("site.hydralauncher.hydra");
|
||||||
|
|
||||||
protocol.handle("hydra", (request) =>
|
protocol.handle("local", (request) => {
|
||||||
net.fetch("file://" + request.url.slice("hydra://".length))
|
const filePath = request.url.slice("local:".length);
|
||||||
);
|
return net.fetch(url.pathToFileURL(decodeURI(filePath)).toString());
|
||||||
|
});
|
||||||
|
|
||||||
await dataSource.initialize();
|
await dataSource.initialize();
|
||||||
await dataSource.runMigrations();
|
await dataSource.runMigrations();
|
||||||
@ -71,7 +73,7 @@ app.on("browser-window-created", (_, window) => {
|
|||||||
optimizer.watchWindowShortcuts(window);
|
optimizer.watchWindowShortcuts(window);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on("second-instance", (_event, commandLine) => {
|
app.on("second-instance", (_event) => {
|
||||||
// Someone tried to run a second instance, we should focus our window.
|
// Someone tried to run a second instance, we should focus our window.
|
||||||
if (WindowManager.mainWindow) {
|
if (WindowManager.mainWindow) {
|
||||||
if (WindowManager.mainWindow.isMinimized())
|
if (WindowManager.mainWindow.isMinimized())
|
||||||
@ -82,14 +84,16 @@ app.on("second-instance", (_event, commandLine) => {
|
|||||||
WindowManager.createMainWindow();
|
WindowManager.createMainWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
const [, path] = commandLine.pop()?.split("://") ?? [];
|
// const [, path] = commandLine.pop()?.split("://") ?? [];
|
||||||
if (path) WindowManager.redirect(path);
|
// if (path) {
|
||||||
|
// WindowManager.redirect(path);
|
||||||
|
// }
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on("open-url", (_event, url) => {
|
// app.on("open-url", (_event, url) => {
|
||||||
const [, path] = url.split("://");
|
// const [, path] = url.split("://");
|
||||||
WindowManager.redirect(path);
|
// WindowManager.redirect(path);
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Quit when all windows are closed, except on macOS. There, it's common
|
// Quit when all windows are closed, except on macOS. There, it's common
|
||||||
// for applications and their menu bar to stay active until the user quits
|
// for applications and their menu bar to stay active until the user quits
|
||||||
|
@ -10,6 +10,7 @@ import { fetchDownloadSourcesAndUpdate } from "./helpers";
|
|||||||
import { publishNewRepacksNotifications } from "./services/notifications";
|
import { publishNewRepacksNotifications } from "./services/notifications";
|
||||||
import { MoreThan } from "typeorm";
|
import { MoreThan } from "typeorm";
|
||||||
import { HydraApi } from "./services/hydra-api";
|
import { HydraApi } from "./services/hydra-api";
|
||||||
|
import { uploadGamesBatch } from "./services/library-sync";
|
||||||
|
|
||||||
startMainLoop();
|
startMainLoop();
|
||||||
|
|
||||||
@ -21,7 +22,9 @@ const loadState = async (userPreferences: UserPreferences | null) => {
|
|||||||
if (userPreferences?.realDebridApiToken)
|
if (userPreferences?.realDebridApiToken)
|
||||||
RealDebridClient.authorize(userPreferences?.realDebridApiToken);
|
RealDebridClient.authorize(userPreferences?.realDebridApiToken);
|
||||||
|
|
||||||
HydraApi.setupApi();
|
HydraApi.setupApi().then(async () => {
|
||||||
|
if (HydraApi.isLoggedIn()) uploadGamesBatch();
|
||||||
|
});
|
||||||
|
|
||||||
const [nextQueueItem] = await downloadQueueRepository.find({
|
const [nextQueueItem] = await downloadQueueRepository.find({
|
||||||
order: {
|
order: {
|
||||||
|
@ -6,8 +6,8 @@ import {
|
|||||||
GameShopCache,
|
GameShopCache,
|
||||||
Repack,
|
Repack,
|
||||||
UserPreferences,
|
UserPreferences,
|
||||||
|
UserAuth,
|
||||||
} from "@main/entity";
|
} from "@main/entity";
|
||||||
import { UserAuth } from "./entity/user-auth";
|
|
||||||
|
|
||||||
export const gameRepository = dataSource.getRepository(Game);
|
export const gameRepository = dataSource.getRepository(Game);
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ export class DownloadManager {
|
|||||||
public static disconnect() {
|
public static disconnect() {
|
||||||
if (this.aria2c) {
|
if (this.aria2c) {
|
||||||
this.aria2c.kill();
|
this.aria2c.kill();
|
||||||
|
this.connected = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,22 +1,100 @@
|
|||||||
import { userAuthRepository } from "@main/repository";
|
import { userAuthRepository } from "@main/repository";
|
||||||
import axios, { AxiosInstance } from "axios";
|
import axios, { AxiosError, AxiosInstance } from "axios";
|
||||||
|
import { WindowManager } from "./window-manager";
|
||||||
|
import url from "url";
|
||||||
|
import { uploadGamesBatch } from "./library-sync";
|
||||||
|
import { clearGamesRemoteIds } from "./library-sync/clear-games-remote-id";
|
||||||
|
import { logger } from "./logger";
|
||||||
|
|
||||||
export class HydraApi {
|
export class HydraApi {
|
||||||
private static instance: AxiosInstance;
|
private static instance: AxiosInstance;
|
||||||
|
|
||||||
private static readonly EXPIRATION_OFFSET_IN_MS = 1000 * 60 * 5;
|
private static readonly EXPIRATION_OFFSET_IN_MS = 1000 * 60 * 5;
|
||||||
|
|
||||||
|
private static secondsToMilliseconds = (seconds: number) => seconds * 1000;
|
||||||
|
|
||||||
private static userAuth = {
|
private static userAuth = {
|
||||||
authToken: "",
|
authToken: "",
|
||||||
refreshToken: "",
|
refreshToken: "",
|
||||||
expirationTimestamp: 0,
|
expirationTimestamp: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static isLoggedIn() {
|
||||||
|
return this.userAuth.authToken !== "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static async handleExternalAuth(uri: string) {
|
||||||
|
const { payload } = url.parse(uri, true).query;
|
||||||
|
|
||||||
|
const decodedBase64 = atob(payload as string);
|
||||||
|
const jsonData = JSON.parse(decodedBase64);
|
||||||
|
|
||||||
|
const { accessToken, expiresIn, refreshToken } = jsonData;
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
const tokenExpirationTimestamp =
|
||||||
|
now.getTime() +
|
||||||
|
this.secondsToMilliseconds(expiresIn) -
|
||||||
|
this.EXPIRATION_OFFSET_IN_MS;
|
||||||
|
|
||||||
|
this.userAuth = {
|
||||||
|
authToken: accessToken,
|
||||||
|
refreshToken: refreshToken,
|
||||||
|
expirationTimestamp: tokenExpirationTimestamp,
|
||||||
|
};
|
||||||
|
|
||||||
|
await userAuthRepository.upsert(
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
accessToken,
|
||||||
|
tokenExpirationTimestamp,
|
||||||
|
refreshToken,
|
||||||
|
},
|
||||||
|
["id"]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (WindowManager.mainWindow) {
|
||||||
|
WindowManager.mainWindow.webContents.send("on-signin");
|
||||||
|
await clearGamesRemoteIds();
|
||||||
|
uploadGamesBatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static async setupApi() {
|
static async setupApi() {
|
||||||
this.instance = axios.create({
|
this.instance = axios.create({
|
||||||
baseURL: import.meta.env.MAIN_VITE_API_URL,
|
baseURL: import.meta.env.MAIN_VITE_API_URL,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.instance.interceptors.request.use(
|
||||||
|
(request) => {
|
||||||
|
logger.log(" ---- REQUEST -----");
|
||||||
|
logger.log(request.method, request.url, request.data);
|
||||||
|
return request;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
logger.log("request error", error);
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.instance.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
logger.log(" ---- RESPONSE -----");
|
||||||
|
logger.log(
|
||||||
|
response.status,
|
||||||
|
response.config.method,
|
||||||
|
response.config.url,
|
||||||
|
response.data
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
logger.error("response error", error);
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const userAuth = await userAuthRepository.findOne({
|
const userAuth = await userAuthRepository.findOne({
|
||||||
where: { id: 1 },
|
where: { id: 1 },
|
||||||
});
|
});
|
||||||
@ -29,28 +107,59 @@ export class HydraApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static async revalidateAccessTokenIfExpired() {
|
private static async revalidateAccessTokenIfExpired() {
|
||||||
|
if (!this.userAuth.authToken) {
|
||||||
|
userAuthRepository.delete({ id: 1 });
|
||||||
|
logger.error("user is not logged in");
|
||||||
|
throw new Error("user is not logged in");
|
||||||
|
}
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
if (this.userAuth.expirationTimestamp < now.getTime()) {
|
if (this.userAuth.expirationTimestamp < now.getTime()) {
|
||||||
const response = await this.instance.post(`/auth/refresh`, {
|
try {
|
||||||
refreshToken: this.userAuth.refreshToken,
|
const response = await this.instance.post(`/auth/refresh`, {
|
||||||
});
|
refreshToken: this.userAuth.refreshToken,
|
||||||
|
});
|
||||||
|
|
||||||
const { accessToken, expiresIn } = response.data;
|
const { accessToken, expiresIn } = response.data;
|
||||||
|
|
||||||
const tokenExpirationTimestamp =
|
const tokenExpirationTimestamp =
|
||||||
now.getTime() + expiresIn - this.EXPIRATION_OFFSET_IN_MS;
|
now.getTime() +
|
||||||
|
this.secondsToMilliseconds(expiresIn) -
|
||||||
|
this.EXPIRATION_OFFSET_IN_MS;
|
||||||
|
|
||||||
this.userAuth.authToken = accessToken;
|
this.userAuth.authToken = accessToken;
|
||||||
this.userAuth.expirationTimestamp = tokenExpirationTimestamp;
|
this.userAuth.expirationTimestamp = tokenExpirationTimestamp;
|
||||||
|
|
||||||
userAuthRepository.upsert(
|
userAuthRepository.upsert(
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
accessToken,
|
accessToken,
|
||||||
tokenExpirationTimestamp,
|
tokenExpirationTimestamp,
|
||||||
},
|
},
|
||||||
["id"]
|
["id"]
|
||||||
);
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (
|
||||||
|
err instanceof AxiosError &&
|
||||||
|
(err?.response?.status === 401 || err?.response?.status === 403)
|
||||||
|
) {
|
||||||
|
this.userAuth = {
|
||||||
|
authToken: "",
|
||||||
|
expirationTimestamp: 0,
|
||||||
|
refreshToken: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
userAuthRepository.delete({ id: 1 });
|
||||||
|
|
||||||
|
if (WindowManager.mainWindow) {
|
||||||
|
WindowManager.mainWindow.webContents.send("on-signout");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log("user refresh token expired");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,13 +181,18 @@ export class HydraApi {
|
|||||||
return this.instance.post(url, data, this.getAxiosConfig());
|
return this.instance.post(url, data, this.getAxiosConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
static async put(url, data?: any) {
|
static async put(url: string, data?: any) {
|
||||||
await this.revalidateAccessTokenIfExpired();
|
await this.revalidateAccessTokenIfExpired();
|
||||||
return this.instance.put(url, data, this.getAxiosConfig());
|
return this.instance.put(url, data, this.getAxiosConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
static async patch(url, data?: any) {
|
static async patch(url: string, data?: any) {
|
||||||
await this.revalidateAccessTokenIfExpired();
|
await this.revalidateAccessTokenIfExpired();
|
||||||
return this.instance.patch(url, data, this.getAxiosConfig());
|
return this.instance.patch(url, data, this.getAxiosConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async delete(url: string) {
|
||||||
|
await this.revalidateAccessTokenIfExpired();
|
||||||
|
return this.instance.delete(url, this.getAxiosConfig());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,3 +8,4 @@ export * from "./how-long-to-beat";
|
|||||||
export * from "./process-watcher";
|
export * from "./process-watcher";
|
||||||
export * from "./main-loop";
|
export * from "./main-loop";
|
||||||
export * from "./repacks-manager";
|
export * from "./repacks-manager";
|
||||||
|
export * from "./hydra-api";
|
||||||
|
5
src/main/services/library-sync/clear-games-remote-id.ts
Normal file
5
src/main/services/library-sync/clear-games-remote-id.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { gameRepository } from "@main/repository";
|
||||||
|
|
||||||
|
export const clearGamesRemoteIds = () => {
|
||||||
|
return gameRepository.update({}, { remoteId: null });
|
||||||
|
};
|
11
src/main/services/library-sync/create-game.ts
Normal file
11
src/main/services/library-sync/create-game.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Game } from "@main/entity";
|
||||||
|
import { HydraApi } from "../hydra-api";
|
||||||
|
|
||||||
|
export const createGame = async (game: Game) => {
|
||||||
|
return HydraApi.post(`/games`, {
|
||||||
|
objectId: game.objectID,
|
||||||
|
playTimeInMilliseconds: Math.trunc(game.playTimeInMilliseconds),
|
||||||
|
shop: game.shop,
|
||||||
|
lastTimePlayed: game.lastTimePlayed,
|
||||||
|
});
|
||||||
|
};
|
4
src/main/services/library-sync/index.ts
Normal file
4
src/main/services/library-sync/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./merge-with-remote-games";
|
||||||
|
export * from "./upload-games-batch";
|
||||||
|
export * from "./update-game-playtime";
|
||||||
|
export * from "./create-game";
|
72
src/main/services/library-sync/merge-with-remote-games.ts
Normal file
72
src/main/services/library-sync/merge-with-remote-games.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { gameRepository } from "@main/repository";
|
||||||
|
import { HydraApi } from "../hydra-api";
|
||||||
|
import { steamGamesWorker } from "@main/workers";
|
||||||
|
import { getSteamAppAsset } from "@main/helpers";
|
||||||
|
import { logger } from "../logger";
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
|
export const mergeWithRemoteGames = async () => {
|
||||||
|
try {
|
||||||
|
const games = await HydraApi.get("/games");
|
||||||
|
|
||||||
|
for (const game of games.data) {
|
||||||
|
const localGame = await gameRepository.findOne({
|
||||||
|
where: {
|
||||||
|
objectID: game.objectId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (localGame) {
|
||||||
|
const updatedLastTimePlayed =
|
||||||
|
localGame.lastTimePlayed == null ||
|
||||||
|
(game.lastTimePlayed &&
|
||||||
|
new Date(game.lastTimePlayed) > localGame.lastTimePlayed)
|
||||||
|
? game.lastTimePlayed
|
||||||
|
: localGame.lastTimePlayed;
|
||||||
|
|
||||||
|
const updatedPlayTime =
|
||||||
|
localGame.playTimeInMilliseconds < game.playTimeInMilliseconds
|
||||||
|
? game.playTimeInMilliseconds
|
||||||
|
: localGame.playTimeInMilliseconds;
|
||||||
|
|
||||||
|
gameRepository.update(
|
||||||
|
{
|
||||||
|
objectID: game.objectId,
|
||||||
|
shop: "steam",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
remoteId: game.id,
|
||||||
|
lastTimePlayed: updatedLastTimePlayed,
|
||||||
|
playTimeInMilliseconds: updatedPlayTime,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const steamGame = await steamGamesWorker.run(Number(game.objectId), {
|
||||||
|
name: "getById",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (steamGame) {
|
||||||
|
const iconUrl = steamGame?.clientIcon
|
||||||
|
? getSteamAppAsset("icon", game.objectId, steamGame.clientIcon)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
gameRepository.insert({
|
||||||
|
objectID: game.objectId,
|
||||||
|
title: steamGame?.name,
|
||||||
|
remoteId: game.id,
|
||||||
|
shop: game.shop,
|
||||||
|
iconUrl,
|
||||||
|
lastTimePlayed: game.lastTimePlayed,
|
||||||
|
playTimeInMilliseconds: game.playTimeInMilliseconds,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof AxiosError) {
|
||||||
|
logger.error("getRemoteGames", err.response, err.message);
|
||||||
|
} else {
|
||||||
|
logger.error("getRemoteGames", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
13
src/main/services/library-sync/update-game-playtime.ts
Normal file
13
src/main/services/library-sync/update-game-playtime.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Game } from "@main/entity";
|
||||||
|
import { HydraApi } from "../hydra-api";
|
||||||
|
|
||||||
|
export const updateGamePlaytime = async (
|
||||||
|
game: Game,
|
||||||
|
deltaInMillis: number,
|
||||||
|
lastTimePlayed: Date
|
||||||
|
) => {
|
||||||
|
return HydraApi.put(`/games/${game.remoteId}`, {
|
||||||
|
playTimeDeltaInSeconds: Math.trunc(deltaInMillis / 1000),
|
||||||
|
lastTimePlayed,
|
||||||
|
});
|
||||||
|
};
|
44
src/main/services/library-sync/upload-games-batch.ts
Normal file
44
src/main/services/library-sync/upload-games-batch.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { gameRepository } from "@main/repository";
|
||||||
|
import { chunk } from "lodash-es";
|
||||||
|
import { IsNull } from "typeorm";
|
||||||
|
import { HydraApi } from "../hydra-api";
|
||||||
|
import { logger } from "../logger";
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
|
import { mergeWithRemoteGames } from "./merge-with-remote-games";
|
||||||
|
import { WindowManager } from "../window-manager";
|
||||||
|
|
||||||
|
export const uploadGamesBatch = async () => {
|
||||||
|
try {
|
||||||
|
const games = await gameRepository.find({
|
||||||
|
where: { remoteId: IsNull(), isDeleted: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
const gamesChunks = chunk(games, 200);
|
||||||
|
|
||||||
|
for (const chunk of gamesChunks) {
|
||||||
|
await HydraApi.post(
|
||||||
|
"/games/batch",
|
||||||
|
chunk.map((game) => {
|
||||||
|
return {
|
||||||
|
objectId: game.objectID,
|
||||||
|
playTimeInMilliseconds: Math.trunc(game.playTimeInMilliseconds),
|
||||||
|
shop: game.shop,
|
||||||
|
lastTimePlayed: game.lastTimePlayed,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await mergeWithRemoteGames();
|
||||||
|
|
||||||
|
if (WindowManager.mainWindow)
|
||||||
|
WindowManager.mainWindow.webContents.send("on-library-batch-complete");
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof AxiosError) {
|
||||||
|
logger.error("uploadGamesBatch", err.response, err.message);
|
||||||
|
} else {
|
||||||
|
logger.error("uploadGamesBatch", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -4,8 +4,13 @@ import { IsNull, Not } from "typeorm";
|
|||||||
import { gameRepository } from "@main/repository";
|
import { gameRepository } from "@main/repository";
|
||||||
import { getProcesses } from "@main/helpers";
|
import { getProcesses } from "@main/helpers";
|
||||||
import { WindowManager } from "./window-manager";
|
import { WindowManager } from "./window-manager";
|
||||||
|
import { createGame, updateGamePlaytime } from "./library-sync";
|
||||||
|
import { GameRunning } from "@types";
|
||||||
|
|
||||||
const gamesPlaytime = new Map<number, number>();
|
export const gamesPlaytime = new Map<
|
||||||
|
number,
|
||||||
|
{ lastTick: number; firstTick: number }
|
||||||
|
>();
|
||||||
|
|
||||||
export const watchProcesses = async () => {
|
export const watchProcesses = async () => {
|
||||||
const games = await gameRepository.find({
|
const games = await gameRepository.find({
|
||||||
@ -37,26 +42,67 @@ export const watchProcesses = async () => {
|
|||||||
|
|
||||||
if (gameProcess) {
|
if (gameProcess) {
|
||||||
if (gamesPlaytime.has(game.id)) {
|
if (gamesPlaytime.has(game.id)) {
|
||||||
const zero = gamesPlaytime.get(game.id) ?? 0;
|
const gamePlaytime = gamesPlaytime.get(game.id)!;
|
||||||
const delta = performance.now() - zero;
|
|
||||||
|
|
||||||
if (WindowManager.mainWindow) {
|
const zero = gamePlaytime.lastTick;
|
||||||
WindowManager.mainWindow.webContents.send("on-playtime", game.id);
|
const delta = performance.now() - zero;
|
||||||
}
|
|
||||||
|
|
||||||
await gameRepository.update(game.id, {
|
await gameRepository.update(game.id, {
|
||||||
playTimeInMilliseconds: game.playTimeInMilliseconds + delta,
|
playTimeInMilliseconds: game.playTimeInMilliseconds + delta,
|
||||||
lastTimePlayed: new Date(),
|
lastTimePlayed: new Date(),
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
gamesPlaytime.set(game.id, performance.now());
|
gamesPlaytime.set(game.id, {
|
||||||
|
...gamePlaytime,
|
||||||
|
lastTick: performance.now(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (game.remoteId) {
|
||||||
|
updateGamePlaytime(game, 0, new Date());
|
||||||
|
} else {
|
||||||
|
createGame({ ...game, lastTimePlayed: new Date() }).then(
|
||||||
|
(response) => {
|
||||||
|
const { id: remoteId } = response.data;
|
||||||
|
gameRepository.update({ objectID: game.objectID }, { remoteId });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
gamesPlaytime.set(game.id, {
|
||||||
|
lastTick: performance.now(),
|
||||||
|
firstTick: performance.now(),
|
||||||
|
});
|
||||||
|
}
|
||||||
} else if (gamesPlaytime.has(game.id)) {
|
} else if (gamesPlaytime.has(game.id)) {
|
||||||
|
const gamePlaytime = gamesPlaytime.get(game.id)!;
|
||||||
gamesPlaytime.delete(game.id);
|
gamesPlaytime.delete(game.id);
|
||||||
|
|
||||||
if (WindowManager.mainWindow) {
|
if (game.remoteId) {
|
||||||
WindowManager.mainWindow.webContents.send("on-game-close", game.id);
|
updateGamePlaytime(
|
||||||
|
game,
|
||||||
|
performance.now() - gamePlaytime.firstTick,
|
||||||
|
game.lastTimePlayed!
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
createGame(game).then((response) => {
|
||||||
|
const { id: remoteId } = response.data;
|
||||||
|
gameRepository.update({ objectID: game.objectID }, { remoteId });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (WindowManager.mainWindow) {
|
||||||
|
const gamesRunning = Array.from(gamesPlaytime.entries()).map((entry) => {
|
||||||
|
return {
|
||||||
|
id: entry[0],
|
||||||
|
sessionDurationInMillis: performance.now() - entry[1].firstTick,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
WindowManager.mainWindow.webContents.send(
|
||||||
|
"on-games-running",
|
||||||
|
gamesRunning as Pick<GameRunning, "id" | "sessionDurationInMillis">[]
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -15,6 +15,7 @@ import icon from "@resources/icon.png?asset";
|
|||||||
import trayIcon from "@resources/tray-icon.png?asset";
|
import trayIcon from "@resources/tray-icon.png?asset";
|
||||||
import { gameRepository, userPreferencesRepository } from "@main/repository";
|
import { gameRepository, userPreferencesRepository } from "@main/repository";
|
||||||
import { IsNull, Not } from "typeorm";
|
import { IsNull, Not } from "typeorm";
|
||||||
|
import { HydraApi } from "./hydra-api";
|
||||||
|
|
||||||
export class WindowManager {
|
export class WindowManager {
|
||||||
public static mainWindow: Electron.BrowserWindow | null = null;
|
public static mainWindow: Electron.BrowserWindow | null = null;
|
||||||
@ -80,6 +81,41 @@ export class WindowManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static openAuthWindow() {
|
||||||
|
if (this.mainWindow) {
|
||||||
|
const authWindow = new BrowserWindow({
|
||||||
|
width: 600,
|
||||||
|
height: 640,
|
||||||
|
backgroundColor: "#1c1c1c",
|
||||||
|
parent: this.mainWindow,
|
||||||
|
modal: true,
|
||||||
|
show: false,
|
||||||
|
maximizable: false,
|
||||||
|
resizable: false,
|
||||||
|
minimizable: false,
|
||||||
|
webPreferences: {
|
||||||
|
sandbox: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
authWindow.removeMenu();
|
||||||
|
|
||||||
|
authWindow.loadURL("https://auth.hydra.losbroxas.org/");
|
||||||
|
|
||||||
|
authWindow.once("ready-to-show", () => {
|
||||||
|
authWindow.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
authWindow.webContents.on("will-navigate", (_event, url) => {
|
||||||
|
if (url.startsWith("hydralauncher://auth")) {
|
||||||
|
authWindow.close();
|
||||||
|
|
||||||
|
HydraApi.handleExternalAuth(url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static redirect(hash: string) {
|
public static redirect(hash: string) {
|
||||||
if (!this.mainWindow) this.createMainWindow();
|
if (!this.mainWindow) this.createMainWindow();
|
||||||
this.loadURL(hash);
|
this.loadURL(hash);
|
||||||
|
@ -11,6 +11,7 @@ export const steamGamesWorker = new Piscina({
|
|||||||
workerData: {
|
workerData: {
|
||||||
steamGamesPath: path.join(seedsPath, "steam-games.json"),
|
steamGamesPath: path.join(seedsPath, "steam-games.json"),
|
||||||
},
|
},
|
||||||
|
maxThreads: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const downloadSourceWorker = new Piscina({
|
export const downloadSourceWorker = new Piscina({
|
||||||
|
@ -8,6 +8,7 @@ import type {
|
|||||||
UserPreferences,
|
UserPreferences,
|
||||||
AppUpdaterEvent,
|
AppUpdaterEvent,
|
||||||
StartGameDownloadPayload,
|
StartGameDownloadPayload,
|
||||||
|
GameRunning,
|
||||||
} from "@types";
|
} from "@types";
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld("electron", {
|
contextBridge.exposeInMainWorld("electron", {
|
||||||
@ -84,17 +85,21 @@ contextBridge.exposeInMainWorld("electron", {
|
|||||||
ipcRenderer.invoke("deleteGameFolder", gameId),
|
ipcRenderer.invoke("deleteGameFolder", gameId),
|
||||||
getGameByObjectID: (objectID: string) =>
|
getGameByObjectID: (objectID: string) =>
|
||||||
ipcRenderer.invoke("getGameByObjectID", objectID),
|
ipcRenderer.invoke("getGameByObjectID", objectID),
|
||||||
onPlaytime: (cb: (gameId: number) => void) => {
|
onGamesRunning: (
|
||||||
const listener = (_event: Electron.IpcRendererEvent, gameId: number) =>
|
cb: (
|
||||||
cb(gameId);
|
gamesRunning: Pick<GameRunning, "id" | "sessionDurationInMillis">[]
|
||||||
ipcRenderer.on("on-playtime", listener);
|
) => void
|
||||||
return () => ipcRenderer.removeListener("on-playtime", listener);
|
) => {
|
||||||
|
const listener = (_event: Electron.IpcRendererEvent, gamesRunning) =>
|
||||||
|
cb(gamesRunning);
|
||||||
|
ipcRenderer.on("on-games-running", listener);
|
||||||
|
return () => ipcRenderer.removeListener("on-games-running", listener);
|
||||||
},
|
},
|
||||||
onGameClose: (cb: (gameId: number) => void) => {
|
onLibraryBatchComplete: (cb: () => void) => {
|
||||||
const listener = (_event: Electron.IpcRendererEvent, gameId: number) =>
|
const listener = (_event: Electron.IpcRendererEvent) => cb();
|
||||||
cb(gameId);
|
ipcRenderer.on("on-library-batch-complete", listener);
|
||||||
ipcRenderer.on("on-game-close", listener);
|
return () =>
|
||||||
return () => ipcRenderer.removeListener("on-game-close", listener);
|
ipcRenderer.removeListener("on-library-batch-complete", listener);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Hardware */
|
/* Hardware */
|
||||||
@ -106,6 +111,7 @@ contextBridge.exposeInMainWorld("electron", {
|
|||||||
getVersion: () => ipcRenderer.invoke("getVersion"),
|
getVersion: () => ipcRenderer.invoke("getVersion"),
|
||||||
getDefaultDownloadsPath: () => ipcRenderer.invoke("getDefaultDownloadsPath"),
|
getDefaultDownloadsPath: () => ipcRenderer.invoke("getDefaultDownloadsPath"),
|
||||||
openExternal: (src: string) => ipcRenderer.invoke("openExternal", src),
|
openExternal: (src: string) => ipcRenderer.invoke("openExternal", src),
|
||||||
|
isUserLoggedIn: () => ipcRenderer.invoke("isUserLoggedIn"),
|
||||||
showOpenDialog: (options: Electron.OpenDialogOptions) =>
|
showOpenDialog: (options: Electron.OpenDialogOptions) =>
|
||||||
ipcRenderer.invoke("showOpenDialog", options),
|
ipcRenderer.invoke("showOpenDialog", options),
|
||||||
platform: process.platform,
|
platform: process.platform,
|
||||||
@ -125,4 +131,27 @@ contextBridge.exposeInMainWorld("electron", {
|
|||||||
},
|
},
|
||||||
checkForUpdates: () => ipcRenderer.invoke("checkForUpdates"),
|
checkForUpdates: () => ipcRenderer.invoke("checkForUpdates"),
|
||||||
restartAndInstallUpdate: () => ipcRenderer.invoke("restartAndInstallUpdate"),
|
restartAndInstallUpdate: () => ipcRenderer.invoke("restartAndInstallUpdate"),
|
||||||
|
|
||||||
|
/* Profile */
|
||||||
|
getMe: () => ipcRenderer.invoke("getMe"),
|
||||||
|
updateProfile: (displayName: string, newProfileImagePath: string | null) =>
|
||||||
|
ipcRenderer.invoke("updateProfile", displayName, newProfileImagePath),
|
||||||
|
|
||||||
|
/* User */
|
||||||
|
getUser: (userId: string) => ipcRenderer.invoke("getUser", userId),
|
||||||
|
|
||||||
|
/* Auth */
|
||||||
|
signOut: () => ipcRenderer.invoke("signOut"),
|
||||||
|
openAuthWindow: () => ipcRenderer.invoke("openAuthWindow"),
|
||||||
|
getSessionHash: () => ipcRenderer.invoke("getSessionHash"),
|
||||||
|
onSignIn: (cb: () => void) => {
|
||||||
|
const listener = (_event: Electron.IpcRendererEvent) => cb();
|
||||||
|
ipcRenderer.on("on-signin", listener);
|
||||||
|
return () => ipcRenderer.removeListener("on-signin", listener);
|
||||||
|
},
|
||||||
|
onSignOut: (cb: () => void) => {
|
||||||
|
const listener = (_event: Electron.IpcRendererEvent) => cb();
|
||||||
|
ipcRenderer.on("on-signout", listener);
|
||||||
|
return () => ipcRenderer.removeListener("on-signout", listener);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<title>Hydra</title>
|
<title>Hydra</title>
|
||||||
<meta
|
<meta
|
||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://steamcdn-a.akamaihd.net https://shared.akamai.steamstatic.com https://cdn.cloudflare.steamstatic.com https://cdn2.steamgriddb.com https://cdn.akamai.steamstatic.com; media-src 'self' data: https://steamcdn-a.akamaihd.net https://cdn.cloudflare.steamstatic.com https://cdn2.steamgriddb.com https://cdn.akamai.steamstatic.com https://shared.akamai.steamstatic.com;"
|
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: local: https://*.s3.amazonaws.com https://steamcdn-a.akamaihd.net https://shared.akamai.steamstatic.com https://cdn.cloudflare.steamstatic.com https://cdn2.steamgriddb.com https://cdn.akamai.steamstatic.com; media-src 'self' local: data: https://steamcdn-a.akamaihd.net https://cdn.cloudflare.steamstatic.com https://cdn2.steamgriddb.com https://cdn.akamai.steamstatic.com https://shared.akamai.steamstatic.com;"
|
||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body style="background-color: #1c1c1c">
|
<body style="background-color: #1c1c1c">
|
||||||
|
@ -111,6 +111,6 @@ export const titleBar = style({
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
padding: `0 ${SPACING_UNIT * 2}px`,
|
padding: `0 ${SPACING_UNIT * 2}px`,
|
||||||
WebkitAppRegion: "drag",
|
WebkitAppRegion: "drag",
|
||||||
zIndex: "2",
|
zIndex: "4",
|
||||||
borderBottom: `1px solid ${vars.color.border}`,
|
borderBottom: `1px solid ${vars.color.border}`,
|
||||||
} as ComplexStyleRule);
|
} as ComplexStyleRule);
|
||||||
|
@ -7,6 +7,8 @@ import {
|
|||||||
useAppSelector,
|
useAppSelector,
|
||||||
useDownload,
|
useDownload,
|
||||||
useLibrary,
|
useLibrary,
|
||||||
|
useToast,
|
||||||
|
useUserDetails,
|
||||||
} from "@renderer/hooks";
|
} from "@renderer/hooks";
|
||||||
|
|
||||||
import * as styles from "./app.css";
|
import * as styles from "./app.css";
|
||||||
@ -18,7 +20,11 @@ import {
|
|||||||
setUserPreferences,
|
setUserPreferences,
|
||||||
toggleDraggingDisabled,
|
toggleDraggingDisabled,
|
||||||
closeToast,
|
closeToast,
|
||||||
|
setUserDetails,
|
||||||
|
setProfileBackground,
|
||||||
|
setGameRunning,
|
||||||
} from "@renderer/features";
|
} from "@renderer/features";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export interface AppProps {
|
export interface AppProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@ -26,21 +32,30 @@ export interface AppProps {
|
|||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const contentRef = useRef<HTMLDivElement>(null);
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
const { updateLibrary } = useLibrary();
|
const { updateLibrary, library } = useLibrary();
|
||||||
|
|
||||||
|
const { t } = useTranslation("app");
|
||||||
|
|
||||||
const { clearDownload, setLastPacket } = useDownload();
|
const { clearDownload, setLastPacket } = useDownload();
|
||||||
|
|
||||||
|
const { fetchUserDetails, updateUserDetails, clearUserDetails } =
|
||||||
|
useUserDetails();
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
const search = useAppSelector((state) => state.search.value);
|
const search = useAppSelector((state) => state.search.value);
|
||||||
|
|
||||||
const draggingDisabled = useAppSelector(
|
const draggingDisabled = useAppSelector(
|
||||||
(state) => state.window.draggingDisabled
|
(state) => state.window.draggingDisabled
|
||||||
);
|
);
|
||||||
|
|
||||||
const toast = useAppSelector((state) => state.toast);
|
const toast = useAppSelector((state) => state.toast);
|
||||||
|
|
||||||
|
const { showSuccessToast } = useToast();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Promise.all([window.electron.getUserPreferences(), updateLibrary()]).then(
|
Promise.all([window.electron.getUserPreferences(), updateLibrary()]).then(
|
||||||
([preferences]) => {
|
([preferences]) => {
|
||||||
@ -67,6 +82,75 @@ export function App() {
|
|||||||
};
|
};
|
||||||
}, [clearDownload, setLastPacket, updateLibrary]);
|
}, [clearDownload, setLastPacket, updateLibrary]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const cachedUserDetails = window.localStorage.getItem("userDetails");
|
||||||
|
|
||||||
|
if (cachedUserDetails) {
|
||||||
|
const { profileBackground, ...userDetails } =
|
||||||
|
JSON.parse(cachedUserDetails);
|
||||||
|
|
||||||
|
dispatch(setUserDetails(userDetails));
|
||||||
|
dispatch(setProfileBackground(profileBackground));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.electron.isUserLoggedIn().then((isLoggedIn) => {
|
||||||
|
if (isLoggedIn) {
|
||||||
|
fetchUserDetails().then((response) => {
|
||||||
|
if (response) updateUserDetails(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [fetchUserDetails, updateUserDetails, dispatch]);
|
||||||
|
|
||||||
|
const onSignIn = useCallback(() => {
|
||||||
|
fetchUserDetails().then((response) => {
|
||||||
|
if (response) {
|
||||||
|
updateUserDetails(response);
|
||||||
|
showSuccessToast(t("successfully_signed_in"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [fetchUserDetails, t, showSuccessToast, updateUserDetails]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribe = window.electron.onGamesRunning((gamesRunning) => {
|
||||||
|
if (gamesRunning.length) {
|
||||||
|
const lastGame = gamesRunning[gamesRunning.length - 1];
|
||||||
|
const libraryGame = library.find(
|
||||||
|
(library) => library.id === lastGame.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (libraryGame) {
|
||||||
|
dispatch(
|
||||||
|
setGameRunning({
|
||||||
|
...libraryGame,
|
||||||
|
sessionDurationInMillis: lastGame.sessionDurationInMillis,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dispatch(setGameRunning(null));
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribe();
|
||||||
|
};
|
||||||
|
}, [dispatch, library]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const listeners = [
|
||||||
|
window.electron.onSignIn(onSignIn),
|
||||||
|
window.electron.onLibraryBatchComplete(() => {
|
||||||
|
updateLibrary();
|
||||||
|
}),
|
||||||
|
window.electron.onSignOut(() => clearUserDetails()),
|
||||||
|
];
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
listeners.forEach((unsubscribe) => unsubscribe());
|
||||||
|
};
|
||||||
|
}, [onSignIn, updateLibrary, clearUserDetails]);
|
||||||
|
|
||||||
const handleSearch = useCallback(
|
const handleSearch = useCallback(
|
||||||
(query: string) => {
|
(query: string) => {
|
||||||
dispatch(setSearch(query));
|
dispatch(setSearch(query));
|
||||||
@ -119,6 +203,13 @@ export function App() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Toast
|
||||||
|
visible={toast.visible}
|
||||||
|
message={toast.message}
|
||||||
|
type={toast.type}
|
||||||
|
onClose={handleToastClose}
|
||||||
|
/>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
|
|
||||||
@ -136,13 +227,6 @@ export function App() {
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<BottomPanel />
|
<BottomPanel />
|
||||||
|
|
||||||
<Toast
|
|
||||||
visible={toast.visible}
|
|
||||||
message={toast.message}
|
|
||||||
type={toast.type}
|
|
||||||
onClose={handleToastClose}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { keyframes } from "@vanilla-extract/css";
|
import { keyframes } from "@vanilla-extract/css";
|
||||||
import { recipe } from "@vanilla-extract/recipes";
|
import { recipe } from "@vanilla-extract/recipes";
|
||||||
|
|
||||||
import { SPACING_UNIT } from "../../theme.css";
|
import { SPACING_UNIT, vars } from "../../theme.css";
|
||||||
|
|
||||||
export const backdropFadeIn = keyframes({
|
export const backdropFadeIn = keyframes({
|
||||||
"0%": { backdropFilter: "blur(0px)", backgroundColor: "rgba(0, 0, 0, 0.5)" },
|
"0%": { backdropFilter: "blur(0px)", backgroundColor: "rgba(0, 0, 0, 0.5)" },
|
||||||
@ -30,8 +30,8 @@ export const backdrop = recipe({
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
zIndex: 1,
|
zIndex: vars.zIndex.backdrop,
|
||||||
top: 0,
|
top: "0",
|
||||||
padding: `${SPACING_UNIT * 3}px`,
|
padding: `${SPACING_UNIT * 3}px`,
|
||||||
backdropFilter: "blur(2px)",
|
backdropFilter: "blur(2px)",
|
||||||
transition: "all ease 0.2s",
|
transition: "all ease 0.2s",
|
||||||
|
@ -12,7 +12,7 @@ export const bottomPanel = style({
|
|||||||
transition: "all ease 0.2s",
|
transition: "all ease 0.2s",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
zIndex: "1",
|
zIndex: vars.zIndex.bottomPanel,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const downloadsButton = style({
|
export const downloadsButton = style({
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { useDownload } from "@renderer/hooks";
|
import { useDownload, useUserDetails } from "@renderer/hooks";
|
||||||
|
|
||||||
import * as styles from "./bottom-panel.css";
|
import * as styles from "./bottom-panel.css";
|
||||||
|
|
||||||
@ -13,16 +13,23 @@ export function BottomPanel() {
|
|||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const { userDetails } = useUserDetails();
|
||||||
|
|
||||||
const { lastPacket, progress, downloadSpeed, eta } = useDownload();
|
const { lastPacket, progress, downloadSpeed, eta } = useDownload();
|
||||||
|
|
||||||
const isGameDownloading = !!lastPacket?.game;
|
const isGameDownloading = !!lastPacket?.game;
|
||||||
|
|
||||||
const [version, setVersion] = useState("");
|
const [version, setVersion] = useState("");
|
||||||
|
const [sessionHash, setSessionHash] = useState<null | string>("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.electron.getVersion().then((result) => setVersion(result));
|
window.electron.getVersion().then((result) => setVersion(result));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.electron.getSessionHash().then((result) => setSessionHash(result));
|
||||||
|
}, [userDetails?.id]);
|
||||||
|
|
||||||
const status = useMemo(() => {
|
const status = useMemo(() => {
|
||||||
if (isGameDownloading) {
|
if (isGameDownloading) {
|
||||||
if (lastPacket?.isDownloadingMetadata)
|
if (lastPacket?.isDownloadingMetadata)
|
||||||
@ -65,7 +72,8 @@ export function BottomPanel() {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<small>
|
<small>
|
||||||
v{version} "{VERSION_CODENAME}"
|
{sessionHash ? `${sessionHash} -` : ""} v{version} "
|
||||||
|
{VERSION_CODENAME}"
|
||||||
</small>
|
</small>
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
|
@ -39,6 +39,7 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
|
|||||||
|
|
||||||
const title = useMemo(() => {
|
const title = useMemo(() => {
|
||||||
if (location.pathname.startsWith("/game")) return headerTitle;
|
if (location.pathname.startsWith("/game")) return headerTitle;
|
||||||
|
if (location.pathname.startsWith("/user")) return headerTitle;
|
||||||
if (location.pathname.startsWith("/search")) return t("search_results");
|
if (location.pathname.startsWith("/search")) return t("search_results");
|
||||||
|
|
||||||
return t(pathTitle[location.pathname]);
|
return t(pathTitle[location.pathname]);
|
||||||
|
@ -9,8 +9,8 @@ import {
|
|||||||
} from "@renderer/helpers";
|
} from "@renderer/helpers";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const FEATURED_GAME_TITLE = "Horizon Forbidden West™ Complete Edition";
|
const FEATURED_GAME_TITLE = "Ghost of Tsushima DIRECTOR'S CUT";
|
||||||
const FEATURED_GAME_ID = "2420110";
|
const FEATURED_GAME_ID = "2215430";
|
||||||
|
|
||||||
export function Hero() {
|
export function Hero() {
|
||||||
const [featuredGameDetails, setFeaturedGameDetails] =
|
const [featuredGameDetails, setFeaturedGameDetails] =
|
||||||
|
66
src/renderer/src/components/sidebar/sidebar-profile.css.ts
Normal file
66
src/renderer/src/components/sidebar/sidebar-profile.css.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { style } from "@vanilla-extract/css";
|
||||||
|
|
||||||
|
import { SPACING_UNIT, vars } from "../../theme.css";
|
||||||
|
|
||||||
|
export const profileButton = style({
|
||||||
|
display: "flex",
|
||||||
|
cursor: "pointer",
|
||||||
|
transition: "all ease 0.1s",
|
||||||
|
padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 2}px`,
|
||||||
|
color: vars.color.muted,
|
||||||
|
borderBottom: `solid 1px ${vars.color.border}`,
|
||||||
|
boxShadow: "0px 0px 15px 0px rgb(0 0 0 / 70%)",
|
||||||
|
":hover": {
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.15)",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const profileButtonContent = style({
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: `${SPACING_UNIT + SPACING_UNIT / 2}px`,
|
||||||
|
height: "40px",
|
||||||
|
width: "100%",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const profileAvatar = style({
|
||||||
|
width: "35px",
|
||||||
|
height: "35px",
|
||||||
|
borderRadius: "50%",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
backgroundColor: vars.color.background,
|
||||||
|
border: `solid 1px ${vars.color.border}`,
|
||||||
|
position: "relative",
|
||||||
|
objectFit: "cover",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const profileButtonInformation = style({
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "flex-start",
|
||||||
|
flex: "1",
|
||||||
|
minWidth: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const statusBadge = style({
|
||||||
|
width: "9px",
|
||||||
|
height: "9px",
|
||||||
|
borderRadius: "50%",
|
||||||
|
backgroundColor: vars.color.danger,
|
||||||
|
position: "absolute",
|
||||||
|
bottom: "-2px",
|
||||||
|
right: "-3px",
|
||||||
|
zIndex: "1",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const profileButtonTitle = style({
|
||||||
|
fontWeight: "bold",
|
||||||
|
fontSize: vars.size.body,
|
||||||
|
width: "100%",
|
||||||
|
textAlign: "left",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
});
|
75
src/renderer/src/components/sidebar/sidebar-profile.tsx
Normal file
75
src/renderer/src/components/sidebar/sidebar-profile.tsx
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { PersonIcon } from "@primer/octicons-react";
|
||||||
|
import * as styles from "./sidebar-profile.css";
|
||||||
|
|
||||||
|
import { useAppSelector, useUserDetails } from "@renderer/hooks";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
export function SidebarProfile() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const { t } = useTranslation("sidebar");
|
||||||
|
|
||||||
|
const { userDetails, profileBackground } = useUserDetails();
|
||||||
|
|
||||||
|
const { gameRunning } = useAppSelector((state) => state.gameRunning);
|
||||||
|
|
||||||
|
const handleButtonClick = () => {
|
||||||
|
if (userDetails === null) {
|
||||||
|
window.electron.openAuthWindow();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate(`/user/${userDetails!.id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const profileButtonBackground = useMemo(() => {
|
||||||
|
if (profileBackground) return profileBackground;
|
||||||
|
return undefined;
|
||||||
|
}, [profileBackground]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={styles.profileButton}
|
||||||
|
style={{ background: profileButtonBackground }}
|
||||||
|
onClick={handleButtonClick}
|
||||||
|
>
|
||||||
|
<div className={styles.profileButtonContent}>
|
||||||
|
<div className={styles.profileAvatar}>
|
||||||
|
{userDetails?.profileImageUrl ? (
|
||||||
|
<img
|
||||||
|
className={styles.profileAvatar}
|
||||||
|
src={userDetails.profileImageUrl}
|
||||||
|
alt={userDetails.displayName}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<PersonIcon />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.profileButtonInformation}>
|
||||||
|
<p className={styles.profileButtonTitle}>
|
||||||
|
{userDetails ? userDetails.displayName : t("sign_in")}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{userDetails && gameRunning && (
|
||||||
|
<div>
|
||||||
|
<small>{gameRunning.title}</small>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{userDetails && gameRunning && (
|
||||||
|
<img
|
||||||
|
alt={gameRunning.title}
|
||||||
|
width={24}
|
||||||
|
style={{ borderRadius: 4 }}
|
||||||
|
src={gameRunning.iconUrl}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
@ -125,46 +125,3 @@ export const section = style({
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
paddingBottom: `${SPACING_UNIT}px`,
|
paddingBottom: `${SPACING_UNIT}px`,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const profileButton = style({
|
|
||||||
display: "flex",
|
|
||||||
cursor: "pointer",
|
|
||||||
transition: "all ease 0.1s",
|
|
||||||
gap: `${SPACING_UNIT + SPACING_UNIT / 2}px`,
|
|
||||||
alignItems: "center",
|
|
||||||
padding: `${SPACING_UNIT * 2}px ${SPACING_UNIT * 2}px`,
|
|
||||||
color: vars.color.muted,
|
|
||||||
borderBottom: `solid 1px ${vars.color.border}`,
|
|
||||||
boxShadow: "0px 0px 15px 0px #000000",
|
|
||||||
":hover": {
|
|
||||||
backgroundColor: "rgba(255, 255, 255, 0.15)",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const profileAvatar = style({
|
|
||||||
width: "30px",
|
|
||||||
height: "30px",
|
|
||||||
borderRadius: "50%",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
backgroundColor: vars.color.background,
|
|
||||||
position: "relative",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const profileButtonInformation = style({
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "flex-start",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const statusBadge = style({
|
|
||||||
width: "9px",
|
|
||||||
height: "9px",
|
|
||||||
borderRadius: "50%",
|
|
||||||
backgroundColor: vars.color.danger,
|
|
||||||
position: "absolute",
|
|
||||||
bottom: "-2px",
|
|
||||||
right: "-3px",
|
|
||||||
zIndex: "1",
|
|
||||||
});
|
|
||||||
|
@ -13,7 +13,7 @@ import * as styles from "./sidebar.css";
|
|||||||
import { buildGameDetailsPath } from "@renderer/helpers";
|
import { buildGameDetailsPath } from "@renderer/helpers";
|
||||||
|
|
||||||
import SteamLogo from "@renderer/assets/steam-logo.svg?react";
|
import SteamLogo from "@renderer/assets/steam-logo.svg?react";
|
||||||
import { PersonIcon } from "@primer/octicons-react";
|
import { SidebarProfile } from "./sidebar-profile";
|
||||||
|
|
||||||
const SIDEBAR_MIN_WIDTH = 200;
|
const SIDEBAR_MIN_WIDTH = 200;
|
||||||
const SIDEBAR_INITIAL_WIDTH = 250;
|
const SIDEBAR_INITIAL_WIDTH = 250;
|
||||||
@ -154,18 +154,7 @@ export function Sidebar() {
|
|||||||
maxWidth: sidebarWidth,
|
maxWidth: sidebarWidth,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<button type="button" className={styles.profileButton}>
|
<SidebarProfile />
|
||||||
<div className={styles.profileAvatar}>
|
|
||||||
<PersonIcon />
|
|
||||||
|
|
||||||
<div className={styles.statusBadge} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.profileButtonInformation}>
|
|
||||||
<p style={{ fontWeight: "bold" }}>hydra</p>
|
|
||||||
<p style={{ fontSize: 12 }}>Jogando ABC</p>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={styles.content({
|
className={styles.content({
|
||||||
|
@ -31,7 +31,7 @@ export const toast = recipe({
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
zIndex: "0",
|
zIndex: vars.zIndex.toast,
|
||||||
maxWidth: "500px",
|
maxWidth: "500px",
|
||||||
},
|
},
|
||||||
variants: {
|
variants: {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Downloader } from "@shared";
|
import { Downloader } from "@shared";
|
||||||
|
|
||||||
export const VERSION_CODENAME = "Exodus";
|
export const VERSION_CODENAME = "Leviticus";
|
||||||
|
|
||||||
export const DOWNLOADER_NAME = {
|
export const DOWNLOADER_NAME = {
|
||||||
[Downloader.RealDebrid]: "Real-Debrid",
|
[Downloader.RealDebrid]: "Real-Debrid",
|
||||||
|
@ -109,20 +109,19 @@ export function GameDetailsContextProvider({
|
|||||||
}, [objectID, gameTitle, dispatch]);
|
}, [objectID, gameTitle, dispatch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const listeners = [
|
const unsubscribe = window.electron.onGamesRunning((gamesIds) => {
|
||||||
window.electron.onGameClose(() => {
|
const updatedIsGameRunning =
|
||||||
if (isGameRunning) setisGameRunning(false);
|
!!game?.id &&
|
||||||
}),
|
!!gamesIds.find((gameRunning) => gameRunning.id == game.id);
|
||||||
window.electron.onPlaytime((gameId) => {
|
|
||||||
if (gameId === game?.id) {
|
|
||||||
if (!isGameRunning) setisGameRunning(true);
|
|
||||||
updateGame();
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
|
if (isGameRunning != updatedIsGameRunning) {
|
||||||
|
updateGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
setisGameRunning(updatedIsGameRunning);
|
||||||
|
});
|
||||||
return () => {
|
return () => {
|
||||||
listeners.forEach((unsubscribe) => unsubscribe());
|
unsubscribe();
|
||||||
};
|
};
|
||||||
}, [game?.id, isGameRunning, updateGame]);
|
}, [game?.id, isGameRunning, updateGame]);
|
||||||
|
|
||||||
|
27
src/renderer/src/declaration.d.ts
vendored
27
src/renderer/src/declaration.d.ts
vendored
@ -13,6 +13,7 @@ import type {
|
|||||||
StartGameDownloadPayload,
|
StartGameDownloadPayload,
|
||||||
RealDebridUser,
|
RealDebridUser,
|
||||||
DownloadSource,
|
DownloadSource,
|
||||||
|
UserProfile,
|
||||||
} from "@types";
|
} from "@types";
|
||||||
import type { DiskSpace } from "check-disk-space";
|
import type { DiskSpace } from "check-disk-space";
|
||||||
|
|
||||||
@ -70,8 +71,12 @@ declare global {
|
|||||||
removeGame: (gameId: number) => Promise<void>;
|
removeGame: (gameId: number) => Promise<void>;
|
||||||
deleteGameFolder: (gameId: number) => Promise<unknown>;
|
deleteGameFolder: (gameId: number) => Promise<unknown>;
|
||||||
getGameByObjectID: (objectID: string) => Promise<Game | null>;
|
getGameByObjectID: (objectID: string) => Promise<Game | null>;
|
||||||
onPlaytime: (cb: (gameId: number) => void) => () => Electron.IpcRenderer;
|
onGamesRunning: (
|
||||||
onGameClose: (cb: (gameId: number) => void) => () => Electron.IpcRenderer;
|
cb: (
|
||||||
|
gamesRunning: Pick<GameRunning, "id" | "sessionDurationInMillis">[]
|
||||||
|
) => void
|
||||||
|
) => () => Electron.IpcRenderer;
|
||||||
|
onLibraryBatchComplete: (cb: () => void) => () => Electron.IpcRenderer;
|
||||||
|
|
||||||
/* User preferences */
|
/* User preferences */
|
||||||
getUserPreferences: () => Promise<UserPreferences | null>;
|
getUserPreferences: () => Promise<UserPreferences | null>;
|
||||||
@ -95,6 +100,7 @@ declare global {
|
|||||||
|
|
||||||
/* Misc */
|
/* Misc */
|
||||||
openExternal: (src: string) => Promise<void>;
|
openExternal: (src: string) => Promise<void>;
|
||||||
|
isUserLoggedIn: () => Promise<boolean>;
|
||||||
getVersion: () => Promise<string>;
|
getVersion: () => Promise<string>;
|
||||||
ping: () => string;
|
ping: () => string;
|
||||||
getDefaultDownloadsPath: () => Promise<string>;
|
getDefaultDownloadsPath: () => Promise<string>;
|
||||||
@ -109,6 +115,23 @@ declare global {
|
|||||||
) => () => Electron.IpcRenderer;
|
) => () => Electron.IpcRenderer;
|
||||||
checkForUpdates: () => Promise<boolean>;
|
checkForUpdates: () => Promise<boolean>;
|
||||||
restartAndInstallUpdate: () => Promise<void>;
|
restartAndInstallUpdate: () => Promise<void>;
|
||||||
|
|
||||||
|
/* Auth */
|
||||||
|
signOut: () => Promise<void>;
|
||||||
|
openAuthWindow: () => Promise<void>;
|
||||||
|
getSessionHash: () => Promise<string | null>;
|
||||||
|
onSignIn: (cb: () => void) => () => Electron.IpcRenderer;
|
||||||
|
onSignOut: (cb: () => void) => () => Electron.IpcRenderer;
|
||||||
|
|
||||||
|
/* User */
|
||||||
|
getUser: (userId: string) => Promise<UserProfile | null>;
|
||||||
|
|
||||||
|
/* Profile */
|
||||||
|
getMe: () => Promise<UserProfile | null>;
|
||||||
|
updateProfile: (
|
||||||
|
displayName: string,
|
||||||
|
newProfileImagePath: string | null
|
||||||
|
) => Promise<UserProfile>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
|
@ -4,3 +4,5 @@ export * from "./use-preferences-slice";
|
|||||||
export * from "./download-slice";
|
export * from "./download-slice";
|
||||||
export * from "./window-slice";
|
export * from "./window-slice";
|
||||||
export * from "./toast-slice";
|
export * from "./toast-slice";
|
||||||
|
export * from "./user-details-slice";
|
||||||
|
export * from "./running-game-slice";
|
||||||
|
22
src/renderer/src/features/running-game-slice.ts
Normal file
22
src/renderer/src/features/running-game-slice.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||||
|
import { GameRunning } from "@types";
|
||||||
|
|
||||||
|
export interface GameRunningState {
|
||||||
|
gameRunning: GameRunning | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: GameRunningState = {
|
||||||
|
gameRunning: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const gameRunningSlice = createSlice({
|
||||||
|
name: "running-game",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setGameRunning: (state, action: PayloadAction<GameRunning | null>) => {
|
||||||
|
state.gameRunning = action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { setGameRunning } = gameRunningSlice.actions;
|
28
src/renderer/src/features/user-details-slice.ts
Normal file
28
src/renderer/src/features/user-details-slice.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||||
|
import type { UserDetails } from "@types";
|
||||||
|
|
||||||
|
export interface UserDetailsState {
|
||||||
|
userDetails: UserDetails | null;
|
||||||
|
profileBackground: null | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: UserDetailsState = {
|
||||||
|
userDetails: null,
|
||||||
|
profileBackground: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const userDetailsSlice = createSlice({
|
||||||
|
name: "user-details",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setUserDetails: (state, action: PayloadAction<UserDetails | null>) => {
|
||||||
|
state.userDetails = action.payload;
|
||||||
|
},
|
||||||
|
setProfileBackground: (state, action: PayloadAction<string | null>) => {
|
||||||
|
state.profileBackground = action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { setUserDetails, setProfileBackground } =
|
||||||
|
userDetailsSlice.actions;
|
@ -1,5 +1,7 @@
|
|||||||
import type { GameShop } from "@types";
|
import type { GameShop } from "@types";
|
||||||
|
|
||||||
|
import Color from "color";
|
||||||
|
|
||||||
export const steamUrlBuilder = {
|
export const steamUrlBuilder = {
|
||||||
library: (objectID: string) =>
|
library: (objectID: string) =>
|
||||||
`https://steamcdn-a.akamaihd.net/steam/apps/${objectID}/header.jpg`,
|
`https://steamcdn-a.akamaihd.net/steam/apps/${objectID}/header.jpg`,
|
||||||
@ -40,3 +42,6 @@ export const buildGameDetailsPath = (
|
|||||||
const searchParams = new URLSearchParams({ title: game.title, ...params });
|
const searchParams = new URLSearchParams({ title: game.title, ...params });
|
||||||
return `/game/${game.shop}/${game.objectID}?${searchParams.toString()}`;
|
return `/game/${game.shop}/${game.objectID}?${searchParams.toString()}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const darkenColor = (color: string, amount: number, alpha: number = 1) =>
|
||||||
|
new Color(color).darken(amount).alpha(alpha).toString();
|
||||||
|
@ -3,3 +3,4 @@ export * from "./use-library";
|
|||||||
export * from "./use-date";
|
export * from "./use-date";
|
||||||
export * from "./use-toast";
|
export * from "./use-toast";
|
||||||
export * from "./redux";
|
export * from "./redux";
|
||||||
|
export * from "./use-user-details";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { formatDistance } from "date-fns";
|
import { formatDistance, subMilliseconds } from "date-fns";
|
||||||
import type { FormatDistanceOptions } from "date-fns";
|
import type { FormatDistanceOptions } from "date-fns";
|
||||||
import {
|
import {
|
||||||
ptBR,
|
ptBR,
|
||||||
@ -52,5 +52,20 @@ export function useDate() {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
formatDiffInMillis: (
|
||||||
|
millis: number,
|
||||||
|
baseDate: string | number | Date,
|
||||||
|
options?: FormatDistanceOptions
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
return formatDistance(subMilliseconds(new Date(), millis), baseDate, {
|
||||||
|
...options,
|
||||||
|
locale: getDateLocale(),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
84
src/renderer/src/hooks/use-user-details.ts
Normal file
84
src/renderer/src/hooks/use-user-details.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { useCallback } from "react";
|
||||||
|
import { average } from "color.js";
|
||||||
|
|
||||||
|
import { useAppDispatch, useAppSelector } from "./redux";
|
||||||
|
import { setProfileBackground, setUserDetails } from "@renderer/features";
|
||||||
|
import { darkenColor } from "@renderer/helpers";
|
||||||
|
import { UserDetails } from "@types";
|
||||||
|
|
||||||
|
export function useUserDetails() {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const { userDetails, profileBackground } = useAppSelector(
|
||||||
|
(state) => state.userDetails
|
||||||
|
);
|
||||||
|
|
||||||
|
const clearUserDetails = useCallback(async () => {
|
||||||
|
dispatch(setUserDetails(null));
|
||||||
|
dispatch(setProfileBackground(null));
|
||||||
|
|
||||||
|
window.localStorage.removeItem("userDetails");
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const signOut = useCallback(async () => {
|
||||||
|
clearUserDetails();
|
||||||
|
|
||||||
|
return window.electron.signOut();
|
||||||
|
}, [clearUserDetails]);
|
||||||
|
|
||||||
|
const updateUserDetails = useCallback(
|
||||||
|
async (userDetails: UserDetails) => {
|
||||||
|
dispatch(setUserDetails(userDetails));
|
||||||
|
|
||||||
|
if (userDetails.profileImageUrl) {
|
||||||
|
const output = await average(userDetails.profileImageUrl, {
|
||||||
|
amount: 1,
|
||||||
|
format: "hex",
|
||||||
|
});
|
||||||
|
|
||||||
|
const profileBackground = `linear-gradient(135deg, ${darkenColor(output as string, 0.6)}, ${darkenColor(output as string, 0.8, 0.7)})`;
|
||||||
|
dispatch(setProfileBackground(profileBackground));
|
||||||
|
|
||||||
|
window.localStorage.setItem(
|
||||||
|
"userDetails",
|
||||||
|
JSON.stringify({ ...userDetails, profileBackground })
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const profileBackground = `#151515B3`;
|
||||||
|
dispatch(setProfileBackground(profileBackground));
|
||||||
|
|
||||||
|
window.localStorage.setItem(
|
||||||
|
"userDetails",
|
||||||
|
JSON.stringify({ ...userDetails, profileBackground })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchUserDetails = useCallback(async () => {
|
||||||
|
return window.electron.getMe();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const patchUser = useCallback(
|
||||||
|
async (displayName: string, imageProfileUrl: string | null) => {
|
||||||
|
const response = await window.electron.updateProfile(
|
||||||
|
displayName,
|
||||||
|
imageProfileUrl
|
||||||
|
);
|
||||||
|
|
||||||
|
return updateUserDetails(response);
|
||||||
|
},
|
||||||
|
[updateUserDetails]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
userDetails,
|
||||||
|
fetchUserDetails,
|
||||||
|
signOut,
|
||||||
|
clearUserDetails,
|
||||||
|
updateUserDetails,
|
||||||
|
patchUser,
|
||||||
|
profileBackground,
|
||||||
|
};
|
||||||
|
}
|
@ -27,6 +27,7 @@ import {
|
|||||||
import { store } from "./store";
|
import { store } from "./store";
|
||||||
|
|
||||||
import * as resources from "@locales";
|
import * as resources from "@locales";
|
||||||
|
import { User } from "./pages/user/user";
|
||||||
|
|
||||||
i18n
|
i18n
|
||||||
.use(LanguageDetector)
|
.use(LanguageDetector)
|
||||||
@ -54,6 +55,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
|||||||
<Route path="/game/:shop/:objectID" Component={GameDetails} />
|
<Route path="/game/:shop/:objectID" Component={GameDetails} />
|
||||||
<Route path="/search" Component={SearchResults} />
|
<Route path="/search" Component={SearchResults} />
|
||||||
<Route path="/settings" Component={Settings} />
|
<Route path="/settings" Component={Settings} />
|
||||||
|
<Route path="/user/:userId" Component={User} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
|
@ -138,8 +138,9 @@ export const randomizerButton = style({
|
|||||||
bottom: `${26 + SPACING_UNIT * 2}px`,
|
bottom: `${26 + SPACING_UNIT * 2}px`,
|
||||||
/* Scroll bar + spacing */
|
/* Scroll bar + spacing */
|
||||||
right: `${9 + SPACING_UNIT * 2}px`,
|
right: `${9 + SPACING_UNIT * 2}px`,
|
||||||
boxShadow: "rgba(255, 255, 255, 0.1) 0px 0px 10px 3px",
|
boxShadow: "rgba(255, 255, 255, 0.1) 0px 0px 10px 1px",
|
||||||
border: `solid 2px ${vars.color.border}`,
|
border: `solid 2px ${vars.color.border}`,
|
||||||
|
zIndex: "1",
|
||||||
backgroundColor: vars.color.background,
|
backgroundColor: vars.color.background,
|
||||||
":hover": {
|
":hover": {
|
||||||
backgroundColor: vars.color.background,
|
backgroundColor: vars.color.background,
|
||||||
@ -149,6 +150,12 @@ export const randomizerButton = style({
|
|||||||
":active": {
|
":active": {
|
||||||
transform: "scale(0.98)",
|
transform: "scale(0.98)",
|
||||||
},
|
},
|
||||||
|
":disabled": {
|
||||||
|
boxShadow: "none",
|
||||||
|
transform: "none",
|
||||||
|
opacity: "0.8",
|
||||||
|
backgroundColor: vars.color.background,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const heroPanelSkeleton = style({
|
export const heroPanelSkeleton = style({
|
||||||
|
@ -27,6 +27,7 @@ import { Downloader } from "@shared";
|
|||||||
|
|
||||||
export function GameDetails() {
|
export function GameDetails() {
|
||||||
const [randomGame, setRandomGame] = useState<Steam250Game | null>(null);
|
const [randomGame, setRandomGame] = useState<Steam250Game | null>(null);
|
||||||
|
const [randomizerLocked, setRandomizerLocked] = useState(false);
|
||||||
|
|
||||||
const { objectID } = useParams();
|
const { objectID } = useParams();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
@ -54,6 +55,18 @@ export function GameDetails() {
|
|||||||
{ fromRandomizer: "1" }
|
{ fromRandomizer: "1" }
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
setRandomizerLocked(true);
|
||||||
|
|
||||||
|
const zero = performance.now();
|
||||||
|
|
||||||
|
requestAnimationFrame(function animateLock(time) {
|
||||||
|
if (time - zero <= 1000) {
|
||||||
|
requestAnimationFrame(animateLock);
|
||||||
|
} else {
|
||||||
|
setRandomizerLocked(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -118,7 +131,7 @@ export function GameDetails() {
|
|||||||
className={styles.randomizerButton}
|
className={styles.randomizerButton}
|
||||||
onClick={handleRandomizerClick}
|
onClick={handleRandomizerClick}
|
||||||
theme="outline"
|
theme="outline"
|
||||||
disabled={!randomGame}
|
disabled={!randomGame || randomizerLocked}
|
||||||
>
|
>
|
||||||
<div style={{ width: 16, height: 16, position: "relative" }}>
|
<div style={{ width: 16, height: 16, position: "relative" }}>
|
||||||
<Lottie
|
<Lottie
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { style } from "@vanilla-extract/css";
|
import { style } from "@vanilla-extract/css";
|
||||||
import { recipe } from "@vanilla-extract/recipes";
|
|
||||||
|
|
||||||
import { SPACING_UNIT } from "../../theme.css";
|
import { SPACING_UNIT } from "../../theme.css";
|
||||||
|
|
||||||
@ -17,11 +16,9 @@ export const content = style({
|
|||||||
flex: "1",
|
flex: "1",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const cards = recipe({
|
export const cards = style({
|
||||||
base: {
|
display: "grid",
|
||||||
display: "grid",
|
gridTemplateColumns: "repeat(2, 1fr)",
|
||||||
gridTemplateColumns: "repeat(2, 1fr)",
|
gap: `${SPACING_UNIT * 2}px`,
|
||||||
gap: `${SPACING_UNIT * 2}px`,
|
transition: "all ease 0.2s",
|
||||||
transition: "all ease 0.2s",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
@ -65,7 +65,7 @@ export function AddDownloadSourceModal({
|
|||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
label={t("download_source_url")}
|
label={t("download_source_url")}
|
||||||
placeholder="Insert a valid JSON url"
|
placeholder={t("insert_valid_json_url")}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => setValue(e.target.value)}
|
onChange={(e) => setValue(e.target.value)}
|
||||||
rightContent={
|
rightContent={
|
||||||
@ -99,14 +99,16 @@ export function AddDownloadSourceModal({
|
|||||||
>
|
>
|
||||||
<h4>{validationResult?.name}</h4>
|
<h4>{validationResult?.name}</h4>
|
||||||
<small>
|
<small>
|
||||||
Found{" "}
|
{t("found_download_option", {
|
||||||
{validationResult?.downloadCount.toLocaleString(undefined)}{" "}
|
count: validationResult?.downloadCount,
|
||||||
download options
|
countFormatted:
|
||||||
|
validationResult?.downloadCount.toLocaleString(),
|
||||||
|
})}
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button type="button" onClick={handleAddDownloadSource}>
|
<Button type="button" onClick={handleAddDownloadSource}>
|
||||||
Import
|
{t("import")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
318
src/renderer/src/pages/user/user-content.tsx
Normal file
318
src/renderer/src/pages/user/user-content.tsx
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
import { UserGame, UserProfile } from "@types";
|
||||||
|
import cn from "classnames";
|
||||||
|
|
||||||
|
import * as styles from "./user.css";
|
||||||
|
import { SPACING_UNIT, vars } from "@renderer/theme.css";
|
||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import SteamLogo from "@renderer/assets/steam-logo.svg?react";
|
||||||
|
import {
|
||||||
|
useAppSelector,
|
||||||
|
useDate,
|
||||||
|
useToast,
|
||||||
|
useUserDetails,
|
||||||
|
} from "@renderer/hooks";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { buildGameDetailsPath, steamUrlBuilder } from "@renderer/helpers";
|
||||||
|
import { PersonIcon, TelescopeIcon } from "@primer/octicons-react";
|
||||||
|
import { Button, Link } from "@renderer/components";
|
||||||
|
import { UserEditProfileModal } from "./user-edit-modal";
|
||||||
|
import { UserSignOutModal } from "./user-signout-modal";
|
||||||
|
|
||||||
|
const MAX_MINUTES_TO_SHOW_IN_PLAYTIME = 120;
|
||||||
|
|
||||||
|
export interface ProfileContentProps {
|
||||||
|
userProfile: UserProfile;
|
||||||
|
updateUserProfile: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UserContent({
|
||||||
|
userProfile,
|
||||||
|
updateUserProfile,
|
||||||
|
}: ProfileContentProps) {
|
||||||
|
const { t, i18n } = useTranslation("user_profile");
|
||||||
|
|
||||||
|
const { userDetails, profileBackground, signOut } = useUserDetails();
|
||||||
|
const { showSuccessToast } = useToast();
|
||||||
|
|
||||||
|
const [showEditProfileModal, setShowEditProfileModal] = useState(false);
|
||||||
|
const [showSignOutModal, setShowSignOutModal] = useState(false);
|
||||||
|
|
||||||
|
const { gameRunning } = useAppSelector((state) => state.gameRunning);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const numberFormatter = useMemo(() => {
|
||||||
|
return new Intl.NumberFormat(i18n.language, {
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
});
|
||||||
|
}, [i18n.language]);
|
||||||
|
|
||||||
|
const { formatDistance, formatDiffInMillis } = useDate();
|
||||||
|
|
||||||
|
const formatPlayTime = () => {
|
||||||
|
const seconds = userProfile.libraryGames.reduce(
|
||||||
|
(acc, game) => acc + game.playTimeInSeconds,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const minutes = seconds / 60;
|
||||||
|
|
||||||
|
if (minutes < MAX_MINUTES_TO_SHOW_IN_PLAYTIME) {
|
||||||
|
return t("amount_minutes", {
|
||||||
|
amount: minutes.toFixed(0),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const hours = minutes / 60;
|
||||||
|
return t("amount_hours", { amount: numberFormatter.format(hours) });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGameClick = (game: UserGame) => {
|
||||||
|
navigate(buildGameDetailsPath(game));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditProfile = () => {
|
||||||
|
setShowEditProfileModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmSignout = async () => {
|
||||||
|
await signOut();
|
||||||
|
|
||||||
|
showSuccessToast(t("successfully_signed_out"));
|
||||||
|
|
||||||
|
navigate("/");
|
||||||
|
};
|
||||||
|
|
||||||
|
const isMe = userDetails?.id == userProfile.id;
|
||||||
|
|
||||||
|
const profileContentBoxBackground = useMemo(() => {
|
||||||
|
if (profileBackground) return profileBackground;
|
||||||
|
/* TODO: Render background colors for other users */
|
||||||
|
return undefined;
|
||||||
|
}, [profileBackground]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<UserEditProfileModal
|
||||||
|
visible={showEditProfileModal}
|
||||||
|
onClose={() => setShowEditProfileModal(false)}
|
||||||
|
updateUserProfile={updateUserProfile}
|
||||||
|
userProfile={userProfile}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<UserSignOutModal
|
||||||
|
visible={showSignOutModal}
|
||||||
|
onClose={() => setShowSignOutModal(false)}
|
||||||
|
onConfirm={handleConfirmSignout}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<section
|
||||||
|
className={styles.profileContentBox}
|
||||||
|
style={{
|
||||||
|
padding: `${SPACING_UNIT * 3}px ${SPACING_UNIT * 2}px`,
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{gameRunning && isMe && (
|
||||||
|
<img
|
||||||
|
src={steamUrlBuilder.libraryHero(gameRunning.objectID)}
|
||||||
|
alt={gameRunning.title}
|
||||||
|
className={styles.profileBackground}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: profileContentBoxBackground,
|
||||||
|
position: "absolute",
|
||||||
|
inset: 0,
|
||||||
|
borderRadius: "4px",
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div className={styles.profileAvatarContainer}>
|
||||||
|
{userProfile.profileImageUrl ? (
|
||||||
|
<img
|
||||||
|
className={styles.profileAvatar}
|
||||||
|
alt={userProfile.displayName}
|
||||||
|
src={userProfile.profileImageUrl}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<PersonIcon size={72} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.profileInformation}>
|
||||||
|
<h2 style={{ fontWeight: "bold" }}>{userProfile.displayName}</h2>
|
||||||
|
{isMe && gameRunning && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: `${SPACING_UNIT / 2}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
gap: `${SPACING_UNIT}px`,
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Link to={buildGameDetailsPath(gameRunning)}>
|
||||||
|
{gameRunning.title}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<small>
|
||||||
|
{t("playing_for", {
|
||||||
|
amount: formatDiffInMillis(
|
||||||
|
gameRunning.sessionDurationInMillis,
|
||||||
|
new Date()
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isMe && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "end",
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: `${SPACING_UNIT}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<Button theme="outline" onClick={handleEditProfile}>
|
||||||
|
{t("edit_profile")}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
theme="danger"
|
||||||
|
onClick={() => setShowSignOutModal(true)}
|
||||||
|
>
|
||||||
|
{t("sign_out")}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className={styles.profileContent}>
|
||||||
|
<div className={styles.profileGameSection}>
|
||||||
|
<h2>{t("activity")}</h2>
|
||||||
|
|
||||||
|
{!userProfile.recentGames.length ? (
|
||||||
|
<div className={styles.noDownloads}>
|
||||||
|
<div className={styles.telescopeIcon}>
|
||||||
|
<TelescopeIcon size={24} />
|
||||||
|
</div>
|
||||||
|
<h2>{t("no_recent_activity_title")}</h2>
|
||||||
|
<p style={{ fontFamily: "Fira Sans" }}>
|
||||||
|
{t("no_recent_activity_description")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: `${SPACING_UNIT * 2}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{userProfile.recentGames.map((game) => (
|
||||||
|
<button
|
||||||
|
key={game.objectID}
|
||||||
|
className={cn(styles.feedItem, styles.profileContentBox)}
|
||||||
|
onClick={() => handleGameClick(game)}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className={styles.feedGameIcon}
|
||||||
|
src={game.cover}
|
||||||
|
alt={game.title}
|
||||||
|
/>
|
||||||
|
<div className={styles.gameInformation}>
|
||||||
|
<h4>{game.title}</h4>
|
||||||
|
<small>
|
||||||
|
{t("last_time_played", {
|
||||||
|
period: formatDistance(
|
||||||
|
game.lastTimePlayed!,
|
||||||
|
new Date(),
|
||||||
|
{
|
||||||
|
addSuffix: true,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cn(styles.contentSidebar, styles.profileGameSection)}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: `${SPACING_UNIT * 2}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h2>{t("library")}</h2>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: vars.color.border,
|
||||||
|
height: "1px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<h3 style={{ fontWeight: "400" }}>
|
||||||
|
{userProfile.libraryGames.length}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<small>{t("total_play_time", { amount: formatPlayTime() })}</small>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "repeat(4, 1fr)",
|
||||||
|
gap: `${SPACING_UNIT}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{userProfile.libraryGames.map((game) => (
|
||||||
|
<button
|
||||||
|
key={game.objectID}
|
||||||
|
className={cn(styles.gameListItem, styles.profileContentBox)}
|
||||||
|
onClick={() => handleGameClick(game)}
|
||||||
|
title={game.title}
|
||||||
|
>
|
||||||
|
{game.iconUrl ? (
|
||||||
|
<img
|
||||||
|
className={styles.libraryGameIcon}
|
||||||
|
src={game.iconUrl}
|
||||||
|
alt={game.title}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<SteamLogo className={styles.libraryGameIcon} />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
147
src/renderer/src/pages/user/user-edit-modal.tsx
Normal file
147
src/renderer/src/pages/user/user-edit-modal.tsx
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import { Button, Modal, TextField } from "@renderer/components";
|
||||||
|
import { UserProfile } from "@types";
|
||||||
|
import * as styles from "./user.css";
|
||||||
|
import { DeviceCameraIcon, PersonIcon } from "@primer/octicons-react";
|
||||||
|
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { useToast, useUserDetails } from "@renderer/hooks";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
export interface UserEditProfileModalProps {
|
||||||
|
userProfile: UserProfile;
|
||||||
|
visible: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
updateUserProfile: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UserEditProfileModal = ({
|
||||||
|
userProfile,
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
updateUserProfile,
|
||||||
|
}: UserEditProfileModalProps) => {
|
||||||
|
const { t } = useTranslation("user_profile");
|
||||||
|
|
||||||
|
const [displayName, setDisplayName] = useState("");
|
||||||
|
const [newImagePath, setNewImagePath] = useState<string | null>(null);
|
||||||
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
|
||||||
|
const { patchUser } = useUserDetails();
|
||||||
|
|
||||||
|
const { showSuccessToast, showErrorToast } = useToast();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDisplayName(userProfile.displayName);
|
||||||
|
}, [userProfile.displayName]);
|
||||||
|
|
||||||
|
const handleChangeProfileAvatar = async () => {
|
||||||
|
const { filePaths } = await window.electron.showOpenDialog({
|
||||||
|
properties: ["openFile"],
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: "Image",
|
||||||
|
extensions: ["jpg", "jpeg", "png", "gif", "webp", "bmp"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (filePaths && filePaths.length > 0) {
|
||||||
|
const path = filePaths[0];
|
||||||
|
|
||||||
|
setNewImagePath(path);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveProfile: React.FormEventHandler<HTMLFormElement> = async (
|
||||||
|
event
|
||||||
|
) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setIsSaving(true);
|
||||||
|
|
||||||
|
patchUser(displayName, newImagePath)
|
||||||
|
.then(async () => {
|
||||||
|
await updateUserProfile();
|
||||||
|
showSuccessToast(t("saved_successfully"));
|
||||||
|
cleanFormAndClose();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
showErrorToast(t("try_again"));
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsSaving(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetModal = () => {
|
||||||
|
setDisplayName(userProfile.displayName);
|
||||||
|
setNewImagePath(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanFormAndClose = () => {
|
||||||
|
resetModal();
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const avatarUrl = useMemo(() => {
|
||||||
|
if (newImagePath) return `local:${newImagePath}`;
|
||||||
|
if (userProfile.profileImageUrl) return userProfile.profileImageUrl;
|
||||||
|
return null;
|
||||||
|
}, [newImagePath, userProfile.profileImageUrl]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
visible={visible}
|
||||||
|
title={t("edit_profile")}
|
||||||
|
onClose={cleanFormAndClose}
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
onSubmit={handleSaveProfile}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: `${SPACING_UNIT * 3}px`,
|
||||||
|
width: "350px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={styles.profileAvatarEditContainer}
|
||||||
|
onClick={handleChangeProfileAvatar}
|
||||||
|
>
|
||||||
|
{avatarUrl ? (
|
||||||
|
<img
|
||||||
|
className={styles.profileAvatar}
|
||||||
|
alt={userProfile.displayName}
|
||||||
|
src={avatarUrl}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<PersonIcon size={96} />
|
||||||
|
)}
|
||||||
|
<div className={styles.editProfileImageBadge}>
|
||||||
|
<DeviceCameraIcon size={16} />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label={t("display_name")}
|
||||||
|
value={displayName}
|
||||||
|
required
|
||||||
|
minLength={3}
|
||||||
|
containerProps={{ style: { width: "100%" } }}
|
||||||
|
onChange={(e) => setDisplayName(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
disabled={isSaving}
|
||||||
|
style={{ alignSelf: "end" }}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
{isSaving ? t("saving") : t("save")}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
40
src/renderer/src/pages/user/user-signout-modal.tsx
Normal file
40
src/renderer/src/pages/user/user-signout-modal.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Button, Modal } from "@renderer/components";
|
||||||
|
import * as styles from "./user.css";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
export interface UserEditProfileModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
onConfirm: () => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UserSignOutModal = ({
|
||||||
|
visible,
|
||||||
|
onConfirm,
|
||||||
|
onClose,
|
||||||
|
}: UserEditProfileModalProps) => {
|
||||||
|
const { t } = useTranslation("user_profile");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
visible={visible}
|
||||||
|
title={t("sign_out_modal_title")}
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
<div className={styles.signOutModalContent}>
|
||||||
|
<p style={{ fontFamily: "Fira Sans" }}>{t("sign_out_modal_text")}</p>
|
||||||
|
<div className={styles.signOutModalButtonsContainer}>
|
||||||
|
<Button onClick={onConfirm} theme="danger">
|
||||||
|
{t("sign_out")}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button onClick={onClose} theme="primary">
|
||||||
|
{t("cancel")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
41
src/renderer/src/pages/user/user-skeleton.tsx
Normal file
41
src/renderer/src/pages/user/user-skeleton.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import Skeleton from "react-loading-skeleton";
|
||||||
|
import cn from "classnames";
|
||||||
|
import * as styles from "./user.css";
|
||||||
|
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
export const UserSkeleton = () => {
|
||||||
|
const { t } = useTranslation("user_profile");
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Skeleton className={styles.profileHeaderSkeleton} />
|
||||||
|
<div className={styles.profileContent}>
|
||||||
|
<div className={styles.profileGameSection}>
|
||||||
|
<h2>{t("activity")}</h2>
|
||||||
|
{Array.from({ length: 3 }).map((_, index) => (
|
||||||
|
<Skeleton
|
||||||
|
key={index}
|
||||||
|
height={72}
|
||||||
|
style={{ flex: "1", width: "100%" }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cn(styles.contentSidebar, styles.profileGameSection)}>
|
||||||
|
<h2>{t("library")}</h2>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "repeat(4, 1fr)",
|
||||||
|
gap: `${SPACING_UNIT}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Array.from({ length: 8 }).map((_, index) => (
|
||||||
|
<Skeleton key={index} style={{ aspectRatio: "1" }} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
217
src/renderer/src/pages/user/user.css.ts
Normal file
217
src/renderer/src/pages/user/user.css.ts
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
import { SPACING_UNIT, vars } from "../../theme.css";
|
||||||
|
import { style } from "@vanilla-extract/css";
|
||||||
|
|
||||||
|
export const wrapper = style({
|
||||||
|
padding: "24px",
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: `${SPACING_UNIT * 3}px`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const profileContentBox = style({
|
||||||
|
display: "flex",
|
||||||
|
gap: `${SPACING_UNIT * 3}px`,
|
||||||
|
alignItems: "center",
|
||||||
|
borderRadius: "4px",
|
||||||
|
border: `solid 1px ${vars.color.border}`,
|
||||||
|
width: "100%",
|
||||||
|
boxShadow: "0px 0px 15px 0px rgba(0, 0, 0, 0.7)",
|
||||||
|
transition: "all ease 0.3s",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const profileAvatarContainer = style({
|
||||||
|
width: "96px",
|
||||||
|
height: "96px",
|
||||||
|
borderRadius: "50%",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
backgroundColor: vars.color.background,
|
||||||
|
position: "relative",
|
||||||
|
overflow: "hidden",
|
||||||
|
border: `solid 1px ${vars.color.border}`,
|
||||||
|
boxShadow: "0px 0px 5px 0px rgba(0, 0, 0, 0.7)",
|
||||||
|
zIndex: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const profileAvatarEditContainer = style({
|
||||||
|
width: "128px",
|
||||||
|
height: "128px",
|
||||||
|
display: "flex",
|
||||||
|
borderRadius: "50%",
|
||||||
|
color: vars.color.body,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
backgroundColor: vars.color.background,
|
||||||
|
position: "relative",
|
||||||
|
border: `solid 1px ${vars.color.border}`,
|
||||||
|
boxShadow: "0px 0px 5px 0px rgba(0, 0, 0, 0.7)",
|
||||||
|
cursor: "pointer",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const profileAvatar = style({
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
borderRadius: "50%",
|
||||||
|
overflow: "hidden",
|
||||||
|
objectFit: "cover",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const profileAvatarEditOverlay = style({
|
||||||
|
position: "absolute",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
backgroundColor: "#00000055",
|
||||||
|
color: vars.color.muted,
|
||||||
|
zIndex: 1,
|
||||||
|
cursor: "pointer",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const profileInformation = style({
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: `${SPACING_UNIT}px`,
|
||||||
|
alignItems: "flex-start",
|
||||||
|
color: "#c0c1c7",
|
||||||
|
zIndex: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const profileContent = style({
|
||||||
|
display: "flex",
|
||||||
|
height: "100%",
|
||||||
|
flexDirection: "row",
|
||||||
|
gap: `${SPACING_UNIT * 4}px`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const profileGameSection = style({
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: `${SPACING_UNIT * 2}px`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const contentSidebar = style({
|
||||||
|
width: "100%",
|
||||||
|
"@media": {
|
||||||
|
"(min-width: 768px)": {
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: "150px",
|
||||||
|
},
|
||||||
|
"(min-width: 1024px)": {
|
||||||
|
maxWidth: "250px",
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const feedGameIcon = style({
|
||||||
|
height: "100%",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const libraryGameIcon = style({
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
borderRadius: "4px",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const feedItem = style({
|
||||||
|
color: vars.color.body,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
gap: `${SPACING_UNIT * 2}px`,
|
||||||
|
width: "100%",
|
||||||
|
height: "72px",
|
||||||
|
transition: "all ease 0.2s",
|
||||||
|
cursor: "pointer",
|
||||||
|
zIndex: "1",
|
||||||
|
":hover": {
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.15)",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const gameListItem = style({
|
||||||
|
color: vars.color.body,
|
||||||
|
transition: "all ease 0.2s",
|
||||||
|
cursor: "pointer",
|
||||||
|
zIndex: "1",
|
||||||
|
overflow: "hidden",
|
||||||
|
padding: `${SPACING_UNIT + SPACING_UNIT / 2}px`,
|
||||||
|
":hover": {
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.15)",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const gameInformation = style({
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "flex-start",
|
||||||
|
gap: `${SPACING_UNIT / 2}px`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const profileHeaderSkeleton = style({
|
||||||
|
height: "144px",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const editProfileImageBadge = style({
|
||||||
|
width: "28px",
|
||||||
|
height: "28px",
|
||||||
|
borderRadius: "50%",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
color: vars.color.background,
|
||||||
|
backgroundColor: vars.color.muted,
|
||||||
|
position: "absolute",
|
||||||
|
bottom: "0px",
|
||||||
|
right: "0px",
|
||||||
|
zIndex: "1",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const telescopeIcon = style({
|
||||||
|
width: "60px",
|
||||||
|
height: "60px",
|
||||||
|
borderRadius: "50%",
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.06)",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
marginBottom: `${SPACING_UNIT * 2}px`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const noDownloads = style({
|
||||||
|
display: "flex",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: `${SPACING_UNIT}px`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const signOutModalContent = style({
|
||||||
|
display: "flex",
|
||||||
|
width: "100%",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: `${SPACING_UNIT}px`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const signOutModalButtonsContainer = style({
|
||||||
|
display: "flex",
|
||||||
|
width: "100%",
|
||||||
|
justifyContent: "end",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: `${SPACING_UNIT}px`,
|
||||||
|
paddingTop: `${SPACING_UNIT}px`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const profileBackground = style({
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
position: "absolute",
|
||||||
|
objectFit: "cover",
|
||||||
|
left: "0",
|
||||||
|
top: "0",
|
||||||
|
borderRadius: "4px",
|
||||||
|
});
|
45
src/renderer/src/pages/user/user.tsx
Normal file
45
src/renderer/src/pages/user/user.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { UserProfile } from "@types";
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { setHeaderTitle } from "@renderer/features";
|
||||||
|
import { useAppDispatch } from "@renderer/hooks";
|
||||||
|
import { UserSkeleton } from "./user-skeleton";
|
||||||
|
import { UserContent } from "./user-content";
|
||||||
|
import { SkeletonTheme } from "react-loading-skeleton";
|
||||||
|
import { vars } from "@renderer/theme.css";
|
||||||
|
import * as styles from "./user.css";
|
||||||
|
|
||||||
|
export const User = () => {
|
||||||
|
const { userId } = useParams();
|
||||||
|
const [userProfile, setUserProfile] = useState<UserProfile>();
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const getUserProfile = useCallback(() => {
|
||||||
|
return window.electron.getUser(userId!).then((userProfile) => {
|
||||||
|
if (userProfile) {
|
||||||
|
dispatch(setHeaderTitle(userProfile.displayName));
|
||||||
|
setUserProfile(userProfile);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [dispatch, userId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getUserProfile();
|
||||||
|
}, [getUserProfile]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SkeletonTheme baseColor={vars.color.background} highlightColor="#444">
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
{userProfile ? (
|
||||||
|
<UserContent
|
||||||
|
userProfile={userProfile}
|
||||||
|
updateUserProfile={getUserProfile}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<UserSkeleton />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</SkeletonTheme>
|
||||||
|
);
|
||||||
|
};
|
@ -6,6 +6,8 @@ import {
|
|||||||
searchSlice,
|
searchSlice,
|
||||||
userPreferencesSlice,
|
userPreferencesSlice,
|
||||||
toastSlice,
|
toastSlice,
|
||||||
|
userDetailsSlice,
|
||||||
|
gameRunningSlice,
|
||||||
} from "@renderer/features";
|
} from "@renderer/features";
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
@ -16,6 +18,8 @@ export const store = configureStore({
|
|||||||
userPreferences: userPreferencesSlice.reducer,
|
userPreferences: userPreferencesSlice.reducer,
|
||||||
download: downloadSlice.reducer,
|
download: downloadSlice.reducer,
|
||||||
toast: toastSlice.reducer,
|
toast: toastSlice.reducer,
|
||||||
|
userDetails: userDetailsSlice.reducer,
|
||||||
|
gameRunning: gameRunningSlice.reducer,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -21,4 +21,10 @@ export const vars = createGlobalTheme(":root", {
|
|||||||
body: "14px",
|
body: "14px",
|
||||||
small: "12px",
|
small: "12px",
|
||||||
},
|
},
|
||||||
|
zIndex: {
|
||||||
|
toast: "2",
|
||||||
|
bottomPanel: "3",
|
||||||
|
titleBar: "4",
|
||||||
|
backdrop: "4",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -85,6 +85,16 @@ export interface CatalogueEntry {
|
|||||||
repacks: GameRepack[];
|
repacks: GameRepack[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UserGame {
|
||||||
|
objectID: string;
|
||||||
|
shop: GameShop;
|
||||||
|
title: string;
|
||||||
|
iconUrl: string | null;
|
||||||
|
cover: string;
|
||||||
|
playTimeInSeconds: number;
|
||||||
|
lastTimePlayed: Date | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DownloadQueue {
|
export interface DownloadQueue {
|
||||||
id: number;
|
id: number;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
@ -117,6 +127,15 @@ export interface Game {
|
|||||||
|
|
||||||
export type LibraryGame = Omit<Game, "repacks">;
|
export type LibraryGame = Omit<Game, "repacks">;
|
||||||
|
|
||||||
|
export interface GameRunning {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
iconUrl: string;
|
||||||
|
objectID: string;
|
||||||
|
shop: GameShop;
|
||||||
|
sessionDurationInMillis: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DownloadProgress {
|
export interface DownloadProgress {
|
||||||
downloadSpeed: number;
|
downloadSpeed: number;
|
||||||
timeRemaining: number;
|
timeRemaining: number;
|
||||||
@ -234,6 +253,20 @@ export interface RealDebridUser {
|
|||||||
expiration: string;
|
expiration: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UserDetails {
|
||||||
|
id: string;
|
||||||
|
displayName: string;
|
||||||
|
profileImageUrl: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserProfile {
|
||||||
|
id: string;
|
||||||
|
displayName: string;
|
||||||
|
profileImageUrl: string | null;
|
||||||
|
libraryGames: UserGame[];
|
||||||
|
recentGames: UserGame[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface DownloadSource {
|
export interface DownloadSource {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
115
yarn.lock
115
yarn.lock
@ -1251,6 +1251,25 @@
|
|||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
"@types/responselike" "^1.0.0"
|
"@types/responselike" "^1.0.0"
|
||||||
|
|
||||||
|
"@types/color-convert@*":
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.3.tgz#e93f5c991eda87a945058b47044f5f0008b0dce9"
|
||||||
|
integrity sha512-2Q6wzrNiuEvYxVQqhh7sXM2mhIhvZR/Paq4FdsQkOMgWsCIkKvSGj8Le1/XalulrmgOzPMqNa0ix+ePY4hTrfg==
|
||||||
|
dependencies:
|
||||||
|
"@types/color-name" "*"
|
||||||
|
|
||||||
|
"@types/color-name@*":
|
||||||
|
version "1.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.4.tgz#e002611ff627347818d440a05e81650e9a4053b8"
|
||||||
|
integrity sha512-hulKeREDdLFesGQjl96+4aoJSHY5b2GRjagzzcqCfIrWhe5vkCqIvrLbqzBaI1q94Vg8DNJZZqTR5ocdWmWclg==
|
||||||
|
|
||||||
|
"@types/color@^3.0.6":
|
||||||
|
version "3.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/color/-/color-3.0.6.tgz#29c27a99d4de2975e1676712679a0bd7f646a3fb"
|
||||||
|
integrity sha512-NMiNcZFRUAiUUCCf7zkAelY8eV3aKqfbzyFQlXpPIEeoNDbsEHGpb854V3gzTsGKYj830I5zPuOwU/TP5/cW6A==
|
||||||
|
dependencies:
|
||||||
|
"@types/color-convert" "*"
|
||||||
|
|
||||||
"@types/conventional-commits-parser@^5.0.0":
|
"@types/conventional-commits-parser@^5.0.0":
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz#8c9d23e0b415b24b91626d07017303755d542dc8"
|
resolved "https://registry.yarnpkg.com/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz#8c9d23e0b415b24b91626d07017303755d542dc8"
|
||||||
@ -1296,6 +1315,13 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
|
||||||
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
|
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
|
||||||
|
|
||||||
|
"@types/jsonwebtoken@^9.0.6":
|
||||||
|
version "9.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz#d1af3544d99ad992fb6681bbe60676e06b032bd3"
|
||||||
|
integrity sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/keyv@^3.1.4":
|
"@types/keyv@^3.1.4":
|
||||||
version "3.1.4"
|
version "3.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6"
|
resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6"
|
||||||
@ -2037,6 +2063,11 @@ buffer-crc32@~0.2.3:
|
|||||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||||
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
|
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
|
||||||
|
|
||||||
|
buffer-equal-constant-time@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
||||||
|
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
|
||||||
|
|
||||||
buffer-equal@^1.0.0:
|
buffer-equal@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.1.tgz#2f7651be5b1b3f057fcd6e7ee16cf34767077d90"
|
resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.1.tgz#2f7651be5b1b3f057fcd6e7ee16cf34767077d90"
|
||||||
@ -2705,6 +2736,13 @@ eastasianwidth@^0.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
|
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
|
||||||
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
|
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
|
||||||
|
|
||||||
|
ecdsa-sig-formatter@1.0.11:
|
||||||
|
version "1.0.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
|
||||||
|
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
|
||||||
|
dependencies:
|
||||||
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
ejs@^3.1.8:
|
ejs@^3.1.8:
|
||||||
version "3.1.10"
|
version "3.1.10"
|
||||||
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b"
|
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b"
|
||||||
@ -3305,6 +3343,15 @@ file-type@^18.7.0:
|
|||||||
strtok3 "^7.0.0"
|
strtok3 "^7.0.0"
|
||||||
token-types "^5.0.1"
|
token-types "^5.0.1"
|
||||||
|
|
||||||
|
file-type@^19.0.0:
|
||||||
|
version "19.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/file-type/-/file-type-19.0.0.tgz#62a6cadc43f73ba38c53e1a174943a75fdafafa9"
|
||||||
|
integrity sha512-s7cxa7/leUWLiXO78DVVfBVse+milos9FitauDLG1pI7lNaJ2+5lzPnr2N24ym+84HVwJL6hVuGfgVE+ALvU8Q==
|
||||||
|
dependencies:
|
||||||
|
readable-web-to-node-stream "^3.0.2"
|
||||||
|
strtok3 "^7.0.0"
|
||||||
|
token-types "^5.0.1"
|
||||||
|
|
||||||
file-uri-to-path@1.0.0:
|
file-uri-to-path@1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
|
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
|
||||||
@ -4267,6 +4314,22 @@ jsonparse@^1.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
|
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
|
||||||
integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==
|
integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==
|
||||||
|
|
||||||
|
jsonwebtoken@^9.0.2:
|
||||||
|
version "9.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3"
|
||||||
|
integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==
|
||||||
|
dependencies:
|
||||||
|
jws "^3.2.2"
|
||||||
|
lodash.includes "^4.3.0"
|
||||||
|
lodash.isboolean "^3.0.3"
|
||||||
|
lodash.isinteger "^4.0.4"
|
||||||
|
lodash.isnumber "^3.0.3"
|
||||||
|
lodash.isplainobject "^4.0.6"
|
||||||
|
lodash.isstring "^4.0.1"
|
||||||
|
lodash.once "^4.0.0"
|
||||||
|
ms "^2.1.1"
|
||||||
|
semver "^7.5.4"
|
||||||
|
|
||||||
"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5:
|
"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5:
|
||||||
version "3.3.5"
|
version "3.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a"
|
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a"
|
||||||
@ -4277,6 +4340,23 @@ jsonparse@^1.2.0:
|
|||||||
object.assign "^4.1.4"
|
object.assign "^4.1.4"
|
||||||
object.values "^1.1.6"
|
object.values "^1.1.6"
|
||||||
|
|
||||||
|
jwa@^1.4.1:
|
||||||
|
version "1.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
|
||||||
|
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
|
||||||
|
dependencies:
|
||||||
|
buffer-equal-constant-time "1.0.1"
|
||||||
|
ecdsa-sig-formatter "1.0.11"
|
||||||
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
|
jws@^3.2.2:
|
||||||
|
version "3.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
|
||||||
|
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
|
||||||
|
dependencies:
|
||||||
|
jwa "^1.4.1"
|
||||||
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
keyv@^4.0.0, keyv@^4.5.3:
|
keyv@^4.0.0, keyv@^4.5.3:
|
||||||
version "4.5.4"
|
version "4.5.4"
|
||||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
|
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
|
||||||
@ -4348,16 +4428,41 @@ lodash.escaperegexp@^4.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
|
resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
|
||||||
integrity sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==
|
integrity sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==
|
||||||
|
|
||||||
|
lodash.includes@^4.3.0:
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
|
||||||
|
integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
|
||||||
|
|
||||||
|
lodash.isboolean@^3.0.3:
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
|
||||||
|
integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
|
||||||
|
|
||||||
lodash.isequal@^4.5.0:
|
lodash.isequal@^4.5.0:
|
||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||||
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
|
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
|
||||||
|
|
||||||
|
lodash.isinteger@^4.0.4:
|
||||||
|
version "4.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
|
||||||
|
integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==
|
||||||
|
|
||||||
|
lodash.isnumber@^3.0.3:
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
|
||||||
|
integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==
|
||||||
|
|
||||||
lodash.isplainobject@^4.0.6:
|
lodash.isplainobject@^4.0.6:
|
||||||
version "4.0.6"
|
version "4.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||||
integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
|
integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
|
||||||
|
|
||||||
|
lodash.isstring@^4.0.1:
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||||
|
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
|
||||||
|
|
||||||
lodash.kebabcase@^4.1.1:
|
lodash.kebabcase@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
|
resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
|
||||||
@ -4373,6 +4478,11 @@ lodash.mergewith@^4.6.2:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
|
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
|
||||||
integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
|
integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
|
||||||
|
|
||||||
|
lodash.once@^4.0.0:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||||
|
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
|
||||||
|
|
||||||
lodash.snakecase@^4.1.1:
|
lodash.snakecase@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d"
|
resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d"
|
||||||
@ -4633,6 +4743,11 @@ ms@2.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||||
|
|
||||||
|
ms@^2.1.1:
|
||||||
|
version "2.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||||
|
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||||
|
|
||||||
mz@^2.4.0:
|
mz@^2.4.0:
|
||||||
version "2.7.0"
|
version "2.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
|
resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
|
||||||
|
Loading…
Reference in New Issue
Block a user